Merge "Update mediaprovider owners with new team members" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 2047168..8591a9c 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -77,6 +77,7 @@
         "camera_platform_flags_core_java_lib",
         "com.android.hardware.input-aconfig-java",
         "com.android.input.flags-aconfig-java",
+        "com.android.internal.compat.flags-aconfig-java",
         "com.android.internal.foldables.flags-aconfig-java",
         "com.android.internal.pm.pkg.component.flags-aconfig-java",
         "com.android.media.flags.bettertogether-aconfig-java",
@@ -310,6 +311,8 @@
 aconfig_declarations {
     name: "android.os.flags-aconfig",
     package: "android.os",
+    exportable: true,
+    container: "system",
     srcs: ["core/java/android/os/*.aconfig"],
 }
 
@@ -326,6 +329,30 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "android.os.flags-aconfig-java-export",
+    aconfig_declarations: "android.os.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+    mode: "exported",
+}
+
+cc_aconfig_library {
+    name: "android.os.flags-aconfig-cc",
+    aconfig_declarations: "android.os.flags-aconfig",
+}
+
+cc_aconfig_library {
+    name: "android.os.flags-aconfig-cc-test",
+    aconfig_declarations: "android.os.flags-aconfig",
+    mode: "test",
+}
+
+cc_aconfig_library {
+    name: "android.os.flags-aconfig-cc-host",
+    aconfig_declarations: "android.os.flags-aconfig",
+    host_supported: true,
+}
+
 // VirtualDeviceManager
 cc_aconfig_library {
     name: "android.companion.virtualdevice.flags-aconfig-cc",
@@ -483,6 +510,13 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "android.content.res.flags-aconfig-java-host",
+    aconfig_declarations: "android.content.res.flags-aconfig",
+    host_supported: true,
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Media BetterTogether
 aconfig_declarations {
     name: "com.android.media.flags.bettertogether-aconfig",
@@ -630,6 +664,13 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// Platform Compat
+java_aconfig_library {
+    name: "com.android.internal.compat.flags-aconfig-java",
+    aconfig_declarations: "compat_logging_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Multi user
 aconfig_declarations {
     name: "android.multiuser.flags-aconfig",
diff --git a/Android.bp b/Android.bp
index 5ada10d..8d7ab98 100644
--- a/Android.bp
+++ b/Android.bp
@@ -386,6 +386,7 @@
         // TODO(b/120066492): remove gps_debug and protolog.conf.json when the build
         // system propagates "required" properly.
         "gps_debug.conf",
+        "protolog.conf.json.gz",
         "core.protolog.pb",
         "framework-res",
         // any install dependencies should go into framework-minus-apex-install-dependencies
diff --git a/PACKAGE_MANAGER_OWNERS b/PACKAGE_MANAGER_OWNERS
index eb5842b..45719a7 100644
--- a/PACKAGE_MANAGER_OWNERS
+++ b/PACKAGE_MANAGER_OWNERS
@@ -1,3 +1,6 @@
+# Bug component: 36137
+# Bug template url: https://b.corp.google.com/issues/new?component=36137&template=198919
+
 alexbuy@google.com
 patb@google.com
 schfan@google.com
\ No newline at end of file
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 4e36075..f43c37b 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -145,6 +145,17 @@
 }
 
 java_library {
+    name: "services.fakes.ravenwood-jarjar",
+    installable: false,
+    srcs: [":services.fakes-sources"],
+    libs: [
+        "ravenwood-framework",
+        "services.core.ravenwood",
+    ],
+    jarjar_rules: ":ravenwood-services-jarjar-rules",
+}
+
+java_library {
     name: "mockito-ravenwood-prebuilt",
     installable: false,
     static_libs: [
@@ -189,11 +200,15 @@
         "ravenwood-helper-runtime",
         "hoststubgen-helper-runtime.ravenwood",
         "services.core.ravenwood-jarjar",
+        "services.fakes.ravenwood-jarjar",
 
         // Provide runtime versions of utils linked in below
         "junit",
         "truth",
+        "flag-junit",
+        "ravenwood-framework",
         "ravenwood-junit-impl",
+        "ravenwood-junit-impl-flag",
         "mockito-ravenwood-prebuilt",
         "inline-mockito-ravenwood-prebuilt",
     ],
@@ -207,6 +222,8 @@
     libs: [
         "junit",
         "truth",
+        "flag-junit",
+        "ravenwood-framework",
         "ravenwood-junit",
         "mockito-ravenwood-prebuilt",
         "inline-mockito-ravenwood-prebuilt",
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/VectorDrawablePerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/VectorDrawablePerfTest.java
index d14e93e..80a9c06 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/VectorDrawablePerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/VectorDrawablePerfTest.java
@@ -27,8 +27,8 @@
 import android.perftests.utils.BitmapUtils;
 import android.perftests.utils.PerfStatusReporter;
 import android.perftests.utils.PerfTestActivity;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
diff --git a/apct-tests/perftests/core/src/android/libcore/AdditionPerfTest.java b/apct-tests/perftests/core/src/android/libcore/AdditionPerfTest.java
index aa47e0a..80cd86c 100644
--- a/apct-tests/perftests/core/src/android/libcore/AdditionPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/AdditionPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/ArrayCopyPerfTest.java b/apct-tests/perftests/core/src/android/libcore/ArrayCopyPerfTest.java
index 97ab6c7..2f6c378 100644
--- a/apct-tests/perftests/core/src/android/libcore/ArrayCopyPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/ArrayCopyPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/ArrayIterationPerfTest.java b/apct-tests/perftests/core/src/android/libcore/ArrayIterationPerfTest.java
index bb452d3..d17add7 100644
--- a/apct-tests/perftests/core/src/android/libcore/ArrayIterationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/ArrayIterationPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/ArrayListIterationPerfTest.java b/apct-tests/perftests/core/src/android/libcore/ArrayListIterationPerfTest.java
index ff6d46f..3a57db8 100644
--- a/apct-tests/perftests/core/src/android/libcore/ArrayListIterationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/ArrayListIterationPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/BigIntegerPerfTest.java b/apct-tests/perftests/core/src/android/libcore/BigIntegerPerfTest.java
index e0c12dd..3fb3bc8 100644
--- a/apct-tests/perftests/core/src/android/libcore/BigIntegerPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/BigIntegerPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/BufferedZipFilePerfTest.java b/apct-tests/perftests/core/src/android/libcore/BufferedZipFilePerfTest.java
index 04ef09e4..2a1b5d1 100644
--- a/apct-tests/perftests/core/src/android/libcore/BufferedZipFilePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/BufferedZipFilePerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/ClassLoaderResourcePerfTest.java b/apct-tests/perftests/core/src/android/libcore/ClassLoaderResourcePerfTest.java
index 4ae88b8..5f599ea 100644
--- a/apct-tests/perftests/core/src/android/libcore/ClassLoaderResourcePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/ClassLoaderResourcePerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Assert;
diff --git a/apct-tests/perftests/core/src/android/libcore/ClonePerfTest.java b/apct-tests/perftests/core/src/android/libcore/ClonePerfTest.java
index 5e73916..ea24984 100644
--- a/apct-tests/perftests/core/src/android/libcore/ClonePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/ClonePerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/DeepArrayOpsPerfTest.java b/apct-tests/perftests/core/src/android/libcore/DeepArrayOpsPerfTest.java
index 3ebaa4c..82247dc 100644
--- a/apct-tests/perftests/core/src/android/libcore/DeepArrayOpsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/DeepArrayOpsPerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/FieldAccessPerfTest.java b/apct-tests/perftests/core/src/android/libcore/FieldAccessPerfTest.java
index da94ae1..0bebf04 100644
--- a/apct-tests/perftests/core/src/android/libcore/FieldAccessPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/FieldAccessPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/HashedCollectionsPerfTest.java b/apct-tests/perftests/core/src/android/libcore/HashedCollectionsPerfTest.java
index 9446d99c..55c1027 100644
--- a/apct-tests/perftests/core/src/android/libcore/HashedCollectionsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/HashedCollectionsPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTest.java b/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTest.java
index be2a7e9..da60a77 100644
--- a/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/MethodInvocationPerfTest.java b/apct-tests/perftests/core/src/android/libcore/MethodInvocationPerfTest.java
index ca99779..6d9d0c9 100644
--- a/apct-tests/perftests/core/src/android/libcore/MethodInvocationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/MethodInvocationPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/MultiplicationPerfTest.java b/apct-tests/perftests/core/src/android/libcore/MultiplicationPerfTest.java
index 8496fbe..09b0977 100644
--- a/apct-tests/perftests/core/src/android/libcore/MultiplicationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/MultiplicationPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/ReferenceGetPerfTest.java b/apct-tests/perftests/core/src/android/libcore/ReferenceGetPerfTest.java
index bb79424..ba21ed3 100644
--- a/apct-tests/perftests/core/src/android/libcore/ReferenceGetPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/ReferenceGetPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/ReferencePerfTest.java b/apct-tests/perftests/core/src/android/libcore/ReferencePerfTest.java
index 05a3e12..293752e 100644
--- a/apct-tests/perftests/core/src/android/libcore/ReferencePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/ReferencePerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/SmallBigIntegerPerfTest.java b/apct-tests/perftests/core/src/android/libcore/SmallBigIntegerPerfTest.java
index 65a2fdb..528b751 100644
--- a/apct-tests/perftests/core/src/android/libcore/SmallBigIntegerPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/SmallBigIntegerPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/StringDexCachePerfTest.java b/apct-tests/perftests/core/src/android/libcore/StringDexCachePerfTest.java
index 4f5c54d..1f301ac 100644
--- a/apct-tests/perftests/core/src/android/libcore/StringDexCachePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/StringDexCachePerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/StringIterationPerfTest.java b/apct-tests/perftests/core/src/android/libcore/StringIterationPerfTest.java
index 08ad926..4268325 100644
--- a/apct-tests/perftests/core/src/android/libcore/StringIterationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/StringIterationPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java b/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java
index 20f1309..6363e9c 100644
--- a/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/VirtualVersusInterfacePerfTest.java b/apct-tests/perftests/core/src/android/libcore/VirtualVersusInterfacePerfTest.java
index 7e71976..cb3d3ac 100644
--- a/apct-tests/perftests/core/src/android/libcore/VirtualVersusInterfacePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/VirtualVersusInterfacePerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/XmlSerializePerfTest.java b/apct-tests/perftests/core/src/android/libcore/XmlSerializePerfTest.java
index b1b594d..5be8ee6 100644
--- a/apct-tests/perftests/core/src/android/libcore/XmlSerializePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/XmlSerializePerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/XmlSerializerPerfTest.java b/apct-tests/perftests/core/src/android/libcore/XmlSerializerPerfTest.java
index 412cb5a..a37b89d 100644
--- a/apct-tests/perftests/core/src/android/libcore/XmlSerializerPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/XmlSerializerPerfTest.java
@@ -18,9 +18,9 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Xml;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import libcore.util.XmlObjectFactory;
diff --git a/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java b/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java
index 3a45d40..ed669be 100644
--- a/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/ZipFileReadPerfTest.java b/apct-tests/perftests/core/src/android/libcore/ZipFileReadPerfTest.java
index 2e89518..d239a05 100644
--- a/apct-tests/perftests/core/src/android/libcore/ZipFileReadPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/ZipFileReadPerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/AnnotatedElementPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/AnnotatedElementPerfTest.java
index d38d519..487295c 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/AnnotatedElementPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/AnnotatedElementPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/BidiPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/BidiPerfTest.java
index cc56868..adc5d8c 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/BidiPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/BidiPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/BigIntegerPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/BigIntegerPerfTest.java
index 662694b..286d703 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/BigIntegerPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/BigIntegerPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/BitSetPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/BitSetPerfTest.java
index 2c0473e..d646202 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/BitSetPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/BitSetPerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/BreakIteratorPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/BreakIteratorPerfTest.java
index 6a2ce58..b887f40 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/BreakIteratorPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/BreakIteratorPerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/BulkPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/BulkPerfTest.java
index b7b7e83..e4eaf12 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/BulkPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/BulkPerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java
index 9ac36d0..cb2438e 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferScalarVersusVectorPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferScalarVersusVectorPerfTest.java
index 5dd9d6e..9ee927c 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferScalarVersusVectorPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferScalarVersusVectorPerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CharacterPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CharacterPerfTest.java
index 0a59899..e4a4db7 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CharacterPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CharacterPerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CharsetForNamePerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CharsetForNamePerfTest.java
index 8da13a9..858c101 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CharsetForNamePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CharsetForNamePerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CharsetPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CharsetPerfTest.java
index 048c50f..a2fb7d7 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CharsetPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CharsetPerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CharsetUtf8PerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CharsetUtf8PerfTest.java
index b753006..2047444 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CharsetUtf8PerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CharsetUtf8PerfTest.java
@@ -18,8 +18,8 @@
 import android.icu.lang.UCharacter;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/ChecksumPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/ChecksumPerfTest.java
index 1d33fcb..4ce8b41 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/ChecksumPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/ChecksumPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CipherInputStreamPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CipherInputStreamPerfTest.java
index 35730ec..6a7ec1a 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CipherInputStreamPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CipherInputStreamPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java
index 42b0588..238c028 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CollatorPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CollatorPerfTest.java
index 6728e73..7e55660 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CollatorPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CollatorPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CollectionsPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CollectionsPerfTest.java
index 69197c3..100798a 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CollectionsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CollectionsPerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/DateFormatPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/DateFormatPerfTest.java
index 4dba139..b6784a8 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/DateFormatPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/DateFormatPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/DecimalFormatPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/DecimalFormatPerfTest.java
index f3eddab..52f9873 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/DecimalFormatPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/DecimalFormatPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/DecimalFormatSymbolsPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/DecimalFormatSymbolsPerfTest.java
index 2bf0418..6105420 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/DecimalFormatSymbolsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/DecimalFormatSymbolsPerfTest.java
@@ -17,8 +17,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/DefaultCharsetPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/DefaultCharsetPerfTest.java
index c3320a4..fae74a5 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/DefaultCharsetPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/DefaultCharsetPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/DnsPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/DnsPerfTest.java
index 7c52ac4..2915363 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/DnsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/DnsPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/DoPrivilegedPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/DoPrivilegedPerfTest.java
index d133359..dd7e5cc 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/DoPrivilegedPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/DoPrivilegedPerfTest.java
@@ -18,15 +18,14 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/DoublePerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/DoublePerfTest.java
index 38904af..e034a47 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/DoublePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/DoublePerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/EqualsHashCodePerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/EqualsHashCodePerfTest.java
index 8391203..fe1b599 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/EqualsHashCodePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/EqualsHashCodePerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java
index 7712aee..ecbfc71 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/FilePerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/FilePerfTest.java
index 783136a..0c14d64 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/FilePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/FilePerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/FloatPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/FloatPerfTest.java
index a995f5c..7d7d83b 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/FloatPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/FloatPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/FormatterPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/FormatterPerfTest.java
index 94c4f08..08dda53 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/FormatterPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/FormatterPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/IdnPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/IdnPerfTest.java
index c60930f..a09ad80 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/IdnPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/IdnPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/IntConstantDivisionPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/IntConstantDivisionPerfTest.java
index abcc972..be22814 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/IntConstantDivisionPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/IntConstantDivisionPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/IntConstantMultiplicationPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/IntConstantMultiplicationPerfTest.java
index c9f0616..4337c90 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/IntConstantMultiplicationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/IntConstantMultiplicationPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/IntConstantRemainderPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/IntConstantRemainderPerfTest.java
index 78f744c..1b6c502 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/IntConstantRemainderPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/IntConstantRemainderPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/IntegralToStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/IntegralToStringPerfTest.java
index 5129fcb..0aa854e 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/IntegralToStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/IntegralToStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/KeyPairGeneratorPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/KeyPairGeneratorPerfTest.java
index 80c4487..9b3d7a0 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/KeyPairGeneratorPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/KeyPairGeneratorPerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/LoopingBackwardsPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/LoopingBackwardsPerfTest.java
index c9b0cbe..1a9e19a 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/LoopingBackwardsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/LoopingBackwardsPerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/MathPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/MathPerfTest.java
index 4c2d7fb..a8a704c 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/MathPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/MathPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/MessageDigestPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/MessageDigestPerfTest.java
index 2dc947a..6da9666 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/MessageDigestPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/MessageDigestPerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/MutableIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/MutableIntPerfTest.java
index d9d4bb5..060d18f 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/MutableIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/MutableIntPerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/NumberFormatPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/NumberFormatPerfTest.java
index dae185e..7cb3b22 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/NumberFormatPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/NumberFormatPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/NumberFormatTrivialFormatLongPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/NumberFormatTrivialFormatLongPerfTest.java
index 5ff2b22..272b45a 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/NumberFormatTrivialFormatLongPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/NumberFormatTrivialFormatLongPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/PriorityQueuePerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/PriorityQueuePerfTest.java
index 48450b4..c3a0966 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/PriorityQueuePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/PriorityQueuePerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/PropertyAccessPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/PropertyAccessPerfTest.java
index 21ccba5..2ac56be 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/PropertyAccessPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/PropertyAccessPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/ProviderPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/ProviderPerfTest.java
index f7bcf12..7ad0141 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/ProviderPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/ProviderPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/RandomPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/RandomPerfTest.java
index d8bff4c..c7b6cb5 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/RandomPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/RandomPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/RealToStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/RealToStringPerfTest.java
index 2542df9..44e5f22 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/RealToStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/RealToStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/ReflectionPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/ReflectionPerfTest.java
index b06662c..6e00b1083 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/ReflectionPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/ReflectionPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/SSLLoopbackPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/SSLLoopbackPerfTest.java
index 694d609..5a9b5c3 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/SSLLoopbackPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/SSLLoopbackPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import libcore.java.security.TestKeyStore;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/SSLSocketFactoryPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/SSLSocketFactoryPerfTest.java
index bdbbcb0..6d48cf2 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/SSLSocketFactoryPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/SSLSocketFactoryPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/SchemePrefixPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/SchemePrefixPerfTest.java
index 5ad62de..8641629 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/SchemePrefixPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/SchemePrefixPerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/SerializationPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/SerializationPerfTest.java
index 1ec22d2..afd1191 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/SerializationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/SerializationPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/SignaturePerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/SignaturePerfTest.java
index a9a0788..6c26133 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/SignaturePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/SignaturePerfTest.java
@@ -17,7 +17,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/SimpleDateFormatPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/SimpleDateFormatPerfTest.java
index c25b0ce..274b51f 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/SimpleDateFormatPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/SimpleDateFormatPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StrictMathPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StrictMathPerfTest.java
index eeccb5b..b4c427b 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StrictMathPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StrictMathPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringBuilderPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringBuilderPerfTest.java
index 10fa8b9..2235cc5 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringBuilderPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringBuilderPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringEqualsPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringEqualsPerfTest.java
index 6854c0d..9ab5000 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringEqualsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringEqualsPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import junit.framework.Assert;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringIsEmptyPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringIsEmptyPerfTest.java
index 79ff646..b1e749c 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringIsEmptyPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringIsEmptyPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringLengthPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringLengthPerfTest.java
index 8dbf9f5..9e57591 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringLengthPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringLengthPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringPerfTest.java
index 36db014..a80514c 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringPerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringReplaceAllPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringReplaceAllPerfTest.java
index 5b4423a..78ae395 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringReplaceAllPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringReplaceAllPerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringReplacePerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringReplacePerfTest.java
index 4d5c792..73911c7 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringReplacePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringReplacePerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringSplitPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringSplitPerfTest.java
index 2bb25ac..1539271 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringSplitPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringSplitPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringToBytesPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringToBytesPerfTest.java
index c004d95..0d5e62b 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringToBytesPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringToBytesPerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringToRealPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringToRealPerfTest.java
index 15516fc..ecdf809 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringToRealPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringToRealPerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/ThreadLocalPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/ThreadLocalPerfTest.java
index f256555..2b2a6b5 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/ThreadLocalPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/ThreadLocalPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/TimeZonePerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/TimeZonePerfTest.java
index 8274512..6eb8fcc 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/TimeZonePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/TimeZonePerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/XMLEntitiesPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/XMLEntitiesPerfTest.java
index ae1e8bc..288c646 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/XMLEntitiesPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/XMLEntitiesPerfTest.java
@@ -18,7 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectGetFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectGetFieldLittleEndianIntPerfTest.java
index e7bb8f8..003c957 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectGetFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectGetFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectGetFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectGetFieldLittleEndianStringPerfTest.java
index 5bac46a..4f21618 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectGetFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectGetFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectGetStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectGetStaticFieldLittleEndianIntPerfTest.java
index 1005a70..210014a 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectGetStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectGetStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectGetStaticFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectGetStaticFieldLittleEndianStringPerfTest.java
index 5224ad3..22c6827 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectGetStaticFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectGetStaticFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectSetFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectSetFieldLittleEndianIntPerfTest.java
index 06696ef..5b39109 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectSetFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectSetFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectSetFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectSetFieldLittleEndianStringPerfTest.java
index a784c52..883e8a7 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectSetFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectSetFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectSetStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectSetStaticFieldLittleEndianIntPerfTest.java
index 4ce0078..50bc85c 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectSetStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectSetStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectSetStaticFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectSetStaticFieldLittleEndianStringPerfTest.java
index 587e201..13fa2bf 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectSetStaticFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/ReflectSetStaticFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeAcquireFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeAcquireFieldLittleEndianIntPerfTest.java
index e06b534..85c9bae9 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeAcquireFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeAcquireFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeAcquireFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeAcquireFieldLittleEndianStringPerfTest.java
index 0fd16a0..2b8f430 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeAcquireFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeAcquireFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeAcquireStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeAcquireStaticFieldLittleEndianIntPerfTest.java
index 7ad42d0..246fa43 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeAcquireStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeAcquireStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeAcquireStaticFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeAcquireStaticFieldLittleEndianStringPerfTest.java
index 76e1f47..d12ffae 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeAcquireStaticFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeAcquireStaticFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeFieldLittleEndianIntPerfTest.java
index b4b7840..5ced115 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeFieldLittleEndianStringPerfTest.java
index 09ed167..b955d50 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeReleaseFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeReleaseFieldLittleEndianIntPerfTest.java
index 920d2e4..601ff34 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeReleaseFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeReleaseFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeReleaseFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeReleaseFieldLittleEndianStringPerfTest.java
index 55ed789..0e567f9 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeReleaseFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeReleaseFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeReleaseStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeReleaseStaticFieldLittleEndianIntPerfTest.java
index ea3057b..6be2870 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeReleaseStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeReleaseStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeReleaseStaticFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeReleaseStaticFieldLittleEndianStringPerfTest.java
index 20558aa9..84c186b 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeReleaseStaticFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeReleaseStaticFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeStaticFieldLittleEndianIntPerfTest.java
index d7b1d29..b093234 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeStaticFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeStaticFieldLittleEndianStringPerfTest.java
index d138dc9..0d2037b4 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeStaticFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandexchangeStaticFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandsetFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandsetFieldLittleEndianIntPerfTest.java
index 36153f2..ee31973 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandsetFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandsetFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandsetFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandsetFieldLittleEndianStringPerfTest.java
index bf4fbc4..0571fef 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandsetFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandsetFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandsetStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandsetStaticFieldLittleEndianIntPerfTest.java
index d3c1b36..f619dab 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandsetStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandsetStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandsetStaticFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandsetStaticFieldLittleEndianStringPerfTest.java
index 90e69a1..fc443fa 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandsetStaticFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleCompareandsetStaticFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetAcquireFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetAcquireFieldLittleEndianIntPerfTest.java
index 96bc104..bf3d58b 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetAcquireFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetAcquireFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetAcquireFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetAcquireFieldLittleEndianStringPerfTest.java
index 2679494..1f4bc31 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetAcquireFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetAcquireFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetAcquireStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetAcquireStaticFieldLittleEndianIntPerfTest.java
index 170dce7..2085552 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetAcquireStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetAcquireStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetAcquireStaticFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetAcquireStaticFieldLittleEndianStringPerfTest.java
index 11d12db..d9c7d7b 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetAcquireStaticFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetAcquireStaticFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetArrayLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetArrayLittleEndianIntPerfTest.java
index bd2a600..acd2533 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetArrayLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetArrayLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetArrayLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetArrayLittleEndianStringPerfTest.java
index 99a09cd..de9944a 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetArrayLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetArrayLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetByteArrayViewBigEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetByteArrayViewBigEndianIntPerfTest.java
index db83606..a863929 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetByteArrayViewBigEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetByteArrayViewBigEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetByteArrayViewLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetByteArrayViewLittleEndianIntPerfTest.java
index 4a8f924..4999b9b 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetByteArrayViewLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetByteArrayViewLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetFieldLittleEndianIntPerfTest.java
index 4e4a9ee..ee80a6f 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetFieldLittleEndianStringPerfTest.java
index 3e7de1d..ec29f7a 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetOpaqueFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetOpaqueFieldLittleEndianIntPerfTest.java
index 67d53b3..ee6a669 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetOpaqueFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetOpaqueFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetOpaqueFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetOpaqueFieldLittleEndianStringPerfTest.java
index 470a1ce..1702b84 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetOpaqueFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetOpaqueFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetOpaqueStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetOpaqueStaticFieldLittleEndianIntPerfTest.java
index 8a982c2..514ddb9 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetOpaqueStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetOpaqueStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetOpaqueStaticFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetOpaqueStaticFieldLittleEndianStringPerfTest.java
index 2c17a69..fbcee69 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetOpaqueStaticFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetOpaqueStaticFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetStaticFieldLittleEndianIntPerfTest.java
index 099b1f4..2c56588 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetStaticFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetStaticFieldLittleEndianStringPerfTest.java
index 7f6b4b8..8fce69e 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetStaticFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetStaticFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetVolatileFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetVolatileFieldLittleEndianIntPerfTest.java
index 8592d30..ef530607 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetVolatileFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetVolatileFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetVolatileFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetVolatileFieldLittleEndianStringPerfTest.java
index 539bd2a..64c0898 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetVolatileFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetVolatileFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetVolatileStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetVolatileStaticFieldLittleEndianIntPerfTest.java
index a36a7b6..939100c 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetVolatileStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetVolatileStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetVolatileStaticFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetVolatileStaticFieldLittleEndianStringPerfTest.java
index 90d2a70..728b199 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetVolatileStaticFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetVolatileStaticFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddAcquireFieldLittleEndianFloatPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddAcquireFieldLittleEndianFloatPerfTest.java
index 4e5fcf3..bf5ef99 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddAcquireFieldLittleEndianFloatPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddAcquireFieldLittleEndianFloatPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddAcquireFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddAcquireFieldLittleEndianIntPerfTest.java
index fd0abf8..d15705e 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddAcquireFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddAcquireFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddAcquireStaticFieldLittleEndianFloatPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddAcquireStaticFieldLittleEndianFloatPerfTest.java
index 9272b11..222a60d 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddAcquireStaticFieldLittleEndianFloatPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddAcquireStaticFieldLittleEndianFloatPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddAcquireStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddAcquireStaticFieldLittleEndianIntPerfTest.java
index a896d0a..7436476 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddAcquireStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddAcquireStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddFieldLittleEndianFloatPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddFieldLittleEndianFloatPerfTest.java
index 671b0a3..cca97f4 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddFieldLittleEndianFloatPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddFieldLittleEndianFloatPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddFieldLittleEndianIntPerfTest.java
index 1eb3f92..170ee73 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddReleaseFieldLittleEndianFloatPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddReleaseFieldLittleEndianFloatPerfTest.java
index f23d5e2..184f796 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddReleaseFieldLittleEndianFloatPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddReleaseFieldLittleEndianFloatPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddReleaseFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddReleaseFieldLittleEndianIntPerfTest.java
index 1613798..7e75c44 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddReleaseFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddReleaseFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddReleaseStaticFieldLittleEndianFloatPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddReleaseStaticFieldLittleEndianFloatPerfTest.java
index 14f1c00..39c386b 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddReleaseStaticFieldLittleEndianFloatPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddReleaseStaticFieldLittleEndianFloatPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddReleaseStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddReleaseStaticFieldLittleEndianIntPerfTest.java
index 8327caf..04ab531 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddReleaseStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddReleaseStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddStaticFieldLittleEndianFloatPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddStaticFieldLittleEndianFloatPerfTest.java
index 6c211fb..b71351f 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddStaticFieldLittleEndianFloatPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddStaticFieldLittleEndianFloatPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddStaticFieldLittleEndianIntPerfTest.java
index d02cd73..e3955c0 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandaddStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndAcquireFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndAcquireFieldLittleEndianIntPerfTest.java
index 0777586..adf05a6 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndAcquireFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndAcquireFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndAcquireStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndAcquireStaticFieldLittleEndianIntPerfTest.java
index 24a949f..4d657d9 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndAcquireStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndAcquireStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndFieldLittleEndianIntPerfTest.java
index 4b94bbe..dc64174 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndReleaseFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndReleaseFieldLittleEndianIntPerfTest.java
index 1784c05..25d5631 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndReleaseFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndReleaseFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndReleaseStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndReleaseStaticFieldLittleEndianIntPerfTest.java
index f85d3ee..de2d548 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndReleaseStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndReleaseStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndStaticFieldLittleEndianIntPerfTest.java
index 81f6779..36544c6 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseAndStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrAcquireFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrAcquireFieldLittleEndianIntPerfTest.java
index 9436fad..fb36d0c 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrAcquireFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrAcquireFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrAcquireStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrAcquireStaticFieldLittleEndianIntPerfTest.java
index 9ebc458..4194b12 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrAcquireStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrAcquireStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrFieldLittleEndianIntPerfTest.java
index ea159a1..355c6e8 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrReleaseFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrReleaseFieldLittleEndianIntPerfTest.java
index a42ec7e..401079d 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrReleaseFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrReleaseFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrReleaseStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrReleaseStaticFieldLittleEndianIntPerfTest.java
index 6f1007e5..322dcbf 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrReleaseStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrReleaseStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrStaticFieldLittleEndianIntPerfTest.java
index 6a73818..c982814 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseOrStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorAcquireFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorAcquireFieldLittleEndianIntPerfTest.java
index e9a365b..0b1cb32 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorAcquireFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorAcquireFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorAcquireStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorAcquireStaticFieldLittleEndianIntPerfTest.java
index fc9191c..4737072 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorAcquireStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorAcquireStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorFieldLittleEndianIntPerfTest.java
index 5919a1d..204cd70 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorReleaseFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorReleaseFieldLittleEndianIntPerfTest.java
index 313e580..b3ffed7 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorReleaseFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorReleaseFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorReleaseStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorReleaseStaticFieldLittleEndianIntPerfTest.java
index 9c8b3ae..d0ab8de 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorReleaseStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorReleaseStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorStaticFieldLittleEndianIntPerfTest.java
index ea618cc..b378b68 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandbitwiseXorStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetAcquireFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetAcquireFieldLittleEndianIntPerfTest.java
index df6f470..c7c66fe 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetAcquireFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetAcquireFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetAcquireFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetAcquireFieldLittleEndianStringPerfTest.java
index 63fd740..98d6bd7 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetAcquireFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetAcquireFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetAcquireStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetAcquireStaticFieldLittleEndianIntPerfTest.java
index a96031e..206358f 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetAcquireStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetAcquireStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetAcquireStaticFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetAcquireStaticFieldLittleEndianStringPerfTest.java
index 3bc25fb..0532e73 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetAcquireStaticFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetAcquireStaticFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetFieldLittleEndianIntPerfTest.java
index 7ffdf11..f192d71 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetFieldLittleEndianStringPerfTest.java
index cc7f3be..0a8909c 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetReleaseFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetReleaseFieldLittleEndianIntPerfTest.java
index 8d54c00..bfcb0f4 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetReleaseFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetReleaseFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetReleaseFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetReleaseFieldLittleEndianStringPerfTest.java
index 22e92dd..c6b0509 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetReleaseFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetReleaseFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetReleaseStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetReleaseStaticFieldLittleEndianIntPerfTest.java
index 08ddc8b..45a01ed 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetReleaseStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetReleaseStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetReleaseStaticFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetReleaseStaticFieldLittleEndianStringPerfTest.java
index 429e090..3047281 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetReleaseStaticFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetReleaseStaticFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetStaticFieldLittleEndianIntPerfTest.java
index d5b31f6..6f1f1a0 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetStaticFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetStaticFieldLittleEndianStringPerfTest.java
index 8667aaa..c4d279f 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetStaticFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleGetandsetStaticFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetArrayLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetArrayLittleEndianIntPerfTest.java
index aa20246..c4f6005 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetArrayLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetArrayLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetArrayLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetArrayLittleEndianStringPerfTest.java
index 9e0210f..a6858c2 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetArrayLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetArrayLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetByteArrayViewBigEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetByteArrayViewBigEndianIntPerfTest.java
index d489168..a994cbe 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetByteArrayViewBigEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetByteArrayViewBigEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetByteArrayViewLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetByteArrayViewLittleEndianIntPerfTest.java
index b06d7ef..65412ec 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetByteArrayViewLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetByteArrayViewLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetFieldLittleEndianIntPerfTest.java
index 8446937..573b0ff 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetFieldLittleEndianStringPerfTest.java
index 34540a3..fe3c0fc 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetOpaqueFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetOpaqueFieldLittleEndianIntPerfTest.java
index c79b513..f398899 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetOpaqueFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetOpaqueFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetOpaqueFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetOpaqueFieldLittleEndianStringPerfTest.java
index 028130d..7493120 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetOpaqueFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetOpaqueFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetOpaqueStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetOpaqueStaticFieldLittleEndianIntPerfTest.java
index 06a5a8c..5e73269 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetOpaqueStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetOpaqueStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetOpaqueStaticFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetOpaqueStaticFieldLittleEndianStringPerfTest.java
index 78eefc8..9a217d1 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetOpaqueStaticFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetOpaqueStaticFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetReleaseFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetReleaseFieldLittleEndianIntPerfTest.java
index cd1bd48..1ce2270 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetReleaseFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetReleaseFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetReleaseFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetReleaseFieldLittleEndianStringPerfTest.java
index 6c0740c..ed84528 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetReleaseFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetReleaseFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetReleaseStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetReleaseStaticFieldLittleEndianIntPerfTest.java
index b95f24b..aeb9640 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetReleaseStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetReleaseStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetReleaseStaticFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetReleaseStaticFieldLittleEndianStringPerfTest.java
index b03cf82..8959a0c 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetReleaseStaticFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetReleaseStaticFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetStaticFieldLittleEndianIntPerfTest.java
index c98c092..4007722 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetStaticFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetStaticFieldLittleEndianStringPerfTest.java
index 625cfc7..7323158 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetStaticFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetStaticFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetVolatileFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetVolatileFieldLittleEndianIntPerfTest.java
index 58319b3..f4119c2 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetVolatileFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetVolatileFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetVolatileFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetVolatileFieldLittleEndianStringPerfTest.java
index f741542..9b9c261 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetVolatileFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetVolatileFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetVolatileStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetVolatileStaticFieldLittleEndianIntPerfTest.java
index 87f6a78..f125384 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetVolatileStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetVolatileStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetVolatileStaticFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetVolatileStaticFieldLittleEndianStringPerfTest.java
index 610345f..2ad605d 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetVolatileStaticFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleSetVolatileStaticFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetAcquireFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetAcquireFieldLittleEndianIntPerfTest.java
index 519d4fd..5ef3bf0 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetAcquireFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetAcquireFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetAcquireFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetAcquireFieldLittleEndianStringPerfTest.java
index 322cf63..0c4ed66 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetAcquireFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetAcquireFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetAcquireStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetAcquireStaticFieldLittleEndianIntPerfTest.java
index f8ccbad..db6bd24 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetAcquireStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetAcquireStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetAcquireStaticFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetAcquireStaticFieldLittleEndianStringPerfTest.java
index 16f1059..d2b0bf7 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetAcquireStaticFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetAcquireStaticFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetFieldLittleEndianIntPerfTest.java
index c7084fe..3cd5ae6 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetFieldLittleEndianStringPerfTest.java
index 9d526b8..6ddfc25 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetPlainFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetPlainFieldLittleEndianIntPerfTest.java
index 8372f6c..375f0bc 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetPlainFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetPlainFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetPlainFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetPlainFieldLittleEndianStringPerfTest.java
index 87e47e7..7e2492a 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetPlainFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetPlainFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetPlainStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetPlainStaticFieldLittleEndianIntPerfTest.java
index aa2e104..190118c 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetPlainStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetPlainStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetPlainStaticFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetPlainStaticFieldLittleEndianStringPerfTest.java
index ebaa080..484ba1b 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetPlainStaticFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetPlainStaticFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetReleaseFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetReleaseFieldLittleEndianIntPerfTest.java
index d90356a..80e4e15 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetReleaseFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetReleaseFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetReleaseFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetReleaseFieldLittleEndianStringPerfTest.java
index 6db995a..fa26c59 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetReleaseFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetReleaseFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetReleaseStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetReleaseStaticFieldLittleEndianIntPerfTest.java
index ecea19e..16bf2a20 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetReleaseStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetReleaseStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetReleaseStaticFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetReleaseStaticFieldLittleEndianStringPerfTest.java
index ab86284..e1716de 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetReleaseStaticFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetReleaseStaticFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetStaticFieldLittleEndianIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetStaticFieldLittleEndianIntPerfTest.java
index 23a33f5..dc6f2ad 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetStaticFieldLittleEndianIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetStaticFieldLittleEndianIntPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetStaticFieldLittleEndianStringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetStaticFieldLittleEndianStringPerfTest.java
index 270b5ad..d1096c6 100644
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetStaticFieldLittleEndianStringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/VarHandleWeakcompareandsetStaticFieldLittleEndianStringPerfTest.java
@@ -18,8 +18,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
diff --git a/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java b/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java
index ba15796..fc3738c 100644
--- a/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java
+++ b/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java
@@ -53,6 +53,7 @@
 import com.android.adservices.service.adselection.AdWithBidArgumentUtil;
 import com.android.adservices.service.adselection.CustomAudienceBiddingSignalsArgumentUtil;
 import com.android.adservices.service.adselection.CustomAudienceScoringSignalsArgumentUtil;
+import com.android.adservices.service.common.NoOpRetryStrategyImpl;
 import com.android.adservices.service.js.IsolateSettings;
 import com.android.adservices.service.js.JSScriptArgument;
 import com.android.adservices.service.js.JSScriptArrayArgument;
@@ -411,7 +412,8 @@
                 jsScript,
                 args,
                 functionName,
-                IsolateSettings.forMaxHeapSizeEnforcementDisabled());
+                IsolateSettings.forMaxHeapSizeEnforcementDisabled(),
+                new NoOpRetryStrategyImpl());
         result.addListener(resultLatch::countDown, sExecutorService);
         return result;
     }
@@ -430,7 +432,8 @@
                 wasmScript,
                 args,
                 functionName,
-                IsolateSettings.forMaxHeapSizeEnforcementDisabled());
+                IsolateSettings.forMaxHeapSizeEnforcementDisabled(),
+                new NoOpRetryStrategyImpl());
         result.addListener(resultLatch::countDown, sExecutorService);
         return result;
     }
diff --git a/apex/jobscheduler/service/Android.bp b/apex/jobscheduler/service/Android.bp
index 5586295..ace56d4 100644
--- a/apex/jobscheduler/service/Android.bp
+++ b/apex/jobscheduler/service/Android.bp
@@ -20,7 +20,9 @@
     ],
 
     libs: [
+        "androidx.annotation_annotation",
         "app-compat-annotations",
+        "error_prone_annotations",
         "framework",
         "services.core",
         "unsupportedappusage",
diff --git a/apex/jobscheduler/service/aconfig/alarm.aconfig b/apex/jobscheduler/service/aconfig/alarm.aconfig
index 3b9b4e7..bb0f3cb 100644
--- a/apex/jobscheduler/service/aconfig/alarm.aconfig
+++ b/apex/jobscheduler/service/aconfig/alarm.aconfig
@@ -9,3 +9,10 @@
       purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "start_user_before_scheduled_alarms"
+    namespace: "multiuser"
+    description: "Persist list of users with alarms scheduled and wakeup stopped users before alarms are due"
+    bug: "314907186"
+}
diff --git a/apex/jobscheduler/service/aconfig/device_idle.aconfig b/apex/jobscheduler/service/aconfig/device_idle.aconfig
index fc24b30..e4cb5ad 100644
--- a/apex/jobscheduler/service/aconfig/device_idle.aconfig
+++ b/apex/jobscheduler/service/aconfig/device_idle.aconfig
@@ -4,5 +4,5 @@
     name: "disable_wakelocks_in_light_idle"
     namespace: "backstage_power"
     description: "Disable wakelocks for background apps while Light Device Idle is active"
-    bug: "299329948"
+    bug: "326607666"
 }
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index ef9ac73..5e6d377 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -4,7 +4,7 @@
     name: "batch_active_bucket_jobs"
     namespace: "backstage_power"
     description: "Include jobs in the ACTIVE bucket in the job batching effort. Don't let them run as freely as they're ready."
-    bug: "299329948"
+    bug: "326607666"
 }
 
 flag {
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 e728a2c..d0a1b02 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -183,6 +183,9 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.zone.ZoneOffsetTransition;
+import java.time.zone.ZoneRules;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
@@ -194,6 +197,7 @@
 import java.util.TimeZone;
 import java.util.TreeSet;
 import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
 
 /**
@@ -234,8 +238,12 @@
 
     private static final long TEMPORARY_QUOTA_DURATION = INTERVAL_DAY;
 
-    // System property read on some device configurations to initialize time properly.
+    // System properties read on some device configurations to initialize time properly and
+    // perform DST transitions at the bootloader level.
     private static final String TIMEOFFSET_PROPERTY = "persist.sys.time.offset";
+    private static final String DST_TRANSITION_PROPERTY = "persist.sys.time.dst_transition";
+    private static final String DST_OFFSET_PROPERTY = "persist.sys.time.dst_offset";
+
 
     private final Intent mBackgroundIntent
             = new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND);
@@ -2200,6 +2208,19 @@
 
             final int gmtOffset = newZone.getOffset(mInjector.getCurrentTimeMillis());
             SystemProperties.set(TIMEOFFSET_PROPERTY, String.valueOf(gmtOffset));
+
+            final ZoneRules rules = newZone.toZoneId().getRules();
+            final ZoneOffsetTransition transition = rules.nextTransition(Instant.now());
+            if (null != transition) {
+                // Get the offset between the time after the DST transition and before.
+                final long transitionOffset = TimeUnit.SECONDS.toMillis((
+                        transition.getOffsetAfter().getTotalSeconds()
+                        - transition.getOffsetBefore().getTotalSeconds()));
+                // Time when the next DST transition is programmed.
+                final long nextTransition = TimeUnit.SECONDS.toMillis(transition.toEpochSecond());
+                SystemProperties.set(DST_TRANSITION_PROPERTY, String.valueOf(nextTransition));
+                SystemProperties.set(DST_OFFSET_PROPERTY, String.valueOf(transitionOffset));
+            }
         }
 
         // Clear the default time zone in the system server process. This forces the next call
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index e96d07f..ee9400f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -46,8 +46,11 @@
 import android.os.PowerManager;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccSlotMapping;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
+import android.util.IntArray;
 import android.util.KeyValueListParser;
 import android.util.Log;
 import android.util.Slog;
@@ -68,6 +71,8 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.Set;
 import java.util.function.Predicate;
 
 /**
@@ -1620,9 +1625,21 @@
         private final Object mSatLock = new Object();
 
         private DeviceIdleInternal mDeviceIdleInternal;
+        private TelephonyManager mTelephonyManager;
+
+        private final boolean mHasFeatureTelephonySubscription;
 
         /** Set of all apps that have been deemed special, keyed by user ID. */
         private final SparseSetArray<String> mSpecialApps = new SparseSetArray<>();
+        /**
+         * Set of carrier privileged apps, keyed by the logical ID of the SIM their privileged
+         * for.
+         */
+        @GuardedBy("mSatLock")
+        private final SparseSetArray<String> mCarrierPrivilegedApps = new SparseSetArray<>();
+        @GuardedBy("mSatLock")
+        private final SparseArray<LogicalIndexCarrierPrivilegesCallback>
+                mCarrierPrivilegedCallbacks = new SparseArray<>();
         @GuardedBy("mSatLock")
         private final ArraySet<String> mPowerAllowlistedApps = new ArraySet<>();
 
@@ -1630,6 +1647,10 @@
             @Override
             public void onReceive(Context context, Intent intent) {
                 switch (intent.getAction()) {
+                    case TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED:
+                        updateCarrierPrivilegedCallbackRegistration();
+                        break;
+
                     case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
                         mHandler.post(SpecialAppTracker.this::updatePowerAllowlistCache);
                         break;
@@ -1637,6 +1658,11 @@
             }
         };
 
+        SpecialAppTracker() {
+            mHasFeatureTelephonySubscription = mContext.getPackageManager()
+                    .hasSystemFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION);
+        }
+
         public boolean isSpecialApp(final int userId, @NonNull String packageName) {
             synchronized (mSatLock) {
                 if (mSpecialApps.contains(UserHandle.USER_ALL, packageName)) {
@@ -1654,6 +1680,12 @@
                 if (mPowerAllowlistedApps.contains(packageName)) {
                     return true;
                 }
+                for (int l = mCarrierPrivilegedApps.size() - 1; l >= 0; --l) {
+                    if (mCarrierPrivilegedApps.contains(
+                            mCarrierPrivilegedApps.keyAt(l), packageName)) {
+                        return true;
+                    }
+                }
             }
             return false;
         }
@@ -1669,9 +1701,12 @@
 
         private void onSystemServicesReady() {
             mDeviceIdleInternal = LocalServices.getService(DeviceIdleInternal.class);
+            mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
 
             synchronized (mLock) {
                 if (mFlexibilityEnabled) {
+                    mHandler.post(
+                            SpecialAppTracker.this::updateCarrierPrivilegedCallbackRegistration);
                     mHandler.post(SpecialAppTracker.this::updatePowerAllowlistCache);
                 }
             }
@@ -1686,6 +1721,13 @@
         private void startTracking() {
             IntentFilter filter = new IntentFilter(
                     PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
+
+            if (mHasFeatureTelephonySubscription) {
+                filter.addAction(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
+
+                updateCarrierPrivilegedCallbackRegistration();
+            }
+
             mContext.registerReceiver(mBroadcastReceiver, filter);
 
             updatePowerAllowlistCache();
@@ -1695,11 +1737,63 @@
             mContext.unregisterReceiver(mBroadcastReceiver);
 
             synchronized (mSatLock) {
+                mCarrierPrivilegedApps.clear();
                 mPowerAllowlistedApps.clear();
                 mSpecialApps.clear();
+
+                for (int i = mCarrierPrivilegedCallbacks.size() - 1; i >= 0; --i) {
+                    mTelephonyManager.unregisterCarrierPrivilegesCallback(
+                            mCarrierPrivilegedCallbacks.valueAt(i));
+                }
+                mCarrierPrivilegedCallbacks.clear();
             }
         }
 
+        private void updateCarrierPrivilegedCallbackRegistration() {
+            if (mTelephonyManager == null) {
+                return;
+            }
+            if (!mHasFeatureTelephonySubscription) {
+                return;
+            }
+
+            Collection<UiccSlotMapping> simSlotMapping = mTelephonyManager.getSimSlotMapping();
+            final ArraySet<String> changedPkgs = new ArraySet<>();
+            synchronized (mSatLock) {
+                final IntArray callbacksToRemove = new IntArray();
+                for (int i = mCarrierPrivilegedCallbacks.size() - 1; i >= 0; --i) {
+                    callbacksToRemove.add(mCarrierPrivilegedCallbacks.keyAt(i));
+                }
+                for (UiccSlotMapping mapping : simSlotMapping) {
+                    final int logicalIndex = mapping.getLogicalSlotIndex();
+                    if (mCarrierPrivilegedCallbacks.contains(logicalIndex)) {
+                        // Callback already exists. No need to create a new one or remove it.
+                        callbacksToRemove.remove(logicalIndex);
+                        continue;
+                    }
+                    final LogicalIndexCarrierPrivilegesCallback callback =
+                            new LogicalIndexCarrierPrivilegesCallback(logicalIndex);
+                    mCarrierPrivilegedCallbacks.put(logicalIndex, callback);
+                    // Upon registration, the callbacks will be called with the current list of
+                    // apps, so there's no need to query the app list synchronously.
+                    mTelephonyManager.registerCarrierPrivilegesCallback(logicalIndex,
+                            AppSchedulingModuleThread.getExecutor(), callback);
+                }
+
+                for (int i = callbacksToRemove.size() - 1; i >= 0; --i) {
+                    final int logicalIndex = callbacksToRemove.get(i);
+                    final LogicalIndexCarrierPrivilegesCallback callback =
+                            mCarrierPrivilegedCallbacks.get(logicalIndex);
+                    mTelephonyManager.unregisterCarrierPrivilegesCallback(callback);
+                    mCarrierPrivilegedCallbacks.remove(logicalIndex);
+                    changedPkgs.addAll(mCarrierPrivilegedApps.get(logicalIndex));
+                    mCarrierPrivilegedApps.remove(logicalIndex);
+                }
+            }
+
+            updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs);
+        }
+
         /**
          * Update the processed special app set for the specified user ID, only looking at the
          * specified set of apps. This method must <b>NEVER</b> be called while holding
@@ -1762,18 +1856,65 @@
             updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs);
         }
 
+        class LogicalIndexCarrierPrivilegesCallback implements
+                TelephonyManager.CarrierPrivilegesCallback {
+            public final int logicalIndex;
+
+            LogicalIndexCarrierPrivilegesCallback(int logicalIndex) {
+                this.logicalIndex = logicalIndex;
+            }
+
+            @Override
+            public void onCarrierPrivilegesChanged(@NonNull Set<String> privilegedPackageNames,
+                    @NonNull Set<Integer> privilegedUids) {
+                final ArraySet<String> changedPkgs = new ArraySet<>();
+                synchronized (mSatLock) {
+                    final ArraySet<String> oldPrivilegedSet =
+                            mCarrierPrivilegedApps.get(logicalIndex);
+                    if (oldPrivilegedSet != null) {
+                        changedPkgs.addAll(oldPrivilegedSet);
+                        mCarrierPrivilegedApps.remove(logicalIndex);
+                    }
+                    for (String pkgName : privilegedPackageNames) {
+                        mCarrierPrivilegedApps.add(logicalIndex, pkgName);
+                        if (!changedPkgs.remove(pkgName)) {
+                            // The package wasn't in the previous set of privileged apps. Add it
+                            // since its state has changed.
+                            changedPkgs.add(pkgName);
+                        }
+                    }
+                }
+
+                // The carrier privileged list doesn't provide a simple userId correlation,
+                // so for now, use USER_ALL for these packages.
+                // TODO(141645789): use the UID list to narrow down to specific userIds
+                updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs);
+            }
+        }
+
         public void dump(@NonNull IndentingPrintWriter pw) {
             pw.println("Special apps:");
             pw.increaseIndent();
 
             synchronized (mSatLock) {
                 for (int u = 0; u < mSpecialApps.size(); ++u) {
+                    pw.print("User ");
                     pw.print(mSpecialApps.keyAt(u));
                     pw.print(": ");
                     pw.println(mSpecialApps.valuesAt(u));
                 }
 
                 pw.println();
+                pw.println("Carrier privileged packages:");
+                pw.increaseIndent();
+                for (int i = 0; i < mCarrierPrivilegedApps.size(); ++i) {
+                    pw.print(mCarrierPrivilegedApps.keyAt(i));
+                    pw.print(": ");
+                    pw.println(mCarrierPrivilegedApps.valuesAt(i));
+                }
+                pw.decreaseIndent();
+
+                pw.println();
                 pw.print("Power allowlisted packages: ");
                 pw.println(mPowerAllowlistedApps);
             }
diff --git a/api/Android.bp b/api/Android.bp
index 8e06366..093ee4b 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -298,6 +298,28 @@
     "org.xmlpull",
 ]
 
+// These are libs from framework-internal-utils that are required (i.e. being referenced)
+// from framework-non-updatable-sources. Add more here when there's a need.
+// DO NOT add the entire framework-internal-utils. It might cause unnecessary circular
+// dependencies gets bigger.
+android_non_updatable_stubs_libs = [
+    "android.hardware.cas-V1.2-java",
+    "android.hardware.health-V1.0-java-constants",
+    "android.hardware.thermal-V1.0-java-constants",
+    "android.hardware.thermal-V2.0-java",
+    "android.hardware.tv.input-V1.0-java-constants",
+    "android.hardware.usb-V1.0-java-constants",
+    "android.hardware.usb-V1.1-java-constants",
+    "android.hardware.usb.gadget-V1.0-java",
+    "android.hardware.vibrator-V1.3-java",
+    "framework-protos",
+]
+
+java_defaults {
+    name: "android-non-updatable-stubs-libs-defaults",
+    libs: android_non_updatable_stubs_libs,
+}
+
 // Defaults for all stubs that include the non-updatable framework. These defaults do not include
 // module symbols, so will not compile correctly on their own. Users must add module APIs to the
 // classpath (or sources) somehow.
@@ -329,18 +351,7 @@
     // from framework-non-updatable-sources. Add more here when there's a need.
     // DO NOT add the entire framework-internal-utils. It might cause unnecessary circular
     // dependencies gets bigger.
-    libs: [
-        "android.hardware.cas-V1.2-java",
-        "android.hardware.health-V1.0-java-constants",
-        "android.hardware.thermal-V1.0-java-constants",
-        "android.hardware.thermal-V2.0-java",
-        "android.hardware.tv.input-V1.0-java-constants",
-        "android.hardware.usb-V1.0-java-constants",
-        "android.hardware.usb-V1.1-java-constants",
-        "android.hardware.usb.gadget-V1.0-java",
-        "android.hardware.vibrator-V1.3-java",
-        "framework-protos",
-    ],
+    libs: android_non_updatable_stubs_libs,
     flags: [
         "--error NoSettingsProvider",
         "--error UnhiddenSystemApi",
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 1f5b83c..c1add03 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -458,13 +458,21 @@
     libs: ["stub-annotations"],
 }
 
+java_defaults {
+    name: "android-non-updatable_everything_from_text_defaults",
+    defaults: [
+        "android-non-updatable_from_text_defaults",
+    ],
+    stubs_type: "everything",
+}
+
 java_api_library {
     name: "android-non-updatable.stubs.from-text",
     api_surface: "public",
     api_contributions: [
         "api-stubs-docs-non-updatable.api.contribution",
     ],
-    defaults: ["android-non-updatable_from_text_defaults"],
+    defaults: ["android-non-updatable_everything_from_text_defaults"],
     full_api_surface_stub: "android_stubs_current.from-text",
 }
 
@@ -475,7 +483,7 @@
         "api-stubs-docs-non-updatable.api.contribution",
         "system-api-stubs-docs-non-updatable.api.contribution",
     ],
-    defaults: ["android-non-updatable_from_text_defaults"],
+    defaults: ["android-non-updatable_everything_from_text_defaults"],
     full_api_surface_stub: "android_system_stubs_current.from-text",
 }
 
@@ -487,7 +495,7 @@
         "system-api-stubs-docs-non-updatable.api.contribution",
         "test-api-stubs-docs-non-updatable.api.contribution",
     ],
-    defaults: ["android-non-updatable_from_text_defaults"],
+    defaults: ["android-non-updatable_everything_from_text_defaults"],
     full_api_surface_stub: "android_test_stubs_current.from-text",
 }
 
@@ -499,7 +507,7 @@
         "system-api-stubs-docs-non-updatable.api.contribution",
         "module-lib-api-stubs-docs-non-updatable.api.contribution",
     ],
-    defaults: ["android-non-updatable_from_text_defaults"],
+    defaults: ["android-non-updatable_everything_from_text_defaults"],
     full_api_surface_stub: "android_module_lib_stubs_current_full.from-text",
 }
 
@@ -515,7 +523,7 @@
         "test-api-stubs-docs-non-updatable.api.contribution",
         "module-lib-api-stubs-docs-non-updatable.api.contribution",
     ],
-    defaults: ["android-non-updatable_from_text_defaults"],
+    defaults: ["android-non-updatable_everything_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
@@ -836,6 +844,7 @@
     ],
     visibility: ["//visibility:public"],
     enable_validation: false,
+    stubs_type: "everything",
 }
 
 java_api_library {
@@ -852,6 +861,7 @@
     ],
     visibility: ["//visibility:public"],
     enable_validation: false,
+    stubs_type: "everything",
 }
 
 java_api_library {
@@ -870,6 +880,7 @@
     ],
     visibility: ["//visibility:public"],
     enable_validation: false,
+    stubs_type: "everything",
 }
 
 java_api_library {
@@ -888,6 +899,7 @@
         "system-api-stubs-docs-non-updatable.api.contribution",
     ],
     enable_validation: false,
+    stubs_type: "everything",
 }
 
 java_api_library {
@@ -908,6 +920,7 @@
     ],
     visibility: ["//visibility:public"],
     enable_validation: false,
+    stubs_type: "everything",
 }
 
 java_api_library {
@@ -922,6 +935,7 @@
     ],
     visibility: ["//visibility:public"],
     enable_validation: false,
+    stubs_type: "everything",
 }
 
 java_api_library {
@@ -947,6 +961,7 @@
         "//visibility:private",
     ],
     enable_validation: false,
+    stubs_type: "everything",
 }
 
 java_api_library {
@@ -964,6 +979,7 @@
     ],
     visibility: ["//visibility:public"],
     enable_validation: false,
+    stubs_type: "everything",
 }
 
 ////////////////////////////////////////////////////////////////////////
diff --git a/boot/Android.bp b/boot/Android.bp
index cdfa7c80..f60bb9e 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -31,6 +31,7 @@
         "car_bootclasspath_fragment",
         "nfc_apex_bootclasspath_fragment",
         "release_crashrecovery_module",
+        "release_package_profiling_module",
     ],
     properties: [
         "fragments",
@@ -122,10 +123,6 @@
             module: "com.android.permission-bootclasspath-fragment",
         },
         {
-            apex: "com.android.profiling",
-            module: "com.android.profiling-bootclasspath-fragment",
-        },
-        {
             apex: "com.android.scheduling",
             module: "com.android.scheduling-bootclasspath-fragment",
         },
@@ -179,6 +176,15 @@
                 },
             ],
         },
+        release_package_profiling_module: {
+            fragments: [
+                // only used if profiling is enabled.
+                {
+                    apex: "com.android.profiling",
+                    module: "com.android.profiling-bootclasspath-fragment",
+                },
+            ],
+        },
     },
 
     // Additional information needed by hidden api processing.
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.cpp b/cmds/hid/jni/com_android_commands_hid_Device.cpp
index 8b8d361..a142450 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.cpp
+++ b/cmds/hid/jni/com_android_commands_hid_Device.cpp
@@ -134,8 +134,9 @@
     return env;
 }
 
-std::unique_ptr<Device> Device::open(int32_t id, const char* name, int32_t vid, int32_t pid,
-                                     uint16_t bus, const std::vector<uint8_t>& descriptor,
+std::unique_ptr<Device> Device::open(int32_t id, const char* name, const char* uniq, int32_t vid,
+                                     int32_t pid, uint16_t bus,
+                                     const std::vector<uint8_t>& descriptor,
                                      std::unique_ptr<DeviceCallback> callback) {
     size_t size = descriptor.size();
     if (size > HID_MAX_DESCRIPTOR_SIZE) {
@@ -152,8 +153,7 @@
     struct uhid_event ev = {};
     ev.type = UHID_CREATE2;
     strlcpy(reinterpret_cast<char*>(ev.u.create2.name), name, sizeof(ev.u.create2.name));
-    std::string uniq = android::base::StringPrintf("Id: %d", id);
-    strlcpy(reinterpret_cast<char*>(ev.u.create2.uniq), uniq.c_str(), sizeof(ev.u.create2.uniq));
+    strlcpy(reinterpret_cast<char*>(ev.u.create2.uniq), uniq, sizeof(ev.u.create2.uniq));
     memcpy(&ev.u.create2.rd_data, descriptor.data(), size * sizeof(ev.u.create2.rd_data[0]));
     ev.u.create2.rd_size = size;
     ev.u.create2.bus = bus;
@@ -314,19 +314,31 @@
     return data;
 }
 
-static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint id, jint vid,
-                        jint pid, jint bus, jbyteArray rawDescriptor, jobject callback) {
+static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jstring rawUniq, jint id,
+                        jint vid, jint pid, jint bus, jbyteArray rawDescriptor, jobject callback) {
     ScopedUtfChars name(env, rawName);
     if (name.c_str() == nullptr) {
         return 0;
     }
 
+    std::string uniq;
+    if (rawUniq != nullptr) {
+        uniq = ScopedUtfChars(env, rawUniq);
+    } else {
+        uniq = android::base::StringPrintf("Id: %d", id);
+    }
+
+    if (uniq.c_str() == nullptr) {
+        return 0;
+    }
+
     std::vector<uint8_t> desc = getData(env, rawDescriptor);
 
     std::unique_ptr<uhid::DeviceCallback> cb(new uhid::DeviceCallback(env, callback));
 
     std::unique_ptr<uhid::Device> d =
-            uhid::Device::open(id, reinterpret_cast<const char*>(name.c_str()), vid, pid, bus, desc,
+            uhid::Device::open(id, reinterpret_cast<const char*>(name.c_str()),
+                               reinterpret_cast<const char*>(uniq.c_str()), vid, pid, bus, desc,
                                std::move(cb));
     return reinterpret_cast<jlong>(d.release());
 }
@@ -370,7 +382,7 @@
 
 static JNINativeMethod sMethods[] = {
         {"nativeOpenDevice",
-         "(Ljava/lang/String;IIII[B"
+         "(Ljava/lang/String;Ljava/lang/String;IIII[B"
          "Lcom/android/commands/hid/Device$DeviceCallback;)J",
          reinterpret_cast<void*>(openDevice)},
         {"nativeSendReport", "(J[B)V", reinterpret_cast<void*>(sendReport)},
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.h b/cmds/hid/jni/com_android_commands_hid_Device.h
index 9c6060d..bc7a909 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.h
+++ b/cmds/hid/jni/com_android_commands_hid_Device.h
@@ -42,8 +42,9 @@
 
 class Device {
 public:
-    static std::unique_ptr<Device> open(int32_t id, const char* name, int32_t vid, int32_t pid,
-                                        uint16_t bus, const std::vector<uint8_t>& descriptor,
+    static std::unique_ptr<Device> open(int32_t id, const char* name, const char* uniq, int32_t vid,
+                                        int32_t pid, uint16_t bus,
+                                        const std::vector<uint8_t>& descriptor,
                                         std::unique_ptr<DeviceCallback> callback);
 
     ~Device();
diff --git a/cmds/hid/src/com/android/commands/hid/Device.java b/cmds/hid/src/com/android/commands/hid/Device.java
index 0415037..4e8adc3 100644
--- a/cmds/hid/src/com/android/commands/hid/Device.java
+++ b/cmds/hid/src/com/android/commands/hid/Device.java
@@ -71,6 +71,7 @@
 
     private static native long nativeOpenDevice(
             String name,
+            String uniq,
             int id,
             int vid,
             int pid,
@@ -89,6 +90,7 @@
     public Device(
             int id,
             String name,
+            String uniq,
             int vid,
             int pid,
             int bus,
@@ -113,8 +115,9 @@
         } else {
             args.arg1 = id + ":" + vid + ":" + pid;
         }
-        args.arg2 = descriptor;
-        args.arg3 = report;
+        args.arg2 = uniq;
+        args.arg3 = descriptor;
+        args.arg4 = report;
         mHandler.obtainMessage(MSG_OPEN_DEVICE, args).sendToTarget();
         mTimeToSend = SystemClock.uptimeMillis();
     }
@@ -167,11 +170,12 @@
                     mPtr =
                             nativeOpenDevice(
                                     (String) args.arg1,
+                                    (String) args.arg2,
                                     args.argi1,
                                     args.argi2,
                                     args.argi3,
                                     args.argi4,
-                                    (byte[]) args.arg2,
+                                    (byte[]) args.arg3,
                                     new DeviceCallback());
                     pauseEvents();
                     break;
diff --git a/cmds/hid/src/com/android/commands/hid/Event.java b/cmds/hid/src/com/android/commands/hid/Event.java
index 3efb797..3b02279 100644
--- a/cmds/hid/src/com/android/commands/hid/Event.java
+++ b/cmds/hid/src/com/android/commands/hid/Event.java
@@ -56,6 +56,7 @@
     private int mId;
     private String mCommand;
     private String mName;
+    private String mUniq;
     private byte[] mDescriptor;
     private int mVid;
     private int mPid;
@@ -78,6 +79,10 @@
         return mName;
     }
 
+    public String getUniq() {
+        return mUniq;
+    }
+
     public byte[] getDescriptor() {
         return mDescriptor;
     }
@@ -116,8 +121,9 @@
 
     public String toString() {
         return "Event{id=" + mId
-            + ", command=" + String.valueOf(mCommand)
-            + ", name=" + String.valueOf(mName)
+            + ", command=" + mCommand
+            + ", name=" + mName
+            + ", uniq=" + mUniq
             + ", descriptor=" + Arrays.toString(mDescriptor)
             + ", vid=" + mVid
             + ", pid=" + mPid
@@ -149,6 +155,10 @@
             mEvent.mName = name;
         }
 
+        public void setUniq(String uniq) {
+            mEvent.mUniq = uniq;
+        }
+
         public void setDescriptor(byte[] descriptor) {
             mEvent.mDescriptor = descriptor;
         }
@@ -247,6 +257,9 @@
                             case "name":
                                 eb.setName(mReader.nextString());
                                 break;
+                            case "uniq":
+                                eb.setUniq(mReader.nextString());
+                                break;
                             case "vid":
                                 eb.setVid(readInt());
                                 break;
diff --git a/cmds/hid/src/com/android/commands/hid/Hid.java b/cmds/hid/src/com/android/commands/hid/Hid.java
index 2db791fe..5ebfd95 100644
--- a/cmds/hid/src/com/android/commands/hid/Hid.java
+++ b/cmds/hid/src/com/android/commands/hid/Hid.java
@@ -117,8 +117,17 @@
                     "Tried to send command \"" + e.getCommand() + "\" to an unregistered device!");
         }
         int id = e.getId();
-        Device d = new Device(id, e.getName(), e.getVendorId(), e.getProductId(), e.getBus(),
-                e.getDescriptor(), e.getReport(), e.getFeatureReports(), e.getOutputs());
+        Device d = new Device(
+                id,
+                e.getName(),
+                e.getUniq(),
+                e.getVendorId(),
+                e.getProductId(),
+                e.getBus(),
+                e.getDescriptor(),
+                e.getReport(),
+                e.getFeatureReports(),
+                e.getOutputs());
         mDevices.append(id, d);
     }
 
diff --git a/cmds/idmap2/tests/FabricatedOverlayTests.cpp b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
index 6b1c7e8..15109d9 100644
--- a/cmds/idmap2/tests/FabricatedOverlayTests.cpp
+++ b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
@@ -144,7 +144,7 @@
               "com.example.target:string/string1", Res_value::TYPE_STRING, "foobar", "")
           .Build();
   ASSERT_TRUE(overlay);
-  TemporaryFile tf;
+  TempFrroFile tf;
   std::ofstream out(tf.path);
   ASSERT_TRUE((*overlay).ToBinaryStream(out));
   out.close();
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index a384305..c85619c 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -274,7 +274,7 @@
                   .Build();
 
   ASSERT_TRUE(frro);
-  TemporaryFile tf;
+  TempFrroFile tf;
   std::ofstream out(tf.path);
   ASSERT_TRUE((*frro).ToBinaryStream(out));
   out.close();
@@ -467,9 +467,9 @@
 TEST(IdmapTests, IdmapHeaderIsUpToDate) {
   fclose(stderr);  // silence expected warnings from libandroidfw
 
-  const std::string target_apk_path = kIdmapRawTargetPath;
-  const std::string overlay_apk_path = kIdmapRawOverlayPath;
-  const std::string overlay_name = kIdmapRawOverlayName;
+  const std::string target_apk_path {kIdmapRawTargetPath};
+  const std::string overlay_apk_path {kIdmapRawOverlayPath};
+  const std::string overlay_name {kIdmapRawOverlayName};
   const PolicyBitmask policies = kIdmapRawDataPolicies;
   const uint32_t target_crc = kIdmapRawDataTargetCrc;
   const uint32_t overlay_crc = kIdmapRawOverlayCrc;
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
index db44c23..1d22553 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -217,7 +217,7 @@
                   .Build();
 
   ASSERT_TRUE(frro);
-  TemporaryFile tf;
+  TempFrroFile tf;
   std::ofstream out(tf.path);
   ASSERT_TRUE((*frro).ToBinaryStream(out));
   out.close();
diff --git a/cmds/idmap2/tests/TestHelpers.h b/cmds/idmap2/tests/TestHelpers.h
index cdc0b8f..bf01c32 100644
--- a/cmds/idmap2/tests/TestHelpers.h
+++ b/cmds/idmap2/tests/TestHelpers.h
@@ -17,11 +17,15 @@
 #ifndef IDMAP2_TESTS_TESTHELPERS_H_
 #define IDMAP2_TESTS_TESTHELPERS_H_
 
+#include <stdio.h>
 #include <string>
+#include <string_view>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
+#include "android-base/file.h"
+
 namespace android::idmap2 {
 
 const unsigned char kIdmapRawData[] = {
@@ -197,12 +201,23 @@
 const unsigned int kIdmapRawDataTargetCrc = 0x1234;
 const unsigned int kIdmapRawOverlayCrc = 0x5678;
 const unsigned int kIdmapRawDataPolicies = 0x11;
-inline const std::string kIdmapRawTargetPath = "targetX.apk";
-inline const std::string kIdmapRawOverlayPath = "overlayX.apk";
-inline const std::string kIdmapRawOverlayName = "OverlayName";
+inline const std::string_view kIdmapRawTargetPath = "targetX.apk";
+inline const std::string_view kIdmapRawOverlayPath = "overlayX.apk";
+inline const std::string_view kIdmapRawOverlayName = "OverlayName";
 
 std::string GetTestDataPath();
 
+class TempFrroFile : public TemporaryFile {
+public:
+  TempFrroFile() {
+    std::string new_path = path;
+    new_path += ".frro";
+    ::rename(path, new_path.c_str());
+    const auto new_len = new_path.copy(path, sizeof(path) - 1);
+    path[new_len] = '\0';
+  }
+};
+
 class Idmap2Tests : public testing::Test {
  protected:
   void SetUp() override {
diff --git a/cmds/svc/src/com/android/commands/svc/PowerCommand.java b/cmds/svc/src/com/android/commands/svc/PowerCommand.java
index a7560b2..12b79f4 100644
--- a/cmds/svc/src/com/android/commands/svc/PowerCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/PowerCommand.java
@@ -23,8 +23,6 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.sysprop.InitProperties;
 
 public class PowerCommand extends Svc.Command {
     private static final int FORCE_SUSPEND_DELAY_DEFAULT_MILLIS = 0;
@@ -142,12 +140,10 @@
     // Check if remote exception is benign during shutdown. Pm can be killed
     // before system server during shutdown, so remote exception can be ignored
     // if it is already in shutdown flow.
+    // sys.powerctl is no longer set to avoid a possible DOS attack (see
+    // bionic/libc/bionic/system_property_set.cpp) so we have no real way of knowing if a
+    // remote exception is real or simply because pm is killed (b/318323013)
+    // So we simply do not display anything.
     private void maybeLogRemoteException(String msg) {
-        String powerProp = SystemProperties.get("sys.powerctl");
-        // Also check if userspace reboot is ongoing, since in case of userspace reboot value of the
-        // sys.powerctl property will be reset.
-        if (powerProp.isEmpty() && !InitProperties.userspace_reboot_in_progress().orElse(false)) {
-            System.err.println(msg);
-        }
     }
 }
diff --git a/cmds/telecom/Android.bp b/cmds/telecom/Android.bp
index be02710..494d2ae3 100644
--- a/cmds/telecom/Android.bp
+++ b/cmds/telecom/Android.bp
@@ -21,5 +21,8 @@
 java_binary {
     name: "telecom",
     wrapper: "telecom.sh",
-    srcs: ["**/*.java"],
+    srcs: [
+        ":telecom-shell-commands-src",
+        "**/*.java",
+    ],
 }
diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
index 1488e14cf..50af5a7 100644
--- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java
+++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
@@ -17,30 +17,22 @@
 package com.android.commands.telecom;
 
 import android.app.ActivityThread;
-import android.content.ComponentName;
 import android.content.Context;
-import android.net.Uri;
-import android.os.IUserManager;
 import android.os.Looper;
-import android.os.Process;
-import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.sysprop.TelephonyProperties;
-import android.telecom.Log;
-import android.telecom.PhoneAccount;
-import android.telecom.PhoneAccountHandle;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
 
-import com.android.internal.os.BaseCommand;
 import com.android.internal.telecom.ITelecomService;
+import com.android.server.telecom.TelecomShellCommand;
 
-import java.io.PrintStream;
-import java.util.Arrays;
-import java.util.stream.Collectors;
+import java.io.FileDescriptor;
 
-public final class Telecom extends BaseCommand {
+/**
+ * @deprecated Use {@code com.android.server.telecom.TelecomShellCommand} instead and execute the
+ * shell command using {@code adb shell cmd telecom...}. This is only here for backwards
+ * compatibility reasons.
+ */
+@Deprecated
+public final class Telecom {
 
     /**
      * Command-line entry point.
@@ -52,458 +44,11 @@
         // TODO: Do it in zygote and RuntimeInit. b/148897549
         ActivityThread.initializeMainlineModules();
 
-      (new Telecom()).run(args);
-    }
-    private static final String CALLING_PACKAGE = Telecom.class.getPackageName();
-    private static final String COMMAND_SET_PHONE_ACCOUNT_ENABLED = "set-phone-account-enabled";
-    private static final String COMMAND_SET_PHONE_ACCOUNT_DISABLED = "set-phone-account-disabled";
-    private static final String COMMAND_REGISTER_PHONE_ACCOUNT = "register-phone-account";
-    private static final String COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT =
-            "set-user-selected-outgoing-phone-account";
-    private static final String COMMAND_REGISTER_SIM_PHONE_ACCOUNT = "register-sim-phone-account";
-    private static final String COMMAND_SET_TEST_CALL_REDIRECTION_APP = "set-test-call-redirection-app";
-    private static final String COMMAND_SET_TEST_CALL_SCREENING_APP = "set-test-call-screening-app";
-    private static final String COMMAND_ADD_OR_REMOVE_CALL_COMPANION_APP =
-            "add-or-remove-call-companion-app";
-    private static final String COMMAND_SET_PHONE_ACCOUNT_SUGGESTION_COMPONENT =
-            "set-phone-acct-suggestion-component";
-    private static final String COMMAND_UNREGISTER_PHONE_ACCOUNT = "unregister-phone-account";
-    private static final String COMMAND_SET_CALL_DIAGNOSTIC_SERVICE = "set-call-diagnostic-service";
-    private static final String COMMAND_SET_DEFAULT_DIALER = "set-default-dialer";
-    private static final String COMMAND_GET_DEFAULT_DIALER = "get-default-dialer";
-    private static final String COMMAND_STOP_BLOCK_SUPPRESSION = "stop-block-suppression";
-    private static final String COMMAND_CLEANUP_STUCK_CALLS = "cleanup-stuck-calls";
-    private static final String COMMAND_CLEANUP_ORPHAN_PHONE_ACCOUNTS =
-            "cleanup-orphan-phone-accounts";
-    private static final String COMMAND_RESET_CAR_MODE = "reset-car-mode";
-    private static final String COMMAND_IS_NON_IN_CALL_SERVICE_BOUND =
-            "is-non-ui-in-call-service-bound";
-
-    /**
-     * Change the system dialer package name if a package name was specified,
-     * Example: adb shell telecom set-system-dialer <PACKAGE>
-     *
-     * Restore it to the default if if argument is "default" or no argument is passed.
-     * Example: adb shell telecom set-system-dialer default
-     */
-    private static final String COMMAND_SET_SYSTEM_DIALER = "set-system-dialer";
-    private static final String COMMAND_GET_SYSTEM_DIALER = "get-system-dialer";
-    private static final String COMMAND_WAIT_ON_HANDLERS = "wait-on-handlers";
-    private static final String COMMAND_SET_SIM_COUNT = "set-sim-count";
-    private static final String COMMAND_GET_SIM_CONFIG = "get-sim-config";
-    private static final String COMMAND_GET_MAX_PHONES = "get-max-phones";
-    private static final String COMMAND_SET_TEST_EMERGENCY_PHONE_ACCOUNT_PACKAGE_FILTER =
-            "set-test-emergency-phone-account-package-filter";
-    /**
-     * Command used to emit a distinct "mark" in the logs.
-     */
-    private static final String COMMAND_LOG_MARK = "log-mark";
-
-    private ComponentName mComponent;
-    private String mAccountId;
-    private ITelecomService mTelecomService;
-    private TelephonyManager mTelephonyManager;
-    private IUserManager mUserManager;
-
-    @Override
-    public void onShowUsage(PrintStream out) {
-        out.println("usage: telecom [subcommand] [options]\n"
-                + "usage: telecom set-phone-account-enabled <COMPONENT> <ID> <USER_SN>\n"
-                + "usage: telecom set-phone-account-disabled <COMPONENT> <ID> <USER_SN>\n"
-                + "usage: telecom register-phone-account <COMPONENT> <ID> <USER_SN> <LABEL>\n"
-                + "usage: telecom register-sim-phone-account [-e] <COMPONENT> <ID> <USER_SN>"
-                        + " <LABEL>: registers a PhoneAccount with CAPABILITY_SIM_SUBSCRIPTION"
-                        + " and optionally CAPABILITY_PLACE_EMERGENCY_CALLS if \"-e\" is provided\n"
-                + "usage: telecom set-user-selected-outgoing-phone-account [-e] <COMPONENT> <ID> "
-                + "<USER_SN>\n"
-                + "usage: telecom set-test-call-redirection-app <PACKAGE>\n"
-                + "usage: telecom set-test-call-screening-app <PACKAGE>\n"
-                + "usage: telecom set-phone-acct-suggestion-component <COMPONENT>\n"
-                + "usage: telecom add-or-remove-call-companion-app <PACKAGE> <1/0>\n"
-                + "usage: telecom register-sim-phone-account <COMPONENT> <ID> <USER_SN>"
-                + " <LABEL> <ADDRESS>\n"
-                + "usage: telecom unregister-phone-account <COMPONENT> <ID> <USER_SN>\n"
-                + "usage: telecom set-call-diagnostic-service <PACKAGE>\n"
-                + "usage: telecom set-default-dialer <PACKAGE>\n"
-                + "usage: telecom get-default-dialer\n"
-                + "usage: telecom get-system-dialer\n"
-                + "usage: telecom wait-on-handlers\n"
-                + "usage: telecom set-sim-count <COUNT>\n"
-                + "usage: telecom get-sim-config\n"
-                + "usage: telecom get-max-phones\n"
-                + "usage: telecom stop-block-suppression: Stop suppressing the blocked number"
-                        + " provider after a call to emergency services.\n"
-                + "usage: telecom cleanup-stuck-calls: Clear any disconnected calls that have"
-                + " gotten wedged in Telecom.\n"
-                + "usage: telecom cleanup-orphan-phone-accounts: remove any phone accounts that"
-                + " no longer have a valid UserHandle or accounts that no longer belongs to an"
-                + " installed package.\n"
-                + "usage: telecom set-emer-phone-account-filter <PACKAGE>\n"
-                + "\n"
-                + "telecom set-phone-account-enabled: Enables the given phone account, if it has"
-                        + " already been registered with Telecom.\n"
-                + "\n"
-                + "telecom set-phone-account-disabled: Disables the given phone account, if it"
-                        + " has already been registered with telecom.\n"
-                + "\n"
-                + "telecom set-call-diagnostic-service: overrides call diagnostic service.\n"
-                + "telecom set-default-dialer: Sets the override default dialer to the given"
-                        + " component; this will override whatever the dialer role is set to.\n"
-                + "\n"
-                + "telecom get-default-dialer: Displays the current default dialer.\n"
-                + "\n"
-                + "telecom get-system-dialer: Displays the current system dialer.\n"
-                + "telecom set-system-dialer: Set the override system dialer to the given"
-                        + " component. To remove the override, send \"default\"\n"
-                + "\n"
-                + "telecom wait-on-handlers: Wait until all handlers finish their work.\n"
-                + "\n"
-                + "telecom set-sim-count: Set num SIMs (2 for DSDS, 1 for single SIM."
-                        + " This may restart the device.\n"
-                + "\n"
-                + "telecom get-sim-config: Get the mSIM config string. \"DSDS\" for DSDS mode,"
-                        + " or \"\" for single SIM\n"
-                + "\n"
-                + "telecom get-max-phones: Get the max supported phones from the modem.\n"
-                + "telecom set-test-emergency-phone-account-package-filter <PACKAGE>: sets a"
-                        + " package name that will be used for test emergency calls. To clear,"
-                        + " send an empty package name. Real emergency calls will still be placed"
-                        + " over Telephony.\n"
-                + "telecom log-mark <MESSAGE>: emits a message into the telecom logs.  Useful for "
-                        + "testers to indicate where in the logs various test steps take place.\n"
-                + "telecom is-non-ui-in-call-service-bound <PACKAGE>: queries a particular "
-                + "non-ui-InCallService in InCallController to determine if it is bound \n"
-        );
-    }
-
-    @Override
-    public void onRun() throws Exception {
-        mTelecomService = ITelecomService.Stub.asInterface(
-                ServiceManager.getService(Context.TELECOM_SERVICE));
-        if (mTelecomService == null) {
-            Log.w(this, "onRun: Can't access telecom manager.");
-            showError("Error: Could not access the Telecom Manager. Is the system running?");
-            return;
-        }
-
         Looper.prepareMainLooper();
+        ITelecomService service = ITelecomService.Stub.asInterface(
+                ServiceManager.getService(Context.TELECOM_SERVICE));
         Context context = ActivityThread.systemMain().getSystemContext();
-        mTelephonyManager = context.getSystemService(TelephonyManager.class);
-        if (mTelephonyManager == null) {
-            Log.w(this, "onRun: Can't access telephony service.");
-            showError("Error: Could not access the Telephony Service. Is the system running?");
-            return;
-        }
-
-        mUserManager = IUserManager.Stub
-                .asInterface(ServiceManager.getService(Context.USER_SERVICE));
-        if (mUserManager == null) {
-            Log.w(this, "onRun: Can't access user manager.");
-            showError("Error: Could not access the User Manager. Is the system running?");
-            return;
-        }
-        Log.i(this, "onRun: parsing command.");
-        String command = nextArgRequired();
-        switch (command) {
-            case COMMAND_SET_PHONE_ACCOUNT_ENABLED:
-                runSetPhoneAccountEnabled(true);
-                break;
-            case COMMAND_SET_PHONE_ACCOUNT_DISABLED:
-                runSetPhoneAccountEnabled(false);
-                break;
-            case COMMAND_REGISTER_PHONE_ACCOUNT:
-                runRegisterPhoneAccount();
-                break;
-            case COMMAND_SET_TEST_CALL_REDIRECTION_APP:
-                runSetTestCallRedirectionApp();
-                break;
-            case COMMAND_SET_TEST_CALL_SCREENING_APP:
-                runSetTestCallScreeningApp();
-                break;
-            case COMMAND_ADD_OR_REMOVE_CALL_COMPANION_APP:
-                runAddOrRemoveCallCompanionApp();
-                break;
-            case COMMAND_SET_PHONE_ACCOUNT_SUGGESTION_COMPONENT:
-                runSetTestPhoneAcctSuggestionComponent();
-                break;
-            case COMMAND_SET_CALL_DIAGNOSTIC_SERVICE:
-                runSetCallDiagnosticService();
-                break;
-            case COMMAND_REGISTER_SIM_PHONE_ACCOUNT:
-                runRegisterSimPhoneAccount();
-                break;
-            case COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT:
-                runSetUserSelectedOutgoingPhoneAccount();
-                break;
-            case COMMAND_UNREGISTER_PHONE_ACCOUNT:
-                runUnregisterPhoneAccount();
-                break;
-            case COMMAND_STOP_BLOCK_SUPPRESSION:
-                runStopBlockSuppression();
-                break;
-            case COMMAND_CLEANUP_STUCK_CALLS:
-                runCleanupStuckCalls();
-                break;
-            case COMMAND_CLEANUP_ORPHAN_PHONE_ACCOUNTS:
-                runCleanupOrphanPhoneAccounts();
-                break;
-            case COMMAND_RESET_CAR_MODE:
-                runResetCarMode();
-                break;
-            case COMMAND_SET_DEFAULT_DIALER:
-                runSetDefaultDialer();
-                break;
-            case COMMAND_GET_DEFAULT_DIALER:
-                runGetDefaultDialer();
-                break;
-            case COMMAND_SET_SYSTEM_DIALER:
-                runSetSystemDialer();
-                break;
-            case COMMAND_GET_SYSTEM_DIALER:
-                runGetSystemDialer();
-                break;
-            case COMMAND_WAIT_ON_HANDLERS:
-                runWaitOnHandler();
-                break;
-            case COMMAND_SET_SIM_COUNT:
-                runSetSimCount();
-                break;
-            case COMMAND_GET_SIM_CONFIG:
-                runGetSimConfig();
-                break;
-            case COMMAND_GET_MAX_PHONES:
-                runGetMaxPhones();
-                break;
-            case COMMAND_IS_NON_IN_CALL_SERVICE_BOUND:
-                runIsNonUiInCallServiceBound();
-                break;
-            case COMMAND_SET_TEST_EMERGENCY_PHONE_ACCOUNT_PACKAGE_FILTER:
-                runSetEmergencyPhoneAccountPackageFilter();
-                break;
-            case COMMAND_LOG_MARK:
-                runLogMark();
-                break;
-            default:
-                Log.w(this, "onRun: unknown command: %s", command);
-                throw new IllegalArgumentException ("unknown command '" + command + "'");
-        }
-    }
-
-    private void runSetPhoneAccountEnabled(boolean enabled) throws RemoteException {
-        final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
-        final boolean success =  mTelecomService.enablePhoneAccount(handle, enabled);
-        if (success) {
-            System.out.println("Success - " + handle + (enabled ? " enabled." : " disabled."));
-        } else {
-            System.out.println("Error - is " + handle + " a valid PhoneAccount?");
-        }
-    }
-
-    private void runRegisterPhoneAccount() throws RemoteException {
-        final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
-        final String label = nextArgRequired();
-        PhoneAccount account = PhoneAccount.builder(handle, label)
-                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER).build();
-        mTelecomService.registerPhoneAccount(account, CALLING_PACKAGE);
-        System.out.println("Success - " + handle + " registered.");
-    }
-
-    private void runRegisterSimPhoneAccount() throws RemoteException {
-        boolean isEmergencyAccount = false;
-        String opt;
-        while ((opt = nextOption()) != null) {
-            switch (opt) {
-                case "-e": {
-                    isEmergencyAccount = true;
-                    break;
-                }
-            }
-        }
-        final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
-        final String label = nextArgRequired();
-        final String address = nextArgRequired();
-        int capabilities = PhoneAccount.CAPABILITY_CALL_PROVIDER
-                | PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
-                | (isEmergencyAccount ? PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS : 0);
-        PhoneAccount account = PhoneAccount.builder(
-            handle, label)
-                .setAddress(Uri.parse(address))
-                .setSubscriptionAddress(Uri.parse(address))
-                .setCapabilities(capabilities)
-                .setShortDescription(label)
-                .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
-                .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL)
-                .build();
-        mTelecomService.registerPhoneAccount(account, CALLING_PACKAGE);
-        System.out.println("Success - " + handle + " registered.");
-    }
-
-    private void runSetTestCallRedirectionApp() throws RemoteException {
-        final String packageName = nextArg();
-        mTelecomService.setTestDefaultCallRedirectionApp(packageName);
-    }
-
-    private void runSetTestCallScreeningApp() throws RemoteException {
-        final String packageName = nextArg();
-        mTelecomService.setTestDefaultCallScreeningApp(packageName);
-    }
-
-    private void runAddOrRemoveCallCompanionApp() throws RemoteException {
-        final String packageName = nextArgRequired();
-        String isAdded = nextArgRequired();
-        boolean isAddedBool = "1".equals(isAdded);
-        mTelecomService.addOrRemoveTestCallCompanionApp(packageName, isAddedBool);
-    }
-
-    private void runSetCallDiagnosticService() throws RemoteException {
-        String packageName = nextArg();
-        if ("default".equals(packageName)) packageName = null;
-        mTelecomService.setTestCallDiagnosticService(packageName);
-        System.out.println("Success - " + packageName + " set as call diagnostic service.");
-    }
-
-    private void runSetTestPhoneAcctSuggestionComponent() throws RemoteException {
-        final String componentName = nextArg();
-        mTelecomService.setTestPhoneAcctSuggestionComponent(componentName);
-    }
-
-    private void runSetUserSelectedOutgoingPhoneAccount() throws RemoteException {
-        Log.i(this, "runSetUserSelectedOutgoingPhoneAccount");
-        final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
-        mTelecomService.setUserSelectedOutgoingPhoneAccount(handle);
-        System.out.println("Success - " + handle + " set as default outgoing account.");
-    }
-
-    private void runUnregisterPhoneAccount() throws RemoteException {
-        final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
-        mTelecomService.unregisterPhoneAccount(handle, CALLING_PACKAGE);
-        System.out.println("Success - " + handle + " unregistered.");
-    }
-
-    private void runStopBlockSuppression() throws RemoteException {
-        mTelecomService.stopBlockSuppression();
-    }
-
-    private void runCleanupStuckCalls() throws RemoteException {
-        mTelecomService.cleanupStuckCalls();
-    }
-
-    private void runCleanupOrphanPhoneAccounts() throws RemoteException {
-        System.out.println("Success - cleaned up " + mTelecomService.cleanupOrphanPhoneAccounts()
-                + "  phone accounts.");
-    }
-
-    private void runResetCarMode() throws RemoteException {
-        mTelecomService.resetCarMode();
-    }
-
-    private void runSetDefaultDialer() throws RemoteException {
-        String packageName = nextArg();
-        if ("default".equals(packageName)) packageName = null;
-        mTelecomService.setTestDefaultDialer(packageName);
-        System.out.println("Success - " + packageName + " set as override default dialer.");
-    }
-
-    private void runSetSystemDialer() throws RemoteException {
-        final String flatComponentName = nextArg();
-        final ComponentName componentName = (flatComponentName.equals("default")
-                ? null : parseComponentName(flatComponentName));
-        mTelecomService.setSystemDialer(componentName);
-        System.out.println("Success - " + componentName + " set as override system dialer.");
-    }
-
-    private void runGetDefaultDialer() throws RemoteException {
-        System.out.println(mTelecomService.getDefaultDialerPackage(CALLING_PACKAGE));
-    }
-
-    private void runGetSystemDialer() throws RemoteException {
-        System.out.println(mTelecomService.getSystemDialerPackage(CALLING_PACKAGE));
-    }
-
-    private void runWaitOnHandler() throws RemoteException {
-
-    }
-
-    private void runSetSimCount() throws RemoteException {
-        if (!callerIsRoot()) {
-            System.out.println("set-sim-count requires adb root");
-            return;
-        }
-        int numSims = Integer.parseInt(nextArgRequired());
-        System.out.println("Setting sim count to " + numSims + ". Device may reboot");
-        mTelephonyManager.switchMultiSimConfig(numSims);
-    }
-
-    /**
-     * prints out whether a particular non-ui InCallServices is bound in a call
-     */
-    public void runIsNonUiInCallServiceBound() throws RemoteException {
-        if (TextUtils.isEmpty(mArgs.peekNextArg())) {
-            System.out.println("No Argument passed. Please pass a <PACKAGE_NAME> to lookup.");
-        } else {
-            System.out.println(
-                    String.valueOf(mTelecomService.isNonUiInCallServiceBound(nextArg())));
-        }
-    }
-
-    /**
-     * Prints the mSIM config to the console.
-     * "DSDS" for a phone in DSDS mode
-     * "" (empty string) for a phone in SS mode
-     */
-    private void runGetSimConfig() throws RemoteException {
-        System.out.println(TelephonyProperties.multi_sim_config().orElse(""));
-    }
-
-    private void runGetMaxPhones() throws RemoteException {
-        // how many logical modems can be potentially active simultaneously
-        System.out.println(mTelephonyManager.getSupportedModemCount());
-    }
-
-    private void runSetEmergencyPhoneAccountPackageFilter() throws RemoteException {
-        String packageName = mArgs.getNextArg();
-        if (TextUtils.isEmpty(packageName)) {
-            mTelecomService.setTestEmergencyPhoneAccountPackageNameFilter(null);
-            System.out.println("Success - filter cleared");
-        } else {
-            mTelecomService.setTestEmergencyPhoneAccountPackageNameFilter(packageName);
-            System.out.println("Success = filter set to " + packageName);
-        }
-
-    }
-
-    private void runLogMark() throws RemoteException {
-        String message = Arrays.stream(mArgs.peekRemainingArgs()).collect(Collectors.joining(" "));
-        mTelecomService.requestLogMark(message);
-    }
-
-    private PhoneAccountHandle getPhoneAccountHandleFromArgs() throws RemoteException {
-        if (TextUtils.isEmpty(mArgs.peekNextArg())) {
-            return null;
-        }
-        final ComponentName component = parseComponentName(nextArgRequired());
-        final String accountId = nextArgRequired();
-        final String userSnInStr = nextArgRequired();
-        UserHandle userHandle;
-        try {
-            final int userSn = Integer.parseInt(userSnInStr);
-            userHandle = UserHandle.of(mUserManager.getUserHandle(userSn));
-        } catch (NumberFormatException ex) {
-            Log.w(this, "getPhoneAccountHandleFromArgs - invalid user %s", userSnInStr);
-            throw new IllegalArgumentException ("Invalid user serial number " + userSnInStr);
-        }
-        return new PhoneAccountHandle(component, accountId, userHandle);
-    }
-
-    private boolean callerIsRoot() {
-        return Process.ROOT_UID == Process.myUid();
-    }
-
-    private ComponentName parseComponentName(String component) {
-        ComponentName cn = ComponentName.unflattenFromString(component);
-        if (cn == null) {
-            throw new IllegalArgumentException ("Invalid component " + component);
-        }
-        return cn;
+        new TelecomShellCommand(service, context).exec(null, FileDescriptor.in,
+                FileDescriptor.out, FileDescriptor.err, args);
     }
 }
diff --git a/core/api/current.txt b/core/api/current.txt
index 386396c..7552b5c0 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -203,7 +203,7 @@
     field public static final String MANAGE_DEVICE_POLICY_SYSTEM_APPS = "android.permission.MANAGE_DEVICE_POLICY_SYSTEM_APPS";
     field public static final String MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS = "android.permission.MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS";
     field public static final String MANAGE_DEVICE_POLICY_SYSTEM_UPDATES = "android.permission.MANAGE_DEVICE_POLICY_SYSTEM_UPDATES";
-    field @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled") public static final String MANAGE_DEVICE_POLICY_THREAD_NETWORK = "android.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK";
+    field @FlaggedApi("com.android.net.thread.platform.flags.thread_user_restriction_enabled") public static final String MANAGE_DEVICE_POLICY_THREAD_NETWORK = "android.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK";
     field public static final String MANAGE_DEVICE_POLICY_TIME = "android.permission.MANAGE_DEVICE_POLICY_TIME";
     field public static final String MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING = "android.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING";
     field public static final String MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER = "android.permission.MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER";
@@ -691,7 +691,6 @@
     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 @FlaggedApi("android.nfc.Flags.FLAG_OBSERVE_MODE") public static final int defaultToObserveMode;
     field public static final int defaultValue = 16843245; // 0x10101ed
     field public static final int defaultWidth = 16844020; // 0x10104f4
     field public static final int delay = 16843212; // 0x10101cc
@@ -1501,6 +1500,7 @@
     field public static final int shortcutId = 16844072; // 0x1010528
     field public static final int shortcutLongLabel = 16844074; // 0x101052a
     field public static final int shortcutShortLabel = 16844073; // 0x1010529
+    field @FlaggedApi("android.nfc.Flags.FLAG_OBSERVE_MODE") public static final int shouldDefaultToObserveMode;
     field public static final int shouldDisableView = 16843246; // 0x10101ee
     field public static final int shouldUseDefaultUnfoldTransition = 16844364; // 0x101064c
     field public static final int showAsAction = 16843481; // 0x10102d9
@@ -3350,7 +3350,6 @@
     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 @FlaggedApi("android.view.accessibility.braille_display_hid") public void clearTestBrailleDisplayController();
     method public final void disableSelf();
     method public final boolean dispatchGesture(@NonNull android.accessibilityservice.GestureDescription, @Nullable android.accessibilityservice.AccessibilityService.GestureResultCallback, @Nullable android.os.Handler);
     method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
@@ -3386,7 +3385,6 @@
     method public boolean setCacheEnabled(boolean);
     method public void setGestureDetectionPassthroughRegion(int, @NonNull android.graphics.Region);
     method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
-    method @FlaggedApi("android.view.accessibility.braille_display_hid") public void setTestBrailleDisplayController(@NonNull android.accessibilityservice.BrailleDisplayController);
     method public void setTouchExplorationPassthroughRegion(int, @NonNull android.graphics.Region);
     method public void takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull android.accessibilityservice.AccessibilityService.TakeScreenshotCallback);
     method public void takeScreenshotOfWindow(int, @NonNull java.util.concurrent.Executor, @NonNull android.accessibilityservice.AccessibilityService.TakeScreenshotCallback);
@@ -4447,7 +4445,7 @@
     method public final android.media.session.MediaController getMediaController();
     method @NonNull public android.view.MenuInflater getMenuInflater();
     method @NonNull public android.window.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
-    method public final android.app.Activity getParent();
+    method @Deprecated public final android.app.Activity getParent();
     method @Nullable public android.content.Intent getParentActivityIntent();
     method public android.content.SharedPreferences getPreferences(int);
     method @Nullable public android.net.Uri getReferrer();
@@ -4465,7 +4463,7 @@
     method public void invalidateOptionsMenu();
     method public boolean isActivityTransitionRunning();
     method public boolean isChangingConfigurations();
-    method public final boolean isChild();
+    method @Deprecated public final boolean isChild();
     method public boolean isDestroyed();
     method public boolean isFinishing();
     method public boolean isImmersive();
@@ -4484,7 +4482,7 @@
     method @CallSuper public void onActionModeStarted(android.view.ActionMode);
     method public void onActivityReenter(int, android.content.Intent);
     method protected void onActivityResult(int, int, android.content.Intent);
-    method @FlaggedApi("android.security.content_uri_permission_apis") public void onActivityResult(int, int, @NonNull android.content.Intent, @NonNull android.app.ComponentCaller);
+    method @FlaggedApi("android.security.content_uri_permission_apis") public void onActivityResult(int, int, @Nullable android.content.Intent, @NonNull android.app.ComponentCaller);
     method @Deprecated public void onAttachFragment(android.app.Fragment);
     method public void onAttachedToWindow();
     method @Deprecated public void onBackPressed();
@@ -6087,7 +6085,7 @@
 
   public class GrammaticalInflectionManager {
     method public int getApplicationGrammaticalGender();
-    method @FlaggedApi("android.app.system_terms_of_address_enabled") public int getSystemGrammaticalGender();
+    method @FlaggedApi("android.app.system_terms_of_address_enabled") @RequiresPermission("android.permission.READ_SYSTEM_GRAMMATICAL_GENDER") public int getSystemGrammaticalGender();
     method public void setRequestedApplicationGrammaticalGender(int);
   }
 
@@ -6676,9 +6674,9 @@
     method @Deprecated public android.app.Notification.Builder addPerson(String);
     method @NonNull public android.app.Notification.Builder addPerson(android.app.Person);
     method @NonNull public android.app.Notification build();
-    method public android.widget.RemoteViews createBigContentView();
-    method public android.widget.RemoteViews createContentView();
-    method public android.widget.RemoteViews createHeadsUpContentView();
+    method @Deprecated public android.widget.RemoteViews createBigContentView();
+    method @Deprecated public android.widget.RemoteViews createContentView();
+    method @Deprecated public android.widget.RemoteViews createHeadsUpContentView();
     method @NonNull public android.app.Notification.Builder extend(android.app.Notification.Extender);
     method public android.os.Bundle getExtras();
     method @Deprecated public android.app.Notification getNotification();
@@ -8086,7 +8084,7 @@
     method public CharSequence getStartUserSessionMessage(@NonNull android.content.ComponentName);
     method @Deprecated public boolean getStorageEncryption(@Nullable android.content.ComponentName);
     method public int getStorageEncryptionStatus();
-    method @FlaggedApi("android.app.admin.flags.esim_management_enabled") @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS) public java.util.Set<java.lang.Integer> getSubscriptionsIds();
+    method @FlaggedApi("android.app.admin.flags.esim_management_enabled") @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS) public java.util.Set<java.lang.Integer> getSubscriptionIds();
     method @Nullable public android.app.admin.SystemUpdatePolicy getSystemUpdatePolicy();
     method @Nullable public android.os.PersistableBundle getTransferOwnershipBundle();
     method @Nullable public java.util.List<android.os.PersistableBundle> getTrustAgentConfiguration(@Nullable android.content.ComponentName, @NonNull android.content.ComponentName);
@@ -9604,13 +9602,13 @@
     method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
     method @FlaggedApi("android.appwidget.flags.generated_previews") @Nullable public android.widget.RemoteViews getWidgetPreview(@NonNull android.content.ComponentName, @Nullable android.os.UserHandle, int);
     method public boolean isRequestPinAppWidgetSupported();
-    method public void notifyAppWidgetViewDataChanged(int[], int);
-    method public void notifyAppWidgetViewDataChanged(int, int);
+    method @Deprecated public void notifyAppWidgetViewDataChanged(int[], int);
+    method @Deprecated public void notifyAppWidgetViewDataChanged(int, int);
     method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
     method public void partiallyUpdateAppWidget(int, android.widget.RemoteViews);
     method @FlaggedApi("android.appwidget.flags.generated_previews") public void removeWidgetPreview(@NonNull android.content.ComponentName, int);
     method public boolean requestPinAppWidget(@NonNull android.content.ComponentName, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent);
-    method @FlaggedApi("android.appwidget.flags.generated_previews") public void setWidgetPreview(@NonNull android.content.ComponentName, int, @NonNull android.widget.RemoteViews);
+    method @FlaggedApi("android.appwidget.flags.generated_previews") public boolean setWidgetPreview(@NonNull android.content.ComponentName, int, @NonNull android.widget.RemoteViews);
     method public void updateAppWidget(int[], android.widget.RemoteViews);
     method public void updateAppWidget(int, android.widget.RemoteViews);
     method public void updateAppWidget(android.content.ComponentName, android.widget.RemoteViews);
@@ -10733,7 +10731,6 @@
     field public static final String DROPBOX_SERVICE = "dropbox";
     field public static final String EUICC_SERVICE = "euicc";
     field public static final String FILE_INTEGRITY_SERVICE = "file_integrity";
-    field public static final String FINGERPRINT_SERVICE = "fingerprint";
     field public static final String GAME_SERVICE = "game";
     field public static final String GRAMMATICAL_INFLECTION_SERVICE = "grammatical_inflection";
     field public static final String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
@@ -13121,7 +13118,6 @@
     field public static final String FEATURE_CAMERA_LEVEL_FULL = "android.hardware.camera.level.full";
     field public static final String FEATURE_CANT_SAVE_STATE = "android.software.cant_save_state";
     field public static final String FEATURE_COMPANION_DEVICE_SETUP = "android.software.companion_device_setup";
-    field @FlaggedApi("android.view.inputmethod.concurrent_input_methods") public static final String FEATURE_CONCURRENT_INPUT_METHODS = "android.software.concurrent_input_methods";
     field @Deprecated public static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
     field public static final String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
     field public static final String FEATURE_CONTROLS = "android.software.controls";
@@ -13218,7 +13214,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 @FlaggedApi("com.android.net.thread.flags.thread_enabled_platform") public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network";
+    field @FlaggedApi("com.android.net.thread.platform.flags.thread_enabled_platform") 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";
@@ -13510,7 +13506,7 @@
     field public static final int FLAG_USE_APP_ZYGOTE = 8; // 0x8
     field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CAMERA}, anyOf={android.Manifest.permission.CAMERA}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 64; // 0x40
     field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE}, anyOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.CHANGE_NETWORK_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, android.Manifest.permission.NFC, android.Manifest.permission.TRANSMIT_IR, android.Manifest.permission.UWB_RANGING}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10
-    field @Deprecated @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1; // 0x1
+    field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1; // 0x1
     field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_HEALTH}, anyOf={android.Manifest.permission.ACTIVITY_RECOGNITION, android.Manifest.permission.BODY_SENSORS, android.Manifest.permission.HIGH_SAMPLING_RATE_SENSORS}) public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 256; // 0x100
     field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_LOCATION}, anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 8; // 0x8
     field public static final int FOREGROUND_SERVICE_TYPE_MANIFEST = -1; // 0xffffffff
@@ -15711,7 +15707,7 @@
     method public boolean clipRect(int, int, int, int);
     method @FlaggedApi("com.android.graphics.hwui.flags.clip_shader") public void clipShader(@NonNull android.graphics.Shader);
     method public void concat(@Nullable android.graphics.Matrix);
-    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void concat44(@Nullable android.graphics.Matrix44);
+    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void concat(@Nullable android.graphics.Matrix44);
     method public void disableZ();
     method public void drawARGB(int, int, int, int);
     method public void drawArc(@NonNull android.graphics.RectF, float, float, boolean, @NonNull android.graphics.Paint);
@@ -15754,6 +15750,7 @@
     method public void drawRect(@NonNull android.graphics.RectF, @NonNull android.graphics.Paint);
     method public void drawRect(@NonNull android.graphics.Rect, @NonNull android.graphics.Paint);
     method public void drawRect(float, float, float, float, @NonNull android.graphics.Paint);
+    method @FlaggedApi("com.android.graphics.hwui.flags.draw_region") public void drawRegion(@NonNull android.graphics.Region, @NonNull android.graphics.Paint);
     method public void drawRenderNode(@NonNull android.graphics.RenderNode);
     method public void drawRoundRect(@NonNull android.graphics.RectF, float, float, @NonNull android.graphics.Paint);
     method public void drawRoundRect(float, float, float, float, float, float, @NonNull android.graphics.Paint);
@@ -16364,7 +16361,7 @@
     ctor @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public Matrix44();
     ctor @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public Matrix44(@NonNull android.graphics.Matrix);
     method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 concat(@NonNull android.graphics.Matrix44);
-    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public float get(int, int);
+    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public float get(@IntRange(from=0, to=3) int, @IntRange(from=0, to=3) int);
     method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void getValues(@NonNull float[]);
     method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public boolean invert();
     method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public boolean isIdentity();
@@ -16373,7 +16370,7 @@
     method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void reset();
     method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 rotate(float, float, float, float);
     method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 scale(float, float, float);
-    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void set(int, int, float);
+    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void set(@IntRange(from=0, to=3) int, @IntRange(from=0, to=3) int, float);
     method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void setValues(@NonNull float[]);
     method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 translate(float, float, float);
   }
@@ -18054,24 +18051,6 @@
     method public android.graphics.pdf.PdfDocument.PageInfo.Builder setContentRect(android.graphics.Rect);
   }
 
-  public final class PdfRenderer implements java.lang.AutoCloseable {
-    ctor public PdfRenderer(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
-    method public void close();
-    method public int getPageCount();
-    method public android.graphics.pdf.PdfRenderer.Page openPage(int);
-    method public boolean shouldScaleForPrinting();
-  }
-
-  public final class PdfRenderer.Page implements java.lang.AutoCloseable {
-    method public void close();
-    method public int getHeight();
-    method public int getIndex();
-    method public int getWidth();
-    method public void render(@NonNull android.graphics.Bitmap, @Nullable android.graphics.Rect, @Nullable android.graphics.Matrix, int);
-    field public static final int RENDER_MODE_FOR_DISPLAY = 1; // 0x1
-    field public static final int RENDER_MODE_FOR_PRINT = 2; // 0x2
-  }
-
 }
 
 package android.graphics.text {
@@ -19697,6 +19676,7 @@
 
   public final class CaptureRequest extends android.hardware.camera2.CameraMetadata<android.hardware.camera2.CaptureRequest.Key<?>> implements android.os.Parcelable {
     method public int describeContents();
+    method @FlaggedApi("com.android.internal.camera.flags.surface_leak_fix") protected void finalize();
     method @Nullable public <T> T get(android.hardware.camera2.CaptureRequest.Key<T>);
     method @NonNull public java.util.List<android.hardware.camera2.CaptureRequest.Key<?>> getKeys();
     method @Nullable public Object getTag();
@@ -20382,54 +20362,6 @@
 
 }
 
-package android.hardware.fingerprint {
-
-  @Deprecated public class FingerprintManager {
-    method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.USE_BIOMETRIC, android.Manifest.permission.USE_FINGERPRINT}) public void authenticate(@Nullable android.hardware.fingerprint.FingerprintManager.CryptoObject, @Nullable android.os.CancellationSignal, int, @NonNull android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, @Nullable android.os.Handler);
-    method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean hasEnrolledFingerprints();
-    method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean isHardwareDetected();
-    field public static final int FINGERPRINT_ACQUIRED_GOOD = 0; // 0x0
-    field public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; // 0x3
-    field public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; // 0x2
-    field public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; // 0x1
-    field public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; // 0x5
-    field public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; // 0x4
-    field public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5
-    field public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; // 0xc
-    field public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1
-    field public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7
-    field public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; // 0x9
-    field public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; // 0xb
-    field public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4
-    field public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3
-    field public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2
-    field public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; // 0xa
-    field public static final int FINGERPRINT_ERROR_VENDOR = 8; // 0x8
-  }
-
-  @Deprecated public abstract static class FingerprintManager.AuthenticationCallback {
-    ctor @Deprecated public FingerprintManager.AuthenticationCallback();
-    method @Deprecated public void onAuthenticationError(int, CharSequence);
-    method @Deprecated public void onAuthenticationFailed();
-    method @Deprecated public void onAuthenticationHelp(int, CharSequence);
-    method @Deprecated public void onAuthenticationSucceeded(android.hardware.fingerprint.FingerprintManager.AuthenticationResult);
-  }
-
-  @Deprecated public static class FingerprintManager.AuthenticationResult {
-    method @Deprecated public android.hardware.fingerprint.FingerprintManager.CryptoObject getCryptoObject();
-  }
-
-  @Deprecated public static final class FingerprintManager.CryptoObject {
-    ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull java.security.Signature);
-    ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Cipher);
-    ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Mac);
-    method @Deprecated public javax.crypto.Cipher getCipher();
-    method @Deprecated public javax.crypto.Mac getMac();
-    method @Deprecated public java.security.Signature getSignature();
-  }
-
-}
-
 package android.hardware.input {
 
   public final class HostUsiVersion implements android.os.Parcelable {
@@ -28122,7 +28054,7 @@
     method public void sendSigningResult(@NonNull String, @NonNull byte[]);
     method public void sendTrackInfoList(@Nullable java.util.List<android.media.tv.TvTrackInfo>);
     method public void setCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdView.TvAdCallback);
-    method public void setOnUnhandledInputEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdView.OnUnhandledInputEventListener);
+    method public void setOnUnhandledInputEventListener(@NonNull android.media.tv.ad.TvAdView.OnUnhandledInputEventListener);
     method public boolean setTvView(@Nullable android.media.tv.TvView);
     method public void startAdService();
     method public void stopAdService();
@@ -34086,7 +34018,7 @@
     field @FlaggedApi("android.app.admin.flags.esim_management_enabled") public static final String DISALLOW_SIM_GLOBALLY = "no_sim_globally";
     field public static final String DISALLOW_SMS = "no_sms";
     field public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
-    field @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled") public static final String DISALLOW_THREAD_NETWORK = "no_thread_network";
+    field @FlaggedApi("com.android.net.thread.platform.flags.thread_user_restriction_enabled") public static final String DISALLOW_THREAD_NETWORK = "no_thread_network";
     field public static final String DISALLOW_ULTRA_WIDEBAND_RADIO = "no_ultra_wideband_radio";
     field public static final String DISALLOW_UNIFIED_PASSWORD = "no_unified_password";
     field public static final String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
@@ -35817,54 +35749,6 @@
     field public static final String LONGITUDE = "longitude";
   }
 
-  @FlaggedApi("android.provider.user_keys") public final class ContactKeysManager {
-    method @NonNull @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public java.util.List<android.provider.ContactKeysManager.ContactKey> getAllContactKeys(@NonNull String);
-    method @NonNull @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public java.util.List<android.provider.ContactKeysManager.SelfKey> getAllSelfKeys();
-    method @Nullable @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public android.provider.ContactKeysManager.ContactKey getContactKey(@NonNull String, @NonNull String, @NonNull String);
-    method public static int getMaxKeySizeBytes();
-    method @NonNull @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public java.util.List<android.provider.ContactKeysManager.ContactKey> getOwnerContactKeys(@NonNull String);
-    method @NonNull @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public java.util.List<android.provider.ContactKeysManager.SelfKey> getOwnerSelfKeys();
-    method @Nullable @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public android.provider.ContactKeysManager.SelfKey getSelfKey(@NonNull String, @NonNull String);
-    method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean removeContactKey(@NonNull String, @NonNull String, @NonNull String);
-    method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean removeSelfKey(@NonNull String, @NonNull String);
-    method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateContactKeyLocalVerificationState(@NonNull String, @NonNull String, @NonNull String, int);
-    method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateContactKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, int);
-    method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public void updateOrInsertContactKey(@NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
-    method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateOrInsertSelfKey(@NonNull String, @NonNull String, @NonNull byte[]);
-    method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateSelfKeyRemoteVerificationState(@NonNull String, @NonNull String, int);
-    field public static final int VERIFICATION_STATE_UNVERIFIED = 0; // 0x0
-    field public static final int VERIFICATION_STATE_VERIFICATION_FAILED = 1; // 0x1
-    field public static final int VERIFICATION_STATE_VERIFIED = 2; // 0x2
-  }
-
-  public static final class ContactKeysManager.ContactKey implements android.os.Parcelable {
-    method public int describeContents();
-    method @NonNull public String getAccountId();
-    method @Nullable public String getDeviceId();
-    method @Nullable public String getDisplayName();
-    method @Nullable public String getEmailAddress();
-    method @Nullable public byte[] getKeyValue();
-    method public int getLocalVerificationState();
-    method @NonNull public String getOwnerPackageName();
-    method @Nullable public String getPhoneNumber();
-    method public int getRemoteVerificationState();
-    method public long getTimeUpdated();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.provider.ContactKeysManager.ContactKey> CREATOR;
-  }
-
-  public static final class ContactKeysManager.SelfKey implements android.os.Parcelable {
-    method public int describeContents();
-    method @NonNull public String getAccountId();
-    method @Nullable public String getDeviceId();
-    method @Nullable public byte[] getKeyValue();
-    method @NonNull public String getOwnerPackageName();
-    method public int getRemoteVerificationState();
-    method public long getTimeUpdated();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.provider.ContactKeysManager.SelfKey> CREATOR;
-  }
-
   @Deprecated public class Contacts {
     field @Deprecated public static final String AUTHORITY = "contacts";
     field @Deprecated public static final android.net.Uri CONTENT_URI;
@@ -37165,6 +37049,54 @@
     method public final int update(android.net.Uri, android.content.ContentValues, String, String[]);
   }
 
+  @FlaggedApi("android.provider.user_keys") public final class E2eeContactKeysManager {
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public java.util.List<android.provider.E2eeContactKeysManager.E2eeContactKey> getAllE2eeContactKeys(@NonNull String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public java.util.List<android.provider.E2eeContactKeysManager.E2eeSelfKey> getAllE2eeSelfKeys();
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public android.provider.E2eeContactKeysManager.E2eeContactKey getE2eeContactKey(@NonNull String, @NonNull String, @NonNull String);
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public android.provider.E2eeContactKeysManager.E2eeSelfKey getE2eeSelfKey(@NonNull String, @NonNull String);
+    method public static int getMaxKeySizeBytes();
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public java.util.List<android.provider.E2eeContactKeysManager.E2eeContactKey> getOwnerE2eeContactKeys(@NonNull String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public java.util.List<android.provider.E2eeContactKeysManager.E2eeSelfKey> getOwnerE2eeSelfKeys();
+    method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean removeE2eeContactKey(@NonNull String, @NonNull String, @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean removeE2eeSelfKey(@NonNull String, @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateE2eeContactKeyLocalVerificationState(@NonNull String, @NonNull String, @NonNull String, int);
+    method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateE2eeContactKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, int);
+    method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateE2eeSelfKeyRemoteVerificationState(@NonNull String, @NonNull String, int);
+    method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public void updateOrInsertE2eeContactKey(@NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
+    method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateOrInsertE2eeSelfKey(@NonNull String, @NonNull String, @NonNull byte[]);
+    field public static final int VERIFICATION_STATE_UNVERIFIED = 0; // 0x0
+    field public static final int VERIFICATION_STATE_VERIFICATION_FAILED = 1; // 0x1
+    field public static final int VERIFICATION_STATE_VERIFIED = 2; // 0x2
+  }
+
+  public static final class E2eeContactKeysManager.E2eeContactKey implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getAccountId();
+    method @Nullable public String getDeviceId();
+    method @Nullable public String getDisplayName();
+    method @Nullable public String getEmailAddress();
+    method @Nullable public byte[] getKeyValue();
+    method public int getLocalVerificationState();
+    method @NonNull public String getOwnerPackageName();
+    method @Nullable public String getPhoneNumber();
+    method public int getRemoteVerificationState();
+    method public long getTimeUpdated();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.provider.E2eeContactKeysManager.E2eeContactKey> CREATOR;
+  }
+
+  public static final class E2eeContactKeysManager.E2eeSelfKey implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getAccountId();
+    method @Nullable public String getDeviceId();
+    method @Nullable public byte[] getKeyValue();
+    method @NonNull public String getOwnerPackageName();
+    method public int getRemoteVerificationState();
+    method public long getTimeUpdated();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.provider.E2eeContactKeysManager.E2eeSelfKey> CREATOR;
+  }
+
   @Deprecated public final class FontRequest {
     ctor @Deprecated public FontRequest(@NonNull String, @NonNull String, @NonNull String);
     ctor @Deprecated public FontRequest(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.List<java.util.List<byte[]>>);
@@ -39801,6 +39733,7 @@
     method @Deprecated public boolean isInsideSecureHardware();
     method public boolean isInvalidatedByBiometricEnrollment();
     method public boolean isTrustedUserPresenceRequired();
+    method @FlaggedApi("android.security.keyinfo_unlocked_device_required") public boolean isUnlockedDeviceRequired();
     method public boolean isUserAuthenticationRequired();
     method public boolean isUserAuthenticationRequirementEnforcedBySecureHardware();
     method public boolean isUserAuthenticationValidWhileOnBody();
@@ -41155,19 +41088,19 @@
     method public final android.service.notification.StatusBarNotification[] getSnoozedNotifications();
     method public final void migrateNotificationFilter(int, @Nullable java.util.List<java.lang.String>);
     method public android.os.IBinder onBind(android.content.Intent);
-    method public void onInterruptionFilterChanged(int);
-    method public void onListenerConnected();
-    method public void onListenerDisconnected();
-    method public void onListenerHintsChanged(int);
-    method public void onNotificationChannelGroupModified(String, android.os.UserHandle, android.app.NotificationChannelGroup, int);
-    method public void onNotificationChannelModified(String, android.os.UserHandle, android.app.NotificationChannel, int);
-    method public void onNotificationPosted(android.service.notification.StatusBarNotification);
-    method public void onNotificationPosted(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
-    method public void onNotificationRankingUpdate(android.service.notification.NotificationListenerService.RankingMap);
-    method public void onNotificationRemoved(android.service.notification.StatusBarNotification);
-    method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
-    method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, int);
-    method public void onSilentStatusBarIconsVisibilityChanged(boolean);
+    method @UiThread public void onInterruptionFilterChanged(int);
+    method @UiThread public void onListenerConnected();
+    method @UiThread public void onListenerDisconnected();
+    method @UiThread public void onListenerHintsChanged(int);
+    method @UiThread public void onNotificationChannelGroupModified(String, android.os.UserHandle, android.app.NotificationChannelGroup, int);
+    method @UiThread public void onNotificationChannelModified(String, android.os.UserHandle, android.app.NotificationChannel, int);
+    method @UiThread public void onNotificationPosted(android.service.notification.StatusBarNotification);
+    method @UiThread public void onNotificationPosted(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
+    method @UiThread public void onNotificationRankingUpdate(android.service.notification.NotificationListenerService.RankingMap);
+    method @UiThread public void onNotificationRemoved(android.service.notification.StatusBarNotification);
+    method @UiThread public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
+    method @UiThread public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, int);
+    method @UiThread public void onSilentStatusBarIconsVisibilityChanged(boolean);
     method public final void requestInterruptionFilter(int);
     method public final void requestListenerHints(int);
     method public static void requestRebind(android.content.ComponentName);
@@ -52514,8 +52447,8 @@
     method public final void cancelPendingInputEvents();
     method public boolean checkInputConnectionProxy(android.view.View);
     method public void clearAnimation();
-    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void clearCredentialManagerRequest();
     method public void clearFocus();
+    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void clearPendingCredentialRequest();
     method public void clearViewTranslationCallback();
     method public static int combineMeasuredStates(int, int);
     method protected int computeHorizontalScrollExtent();
@@ -52624,8 +52557,6 @@
     method @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") public final int getContentSensitivity();
     method @UiContext public final android.content.Context getContext();
     method protected android.view.ContextMenu.ContextMenuInfo getContextMenuInfo();
-    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public final android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getCredentialManagerCallback();
-    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public final android.credentials.GetCredentialRequest getCredentialManagerRequest();
     method public final boolean getDefaultFocusHighlightEnabled();
     method public static int getDefaultSize(int, int);
     method public android.view.Display getDisplay();
@@ -52710,6 +52641,8 @@
     method public int getPaddingTop();
     method public final android.view.ViewParent getParent();
     method public android.view.ViewParent getParentForAccessibility();
+    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public final android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getPendingCredentialCallback();
+    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public final android.credentials.GetCredentialRequest getPendingCredentialRequest();
     method public float getPivotX();
     method public float getPivotY();
     method public android.view.PointerIcon getPointerIcon();
@@ -53010,7 +52943,6 @@
     method public void setContentDescription(CharSequence);
     method @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") public final void setContentSensitivity(int);
     method public void setContextClickable(boolean);
-    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void setCredentialManagerRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
     method public void setDefaultFocusHighlightEnabled(boolean);
     method @Deprecated public void setDrawingCacheBackgroundColor(@ColorInt int);
     method @Deprecated public void setDrawingCacheEnabled(boolean);
@@ -53089,6 +53021,7 @@
     method public void setOverScrollMode(int);
     method public void setPadding(int, int, int, int);
     method public void setPaddingRelative(int, int, int, int);
+    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void setPendingCredentialRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
     method public void setPivotX(float);
     method public void setPivotY(float);
     method public void setPointerIcon(android.view.PointerIcon);
@@ -53892,10 +53825,10 @@
     method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void clearCredentialManagerRequest();
     method @Nullable public abstract android.view.autofill.AutofillId getAutofillId();
     method public abstract int getChildCount();
-    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getCredentialManagerCallback();
-    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public android.credentials.GetCredentialRequest getCredentialManagerRequest();
     method public abstract android.os.Bundle getExtras();
     method public abstract CharSequence getHint();
+    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getPendingCredentialCallback();
+    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public android.credentials.GetCredentialRequest getPendingCredentialRequest();
     method public abstract CharSequence getText();
     method public abstract int getTextSelectionEnd();
     method public abstract int getTextSelectionStart();
@@ -53918,7 +53851,6 @@
     method public abstract void setClickable(boolean);
     method public abstract void setContentDescription(CharSequence);
     method public abstract void setContextClickable(boolean);
-    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void setCredentialManagerRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
     method public abstract void setDataIsSensitive(boolean);
     method public abstract void setDimens(int, int, int, int, int, int);
     method public abstract void setElevation(float);
@@ -53937,6 +53869,7 @@
     method public void setMaxTextLength(int);
     method public void setMinTextEms(int);
     method public abstract void setOpaque(boolean);
+    method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void setPendingCredentialRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
     method public void setReceiveContentMimeTypes(@Nullable String[]);
     method public abstract void setSelected(boolean);
     method public abstract void setText(CharSequence);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 9c1a8e8..0ab2588 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -652,6 +652,7 @@
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.webkit.WebViewProviderResponse> CREATOR;
     field public static final int STATUS_FAILED_LISTING_WEBVIEW_PACKAGES = 4; // 0x4
+    field public static final int STATUS_FAILED_OTHER = 11; // 0xb
     field public static final int STATUS_FAILED_WAITING_FOR_RELRO = 3; // 0x3
     field public static final int STATUS_SUCCESS = 0; // 0x0
     field @Nullable public final android.content.pm.PackageInfo packageInfo;
diff --git a/core/api/removed.txt b/core/api/removed.txt
index 3c7c0d6..c61f163 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -35,6 +35,7 @@
     method @Deprecated @Nullable public String getFeatureId();
     method public abstract android.content.SharedPreferences getSharedPreferences(java.io.File, int);
     method public abstract java.io.File getSharedPreferencesPath(String);
+    field public static final String FINGERPRINT_SERVICE = "fingerprint";
   }
 
   public class ContextWrapper extends android.content.Context {
@@ -145,6 +146,54 @@
 
 }
 
+package android.hardware.fingerprint {
+
+  @Deprecated public class FingerprintManager {
+    method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.USE_BIOMETRIC, android.Manifest.permission.USE_FINGERPRINT}) public void authenticate(@Nullable android.hardware.fingerprint.FingerprintManager.CryptoObject, @Nullable android.os.CancellationSignal, int, @NonNull android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, @Nullable android.os.Handler);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean hasEnrolledFingerprints();
+    method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean isHardwareDetected();
+    field public static final int FINGERPRINT_ACQUIRED_GOOD = 0; // 0x0
+    field public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; // 0x3
+    field public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; // 0x2
+    field public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; // 0x1
+    field public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; // 0x5
+    field public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; // 0x4
+    field public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5
+    field public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; // 0xc
+    field public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1
+    field public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7
+    field public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; // 0x9
+    field public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; // 0xb
+    field public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4
+    field public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3
+    field public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2
+    field public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; // 0xa
+    field public static final int FINGERPRINT_ERROR_VENDOR = 8; // 0x8
+  }
+
+  @Deprecated public abstract static class FingerprintManager.AuthenticationCallback {
+    ctor public FingerprintManager.AuthenticationCallback();
+    method public void onAuthenticationError(int, CharSequence);
+    method public void onAuthenticationFailed();
+    method public void onAuthenticationHelp(int, CharSequence);
+    method public void onAuthenticationSucceeded(android.hardware.fingerprint.FingerprintManager.AuthenticationResult);
+  }
+
+  @Deprecated public static class FingerprintManager.AuthenticationResult {
+    method public android.hardware.fingerprint.FingerprintManager.CryptoObject getCryptoObject();
+  }
+
+  @Deprecated public static final class FingerprintManager.CryptoObject {
+    ctor public FingerprintManager.CryptoObject(@NonNull java.security.Signature);
+    ctor public FingerprintManager.CryptoObject(@NonNull javax.crypto.Cipher);
+    ctor public FingerprintManager.CryptoObject(@NonNull javax.crypto.Mac);
+    method public javax.crypto.Cipher getCipher();
+    method public javax.crypto.Mac getMac();
+    method public java.security.Signature getSignature();
+  }
+
+}
+
 package android.media {
 
   public final class AudioFormat implements android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index dfd915b..4b04d10 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -70,7 +70,7 @@
     field public static final String BIND_NETWORK_RECOMMENDATION_SERVICE = "android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE";
     field public static final String BIND_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE";
     field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String BIND_ON_DEVICE_INTELLIGENCE_SERVICE = "android.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE";
-    field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String BIND_ON_DEVICE_TRUSTED_SERVICE = "android.permission.BIND_ON_DEVICE_TRUSTED_SERVICE";
+    field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE = "android.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE";
     field public static final String BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE = "android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE";
     field public static final String BIND_PRINT_RECOMMENDATION_SERVICE = "android.permission.BIND_PRINT_RECOMMENDATION_SERVICE";
     field public static final String BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE = "android.permission.BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE";
@@ -388,7 +388,7 @@
     field public static final String SYSTEM_APPLICATION_OVERLAY = "android.permission.SYSTEM_APPLICATION_OVERLAY";
     field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA";
     field public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED";
-    field @FlaggedApi("com.android.net.thread.flags.thread_enabled_platform") public static final String THREAD_NETWORK_PRIVILEGED = "android.permission.THREAD_NETWORK_PRIVILEGED";
+    field @FlaggedApi("com.android.net.thread.platform.flags.thread_enabled_platform") public static final String THREAD_NETWORK_PRIVILEGED = "android.permission.THREAD_NETWORK_PRIVILEGED";
     field public static final String TIS_EXTENSION_INTERFACE = "android.permission.TIS_EXTENSION_INTERFACE";
     field public static final String TOGGLE_AUTOMOTIVE_PROJECTION = "android.permission.TOGGLE_AUTOMOTIVE_PROJECTION";
     field public static final String TRIGGER_LOST_MODE = "android.permission.TRIGGER_LOST_MODE";
@@ -403,7 +403,6 @@
     field @Deprecated public static final String UPDATE_TIME_ZONE_RULES = "android.permission.UPDATE_TIME_ZONE_RULES";
     field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
     field public static final String USER_ACTIVITY = "android.permission.USER_ACTIVITY";
-    field @FlaggedApi("android.hardware.biometrics.face_background_authentication") public static final String USE_BACKGROUND_FACE_AUTHENTICATION = "android.permission.USE_BACKGROUND_FACE_AUTHENTICATION";
     field public static final String USE_COLORIZED_NOTIFICATIONS = "android.permission.USE_COLORIZED_NOTIFICATIONS";
     field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String USE_ON_DEVICE_INTELLIGENCE = "android.permission.USE_ON_DEVICE_INTELLIGENCE";
     field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
@@ -1307,6 +1306,7 @@
 
   public class DevicePolicyManager {
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int checkProvisioningPrecondition(@NonNull String, @NonNull String);
+    method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void clearAuditLogEventCallback();
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
     method @Nullable public android.content.Intent createProvisioningIntentFromNfcIntent(@NonNull android.content.Intent);
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void finalizeWorkProfileProvisioning(@NonNull android.os.UserHandle, @Nullable android.accounts.Account);
@@ -1343,7 +1343,7 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public void setApplicationExemptions(@NonNull String, @NonNull java.util.Set<java.lang.Integer>) throws android.content.pm.PackageManager.NameNotFoundException;
     method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEnabled(boolean);
-    method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback(@NonNull java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.util.List<android.app.admin.SecurityLog.SecurityEvent>>);
+    method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.admin.SecurityLog.SecurityEvent>>);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied();
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean);
     method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setMaxPolicyStorageLimit(int);
@@ -1647,14 +1647,12 @@
     method public int getDensityLevel();
     method @NonNull public java.time.Instant getEndTime();
     method public int getEventType();
-    method @FlaggedApi("android.app.ambient_heart_rate") @IntRange(from=0xffffffff) public int getRatePerMinute();
     method @NonNull public java.time.Instant getStartTime();
     method @NonNull public android.os.PersistableBundle getVendorData();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.ambientcontext.AmbientContextEvent> CREATOR;
     field public static final int EVENT_BACK_DOUBLE_TAP = 3; // 0x3
     field public static final int EVENT_COUGH = 1; // 0x1
-    field @FlaggedApi("android.app.ambient_heart_rate") public static final int EVENT_HEART_RATE = 4; // 0x4
     field public static final int EVENT_SNORE = 2; // 0x2
     field public static final int EVENT_UNKNOWN = 0; // 0x0
     field public static final int EVENT_VENDOR_WEARABLE_START = 100000; // 0x186a0
@@ -1665,7 +1663,6 @@
     field public static final int LEVEL_MEDIUM_HIGH = 4; // 0x4
     field public static final int LEVEL_MEDIUM_LOW = 2; // 0x2
     field public static final int LEVEL_UNKNOWN = 0; // 0x0
-    field @FlaggedApi("android.app.ambient_heart_rate") public static final int RATE_PER_MINUTE_UNKNOWN = -1; // 0xffffffff
   }
 
   public static final class AmbientContextEvent.Builder {
@@ -1675,7 +1672,6 @@
     method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setDensityLevel(int);
     method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setEndTime(@NonNull java.time.Instant);
     method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setEventType(int);
-    method @FlaggedApi("android.app.ambient_heart_rate") @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setRatePerMinute(@IntRange(from=0xffffffff) int);
     method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setStartTime(@NonNull java.time.Instant);
     method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setVendorData(@NonNull android.os.PersistableBundle);
   }
@@ -2227,10 +2223,9 @@
   }
 
   public static final class Feature.Builder {
-    ctor public Feature.Builder(int, int, int, @NonNull android.os.PersistableBundle);
+    ctor public Feature.Builder(int);
     method @NonNull public android.app.ondeviceintelligence.Feature build();
     method @NonNull public android.app.ondeviceintelligence.Feature.Builder setFeatureParams(@NonNull android.os.PersistableBundle);
-    method @NonNull public android.app.ondeviceintelligence.Feature.Builder setId(int);
     method @NonNull public android.app.ondeviceintelligence.Feature.Builder setModelName(@NonNull String);
     method @NonNull public android.app.ondeviceintelligence.Feature.Builder setName(@NonNull String);
     method @NonNull public android.app.ondeviceintelligence.Feature.Builder setType(int);
@@ -2242,7 +2237,7 @@
     ctor public FeatureDetails(@android.app.ondeviceintelligence.FeatureDetails.Status int);
     method public int describeContents();
     method @NonNull public android.os.PersistableBundle getFeatureDetailParams();
-    method @android.app.ondeviceintelligence.FeatureDetails.Status public int getStatus();
+    method @android.app.ondeviceintelligence.FeatureDetails.Status public int getFeatureStatus();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FeatureDetails> CREATOR;
     field public static final int FEATURE_STATUS_AVAILABLE = 3; // 0x3
@@ -2255,27 +2250,16 @@
   @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD}) public static @interface FeatureDetails.Status {
   }
 
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class FilePart implements android.os.Parcelable {
-    ctor public FilePart(@NonNull String, @NonNull android.os.PersistableBundle, @NonNull String) throws java.io.FileNotFoundException;
-    ctor public FilePart(@NonNull String, @NonNull android.os.PersistableBundle, @NonNull java.io.FileInputStream) throws java.io.IOException;
-    method public int describeContents();
-    method @NonNull public java.io.FileInputStream getFileInputStream();
-    method @NonNull public String getFilePartKey();
-    method @NonNull public android.os.PersistableBundle getFilePartParams();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FilePart> CREATOR;
-  }
-
   @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceManager {
     method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeature(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
     method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+    method @Nullable @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public String getRemoteServicePackageName();
     method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getVersion(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.LongConsumer);
     method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamingResponseReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
+    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver);
+    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver);
     method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenCount(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Long,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
-    field public static final String API_VERSION_BUNDLE_KEY = "ApiVersionBundleKey";
+    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenInfo(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
     field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2
     field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0
     field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1
@@ -2309,6 +2293,10 @@
     field public static final int PROCESSING_ERROR_UNKNOWN = 1; // 0x1
   }
 
+  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface ProcessingOutcomeReceiver extends android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> {
+    method public default void onDataAugmentRequest(@NonNull android.app.ondeviceintelligence.Content, @NonNull java.util.function.Consumer<android.app.ondeviceintelligence.Content>);
+  }
+
   @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class ProcessingSignal {
     ctor public ProcessingSignal();
     method public void sendSignal(@NonNull android.os.PersistableBundle);
@@ -2319,8 +2307,18 @@
     method public void onSignalReceived(@NonNull android.os.PersistableBundle);
   }
 
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamingResponseReceiver<R, T, E extends java.lang.Throwable> extends android.os.OutcomeReceiver<R,E> {
-    method public void onNewContent(@NonNull T);
+  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamedProcessingOutcomeReceiver extends android.app.ondeviceintelligence.ProcessingOutcomeReceiver {
+    method public void onNewContent(@NonNull android.app.ondeviceintelligence.Content);
+  }
+
+  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class TokenInfo implements android.os.Parcelable {
+    ctor public TokenInfo(long, @NonNull android.os.PersistableBundle);
+    ctor public TokenInfo(long);
+    method public int describeContents();
+    method public long getCount();
+    method @NonNull public android.os.PersistableBundle getInfoParams();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.TokenInfo> CREATOR;
   }
 
 }
@@ -3763,7 +3761,6 @@
     field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String ECM_ENHANCED_CONFIRMATION_SERVICE = "ecm_enhanced_confirmation";
     field public static final String ETHERNET_SERVICE = "ethernet";
     field public static final String EUICC_CARD_SERVICE = "euicc_card";
-    field @FlaggedApi("android.hardware.biometrics.face_background_authentication") public static final String FACE_SERVICE = "face";
     field public static final String FONT_SERVICE = "font";
     field public static final String HDMI_CONTROL_SERVICE = "hdmi_control";
     field public static final String MEDIA_TRANSCODING_SERVICE = "media_transcoding";
@@ -3787,7 +3784,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 @FlaggedApi("com.android.net.thread.flags.thread_enabled_platform") public static final String THREAD_NETWORK_SERVICE = "thread_network";
+    field @FlaggedApi("com.android.net.thread.platform.flags.thread_enabled_platform") 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";
@@ -4390,7 +4387,7 @@
     field public static final int PROTECTION_FLAG_MODULE = 4194304; // 0x400000
     field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
     field public static final int PROTECTION_FLAG_RECENTS = 33554432; // 0x2000000
-    field public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
+    field @Deprecated public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
     field public static final int PROTECTION_FLAG_ROLE = 67108864; // 0x4000000
     field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
     field public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 32768; // 0x8000
@@ -4825,7 +4822,7 @@
     method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean areAnySensorPrivacyTogglesEnabled(int);
-    method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @NonNull @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public java.util.Map<java.lang.String,java.lang.Boolean> getCameraPrivacyAllowlist();
+    method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @NonNull @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public java.util.List<java.lang.String> getCameraPrivacyAllowlist();
     method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public int getSensorPrivacyState(int, int);
     method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isCameraPrivacyEnabled(@NonNull String);
     method @Deprecated @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int);
@@ -4848,6 +4845,12 @@
     method public boolean isEnabled();
   }
 
+  @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") public static class SensorPrivacyManager.StateTypes {
+    field public static final int DISABLED = 2; // 0x2
+    field public static final int ENABLED = 1; // 0x1
+    field public static final int ENABLED_EXCEPT_ALLOWLISTED_APPS = 3; // 0x3
+  }
+
 }
 
 package android.hardware.biometrics {
@@ -4909,7 +4912,6 @@
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int getImageFormat();
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.util.Size getSize();
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.view.Surface getSurface();
-    method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setColorSpace(int);
     method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setDynamicRangeProfile(long);
   }
 
@@ -4920,6 +4922,7 @@
 
   @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionConfiguration {
     ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public ExtensionConfiguration(int, int, @NonNull java.util.List<android.hardware.camera2.extension.ExtensionOutputConfiguration>, @Nullable android.hardware.camera2.CaptureRequest);
+    method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setColorSpace(int);
   }
 
   @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionOutputConfiguration {
@@ -5135,15 +5138,6 @@
 
 }
 
-package android.hardware.face {
-
-  @FlaggedApi("android.hardware.biometrics.face_background_authentication") public class FaceManager {
-    method @FlaggedApi("android.hardware.biometrics.face_background_authentication") @RequiresPermission(android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION) public void authenticateInBackground(@Nullable java.util.concurrent.Executor, @Nullable android.hardware.biometrics.BiometricPrompt.CryptoObject, @Nullable android.os.CancellationSignal, @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationCallback);
-    method @FlaggedApi("android.hardware.biometrics.face_background_authentication") @RequiresPermission(anyOf={"android.permission.USE_BIOMETRIC_INTERNAL", android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION}) public boolean hasEnrolledTemplates();
-  }
-
-}
-
 package android.hardware.hdmi {
 
   public abstract class HdmiClient {
@@ -6267,7 +6261,7 @@
     method public void addOnCompleteListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.OnCompleteListener);
     method public void addOnCompleteListener(@NonNull android.hardware.radio.ProgramList.OnCompleteListener);
     method public void close();
-    method @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);
@@ -6319,7 +6313,7 @@
     field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; // 0x5
     field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SID_EXT = 5; // 0x5
     field public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; // 0xa
-    field public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb
+    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
@@ -6327,8 +6321,8 @@
     field @Deprecated public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4
     field public static final int IDENTIFIER_TYPE_INVALID = 0; // 0x0
     field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2
-    field 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
@@ -6381,7 +6375,7 @@
     field public static final int CONFIG_DAB_DAB_SOFT_LINKING = 8; // 0x8
     field public static final int CONFIG_DAB_FM_LINKING = 7; // 0x7
     field public static final int CONFIG_DAB_FM_SOFT_LINKING = 9; // 0x9
-    field 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
@@ -10422,7 +10416,6 @@
   @FlaggedApi("android.nfc.enable_nfc_mainline") public final class ApduServiceInfo implements android.os.Parcelable {
     ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public ApduServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo, boolean) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
     method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopFilter(@NonNull String, boolean);
-    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean defaultToObserveMode();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream);
@@ -10452,9 +10445,10 @@
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean requiresUnlock();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void resetOffHostSecureElement();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setCategoryOtherServiceEnabled(boolean);
-    method @FlaggedApi("android.nfc.nfc_observe_mode") public void setDefaultToObserveMode(boolean);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setDynamicAidGroup(@NonNull android.nfc.cardemulation.AidGroup);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setOffHostSecureElement(@NonNull String);
+    method @FlaggedApi("android.nfc.nfc_observe_mode") public void setShouldDefaultToObserveMode(boolean);
+    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean shouldDefaultToObserveMode();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void writeToParcel(@NonNull android.os.Parcel, int);
     field @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.ApduServiceInfo> CREATOR;
   }
@@ -10762,7 +10756,7 @@
     method public final double readDouble();
     method public final java.util.ArrayList<java.lang.Double> readDoubleVector();
     method public final android.os.HwBlob readEmbeddedBuffer(long, long, long, boolean);
-    method @NonNull @Nullable public final android.os.HidlMemory readEmbeddedHidlMemory(long, long, long);
+    method @NonNull public final android.os.HidlMemory readEmbeddedHidlMemory(long, long, long);
     method @Nullable public final android.os.NativeHandle readEmbeddedNativeHandle(long, long);
     method public final float readFloat();
     method public final java.util.ArrayList<java.lang.Float> readFloatVector();
@@ -11535,7 +11529,7 @@
     method public int checkPermissionForPreflight(@NonNull String, @NonNull android.content.AttributionSource);
     method @RequiresPermission(value=android.Manifest.permission.UPDATE_APP_OPS_STATS, conditional=true) public int checkPermissionForStartDataDelivery(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String);
     method public void finishDataDelivery(@NonNull String, @NonNull android.content.AttributionSource);
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @NonNull @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public java.util.Map<java.lang.String,android.permission.PermissionManager.PermissionState> getAllPermissionStates(@NonNull String, @NonNull String);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public java.util.Map<java.lang.String,android.permission.PermissionManager.PermissionState> getAllPermissionStates(@NonNull String, @NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionGrantedPackages();
     method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionRequestedPackages();
     method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public int getPermissionFlags(@NonNull String, @NonNull String, @NonNull String);
@@ -11708,12 +11702,6 @@
     field public static final int ERROR_UNKNOWN = 0; // 0x0
   }
 
-  @FlaggedApi("android.provider.user_keys") public final class ContactKeysManager {
-    method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateContactKeyLocalVerificationState(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int);
-    method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateContactKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int);
-    method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateSelfKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, int);
-  }
-
   @Deprecated public static final class ContactsContract.MetadataSync implements android.provider.BaseColumns android.provider.ContactsContract.MetadataSyncColumns {
     field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_metadata";
     field @Deprecated public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact_metadata";
@@ -11771,6 +11759,12 @@
     field public static final int FLAG_REMOVABLE_USB = 524288; // 0x80000
   }
 
+  @FlaggedApi("android.provider.user_keys") public final class E2eeContactKeysManager {
+    method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateE2eeContactKeyLocalVerificationState(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int);
+    method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateE2eeContactKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int);
+    method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateE2eeSelfKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, int);
+  }
+
   public abstract class SearchIndexableData {
     ctor public SearchIndexableData();
     ctor public SearchIndexableData(android.content.Context);
@@ -12889,7 +12883,7 @@
   }
 
   public abstract class NotificationListenerService extends android.app.Service {
-    method public void onNotificationRemoved(@NonNull android.service.notification.StatusBarNotification, @NonNull android.service.notification.NotificationListenerService.RankingMap, @NonNull android.service.notification.NotificationStats, int);
+    method @UiThread public void onNotificationRemoved(@NonNull android.service.notification.StatusBarNotification, @NonNull android.service.notification.NotificationListenerService.RankingMap, @NonNull android.service.notification.NotificationStats, int);
   }
 
   public static class NotificationListenerService.Ranking {
@@ -12961,25 +12955,42 @@
   @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceIntelligenceService extends android.app.Service {
     ctor public OnDeviceIntelligenceService();
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
-    method public abstract void onDownloadFeature(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull android.app.ondeviceintelligence.DownloadCallback);
-    method public abstract void onGetFeature(int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
-    method public abstract void onGetFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+    method public abstract void onDownloadFeature(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull android.app.ondeviceintelligence.DownloadCallback);
+    method public abstract void onGetFeature(int, int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+    method public abstract void onGetFeatureDetails(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
     method public abstract void onGetReadOnlyFeatureFileDescriptorMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>>);
     method public abstract void onGetVersion(@NonNull java.util.function.LongConsumer);
-    method public abstract void onListFeatures(@NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+    method public abstract void onInferenceServiceConnected();
+    method public abstract void onInferenceServiceDisconnected();
+    method public abstract void onListFeatures(int, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+    method public final void updateProcessingState(@NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>);
     field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
   }
 
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceTrustedInferenceService extends android.app.Service {
-    ctor public OnDeviceTrustedInferenceService();
+  public abstract static class OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException extends java.lang.Exception {
+    ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int);
+    ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int, @NonNull String);
+    method public int getErrorCode();
+  }
+
+  public static class OnDeviceIntelligenceService.OnDeviceUpdateProcessingException extends android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException {
+    ctor public OnDeviceIntelligenceService.OnDeviceUpdateProcessingException(int);
+    ctor public OnDeviceIntelligenceService.OnDeviceUpdateProcessingException(int, @NonNull String);
+    field public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 1; // 0x1
+  }
+
+  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceSandboxedInferenceService extends android.app.Service {
+    ctor public OnDeviceSandboxedInferenceService();
     method public final void fetchFeatureFileInputStreamMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.io.FileInputStream>>);
+    method @NonNull public java.util.concurrent.Executor getCallbackExecutor();
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
-    method @NonNull public abstract void onCountTokens(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<java.lang.Long,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
-    method @NonNull public abstract void onProcessRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
-    method @NonNull public abstract void onProcessRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamingResponseReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
+    method @NonNull public abstract void onProcessRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver);
+    method @NonNull public abstract void onProcessRequestStreaming(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver);
+    method @NonNull public abstract void onTokenInfoRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
+    method public abstract void onUpdateProcessingState(@NonNull android.os.Bundle, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>);
     method public final java.io.FileInputStream openFileInput(@NonNull String) throws java.io.FileNotFoundException;
     method public final void openFileInputAsync(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.io.FileInputStream>) throws java.io.FileNotFoundException;
-    field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceTrustedInferenceService";
+    field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService";
   }
 
 }
@@ -14203,7 +14214,6 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isInEmergencyCall();
     method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isInSelfManagedCall(@NonNull String, @NonNull android.os.UserHandle);
-    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isInSelfManagedCall(@NonNull String, boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUserSelectedOutgoingPhoneAccount(@Nullable android.telecom.PhoneAccountHandle);
     field public static final String ACTION_CURRENT_TTY_MODE_CHANGED = "android.telecom.action.CURRENT_TTY_MODE_CHANGED";
@@ -15320,12 +15330,12 @@
     method @Deprecated public boolean getDataEnabled(int);
     method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.ComponentName getDefaultRespondViaMessageApplication();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getDeviceSoftwareVersion(int);
-    method @FlaggedApi("android.permission.flags.get_emergency_role_holder_api_enabled") @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getEmergencyAssistancePackage();
+    method @FlaggedApi("android.permission.flags.get_emergency_role_holder_api_enabled") @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getEmergencyAssistancePackageName();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getEmergencyCallbackMode();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEmergencyNumberDbVersion();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst();
-    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @Nullable @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_LAST_KNOWN_CELL_ID}) public android.telephony.CellIdentity getLastKnownCellIdentity();
+    method @FlaggedApi("com.android.server.telecom.flags.get_last_known_cell_identity") @Nullable @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_LAST_KNOWN_CELL_ID}) public android.telephony.CellIdentity getLastKnownCellIdentity();
     method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping();
     method public int getMaxNumberOfSimultaneouslyActiveSims();
     method public static long getMaxNumberVerificationTimeoutMillis();
@@ -15383,7 +15393,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean matchesCurrentSimOperator(@NonNull String, int, @Nullable String);
     method public boolean needsOtaServiceProvisioning();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled();
-    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.DUMP) public void persistEmergencyCallDiagnosticData(@NonNull String, @NonNull android.telephony.TelephonyManager.EmergencyCallDiagnosticData);
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.READ_DROPBOX_DATA) public void persistEmergencyCallDiagnosticData(@NonNull String, @NonNull android.telephony.TelephonyManager.EmergencyCallDiagnosticData);
     method @RequiresPermission(android.Manifest.permission.REBOOT) public int prepareForUnattendedReboot();
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerCarrierPrivilegesCallback(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CarrierPrivilegesCallback);
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 1923641..1e72a06 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -511,9 +511,9 @@
 
 InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceIntelligenceService#onBind(android.content.Intent) parameter #0:
     Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
-InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#onBind(android.content.Intent) parameter #0:
+InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService#onBind(android.content.Intent) parameter #0:
     Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
-InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#openFileInput(String) parameter #0:
+InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService#openFileInput(String) parameter #0:
     Invalid nullability on parameter `filename` in method `openFileInput`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
 InvalidNullabilityOverride: android.service.textclassifier.TextClassifierService#onUnbind(android.content.Intent) parameter #0:
     Invalid nullability on parameter `intent` in method `onUnbind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
@@ -571,7 +571,7 @@
     Missing nullability on parameter `args` in method `dump`
 MissingNullability: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context) parameter #0:
     Missing nullability on parameter `base` in method `attachBaseContext`
-MissingNullability: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#openFileInput(String):
+MissingNullability: android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService#openFileInput(String):
     Missing nullability on method `openFileInput` return
 MissingNullability: android.telephony.NetworkService#onUnbind(android.content.Intent) parameter #0:
     Missing nullability on parameter `intent` in method `onUnbind`
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index cdf232c..892567c6 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -251,6 +251,7 @@
     method @RequiresPermission("android.permission.MANAGE_APPOPS") public void getHistoricalOpsFromDiskRaw(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @Nullable java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
     method public static int getNumOps();
     method public boolean isOperationActive(int, int, String);
+    method public int noteOpNoThrow(int, @NonNull android.content.AttributionSource, @Nullable String);
     method @RequiresPermission("android.permission.MANAGE_APPOPS") public void offsetHistory(long);
     method public static String opToPermission(int);
     method public static int permissionToOpCode(String);
@@ -316,9 +317,6 @@
   }
 
   public class ComponentOptions {
-    field public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED = 1; // 0x1
-    field public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2; // 0x2
-    field public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED = 0; // 0x0
   }
 
   public class DownloadManager {
@@ -1368,7 +1366,7 @@
   }
 
   @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public class IntentFactory {
-    method @NonNull public static android.content.Intent createCancelUiIntent(@NonNull android.os.IBinder, boolean, @NonNull String);
+    method @NonNull public static android.content.Intent createCancelUiIntent(@NonNull android.content.Context, @NonNull android.os.IBinder, boolean, @NonNull String);
     method @NonNull public static android.content.Intent createCredentialSelectorIntent(@NonNull android.content.Context, @NonNull android.credentials.selection.RequestInfo, @NonNull java.util.ArrayList<android.credentials.selection.ProviderData>, @NonNull java.util.ArrayList<android.credentials.selection.DisabledProviderData>, @NonNull android.os.ResultReceiver);
   }
 
@@ -1521,6 +1519,7 @@
 package android.hardware {
 
   public final class SensorPrivacyManager {
+    method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setCameraPrivacyAllowlist(@NonNull java.util.List<java.lang.String>);
     method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, int, boolean);
     method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacyState(int, int, int);
   }
@@ -1625,8 +1624,6 @@
 package android.hardware.devicestate {
 
   @FlaggedApi("android.hardware.devicestate.feature.flags.device_state_property_api") public final class DeviceState {
-    ctor @Deprecated public DeviceState(@IntRange(from=android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE_IDENTIFIER, to=android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE_IDENTIFIER) int, @NonNull String, int);
-    ctor public DeviceState(@IntRange(from=android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE_IDENTIFIER, to=android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE_IDENTIFIER) int, @NonNull String, @NonNull java.util.Set<java.lang.Integer>);
     field public static final int PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST = 8; // 0x8
   }
 
@@ -1711,15 +1708,6 @@
 
 }
 
-package android.hardware.fingerprint {
-
-  @Deprecated public class FingerprintManager {
-    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession createTestSession(int);
-    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public java.util.List<android.hardware.biometrics.SensorProperties> getSensorProperties();
-  }
-
-}
-
 package android.hardware.hdmi {
 
   public final class HdmiControlServiceWrapper {
@@ -2033,41 +2021,6 @@
     method public android.media.PlaybackParams setAudioStretchMode(int);
   }
 
-  @FlaggedApi("android.os.vibrator.haptics_customization_enabled") public final class RingtoneSelection {
-    method @NonNull public static android.media.RingtoneSelection fromUri(@Nullable android.net.Uri, int);
-    method public int getSoundSource();
-    method @Nullable public android.net.Uri getSoundUri();
-    method public int getVibrationSource();
-    method @Nullable public android.net.Uri getVibrationUri();
-    method public static boolean isRingtoneSelectionUri(@Nullable android.net.Uri);
-    method @NonNull public android.net.Uri toUri();
-    field public static final String DEFAULT_SELECTION_URI_STRING = "content://media/ringtone";
-    field public static final int FROM_URI_RINGTONE_SELECTION_ONLY = 3; // 0x3
-    field public static final int FROM_URI_RINGTONE_SELECTION_OR_SOUND = 1; // 0x1
-    field public static final int FROM_URI_RINGTONE_SELECTION_OR_VIBRATION = 2; // 0x2
-    field public static final int SOUND_SOURCE_OFF = 1; // 0x1
-    field public static final int SOUND_SOURCE_SYSTEM_DEFAULT = 3; // 0x3
-    field public static final int SOUND_SOURCE_UNSPECIFIED = 0; // 0x0
-    field public static final int SOUND_SOURCE_URI = 2; // 0x2
-    field public static final int VIBRATION_SOURCE_APPLICATION_DEFAULT = 4; // 0x4
-    field public static final int VIBRATION_SOURCE_AUDIO_CHANNEL = 10; // 0xa
-    field public static final int VIBRATION_SOURCE_HAPTIC_GENERATOR = 11; // 0xb
-    field public static final int VIBRATION_SOURCE_OFF = 1; // 0x1
-    field public static final int VIBRATION_SOURCE_SYSTEM_DEFAULT = 3; // 0x3
-    field public static final int VIBRATION_SOURCE_UNSPECIFIED = 0; // 0x0
-    field public static final int VIBRATION_SOURCE_URI = 2; // 0x2
-  }
-
-  @FlaggedApi("android.os.vibrator.haptics_customization_enabled") public static final class RingtoneSelection.Builder {
-    ctor public RingtoneSelection.Builder();
-    ctor public RingtoneSelection.Builder(@NonNull android.media.RingtoneSelection);
-    method @NonNull public android.media.RingtoneSelection build();
-    method @NonNull public android.media.RingtoneSelection.Builder setSoundSource(int);
-    method @NonNull public android.media.RingtoneSelection.Builder setSoundSource(@NonNull android.net.Uri);
-    method @NonNull public android.media.RingtoneSelection.Builder setVibrationSource(int);
-    method @NonNull public android.media.RingtoneSelection.Builder setVibrationSource(@NonNull android.net.Uri);
-  }
-
   public static final class VolumeShaper.Configuration.Builder {
     method @NonNull public android.media.VolumeShaper.Configuration.Builder setOptionFlags(int);
   }
@@ -3966,6 +3919,8 @@
   }
 
   public final class InputMethodInfo implements android.os.Parcelable {
+    ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, boolean, @NonNull String);
+    ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, @NonNull String, boolean, @NonNull String);
     ctor @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, @NonNull String, boolean, boolean, @NonNull String);
     ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, int);
     field public static final int COMPONENT_NAME_MAX_LENGTH = 1000; // 0x3e8
@@ -3980,6 +3935,7 @@
     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 void hideSoftInputFromServerForTest();
     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 @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);
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 658ddbf..685ea63 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -2055,14 +2055,8 @@
     New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_DEFAULT
 UnflaggedApi: android.media.RingtoneSelection#SOUND_SOURCE_OFF:
     New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_OFF
-UnflaggedApi: android.media.RingtoneSelection#SOUND_SOURCE_SYSTEM_DEFAULT:
-    New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_SYSTEM_DEFAULT
-UnflaggedApi: android.media.RingtoneSelection#SOUND_SOURCE_UNSPECIFIED:
-    New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_UNSPECIFIED
 UnflaggedApi: android.media.RingtoneSelection#SOUND_SOURCE_URI:
     New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_URI
-UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_APPLICATION_DEFAULT:
-    New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_APPLICATION_DEFAULT
 UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_APPLICATION_PROVIDED:
     New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_APPLICATION_PROVIDED
 UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_AUDIO_CHANNEL:
@@ -2073,10 +2067,6 @@
     New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_HAPTIC_GENERATOR
 UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_OFF:
     New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_OFF
-UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_SYSTEM_DEFAULT:
-    New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_SYSTEM_DEFAULT
-UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_UNSPECIFIED:
-    New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_UNSPECIFIED
 UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_URI:
     New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_URI
 UnflaggedApi: android.media.RingtoneSelection#fromUri(android.net.Uri, int):
diff --git a/core/api/test-removed.txt b/core/api/test-removed.txt
index d802177..2e44176 100644
--- a/core/api/test-removed.txt
+++ b/core/api/test-removed.txt
@@ -1 +1,10 @@
 // Signature format: 2.0
+package android.hardware.fingerprint {
+
+  @Deprecated public class FingerprintManager {
+    method @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession createTestSession(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public java.util.List<android.hardware.biometrics.SensorProperties> getSensorProperties();
+  }
+
+}
+
diff --git a/core/java/Android.bp b/core/java/Android.bp
index ab1c9a4..4f96206 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -167,6 +167,9 @@
         "com/android/internal/logging/UiEventLoggerImpl.java",
         ":statslog-framework-java-gen",
     ],
+    libs: [
+        "androidx.annotation_annotation",
+    ],
     static_libs: ["modules-utils-uieventlogger-interface"],
 }
 
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 42c3272..d70fa19 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -81,7 +81,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Collections;
 import java.util.List;
-import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 import java.util.function.IntConsumer;
@@ -853,7 +852,6 @@
     private final SparseArray<AccessibilityButtonController> mAccessibilityButtonControllers =
             new SparseArray<>(0);
     private BrailleDisplayController mBrailleDisplayController;
-    private BrailleDisplayController mTestBrailleDisplayController;
 
     private int mGestureStatusCallbackSequence;
 
@@ -3650,46 +3648,10 @@
     public BrailleDisplayController getBrailleDisplayController() {
         BrailleDisplayController.checkApiFlagIsEnabled();
         synchronized (mLock) {
-            if (mTestBrailleDisplayController != null) {
-                return mTestBrailleDisplayController;
-            }
-
             if (mBrailleDisplayController == null) {
                 mBrailleDisplayController = new BrailleDisplayControllerImpl(this, mLock);
             }
             return mBrailleDisplayController;
         }
     }
-
-    /**
-     * Set the {@link BrailleDisplayController} implementation that will be returned by
-     * {@link #getBrailleDisplayController}, to allow this accessibility service to test its
-     * interaction with BrailleDisplayController without requiring a real Braille display.
-     *
-     * <p>For full test fidelity, ensure that this test-only implementation follows the same
-     * behavior specified in the documentation for {@link BrailleDisplayController}, including
-     * thrown exceptions.
-     *
-     * @param controller A test-only implementation of {@link BrailleDisplayController}.
-     */
-    @FlaggedApi(android.view.accessibility.Flags.FLAG_BRAILLE_DISPLAY_HID)
-    public void setTestBrailleDisplayController(@NonNull BrailleDisplayController controller) {
-        BrailleDisplayController.checkApiFlagIsEnabled();
-        Objects.requireNonNull(controller);
-        synchronized (mLock) {
-            mTestBrailleDisplayController = controller;
-        }
-    }
-
-    /**
-     * Clears the {@link BrailleDisplayController} previously set by
-     * {@link #setTestBrailleDisplayController}.
-     */
-    @FlaggedApi(android.view.accessibility.Flags.FLAG_BRAILLE_DISPLAY_HID)
-    public void clearTestBrailleDisplayController() {
-        BrailleDisplayController.checkApiFlagIsEnabled();
-        synchronized (mLock) {
-            mTestBrailleDisplayController = null;
-        }
-    }
 }
diff --git a/core/java/android/accessibilityservice/BrailleDisplayControllerImpl.java b/core/java/android/accessibilityservice/BrailleDisplayControllerImpl.java
index cac1dc4..f1df336 100644
--- a/core/java/android/accessibilityservice/BrailleDisplayControllerImpl.java
+++ b/core/java/android/accessibilityservice/BrailleDisplayControllerImpl.java
@@ -25,9 +25,11 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.view.accessibility.AccessibilityInteractionClient;
 import android.view.accessibility.Flags;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FunctionalUtils;
 
 import java.io.IOException;
@@ -36,24 +38,46 @@
 
 /**
  * Default implementation of {@link BrailleDisplayController}.
+ *
+ * @hide
  */
 // BrailleDisplayControllerImpl is not an API, but it implements BrailleDisplayController APIs.
 // This @FlaggedApi annotation tells the linter that this method delegates API checks to its
 // callers.
 @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
-final class BrailleDisplayControllerImpl implements BrailleDisplayController {
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public final class BrailleDisplayControllerImpl implements BrailleDisplayController {
 
     private final AccessibilityService mAccessibilityService;
     private final Object mLock;
+    private final boolean mIsHidrawSupported;
 
     private IBrailleDisplayConnection mBrailleDisplayConnection;
     private Executor mCallbackExecutor;
     private BrailleDisplayCallback mCallback;
 
+    /**
+     * Read-only property that returns whether HIDRAW access is supported on this device.
+     *
+     * <p>Defaults to true.
+     *
+     * <p>Device manufacturers without HIDRAW kernel support can set this to false in
+     * the device's product makefile.
+     */
+    private static final boolean IS_HIDRAW_SUPPORTED = SystemProperties.getBoolean(
+            "ro.accessibility.support_hidraw", true);
+
     BrailleDisplayControllerImpl(AccessibilityService accessibilityService,
             Object lock) {
+        this(accessibilityService, lock, IS_HIDRAW_SUPPORTED);
+    }
+
+    @VisibleForTesting
+    public BrailleDisplayControllerImpl(AccessibilityService accessibilityService,
+            Object lock, boolean isHidrawSupported) {
         mAccessibilityService = accessibilityService;
         mLock = lock;
+        mIsHidrawSupported = isHidrawSupported;
     }
 
     @Override
@@ -113,6 +137,11 @@
                     createConnection,
             @NonNull Executor callbackExecutor, @NonNull BrailleDisplayCallback callback) {
         BrailleDisplayController.checkApiFlagIsEnabled();
+        if (!mIsHidrawSupported) {
+            callbackExecutor.execute(() -> callback.onConnectionFailed(
+                    BrailleDisplayCallback.FLAG_ERROR_CANNOT_ACCESS));
+            return;
+        }
         if (isConnected()) {
             throw new IllegalStateException(
                     "This service already has a connected Braille display");
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 57c67be..1cc2d25 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1126,8 +1126,8 @@
          * @hide
          */
         @Override
-        public void updateStatusBarAppearance(int appearance) {
-            mTaskDescription.setStatusBarAppearance(appearance);
+        public void updateSystemBarsAppearance(int appearance) {
+            mTaskDescription.setSystemBarsAppearance(appearance);
             setTaskDescription(mTaskDescription);
         }
 
@@ -1254,12 +1254,23 @@
         return mApplication;
     }
 
-    /** Is this activity embedded inside of another activity? */
+    /**
+     * Whether this is a child {@link Activity} of an {@link ActivityGroup}.
+     *
+     * @deprecated {@link ActivityGroup} is deprecated.
+     */
+    @Deprecated
     public final boolean isChild() {
         return mParent != null;
     }
 
-    /** Return the parent activity if this view is an embedded child. */
+    /**
+     * Returns the parent {@link Activity} if this is a child {@link Activity} of an
+     * {@link ActivityGroup}.
+     *
+     * @deprecated {@link ActivityGroup} is deprecated.
+     */
+    @Deprecated
     public final Activity getParent() {
         return mParent;
     }
@@ -5535,6 +5546,15 @@
         }
 
         a.recycle();
+        if (first && mTaskDescription.getSystemBarsAppearance() == 0
+                && mWindow != null && mWindow.getSystemBarAppearance() != 0) {
+            // When the theme is applied for the first time during the activity re-creation process,
+            // the attached window restores the system bars appearance from the old window/activity.
+            // Make sure to restore this appearance in TaskDescription too, to prevent the
+            // #setTaskDescription() call below from incorrectly sending an empty value to the
+            // server.
+            mTaskDescription.setSystemBarsAppearance(mWindow.getSystemBarAppearance());
+        }
         setTaskDescription(mTaskDescription);
     }
 
@@ -7453,7 +7473,7 @@
      *               intent.
      */
     @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS)
-    public void onActivityResult(int requestCode, int resultCode, @NonNull Intent data,
+    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data,
             @NonNull ComponentCaller caller) {
         onActivityResult(requestCode, resultCode, data);
     }
@@ -9601,9 +9621,9 @@
      * Specifies whether the activities below this one in the task can also start other activities
      * or finish the task.
      * <p>
-     * Starting from Target SDK Level {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, apps
-     * are blocked from starting new activities or finishing their task unless the top activity of
-     * such task belong to the same UID for security reasons.
+     * Starting from Target SDK Level {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, apps
+     * may be blocked from starting new activities or finishing their task unless the top activity
+     * of such task belong to the same UID for security reasons.
      * <p>
      * Setting this flag to {@code true} will allow the launching app to ignore the restriction if
      * this activity is on top. Apps matching the UID of this activity are always exempt.
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index f358522..fae4348 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1604,7 +1604,7 @@
         private int mStatusBarColor;
         private int mNavigationBarColor;
         @Appearance
-        private int mStatusBarAppearance;
+        private int mSystemBarsAppearance;
         private boolean mEnsureStatusBarContrastWhenTransparent;
         private boolean mEnsureNavigationBarContrastWhenTransparent;
         private int mResizeMode;
@@ -1804,7 +1804,7 @@
         public TaskDescription(@Nullable String label, @Nullable Icon icon,
                 int colorPrimary, int colorBackground,
                 int statusBarColor, int navigationBarColor,
-                @Appearance int statusBarAppearance,
+                @Appearance int systemBarsAppearance,
                 boolean ensureStatusBarContrastWhenTransparent,
                 boolean ensureNavigationBarContrastWhenTransparent, int resizeMode, int minWidth,
                 int minHeight, int colorBackgroundFloating) {
@@ -1814,7 +1814,7 @@
             mColorBackground = colorBackground;
             mStatusBarColor = statusBarColor;
             mNavigationBarColor = navigationBarColor;
-            mStatusBarAppearance = statusBarAppearance;
+            mSystemBarsAppearance = systemBarsAppearance;
             mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
             mEnsureNavigationBarContrastWhenTransparent =
                     ensureNavigationBarContrastWhenTransparent;
@@ -1843,7 +1843,7 @@
             mColorBackground = other.mColorBackground;
             mStatusBarColor = other.mStatusBarColor;
             mNavigationBarColor = other.mNavigationBarColor;
-            mStatusBarAppearance = other.mStatusBarAppearance;
+            mSystemBarsAppearance = other.mSystemBarsAppearance;
             mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
             mEnsureNavigationBarContrastWhenTransparent =
                     other.mEnsureNavigationBarContrastWhenTransparent;
@@ -1873,8 +1873,8 @@
             if (other.mNavigationBarColor != 0) {
                 mNavigationBarColor = other.mNavigationBarColor;
             }
-            if (other.mStatusBarAppearance != 0) {
-                mStatusBarAppearance = other.mStatusBarAppearance;
+            if (other.mSystemBarsAppearance != 0) {
+                mSystemBarsAppearance = other.mSystemBarsAppearance;
             }
 
             mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
@@ -2148,8 +2148,8 @@
          * @hide
          */
         @Appearance
-        public int getStatusBarAppearance() {
-            return mStatusBarAppearance;
+        public int getSystemBarsAppearance() {
+            return mSystemBarsAppearance;
         }
 
         /**
@@ -2163,8 +2163,8 @@
         /**
          * @hide
          */
-        public void setStatusBarAppearance(@Appearance int statusBarAppearance) {
-            mStatusBarAppearance = statusBarAppearance;
+        public void setSystemBarsAppearance(@Appearance int systemBarsAppearance) {
+            mSystemBarsAppearance = systemBarsAppearance;
         }
 
         /**
@@ -2291,7 +2291,7 @@
             dest.writeInt(mColorBackground);
             dest.writeInt(mStatusBarColor);
             dest.writeInt(mNavigationBarColor);
-            dest.writeInt(mStatusBarAppearance);
+            dest.writeInt(mSystemBarsAppearance);
             dest.writeBoolean(mEnsureStatusBarContrastWhenTransparent);
             dest.writeBoolean(mEnsureNavigationBarContrastWhenTransparent);
             dest.writeInt(mResizeMode);
@@ -2315,7 +2315,7 @@
             mColorBackground = source.readInt();
             mStatusBarColor = source.readInt();
             mNavigationBarColor = source.readInt();
-            mStatusBarAppearance = source.readInt();
+            mSystemBarsAppearance = source.readInt();
             mEnsureStatusBarContrastWhenTransparent = source.readBoolean();
             mEnsureNavigationBarContrastWhenTransparent = source.readBoolean();
             mResizeMode = source.readInt();
@@ -2347,7 +2347,8 @@
                             ? " (contrast when transparent)" : "")
                     + " resizeMode: " + ActivityInfo.resizeModeToString(mResizeMode)
                     + " minWidth: " + mMinWidth + " minHeight: " + mMinHeight
-                    + " colorBackgrounFloating: " + mColorBackgroundFloating;
+                    + " colorBackgrounFloating: " + mColorBackgroundFloating
+                    + " systemBarsAppearance: " + mSystemBarsAppearance;
         }
 
         @Override
@@ -2367,7 +2368,7 @@
             result = result * 31 + mColorBackgroundFloating;
             result = result * 31 + mStatusBarColor;
             result = result * 31 + mNavigationBarColor;
-            result = result * 31 + mStatusBarAppearance;
+            result = result * 31 + mSystemBarsAppearance;
             result = result * 31 + (mEnsureStatusBarContrastWhenTransparent ? 1 : 0);
             result = result * 31 + (mEnsureNavigationBarContrastWhenTransparent ? 1 : 0);
             result = result * 31 + mResizeMode;
@@ -2390,7 +2391,7 @@
                     && mColorBackground == other.mColorBackground
                     && mStatusBarColor == other.mStatusBarColor
                     && mNavigationBarColor == other.mNavigationBarColor
-                    && mStatusBarAppearance == other.mStatusBarAppearance
+                    && mSystemBarsAppearance == other.mSystemBarsAppearance
                     && mEnsureStatusBarContrastWhenTransparent
                             == other.mEnsureStatusBarContrastWhenTransparent
                     && mEnsureNavigationBarContrastWhenTransparent
@@ -6053,20 +6054,6 @@
     }
 
     /**
-     * Checks if the "modern" broadcast queue is enabled.
-     *
-     * @hide
-     */
-    @RequiresPermission(android.Manifest.permission.DUMP)
-    public boolean isModernBroadcastQueueEnabled() {
-        try {
-            return getService().isModernBroadcastQueueEnabled();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Checks if the process represented by the given {@code pid} is frozen.
      *
      * @hide
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 062b89e..e28a6ce 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -226,12 +226,6 @@
     public abstract boolean isSystemReady();
 
     /**
-     * @return {@code true} if system is using the "modern" broadcast queue,
-     *         {@code false} otherwise.
-     */
-    public abstract boolean isModernQueueEnabled();
-
-    /**
      * Enforce capability restrictions on use of the given BroadcastOptions
      */
     public abstract void enforceBroadcastOptionsPermissions(@Nullable Bundle options,
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 2a2c5f0..e094ac6 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -93,15 +93,30 @@
      */
     public static final String EXTRA_USAGE_TIME_REPORT_PACKAGES = "android.usage_time_packages";
 
-    /** No explicit value chosen. The system will decide whether to grant privileges. */
-    public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED =
-            ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
-    /** Allow the {@link PendingIntent} to use the background activity start privileges. */
-    public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED =
-            ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
-    /** Deny the {@link PendingIntent} to use the background activity start privileges. */
-    public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED =
-            ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
+    /** Enumeration of background activity start modes.
+     *
+     * These define if an app wants to grant it's background activity start privileges to a
+     * {@link PendingIntent}.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"MODE_BACKGROUND_ACTIVITY_START_"}, value = {
+            MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED,
+            MODE_BACKGROUND_ACTIVITY_START_ALLOWED,
+            MODE_BACKGROUND_ACTIVITY_START_DENIED})
+    public @interface BackgroundActivityStartMode {}
+    /**
+     * No explicit value chosen. The system will decide whether to grant privileges.
+     */
+    public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED = 0;
+    /**
+     * Allow the {@link PendingIntent} to use the background activity start privileges.
+     */
+    public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED = 1;
+    /**
+     * Deny the {@link PendingIntent} to use the background activity start privileges.
+     */
+    public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2;
 
     /**
      * The package name that created the options.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 074f7e9..ae5cacd 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -40,6 +40,7 @@
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
 import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
+import static com.android.window.flags.Flags.activityWindowInfoFlag;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -63,6 +64,7 @@
 import android.app.servertransaction.ActivityRelaunchItem;
 import android.app.servertransaction.ActivityResultItem;
 import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionListenerController;
 import android.app.servertransaction.DestroyActivityItem;
 import android.app.servertransaction.PauseActivityItem;
 import android.app.servertransaction.PendingTransactionActions;
@@ -606,6 +608,8 @@
         Configuration overrideConfig;
         @NonNull
         private ActivityWindowInfo mActivityWindowInfo;
+        @Nullable
+        private ActivityWindowInfo mLastReportedActivityWindowInfo;
 
         // Used for consolidating configs before sending on to Activity.
         private final Configuration tmpConfig = new Configuration();
@@ -717,7 +721,7 @@
                     }
                     activity.mMainThread.handleActivityConfigurationChanged(
                             ActivityClientRecord.this, overrideConfig, newDisplayId,
-                            false /* alwaysReportChange */);
+                            mActivityWindowInfo, false /* alwaysReportChange */);
                 }
 
                 @Override
@@ -970,6 +974,7 @@
         ContentCaptureOptions contentCaptureOptions;
 
         long[] disabledCompatChanges;
+        long[] mLoggableCompatChanges;
 
         SharedMemory mSerializedSystemFontMap;
 
@@ -1279,6 +1284,7 @@
                 AutofillOptions autofillOptions,
                 ContentCaptureOptions contentCaptureOptions,
                 long[] disabledCompatChanges,
+                long[] loggableCompatChanges,
                 SharedMemory serializedSystemFontMap,
                 long startRequestedElapsedTime,
                 long startRequestedUptime) {
@@ -1333,6 +1339,7 @@
             data.autofillOptions = autofillOptions;
             data.contentCaptureOptions = contentCaptureOptions;
             data.disabledCompatChanges = disabledCompatChanges;
+            data.mLoggableCompatChanges = loggableCompatChanges;
             data.mSerializedSystemFontMap = serializedSystemFontMap;
             data.startRequestedElapsedTime = startRequestedElapsedTime;
             data.startRequestedUptime = startRequestedUptime;
@@ -4069,6 +4076,13 @@
                     ActivityManager.getService().waitForNetworkStateUpdate(mNetworkBlockSeq);
                     mNetworkBlockSeq = INVALID_PROC_STATE_SEQ;
                 } catch (RemoteException ignored) {}
+                if (Flags.clearDnsCacheOnNetworkRulesUpdate()) {
+                    // InetAddress will cache UnknownHostException failures. If the rules got
+                    // updated and the app has network access now, we need to clear the negative
+                    // cache to ensure valid dns queries can work immediately.
+                    // TODO: b/329133769 - Clear only the negative cache once it is available.
+                    InetAddress.clearDnsCache();
+                }
             }
         }
     }
@@ -4180,6 +4194,9 @@
                 pendingActions.setRestoreInstanceState(true);
                 pendingActions.setCallOnPostCreate(true);
             }
+
+            // Trigger ActivityWindowInfo callback if first launch or change from relaunch.
+            handleActivityWindowInfoChanged(r);
         } else {
             // If there was an error, for any reason, tell the activity manager to stop us.
             ActivityClient.getInstance().finishActivity(r.token, Activity.RESULT_CANCELED,
@@ -4558,7 +4575,7 @@
     private void schedulePauseWithUserLeavingHint(ActivityClientRecord r) {
         final ClientTransaction transaction = ClientTransaction.obtain(mAppThread);
         final PauseActivityItem pauseActivityItem = PauseActivityItem.obtain(r.token,
-                r.activity.isFinishing(), /* userLeaving */ true, r.activity.mConfigChangeFlags,
+                r.activity.isFinishing(), /* userLeaving */ true,
                 /* dontReport */ false, /* autoEnteringPip */ false);
         transaction.addTransactionItem(pauseActivityItem);
         executeTransaction(transaction);
@@ -5432,13 +5449,12 @@
 
     @Override
     public void handlePauseActivity(ActivityClientRecord r, boolean finished, boolean userLeaving,
-            int configChanges, boolean autoEnteringPip, PendingTransactionActions pendingActions,
+            boolean autoEnteringPip, PendingTransactionActions pendingActions,
             String reason) {
         if (userLeaving) {
             performUserLeavingActivity(r);
         }
 
-        r.activity.mConfigChangeFlags |= configChanges;
         if (autoEnteringPip) {
             // Set mIsInPictureInPictureMode earlier in case of auto-enter-pip, see also
             // {@link Activity#enterPictureInPictureMode(PictureInPictureParams)}.
@@ -5687,9 +5703,8 @@
     }
 
     @Override
-    public void handleStopActivity(ActivityClientRecord r, int configChanges,
+    public void handleStopActivity(ActivityClientRecord r,
             PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
-        r.activity.mConfigChangeFlags |= configChanges;
 
         final StopInfo stopInfo = new StopInfo();
         performStopActivityInner(r, stopInfo, true /* saveState */, finalStateRequest,
@@ -5859,11 +5874,10 @@
 
     /** Core implementation of activity destroy call. */
     void performDestroyActivity(ActivityClientRecord r, boolean finishing,
-            int configChanges, boolean getNonConfigInstance, String reason) {
+            boolean getNonConfigInstance, String reason) {
         Class<? extends Activity> activityClass;
         if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
         activityClass = r.activity.getClass();
-        r.activity.mConfigChangeFlags |= configChanges;
         if (finishing) {
             r.activity.mFinished = true;
         }
@@ -5928,9 +5942,9 @@
     }
 
     @Override
-    public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges,
+    public void handleDestroyActivity(ActivityClientRecord r, boolean finishing,
             boolean getNonConfigInstance, String reason) {
-        performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason);
+        performDestroyActivity(r, finishing, getNonConfigInstance, reason);
         cleanUpPendingRemoveWindows(r, finishing);
         WindowManager wm = r.activity.getWindowManager();
         View v = r.activity.mDecor;
@@ -6130,7 +6144,7 @@
 
         r.activity.mChangingConfigurations = true;
 
-        handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
+        handleRelaunchActivityInner(r, tmp.pendingResults, tmp.pendingIntents,
                 pendingActions, tmp.startsNotResumed, tmp.overrideConfig, tmp.mActivityWindowInfo,
                 "handleRelaunchActivity");
     }
@@ -6199,7 +6213,7 @@
         executeTransaction(transaction);
     }
 
-    private void handleRelaunchActivityInner(@NonNull ActivityClientRecord r, int configChanges,
+    private void handleRelaunchActivityInner(@NonNull ActivityClientRecord r,
             @Nullable List<ResultInfo> pendingResults,
             @Nullable List<ReferrerIntent> pendingIntents,
             @NonNull PendingTransactionActions pendingActions, boolean startsNotResumed,
@@ -6215,7 +6229,7 @@
             callActivityOnStop(r, true /* saveState */, reason);
         }
 
-        handleDestroyActivity(r, false, configChanges, true, reason);
+        handleDestroyActivity(r, false /* finishing */, true /* getNonConfigInstance */, reason);
 
         r.activity = null;
         r.window = null;
@@ -6659,11 +6673,12 @@
     /**
      * Sets the supplied {@code overrideConfig} as pending for the {@code token}. Calling
      * this method prevents any calls to
-     * {@link #handleActivityConfigurationChanged(ActivityClientRecord, Configuration, int)} from
-     * processing any configurations older than {@code overrideConfig}.
+     * {@link #handleActivityConfigurationChanged(ActivityClientRecord, Configuration, int,
+     * ActivityWindowInfo)} from processing any configurations older than {@code overrideConfig}.
      */
     @Override
-    public void updatePendingActivityConfiguration(IBinder token, Configuration overrideConfig) {
+    public void updatePendingActivityConfiguration(@NonNull IBinder token,
+            @NonNull Configuration overrideConfig) {
         synchronized (mPendingOverrideConfigs) {
             final Configuration pendingOverrideConfig = mPendingOverrideConfigs.get(token);
             if (pendingOverrideConfig != null
@@ -6680,9 +6695,10 @@
     }
 
     @Override
-    public void handleActivityConfigurationChanged(ActivityClientRecord r,
-            @NonNull Configuration overrideConfig, int displayId) {
-        handleActivityConfigurationChanged(r, overrideConfig, displayId,
+    public void handleActivityConfigurationChanged(@NonNull ActivityClientRecord r,
+            @NonNull Configuration overrideConfig, int displayId,
+            @NonNull ActivityWindowInfo activityWindowInfo) {
+        handleActivityConfigurationChanged(r, overrideConfig, displayId, activityWindowInfo,
                 // This is the only place that uses alwaysReportChange=true. The entry point should
                 // be from ActivityConfigurationChangeItem or MoveToDisplayItem, so the server side
                 // has confirmed the activity should handle the configuration instead of relaunch.
@@ -6700,9 +6716,11 @@
      * @param overrideConfig Activity override config.
      * @param displayId Id of the display where activity was moved to, -1 if there was no move and
      *                  value didn't change.
+     * @param activityWindowInfo the window info of the given activity.
      */
-    void handleActivityConfigurationChanged(ActivityClientRecord r,
-            @NonNull Configuration overrideConfig, int displayId, boolean alwaysReportChange) {
+    void handleActivityConfigurationChanged(@NonNull ActivityClientRecord r,
+            @NonNull Configuration overrideConfig, int displayId,
+            @NonNull ActivityWindowInfo activityWindowInfo, boolean alwaysReportChange) {
         synchronized (mPendingOverrideConfigs) {
             final Configuration pendingOverrideConfig = mPendingOverrideConfigs.get(r.token);
             if (overrideConfig.isOtherSeqNewer(pendingOverrideConfig)) {
@@ -6735,6 +6753,8 @@
 
         // Perform updates.
         r.overrideConfig = overrideConfig;
+        r.mActivityWindowInfo = activityWindowInfo;
+
         final ViewRootImpl viewRoot = r.activity.mDecor != null
             ? r.activity.mDecor.getViewRootImpl() : null;
 
@@ -6757,6 +6777,22 @@
             viewRoot.updateConfiguration(displayId);
         }
         mSomeActivitiesChanged = true;
+
+        // Trigger ActivityWindowInfo callback if changed.
+        handleActivityWindowInfoChanged(r);
+    }
+
+    private void handleActivityWindowInfoChanged(@NonNull ActivityClientRecord r) {
+        if (!activityWindowInfoFlag()) {
+            return;
+        }
+        if (r.mActivityWindowInfo == null
+                || r.mActivityWindowInfo.equals(r.mLastReportedActivityWindowInfo)) {
+            return;
+        }
+        r.mLastReportedActivityWindowInfo = r.mActivityWindowInfo;
+        ClientTransactionListenerController.getInstance().onActivityWindowInfoChanged(r.token,
+                r.mActivityWindowInfo);
     }
 
     final void handleProfilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {
@@ -7097,7 +7133,7 @@
         Process.setStartTimes(SystemClock.elapsedRealtime(), SystemClock.uptimeMillis(),
                 data.startRequestedElapsedTime, data.startRequestedUptime);
 
-        AppCompatCallbacks.install(data.disabledCompatChanges);
+        AppCompatCallbacks.install(data.disabledCompatChanges, data.mLoggableCompatChanges);
         // Let libcore handle any compat changes after installing the list of compat changes.
         AppSpecializationHooks.handleCompatChangesBeforeBindingApplication();
 
diff --git a/core/java/android/app/AppCompatCallbacks.java b/core/java/android/app/AppCompatCallbacks.java
index 134cef5..f2debfc 100644
--- a/core/java/android/app/AppCompatCallbacks.java
+++ b/core/java/android/app/AppCompatCallbacks.java
@@ -30,41 +30,59 @@
  */
 public final class AppCompatCallbacks implements Compatibility.BehaviorChangeDelegate {
     private final long[] mDisabledChanges;
+    private final long[] mLoggableChanges;
     private final ChangeReporter mChangeReporter;
 
     /**
-     * Install this class into the current process.
+     * Install this class into the current process using the disabled and loggable changes lists.
      *
      * @param disabledChanges Set of compatibility changes that are disabled for this process.
+     * @param loggableChanges Set of compatibility changes that we want to log.
      */
-    public static void install(long[] disabledChanges) {
-        Compatibility.setBehaviorChangeDelegate(new AppCompatCallbacks(disabledChanges));
+    public static void install(long[] disabledChanges, long[] loggableChanges) {
+        Compatibility.setBehaviorChangeDelegate(
+                new AppCompatCallbacks(disabledChanges, loggableChanges));
     }
 
-    private AppCompatCallbacks(long[] disabledChanges) {
+    private AppCompatCallbacks(long[] disabledChanges, long[] loggableChanges) {
         mDisabledChanges = Arrays.copyOf(disabledChanges, disabledChanges.length);
+        mLoggableChanges = Arrays.copyOf(loggableChanges, loggableChanges.length);
         Arrays.sort(mDisabledChanges);
-        mChangeReporter = new ChangeReporter(
-                ChangeReporter.SOURCE_APP_PROCESS);
+        Arrays.sort(mLoggableChanges);
+        mChangeReporter = new ChangeReporter(ChangeReporter.SOURCE_APP_PROCESS);
+    }
+
+    /**
+     * Helper to determine if a list contains a changeId.
+     *
+     * @param list to search through
+     * @param changeId for which to search in the list
+     * @return true if the given changeId is found in the provided array.
+     */
+    private boolean changeIdInChangeList(long[] list, long changeId) {
+        return Arrays.binarySearch(list, changeId) >= 0;
     }
 
     public void onChangeReported(long changeId) {
-        reportChange(changeId, ChangeReporter.STATE_LOGGED);
+        boolean isLoggable = changeIdInChangeList(mLoggableChanges, changeId);
+        reportChange(changeId, ChangeReporter.STATE_LOGGED, isLoggable);
     }
 
     public boolean isChangeEnabled(long changeId) {
-        if (Arrays.binarySearch(mDisabledChanges, changeId) < 0) {
-            // Not present in the disabled array
-            reportChange(changeId, ChangeReporter.STATE_ENABLED);
+        boolean isEnabled = !changeIdInChangeList(mDisabledChanges, changeId);
+        boolean isLoggable = changeIdInChangeList(mLoggableChanges, changeId);
+        if (isEnabled) {
+            // Not present in the disabled changeId array
+            reportChange(changeId, ChangeReporter.STATE_ENABLED, isLoggable);
             return true;
         }
-        reportChange(changeId, ChangeReporter.STATE_DISABLED);
+        reportChange(changeId, ChangeReporter.STATE_DISABLED, isLoggable);
         return false;
     }
 
-    private void reportChange(long changeId, int state) {
+    private void reportChange(long changeId, int state, boolean isLoggable) {
         int uid = Process.myUid();
-        mChangeReporter.reportChange(uid, changeId, state);
+        mChangeReporter.reportChange(uid, changeId, state, isLoggable);
     }
 
 }
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 39900a0..2afc78c 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -7800,7 +7800,7 @@
         }
         final List<AppOpsManager.PackageOps> result;
         try {
-            result =  mService.getPackagesForOpsForDevice(opCodes, persistentDeviceId);
+            result = mService.getPackagesForOpsForDevice(opCodes, persistentDeviceId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -8272,14 +8272,24 @@
                 cb = new IAppOpsCallback.Stub() {
                     public void opChanged(int op, int uid, String packageName,
                             String persistentDeviceId) {
-                        if (callback instanceof OnOpChangedInternalListener) {
-                            ((OnOpChangedInternalListener)callback).onOpChanged(op, packageName,
-                                persistentDeviceId);
-                        }
-                        if (sAppOpInfos[op].name != null) {
-
-                            callback.onOpChanged(sAppOpInfos[op].name, packageName,
-                                    UserHandle.getUserId(uid), persistentDeviceId);
+                        if (Flags.deviceAwarePermissionApisEnabled()) {
+                            if (callback instanceof OnOpChangedInternalListener) {
+                                ((OnOpChangedInternalListener)callback).onOpChanged(op, packageName,
+                                        persistentDeviceId);
+                            }
+                            if (sAppOpInfos[op].name != null) {
+                                callback.onOpChanged(sAppOpInfos[op].name, packageName,
+                                        UserHandle.getUserId(uid), persistentDeviceId);
+                            }
+                        } else {
+                            if (callback instanceof OnOpChangedInternalListener) {
+                                ((OnOpChangedInternalListener) callback).onOpChanged(op,
+                                        packageName);
+                            }
+                            if (sAppOpInfos[op].name != null) {
+                                callback.onOpChanged(sAppOpInfos[op].name, packageName,
+                                        UserHandle.getUserId(uid));
+                            }
                         }
                     }
                 };
@@ -8940,6 +8950,8 @@
      *
      * @hide
      */
+    @TestApi
+    @SuppressLint("UnflaggedApi")
     public int noteOpNoThrow(int op, @NonNull AttributionSource attributionSource,
             @Nullable String message) {
         return noteOpNoThrow(op, attributionSource.getUid(), attributionSource.getPackageName(),
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index 6ad0313..33d41d3 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -173,8 +173,8 @@
      *                           interrupt the user (e.g. via sound &amp; vibration) while this rule
      *                           is active.
      * @param enabled Whether the rule is enabled.
-     * @deprecated use {@link #AutomaticZenRule(String, ComponentName, ComponentName, Uri,
-     * ZenPolicy, int, boolean)}.
+     *
+     * @deprecated Use {@link AutomaticZenRule.Builder} to construct an {@link AutomaticZenRule}.
      */
     @Deprecated
     public AutomaticZenRule(String name, ComponentName owner, Uri conditionId,
@@ -185,6 +185,8 @@
     /**
      * Creates an automatic zen rule.
      *
+     * <p>Note: Prefer {@link AutomaticZenRule.Builder} to construct an {@link AutomaticZenRule}.
+     *
      * @param name The name of the rule.
      * @param owner The Condition Provider service that owns this rule. This can be null if you're
      *              using {@link NotificationManager#setAutomaticZenRuleState(String, Condition)}
@@ -207,7 +209,6 @@
      *               action ({@link Condition#STATE_TRUE}).
      * @param enabled Whether the rule is enabled.
      */
-    // TODO (b/309088420): deprecate this constructor in favor of the builder
     public AutomaticZenRule(@NonNull String name, @Nullable ComponentName owner,
             @Nullable ComponentName configurationActivity, @NonNull Uri conditionId,
             @Nullable ZenPolicy policy, int interruptionFilter, boolean enabled) {
@@ -368,6 +369,9 @@
 
     /**
      * Sets the zen policy.
+     *
+     * <p>When updating an existing rule via {@link NotificationManager#updateAutomaticZenRule},
+     * a {@code null} value here means the previous policy is retained.
      */
     public void setZenPolicy(@Nullable ZenPolicy zenPolicy) {
         this.mZenPolicy = (zenPolicy == null ? null : zenPolicy.copy());
@@ -390,7 +394,12 @@
      * Sets the configuration activity - an activity that handles
      * {@link NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} that shows the user more information
      * about this rule and/or allows them to configure it. This is required to be non-null for rules
-     * that are not backed by {@link android.service.notification.ConditionProviderService}.
+     * that are not backed by a {@link android.service.notification.ConditionProviderService}.
+     *
+     * <p>This is exclusive with the {@code owner} supplied in the constructor; rules where a
+     * configuration activity is set will not use the
+     * {@link android.service.notification.ConditionProviderService} supplied there to determine
+     * whether the rule should be active.
      */
     public void setConfigurationActivity(@Nullable ComponentName componentName) {
         this.configurationActivity = getTrimmedComponentName(componentName);
@@ -705,7 +714,15 @@
         }
 
         /**
-         * Sets the component (service or activity) that owns this rule.
+         * Sets the component name of the
+         * {@link android.service.notification.ConditionProviderService} that manages this rule
+         * (but note that {@link android.service.notification.ConditionProviderService} is
+         * deprecated in favor of using {@link NotificationManager#setAutomaticZenRuleState} to
+         * notify the system about the state of your rule).
+         *
+         * <p>This is exclusive with {@link #setConfigurationActivity}; rules where a configuration
+         * activity is set will not use the component set here to determine whether the rule
+         * should be active.
          */
         public @NonNull Builder setOwner(@Nullable ComponentName owner) {
             mOwner = owner;
@@ -743,6 +760,11 @@
          * information about this rule and/or allows them to configure it. This is required to be
          * non-null for rules that are not backed by a
          * {@link android.service.notification.ConditionProviderService}.
+         *
+         * <p>This is exclusive with {@link #setOwner}; rules where a configuration
+         * activity is set will not use the
+         * {@link android.service.notification.ConditionProviderService} supplied there to determine
+         * whether the rule should be active.
          */
         public @NonNull Builder setConfigurationActivity(
                 @Nullable ComponentName configurationActivity) {
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 1b5b0fc..60d622d 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -16,6 +16,8 @@
 
 package android.app;
 
+import static android.app.ActivityOptions.BackgroundActivityStartMode;
+
 import android.annotation.CurrentTimeMillisLong;
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
@@ -1132,7 +1134,8 @@
     @SystemApi
     @NonNull
     @Override // to narrow down the return type
-    public BroadcastOptions setPendingIntentBackgroundActivityStartMode(int state) {
+    public BroadcastOptions setPendingIntentBackgroundActivityStartMode(
+            @BackgroundActivityStartMode int state) {
         super.setPendingIntentBackgroundActivityStartMode(state);
         return this;
     }
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 4c92dee..01153c9 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -114,11 +114,11 @@
 
     /** Destroy the activity. */
     public abstract void handleDestroyActivity(@NonNull ActivityClientRecord r, boolean finishing,
-            int configChanges, boolean getNonConfigInstance, String reason);
+            boolean getNonConfigInstance, String reason);
 
     /** Pause the activity. */
     public abstract void handlePauseActivity(@NonNull ActivityClientRecord r, boolean finished,
-            boolean userLeaving, int configChanges, boolean autoEnteringPip,
+            boolean userLeaving, boolean autoEnteringPip,
             PendingTransactionActions pendingActions, String reason);
 
     /**
@@ -146,14 +146,13 @@
     /**
      * Stop the activity.
      * @param r Target activity record.
-     * @param configChanges Activity configuration changes.
      * @param pendingActions Pending actions to be used on this or later stages of activity
      *                       transaction.
      * @param finalStateRequest Flag indicating if this call is handling final lifecycle state
      *                          request for a transaction.
      * @param reason Reason for performing this operation.
      */
-    public abstract void handleStopActivity(@NonNull ActivityClientRecord r, int configChanges,
+    public abstract void handleStopActivity(@NonNull ActivityClientRecord r,
             PendingTransactionActions pendingActions, boolean finalStateRequest, String reason);
 
     /** Report that activity was stopped to server. */
@@ -167,11 +166,12 @@
 
     /** Set pending activity configuration in case it will be updated by other transaction item. */
     public abstract void updatePendingActivityConfiguration(@NonNull IBinder token,
-            Configuration overrideConfig);
+            @NonNull Configuration overrideConfig);
 
     /** Deliver activity (override) configuration change. */
     public abstract void handleActivityConfigurationChanged(@NonNull ActivityClientRecord r,
-            Configuration overrideConfig, int displayId);
+            @NonNull Configuration overrideConfig, int displayId,
+            @NonNull ActivityWindowInfo activityWindowInfo);
 
     /** Deliver {@link android.window.WindowContextInfo} change. */
     public abstract void handleWindowContextInfoChanged(@NonNull IBinder clientToken,
diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java
index ce16ddf..397477d 100644
--- a/core/java/android/app/ComponentOptions.java
+++ b/core/java/android/app/ComponentOptions.java
@@ -16,16 +16,17 @@
 
 package android.app;
 
-import android.annotation.IntDef;
+import static android.app.ActivityOptions.BackgroundActivityStartMode;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.os.Bundle;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
 /**
  * Base class for {@link ActivityOptions} and {@link BroadcastOptions}.
  * @hide
@@ -56,32 +57,6 @@
     private @Nullable Boolean mPendingIntentBalAllowed = null;
     private boolean mPendingIntentBalAllowedByPermission = false;
 
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = {"MODE_BACKGROUND_ACTIVITY_START_"}, value = {
-            MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED,
-            MODE_BACKGROUND_ACTIVITY_START_ALLOWED,
-            MODE_BACKGROUND_ACTIVITY_START_DENIED})
-    public @interface BackgroundActivityStartMode {}
-    /**
-     * No explicit value chosen. The system will decide whether to grant privileges.
-     * @hide
-     */
-    @TestApi
-    public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED = 0;
-    /**
-     * Allow the {@link PendingIntent} to use the background activity start privileges.
-     * @hide
-     */
-    @TestApi
-    public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED = 1;
-    /**
-     * Deny the {@link PendingIntent} to use the background activity start privileges.
-     * @hide
-     */
-    @TestApi
-    public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2;
-
     ComponentOptions() {
     }
 
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index af56cb4..ed0c933 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -272,10 +272,17 @@
 
     @UnsupportedAppUsage
     private Context mOuterContext;
+
+    private final Object mThemeLock = new Object();
+
     @UnsupportedAppUsage
+    @GuardedBy("mThemeLock")
     private int mThemeResource = 0;
+
     @UnsupportedAppUsage
+    @GuardedBy("mThemeLock")
     private Resources.Theme mTheme = null;
+
     @UnsupportedAppUsage
     private PackageManager mPackageManager;
     private Context mReceiverRestrictedContext = null;
@@ -288,7 +295,6 @@
 
     private ContentCaptureOptions mContentCaptureOptions = null;
 
-    private final Object mSync = new Object();
     /**
      * Indicates this {@link Context} can not handle UI components properly and is not associated
      * with a {@link Display} instance.
@@ -340,21 +346,18 @@
      */
     private boolean mOwnsToken = false;
 
-    @GuardedBy("mSync")
-    private File mDatabasesDir;
-    @GuardedBy("mSync")
-    @UnsupportedAppUsage
-    private File mPreferencesDir;
-    @GuardedBy("mSync")
-    private File mFilesDir;
-    @GuardedBy("mSync")
-    private File mCratesDir;
-    @GuardedBy("mSync")
-    private File mNoBackupFilesDir;
-    @GuardedBy("mSync")
-    private File mCacheDir;
-    @GuardedBy("mSync")
-    private File mCodeCacheDir;
+    private final Object mDirsLock = new Object();
+    private volatile File mDatabasesDir;
+    @UnsupportedAppUsage private volatile File mPreferencesDir;
+    private volatile File mFilesDir;
+    private volatile File mCratesDir;
+    private volatile File mNoBackupFilesDir;
+    private volatile File[] mExternalFilesDirs;
+    private volatile File[] mObbDirs;
+    private volatile File mCacheDir;
+    private volatile File mCodeCacheDir;
+    private volatile File[] mExternalCacheDirs;
+    private volatile File[] mExternalMediaDirs;
 
     // The system service cache for the system services that are cached per-ContextImpl.
     @UnsupportedAppUsage
@@ -458,7 +461,7 @@
 
     @Override
     public void setTheme(int resId) {
-        synchronized (mSync) {
+        synchronized (mThemeLock) {
             if (mThemeResource != resId) {
                 mThemeResource = resId;
                 initializeTheme();
@@ -468,14 +471,14 @@
 
     @Override
     public int getThemeResId() {
-        synchronized (mSync) {
+        synchronized (mThemeLock) {
             return mThemeResource;
         }
     }
 
     @Override
     public Resources.Theme getTheme() {
-        synchronized (mSync) {
+        synchronized (mThemeLock) {
             if (mTheme != null) {
                 return mTheme;
             }
@@ -488,6 +491,7 @@
         }
     }
 
+    @GuardedBy("mThemeLock")
     private void initializeTheme() {
         if (mTheme == null) {
             mTheme = mResources.newTheme();
@@ -597,12 +601,18 @@
             if (sp == null) {
                 checkMode(mode);
                 if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
-                    if (isCredentialProtectedStorage()
-                            && !getSystemService(UserManager.class)
-                                    .isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
-                        throw new IllegalStateException("SharedPreferences in credential encrypted "
-                                + "storage are not available until after user (id "
-                                + UserHandle.myUserId() + ") is unlocked");
+                    if (isCredentialProtectedStorage()) {
+                        final UserManager um = getSystemService(UserManager.class);
+                        if (um == null) {
+                            throw new IllegalStateException("SharedPreferences cannot be accessed "
+                                    + "if UserManager is not available. "
+                                    + "(e.g. from inside an isolated process)");
+                        }
+                        if (!um.isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
+                            throw new IllegalStateException("SharedPreferences in "
+                                    + "credential encrypted storage are not available until after "
+                                    + "user (id " + UserHandle.myUserId() + ") is unlocked");
+                        }
                     }
                 }
                 sp = new SharedPreferencesImpl(file, mode);
@@ -731,12 +741,18 @@
 
     @UnsupportedAppUsage
     private File getPreferencesDir() {
-        synchronized (mSync) {
-            if (mPreferencesDir == null) {
-                mPreferencesDir = new File(getDataDir(), "shared_prefs");
+        File localPreferencesDir = mPreferencesDir;
+        if (localPreferencesDir == null) {
+            synchronized (mDirsLock) {
+                localPreferencesDir = mPreferencesDir;
+                if (localPreferencesDir == null) {
+                    localPreferencesDir = new File(getDataDir(), "shared_prefs");
+                    ensurePrivateDirExists(localPreferencesDir);
+                    mPreferencesDir = localPreferencesDir;
+                }
             }
-            return ensurePrivateDirExists(mPreferencesDir);
         }
+        return localPreferencesDir;
     }
 
     @Override
@@ -778,16 +794,16 @@
     /**
      * Common-path handling of app data dir creation
      */
-    private static File ensurePrivateDirExists(File file) {
-        return ensurePrivateDirExists(file, 0771, -1, null);
+    private static void ensurePrivateDirExists(File file) {
+        ensurePrivateDirExists(file, 0771, -1, null);
     }
 
-    private static File ensurePrivateCacheDirExists(File file, String xattr) {
+    private static void ensurePrivateCacheDirExists(File file, String xattr) {
         final int gid = UserHandle.getCacheAppGid(Process.myUid());
-        return ensurePrivateDirExists(file, 02771, gid, xattr);
+        ensurePrivateDirExists(file, 02771, gid, xattr);
     }
 
-    private static File ensurePrivateDirExists(File file, int mode, int gid, String xattr) {
+    private static void ensurePrivateDirExists(File file, int mode, int gid, String xattr) {
         if (!file.exists()) {
             final String path = file.getAbsolutePath();
             try {
@@ -815,17 +831,22 @@
                 }
             }
         }
-        return file;
     }
 
     @Override
     public File getFilesDir() {
-        synchronized (mSync) {
-            if (mFilesDir == null) {
-                mFilesDir = new File(getDataDir(), "files");
+        File localFilesDir = mFilesDir;
+        if (localFilesDir == null) {
+            localFilesDir = mFilesDir;
+            synchronized (mDirsLock) {
+                if (localFilesDir == null) {
+                    localFilesDir = new File(getDataDir(), "files");
+                    ensurePrivateDirExists(localFilesDir);
+                    mFilesDir = localFilesDir;
+                }
             }
-            return ensurePrivateDirExists(mFilesDir);
         }
+        return localFilesDir;
     }
 
     @Override
@@ -835,25 +856,37 @@
         final Path absoluteNormalizedCratePath = cratesRootPath.resolve(crateId)
                 .toAbsolutePath().normalize();
 
-        synchronized (mSync) {
-            if (mCratesDir == null) {
-                mCratesDir = cratesRootPath.toFile();
+        File localCratesDir = mCratesDir;
+        if (localCratesDir == null) {
+            synchronized (mDirsLock) {
+                localCratesDir = mCratesDir;
+                if (localCratesDir == null) {
+                    localCratesDir = cratesRootPath.toFile();
+                    ensurePrivateDirExists(localCratesDir);
+                    mCratesDir = localCratesDir;
+                }
             }
-            ensurePrivateDirExists(mCratesDir);
         }
 
-        File cratedDir = absoluteNormalizedCratePath.toFile();
-        return ensurePrivateDirExists(cratedDir);
+        File crateDir = absoluteNormalizedCratePath.toFile();
+        ensurePrivateDirExists(crateDir);
+        return crateDir;
     }
 
     @Override
     public File getNoBackupFilesDir() {
-        synchronized (mSync) {
-            if (mNoBackupFilesDir == null) {
-                mNoBackupFilesDir = new File(getDataDir(), "no_backup");
+        File localNoBackupFilesDir = mNoBackupFilesDir;
+        if (localNoBackupFilesDir == null) {
+            synchronized (mDirsLock) {
+                localNoBackupFilesDir = mNoBackupFilesDir;
+                if (localNoBackupFilesDir == null) {
+                    localNoBackupFilesDir = new File(getDataDir(), "no_backup");
+                    ensurePrivateDirExists(localNoBackupFilesDir);
+                    mNoBackupFilesDir = localNoBackupFilesDir;
+                }
             }
-            return ensurePrivateDirExists(mNoBackupFilesDir);
         }
+        return localNoBackupFilesDir;
     }
 
     @Override
@@ -865,13 +898,24 @@
 
     @Override
     public File[] getExternalFilesDirs(String type) {
-        synchronized (mSync) {
-            File[] dirs = Environment.buildExternalStorageAppFilesDirs(getPackageName());
-            if (type != null) {
-                dirs = Environment.buildPaths(dirs, type);
+        File[] localExternalFilesDirs = mExternalFilesDirs;
+        if (localExternalFilesDirs == null) {
+            synchronized (mDirsLock) {
+                localExternalFilesDirs = mExternalFilesDirs;
+                if (localExternalFilesDirs == null) {
+                    localExternalFilesDirs =
+                            Environment.buildExternalStorageAppFilesDirs(getPackageName());
+                    if (type != null) {
+                        localExternalFilesDirs =
+                                Environment.buildPaths(localExternalFilesDirs, type);
+                    }
+                    localExternalFilesDirs = ensureExternalDirsExistOrFilter(
+                            localExternalFilesDirs, true /* tryCreateInProcess */);
+                    mExternalFilesDirs = localExternalFilesDirs;
+                }
             }
-            return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
         }
+        return localExternalFilesDirs;
     }
 
     @Override
@@ -883,30 +927,51 @@
 
     @Override
     public File[] getObbDirs() {
-        synchronized (mSync) {
-            File[] dirs = Environment.buildExternalStorageAppObbDirs(getPackageName());
-            return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
+        File[] localObbDirs = mObbDirs;
+        if (mObbDirs == null) {
+            synchronized (mDirsLock) {
+                localObbDirs = mObbDirs;
+                if (localObbDirs == null) {
+                    localObbDirs = Environment.buildExternalStorageAppObbDirs(getPackageName());
+                    localObbDirs = ensureExternalDirsExistOrFilter(
+                            localObbDirs, true /* tryCreateInProcess */);
+                    mObbDirs = localObbDirs;
+                }
+            }
         }
+        return localObbDirs;
     }
 
     @Override
     public File getCacheDir() {
-        synchronized (mSync) {
-            if (mCacheDir == null) {
-                mCacheDir = new File(getDataDir(), "cache");
+        File localCacheDir = mCacheDir;
+        if (localCacheDir == null) {
+            synchronized (mDirsLock) {
+                localCacheDir = mCacheDir;
+                if (localCacheDir == null) {
+                    localCacheDir = new File(getDataDir(), "cache");
+                    ensurePrivateCacheDirExists(localCacheDir, XATTR_INODE_CACHE);
+                    mCacheDir = localCacheDir;
+                }
             }
-            return ensurePrivateCacheDirExists(mCacheDir, XATTR_INODE_CACHE);
         }
+        return localCacheDir;
     }
 
     @Override
     public File getCodeCacheDir() {
-        synchronized (mSync) {
-            if (mCodeCacheDir == null) {
-                mCodeCacheDir = getCodeCacheDirBeforeBind(getDataDir());
+        File localCodeCacheDir = mCodeCacheDir;
+        if (localCodeCacheDir == null) {
+            synchronized (mDirsLock) {
+                localCodeCacheDir = mCodeCacheDir;
+                if (localCodeCacheDir == null) {
+                    localCodeCacheDir = getCodeCacheDirBeforeBind(getDataDir());
+                    ensurePrivateCacheDirExists(localCodeCacheDir, XATTR_INODE_CODE_CACHE);
+                    mCodeCacheDir = localCodeCacheDir;
+                }
             }
-            return ensurePrivateCacheDirExists(mCodeCacheDir, XATTR_INODE_CODE_CACHE);
         }
+        return localCodeCacheDir;
     }
 
     /**
@@ -927,21 +992,37 @@
 
     @Override
     public File[] getExternalCacheDirs() {
-        synchronized (mSync) {
-            File[] dirs = Environment.buildExternalStorageAppCacheDirs(getPackageName());
-            // We don't try to create cache directories in-process, because they need special
-            // setup for accurate quota tracking. This ensures the cache dirs are always
-            // created through StorageManagerService.
-            return ensureExternalDirsExistOrFilter(dirs, false /* tryCreateInProcess */);
+        File[] localExternalCacheDirs = mExternalCacheDirs;
+        if (localExternalCacheDirs == null) {
+            synchronized (mDirsLock) {
+                localExternalCacheDirs = mExternalCacheDirs;
+                if (localExternalCacheDirs == null) {
+                    localExternalCacheDirs =
+                            Environment.buildExternalStorageAppCacheDirs(getPackageName());
+                    localExternalCacheDirs = ensureExternalDirsExistOrFilter(
+                            localExternalCacheDirs, false /* tryCreateInProcess */);
+                    mExternalCacheDirs = localExternalCacheDirs;
+                }
+            }
         }
+        return localExternalCacheDirs;
     }
 
     @Override
     public File[] getExternalMediaDirs() {
-        synchronized (mSync) {
-            File[] dirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
-            return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
+        File[] localExternalMediaDirs = mExternalMediaDirs;
+        if (localExternalMediaDirs == null) {
+            synchronized (mDirsLock) {
+                localExternalMediaDirs = mExternalMediaDirs;
+                if (localExternalMediaDirs == null) {
+                    localExternalMediaDirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
+                    localExternalMediaDirs = ensureExternalDirsExistOrFilter(
+                            localExternalMediaDirs, true /* tryCreateInProcess */);
+                    mExternalMediaDirs = localExternalMediaDirs;
+                }
+            }
         }
+        return localExternalMediaDirs;
     }
 
     /**
@@ -1040,16 +1121,22 @@
     }
 
     private File getDatabasesDir() {
-        synchronized (mSync) {
-            if (mDatabasesDir == null) {
-                if ("android".equals(getPackageName())) {
-                    mDatabasesDir = new File("/data/system");
-                } else {
-                    mDatabasesDir = new File(getDataDir(), "databases");
+        File localDatabasesDir = mDatabasesDir;
+        if (localDatabasesDir == null) {
+            synchronized (mDirsLock) {
+                localDatabasesDir = mDatabasesDir;
+                if (localDatabasesDir == null) {
+                    if ("android".equals(getPackageName())) {
+                        localDatabasesDir = new File("/data/system");
+                    } else {
+                        localDatabasesDir = new File(getDataDir(), "databases");
+                    }
+                    ensurePrivateDirExists(localDatabasesDir);
+                    mDatabasesDir = localDatabasesDir;
                 }
             }
-            return ensurePrivateDirExists(mDatabasesDir);
         }
+        return localDatabasesDir;
     }
 
     @Override
diff --git a/core/java/android/app/DreamManager.java b/core/java/android/app/DreamManager.java
index 7c8b0fd..ef6982e 100644
--- a/core/java/android/app/DreamManager.java
+++ b/core/java/android/app/DreamManager.java
@@ -185,6 +185,22 @@
     }
 
     /**
+     * Whether dreaming can start given user settings and the current dock/charge state.
+     *
+     * @hide
+     */
+    @UserHandleAware
+    @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE)
+    public boolean canStartDreaming(boolean isScreenOn) {
+        try {
+            return mService.canStartDreaming(isScreenOn);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return false;
+    }
+
+    /**
      * Returns whether the device is Dreaming.
      *
      * <p> This is only used for testing the dream service APIs.
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index 7e06735..d1e517b 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -62,7 +62,6 @@
 import android.hardware.usb.UsbAccessory;
 import android.hardware.usb.UsbDevice;
 import android.hardware.usb.UsbManager;
-import android.os.Build;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -128,14 +127,10 @@
      * The FGS type enforcement:
      * deprecating the {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC}.
      *
-     * <p>Starting a FGS with this type from apps with targetSdkVersion
-     * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} or later will result in a warning
-     * in the log.
-     *
      * @hide
      */
     @ChangeId
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @Disabled
     @Overridable
     public static final long FGS_TYPE_DATA_SYNC_DEPRECATION_CHANGE_ID = 255039210L;
 
diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java
index 483a6e1..3e7d665 100644
--- a/core/java/android/app/GrammaticalInflectionManager.java
+++ b/core/java/android/app/GrammaticalInflectionManager.java
@@ -16,8 +16,10 @@
 
 package android.app;
 
+import android.Manifest;
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -123,10 +125,14 @@
     /**
      * Get the current grammatical gender of privileged application from the encrypted file.
      *
-     * @return the value of grammatical gender
+     * @return the value of system grammatical gender only if the calling app has the permission,
+     * otherwise throwing an exception.
+     *
+     * @throws SecurityException if the caller does not have the required permission.
      *
      * @see Configuration#getGrammaticalGender
      */
+    @RequiresPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER)
     @FlaggedApi(Flags.FLAG_SYSTEM_TERMS_OF_ADDRESS_ENABLED)
     @Configuration.GrammaticalGender
     public int getSystemGrammaticalGender() {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 7a95720..5e6b54b 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -898,10 +898,6 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DUMP)")
     void forceDelayBroadcastDelivery(in String targetPackage, long delayedDurationMs);
 
-    /** Checks if the modern broadcast queue is enabled. */
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DUMP)")
-    boolean isModernBroadcastQueueEnabled();
-
     /** Checks if the process represented by the given pid is frozen. */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DUMP)")
     boolean isProcessFrozen(int pid);
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index a04620c..251e4e8 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -90,7 +90,7 @@
             in CompatibilityInfo compatInfo, in Map services,
             in Bundle coreSettings, in String buildSerial, in AutofillOptions autofillOptions,
             in ContentCaptureOptions contentCaptureOptions, in long[] disabledCompatChanges,
-            in SharedMemory serializedSystemFontMap,
+            in long[] loggableCompatChanges, in SharedMemory serializedSystemFontMap,
             long startRequestedElapsedTime, long startRequestedUptime);
     void runIsolatedEntryPoint(in String entryPoint, in String[] entryPointArgs);
     void scheduleExit();
diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java
index 1b19ecd..095cfc5 100644
--- a/core/java/android/app/LocalActivityManager.java
+++ b/core/java/android/app/LocalActivityManager.java
@@ -413,7 +413,7 @@
         if (localLOGV) Log.v(TAG, r.id + ": destroying");
         final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r);
         if (clientRecord != null) {
-            mActivityThread.performDestroyActivity(clientRecord, finish, 0 /* configChanges */,
+            mActivityThread.performDestroyActivity(clientRecord, finish,
                     false /* getNonConfigInstance */, "LocalActivityManager::performDestroy");
         }
         r.activity = null;
@@ -684,7 +684,7 @@
                 if (localLOGV) Log.v(TAG, r.id + ": no corresponding record");
                 continue;
             }
-            mActivityThread.performDestroyActivity(clientRecord, finishing, 0 /* configChanges */,
+            mActivityThread.performDestroyActivity(clientRecord, finishing,
                     false /* getNonConfigInstance */, "LocalActivityManager::dispatchDestroy");
         }
         mActivities.clear();
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index b2be27f..4a06f7d 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -248,7 +248,8 @@
     }
 
     /**
-     * Returns the default locale if specified, otherwise null
+     * Returns the locale the strings in values/strings.xml (the default strings in the directory
+     * with no locale qualifier) are in if specified, otherwise null
      *
      * @return The default Locale or null
      */
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 26f85f7..cd4c0bc 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6068,11 +6068,19 @@
         }
 
         /**
-         * Construct a RemoteViews for the final 1U notification layout. In order:
-         *   1. Custom contentView from the caller
-         *   2. Style's proposed content view
-         *   3. Standard template view
+         * Construct a RemoteViews representing the standard notification layout.
+         *
+         * @deprecated For performance and system health reasons, this API is no longer required to
+         *  be used directly by the System UI when rendering Notifications to the user. While the UI
+         *  returned by this method will still represent the content of the Notification being
+         *  built, it may differ from the visual style of the system.
+         *
+         *  NOTE: this API has always had severe limitations; for example it does not support any
+         *  interactivity, it ignores the app theme, it hard-codes the colors from the system theme
+         *  at the time it is called, and it does Bitmap decoding on the main thread which can cause
+         *  UI jank.
          */
+        @Deprecated
         public RemoteViews createContentView() {
             return createContentView(false /* increasedheight */ );
         }
@@ -6181,8 +6189,19 @@
         }
 
         /**
-         * Construct a RemoteViews for the final big notification layout.
+         * Construct a RemoteViews representing the expanded notification layout.
+         *
+         * @deprecated For performance and system health reasons, this API is no longer required to
+         *  be used directly by the System UI when rendering Notifications to the user. While the UI
+         *  returned by this method will still represent the content of the Notification being
+         *  built, it may differ from the visual style of the system.
+         *
+         *  NOTE: this API has always had severe limitations; for example it does not support any
+         *  interactivity, it ignores the app theme, it hard-codes the colors from the system theme
+         *  at the time it is called, and it does Bitmap decoding on the main thread which can cause
+         *  UI jank.
          */
+        @Deprecated
         public RemoteViews createBigContentView() {
             RemoteViews result = null;
             if (useExistingRemoteView(mN.bigContentView)) {
@@ -6315,8 +6334,19 @@
         }
 
         /**
-         * Construct a RemoteViews for the final heads-up notification layout.
+         * Construct a RemoteViews representing the heads up notification layout.
+         *
+         * @deprecated For performance and system health reasons, this API is no longer required to
+         *  be used directly by the System UI when rendering Notifications to the user. While the UI
+         *  returned by this method will still represent the content of the Notification being
+         *  built, it may differ from the visual style of the system.
+         *
+         *  NOTE: this API has always had severe limitations; for example it does not support any
+         *  interactivity, it ignores the app theme, it hard-codes the colors from the system theme
+         *  at the time it is called, and it does Bitmap decoding on the main thread which can cause
+         *  UI jank.
          */
+        @Deprecated
         public RemoteViews createHeadsUpContentView() {
             return createHeadsUpContentView(false /* useIncreasedHeight */);
         }
@@ -7501,6 +7531,9 @@
         /**
          * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
          * attached to.
+         * <p>
+         * Note: Calling build() multiple times returns the same Notification instance,
+         * so reusing a builder to create multiple Notifications is discouraged.
          *
          * @return the fully constructed Notification.
          */
@@ -7543,6 +7576,7 @@
         /**
          * @hide
          */
+        @SuppressWarnings("HiddenAbstractMethod")
         public abstract boolean areNotificationsVisiblyDifferent(Style other);
 
         /**
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index ab5395e..9f2e473 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -562,6 +562,12 @@
      * audio attributes. Notification channels with an {@link #getImportance() importance} of at
      * least {@link NotificationManager#IMPORTANCE_DEFAULT} should have a sound.
      *
+     * Note: An app-specific sound can be provided in the Uri parameter, but because channels are
+     * persistent for the duration of the app install, and are backed up and restored, the Uri
+     * should be stable. For this reason it is not recommended to use a
+     * {@link ContentResolver#SCHEME_ANDROID_RESOURCE} uri, as resource ids can change on app
+     * upgrade.
+     *
      * Only modifiable before the channel is submitted to
      * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
      */
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index d49a254..b82a1e3 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -270,13 +270,16 @@
      * Integer extra for {@link #ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED} containing the state of
      * the {@link AutomaticZenRule}.
      *
-     * <p>
-     *     The value will be one of {@link #AUTOMATIC_RULE_STATUS_ENABLED},
-     *     {@link #AUTOMATIC_RULE_STATUS_DISABLED}, {@link #AUTOMATIC_RULE_STATUS_REMOVED},
-     *     {@link #AUTOMATIC_RULE_STATUS_UNKNOWN}.
-     * </p>
+     * <p>The value will be one of {@link #AUTOMATIC_RULE_STATUS_ENABLED},
+     * {@link #AUTOMATIC_RULE_STATUS_DISABLED}, {@link #AUTOMATIC_RULE_STATUS_REMOVED},
+     * {@link #AUTOMATIC_RULE_STATUS_ACTIVATED}, {@link #AUTOMATIC_RULE_STATUS_DEACTIVATED}, or
+     * {@link #AUTOMATIC_RULE_STATUS_UNKNOWN}.
+     *
+     * <p>Note that the {@link #AUTOMATIC_RULE_STATUS_ACTIVATED} and
+     * {@link #AUTOMATIC_RULE_STATUS_DEACTIVATED} statuses are only sent to packages targeting
+     * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above; apps targeting a lower SDK version
+     * will be sent {@link #AUTOMATIC_RULE_STATUS_UNKNOWN} in their place instead.
      */
-    // TODO (b/309101513): Add new status types to javadoc
     public static final String EXTRA_AUTOMATIC_ZEN_RULE_STATUS =
             "android.app.extra.AUTOMATIC_ZEN_RULE_STATUS";
 
@@ -370,11 +373,15 @@
             = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
 
     /**
-     * Intent that is broadcast when the state of getNotificationPolicy() changes.
+     * Intent that is broadcast when the state of {@link #getNotificationPolicy()} changes.
      *
      * <p>This broadcast is only sent to registered receivers and (starting from
      * {@link Build.VERSION_CODES#Q}) receivers in packages that have been granted Do Not
      * Disturb access (see {@link #isNotificationPolicyAccessGranted()}).
+     *
+     * <p>Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, most calls to
+     * {@link #setNotificationPolicy(Policy)} will update the app's implicit rule policy instead of
+     * the global policy, so this broadcast will be sent much less frequently.
      */
     @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_NOTIFICATION_POLICY_CHANGED
@@ -1378,12 +1385,16 @@
     /**
      * Updates the given zen rule.
      *
-     * <p>
-     * Throws a SecurityException if policy access is not granted to this package.
+     * <p>Before {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, updating a rule that is not backed
+     * up by a {@link android.service.notification.ConditionProviderService} will deactivate it if
+     * it was previously active. Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, this
+     * will only happen if the rule's definition is actually changing.
+     *
+     * <p>Throws a SecurityException if policy access is not granted to this package.
      * See {@link #isNotificationPolicyAccessGranted}.
      *
-     * <p>
-     * Callers can only update rules that they own. See {@link AutomaticZenRule#getOwner}.
+     * <p>Callers can only update rules that they own. See {@link AutomaticZenRule#getOwner}.
+     *
      * @param id The id of the rule to update
      * @param automaticZenRule the rule to update.
      * @return Whether the rule was successfully updated.
@@ -1744,9 +1755,11 @@
     /**
      * Gets the current user-specified default notification policy.
      *
-     * <p>
+     * <p>For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+     * exceptions, such as companion device managers) this method will return the policy associated
+     * to their implicit {@link AutomaticZenRule} instead, if it exists. See
+     * {@link #setNotificationPolicy(Policy)}.
      */
-    // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
     public Policy getNotificationPolicy() {
         INotificationManager service = getService();
         try {
@@ -1757,15 +1770,20 @@
     }
 
     /**
-     * Sets the current notification policy.
+     * Sets the current notification policy (which applies when {@link #setInterruptionFilter} is
+     * called with the {@link #INTERRUPTION_FILTER_PRIORITY} value).
      *
-     * <p>
-     * Only available if policy access is granted to this package.
-     * See {@link #isNotificationPolicyAccessGranted}.
+     * <p>Apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+     * exceptions, such as companion device managers) cannot modify the global notification policy.
+     * Calling this method will instead create or update an {@link AutomaticZenRule} associated to
+     * the app, using a {@link ZenPolicy} corresponding to the {@link Policy} supplied here, and
+     * which will be activated/deactivated by calls to {@link #setInterruptionFilter(int)}.
+     *
+     * <p>Only available if policy access is granted to this package. See
+     * {@link #isNotificationPolicyAccessGranted}.
      *
      * @param policy The new desired policy.
      */
-    // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
     public void setNotificationPolicy(@NonNull Policy policy) {
         setNotificationPolicy(policy, /* fromUser= */ false);
     }
@@ -2052,10 +2070,12 @@
 
         /** Notification senders to prioritize for calls. One of:
          * PRIORITY_SENDERS_ANY, PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED */
+        @PrioritySenders
         public final int priorityCallSenders;
 
         /** Notification senders to prioritize for messages. One of:
          * PRIORITY_SENDERS_ANY, PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED */
+        @PrioritySenders
         public final int priorityMessageSenders;
 
         /**
@@ -2063,6 +2083,7 @@
          * {@link #CONVERSATION_SENDERS_NONE}, {@link #CONVERSATION_SENDERS_IMPORTANT},
          * {@link #CONVERSATION_SENDERS_ANYONE}.
          */
+        @ConversationSenders
         public final int priorityConversationSenders;
 
         /**
@@ -2630,16 +2651,19 @@
         }
 
         /** @hide **/
+        @PrioritySenders
         public int allowCallsFrom() {
             return priorityCallSenders;
         }
 
         /** @hide **/
+        @PrioritySenders
         public int allowMessagesFrom() {
             return priorityMessageSenders;
         }
 
         /** @hide **/
+        @ConversationSenders
         public int allowConversationsFrom() {
             return priorityConversationSenders;
         }
@@ -2780,11 +2804,17 @@
      * The interruption filter defines which notifications are allowed to
      * interrupt the user (e.g. via sound &amp; vibration) and is applied
      * globally.
-     * <p>
-     * Only available if policy access is granted to this package. See
+     *
+     * <p>Apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+     * exceptions, such as companion device managers) cannot modify the global interruption filter.
+     * Calling this method will instead activate or deactivate an {@link AutomaticZenRule}
+     * associated to the app, using a {@link ZenPolicy} that corresponds to the {@link Policy}
+     * supplied to {@link #setNotificationPolicy(Policy)} (or the global policy when one wasn't
+     * provided).
+     *
+     * <p> Only available if policy access is granted to this package. See
      * {@link #isNotificationPolicyAccessGranted}.
      */
-    // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
     public final void setInterruptionFilter(@InterruptionFilter int interruptionFilter) {
         setInterruptionFilter(interruptionFilter, /* fromUser= */ false);
     }
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 6255260..8b84f06 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -124,6 +124,32 @@
      */
     private LocaleConfig mLocaleConfig = new LocaleConfig(LocaleList.getEmptyLocaleList());
 
+    private final ArrayMap<String, SharedLibraryAssets> mSharedLibAssetsMap =
+            new ArrayMap<>();
+
+    /**
+     * The internal function to register the resources paths of a package (e.g. a shared library).
+     * This will collect the package resources' paths from its ApplicationInfo and add them to all
+     * existing and future contexts while the application is running.
+     */
+    public void registerResourcePaths(@NonNull String uniqueId, @NonNull ApplicationInfo appInfo) {
+        SharedLibraryAssets sharedLibAssets = new SharedLibraryAssets(appInfo.sourceDir,
+                appInfo.splitSourceDirs, appInfo.sharedLibraryFiles,
+                appInfo.resourceDirs, appInfo.overlayPaths);
+
+        synchronized (mLock) {
+            if (mSharedLibAssetsMap.containsKey(uniqueId)) {
+                Slog.v(TAG, "Package resources' paths for uniqueId: " + uniqueId
+                        + " has already been registered, this is a no-op.");
+                return;
+            }
+            mSharedLibAssetsMap.put(uniqueId, sharedLibAssets);
+            appendLibAssetsLocked(sharedLibAssets.getAllAssetPaths());
+            Slog.v(TAG, "The following resources' paths have been added: "
+                    + Arrays.toString(sharedLibAssets.getAllAssetPaths()));
+        }
+    }
+
     private static class ApkKey {
         public final String path;
         public final boolean sharedLib;
@@ -278,6 +304,21 @@
     public ResourcesManager() {
     }
 
+    /**
+     * Inject a customized ResourcesManager instance for testing, return the old ResourcesManager
+     * instance.
+     */
+    @UnsupportedAppUsage
+    @VisibleForTesting
+    public static ResourcesManager setInstance(ResourcesManager resourcesManager) {
+        synchronized (ResourcesManager.class) {
+            ResourcesManager oldResourceManager = sResourcesManager;
+            sResourcesManager = resourcesManager;
+            return oldResourceManager;
+        }
+
+    }
+
     @UnsupportedAppUsage
     public static ResourcesManager getInstance() {
         synchronized (ResourcesManager.class) {
@@ -1480,6 +1521,56 @@
         }
     }
 
+    private void appendLibAssetsLocked(String[] libAssets) {
+        synchronized (mLock) {
+            // Record which ResourcesImpl need updating
+            // (and what ResourcesKey they should update to).
+            final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
+
+            final int implCount = mResourceImpls.size();
+            for (int i = 0; i < implCount; i++) {
+                final ResourcesKey key = mResourceImpls.keyAt(i);
+                final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
+                final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
+                if (impl == null) {
+                    Slog.w(TAG, "Found a ResourcesImpl which is null, skip it and continue to "
+                            + "append shared library assets for next ResourcesImpl.");
+                    continue;
+                }
+
+                var newDirs = new ArrayList<String>();
+                var dirsSet = new ArraySet<String>();
+                if (key.mLibDirs != null) {
+                    final int dirsLength = key.mLibDirs.length;
+                    for (int k = 0; k < dirsLength; k++) {
+                        newDirs.add(key.mLibDirs[k]);
+                        dirsSet.add(key.mLibDirs[k]);
+                    }
+                }
+                final int assetsLength = libAssets.length;
+                for (int j = 0; j < assetsLength; j++) {
+                    if (dirsSet.add(libAssets[j])) {
+                        newDirs.add(libAssets[j]);
+                    }
+                }
+                String[] newLibAssets = newDirs.toArray(new String[0]);
+                if (!Arrays.equals(newLibAssets, key.mLibDirs)) {
+                    updatedResourceKeys.put(impl, new ResourcesKey(
+                            key.mResDir,
+                            key.mSplitResDirs,
+                            key.mOverlayPaths,
+                            newLibAssets,
+                            key.mDisplayId,
+                            key.mOverrideConfiguration,
+                            key.mCompatInfo,
+                            key.mLoaders));
+                }
+            }
+
+            redirectResourcesToNewImplLocked(updatedResourceKeys);
+        }
+    }
+
     private void applyNewResourceDirsLocked(@Nullable final String[] oldSourceDirs,
             @NonNull final ApplicationInfo appInfo) {
         try {
@@ -1689,4 +1780,50 @@
             }
         }
     }
+
+    public static class SharedLibraryAssets{
+        private final String[] mAssetPaths;
+
+        SharedLibraryAssets(String sourceDir, String[] splitSourceDirs, String[] sharedLibraryFiles,
+                String[] resourceDirs, String[] overlayPaths) {
+            mAssetPaths = collectAssetPaths(sourceDir, splitSourceDirs, sharedLibraryFiles,
+                    resourceDirs, overlayPaths);
+        }
+
+        private @NonNull String[] collectAssetPaths(String sourceDir, String[] splitSourceDirs,
+                String[] sharedLibraryFiles, String[] resourceDirs, String[] overlayPaths) {
+            final String[][] inputLists = {
+                    splitSourceDirs, sharedLibraryFiles, resourceDirs, overlayPaths
+            };
+
+            final ArraySet<String> assetPathSet = new ArraySet<>();
+            final List<String> assetPathList = new ArrayList<>();
+            if (sourceDir != null) {
+                assetPathSet.add(sourceDir);
+                assetPathList.add(sourceDir);
+            }
+
+            for (int i = 0; i < inputLists.length; i++) {
+                if (inputLists[i] != null) {
+                    for (int j = 0; j < inputLists[i].length; j++) {
+                        if (assetPathSet.add(inputLists[i][j])) {
+                            assetPathList.add(inputLists[i][j]);
+                        }
+                    }
+                }
+            }
+            return assetPathList.toArray(new String[0]);
+        }
+
+        /**
+         * @return all the asset paths of this collected in this class.
+         */
+        public @NonNull String[] getAllAssetPaths() {
+            return mAssetPaths;
+        }
+    }
+
+    public @NonNull ArrayMap<String, SharedLibraryAssets> getSharedLibAssetsMap() {
+        return new ArrayMap<>(mSharedLibAssetsMap);
+    }
 }
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 08c193f..d01626e 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -212,7 +212,7 @@
 import android.permission.PermissionManager;
 import android.print.IPrintManager;
 import android.print.PrintManager;
-import android.provider.ContactKeysManager;
+import android.provider.E2eeContactKeysManager;
 import android.safetycenter.SafetyCenterFrameworkInitializer;
 import android.scheduling.SchedulingFrameworkInitializer;
 import android.security.FileIntegrityManager;
@@ -1608,16 +1608,16 @@
                     }
                 });
 
-        registerService(Context.CONTACT_KEYS_SERVICE, ContactKeysManager.class,
-                new CachedServiceFetcher<ContactKeysManager>() {
+        registerService(Context.CONTACT_KEYS_SERVICE, E2eeContactKeysManager.class,
+                new CachedServiceFetcher<E2eeContactKeysManager>() {
                     @Override
-                    public ContactKeysManager createService(ContextImpl ctx)
+                    public E2eeContactKeysManager createService(ContextImpl ctx)
                             throws ServiceNotFoundException {
                         if (!android.provider.Flags.userKeys()) {
                             throw new ServiceNotFoundException(
                                     "ContactKeysManager is not supported");
                         }
-                        return new ContactKeysManager(ctx);
+                        return new E2eeContactKeysManager(ctx);
                     }});
 
         // DO NOT do a flag check like this unless the flag is read-only.
diff --git a/core/java/android/app/admin/AccountTypePolicyKey.java b/core/java/android/app/admin/AccountTypePolicyKey.java
index d81eb20..51f3137 100644
--- a/core/java/android/app/admin/AccountTypePolicyKey.java
+++ b/core/java/android/app/admin/AccountTypePolicyKey.java
@@ -19,12 +19,12 @@
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_ACCOUNT_TYPE;
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_BUNDLE_KEY;
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
 import android.os.Bundle;
 import android.os.Parcel;
 
@@ -54,7 +54,7 @@
     @TestApi
     public AccountTypePolicyKey(@NonNull String key, @NonNull String accountType) {
         super(key);
-        if (devicePolicySizeTrackingEnabled()) {
+        if (Flags.devicePolicySizeTrackingInternalEnabled()) {
             PolicySizeVerifier.enforceMaxStringLength(accountType, "accountType");
         }
         mAccountType = Objects.requireNonNull((accountType));
diff --git a/core/java/android/app/admin/BundlePolicyValue.java b/core/java/android/app/admin/BundlePolicyValue.java
index cc5e75f..cb5e986 100644
--- a/core/java/android/app/admin/BundlePolicyValue.java
+++ b/core/java/android/app/admin/BundlePolicyValue.java
@@ -16,10 +16,9 @@
 
 package android.app.admin;
 
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.admin.flags.Flags;
 import android.os.Bundle;
 import android.os.Parcel;
 
@@ -32,8 +31,8 @@
 
     public BundlePolicyValue(Bundle value) {
         super(value);
-        if (devicePolicySizeTrackingEnabled()) {
-            PolicySizeVerifier.enforceMaxParcelableFieldsLength(value);
+        if (Flags.devicePolicySizeTrackingInternalEnabled()) {
+            PolicySizeVerifier.enforceMaxBundleFieldsLength(value);
         }
     }
 
diff --git a/core/java/android/app/admin/ComponentNamePolicyValue.java b/core/java/android/app/admin/ComponentNamePolicyValue.java
index 4d36195..a957dbf 100644
--- a/core/java/android/app/admin/ComponentNamePolicyValue.java
+++ b/core/java/android/app/admin/ComponentNamePolicyValue.java
@@ -16,10 +16,9 @@
 
 package android.app.admin;
 
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.admin.flags.Flags;
 import android.content.ComponentName;
 import android.os.Parcel;
 
@@ -32,7 +31,7 @@
 
     public ComponentNamePolicyValue(@NonNull ComponentName value) {
         super(value);
-        if (devicePolicySizeTrackingEnabled()) {
+        if (Flags.devicePolicySizeTrackingInternalEnabled()) {
             PolicySizeVerifier.enforceMaxComponentNameLength(value);
         }
     }
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 986205a..9ef8b38 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -189,10 +189,13 @@
     @FlaggedApi(FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
     public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2;
 
+    /**
+     * @hide
+     */
     @IntDef({HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED, HEADLESS_DEVICE_OWNER_MODE_AFFILIATED,
             HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER})
     @Retention(RetentionPolicy.SOURCE)
-    private @interface HeadlessDeviceOwnerMode {}
+    public @interface HeadlessDeviceOwnerMode {}
 
     /** @hide */
     public static class PolicyInfo {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 083705b..cb4ed058 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -53,6 +53,7 @@
 import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY;
 import static android.Manifest.permission.SET_TIME;
 import static android.Manifest.permission.SET_TIME_ZONE;
+import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
 import static android.app.admin.flags.Flags.FLAG_DEVICE_THEFT_API_ENABLED;
 import static android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED;
 import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED;
@@ -93,6 +94,7 @@
 import android.app.IServiceConnection;
 import android.app.KeyguardManager;
 import android.app.admin.SecurityLog.SecurityEvent;
+import android.app.admin.flags.Flags;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
@@ -14071,7 +14073,7 @@
     public void setAuditLogEnabled(boolean enabled) {
         throwIfParentInstance("setAuditLogEnabled");
         try {
-            mService.setAuditLogEnabled(mContext.getPackageName(), true);
+            mService.setAuditLogEnabled(mContext.getPackageName(), enabled);
         } catch (RemoteException re) {
             re.rethrowFromSystemServer();
         }
@@ -14090,9 +14092,7 @@
         try {
             return mService.isAuditLogEnabled(mContext.getPackageName());
         } catch (RemoteException re) {
-            re.rethrowFromSystemServer();
-            // unreachable
-            return false;
+            throw re.rethrowFromSystemServer();
         }
     }
 
@@ -14102,8 +14102,8 @@
      * is enforced by the caller. Disabling the policy clears the callback. Each time a new callback
      * is set, it will first be invoked with all the audit log events available at the time.
      *
-     * @param callback callback to invoke when new audit log events become available or {@code null}
-     *                 to clear the callback.
+     * @param callback The callback to invoke when new audit log events become available.
+     * @param executor The executor through which the callback should be invoked.
      * @hide
      */
     @SystemApi
@@ -14111,11 +14111,10 @@
     @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
     public void setAuditLogEventCallback(
             @NonNull @CallbackExecutor Executor executor,
-            @Nullable Consumer<List<SecurityEvent>> callback) {
+            @NonNull Consumer<List<SecurityEvent>> callback) {
         throwIfParentInstance("setAuditLogEventCallback");
-        final IAuditLogEventsCallback wrappedCallback = callback == null
-                ? null
-                : new IAuditLogEventsCallback.Stub() {
+        final IAuditLogEventsCallback wrappedCallback =
+                new IAuditLogEventsCallback.Stub() {
                     @Override
                     public void onNewAuditLogEvents(List<SecurityEvent> events) {
                         executor.execute(() -> callback.accept(events));
@@ -14124,7 +14123,25 @@
         try {
             mService.setAuditLogEventsCallback(mContext.getPackageName(), wrappedCallback);
         } catch (RemoteException re) {
-            re.rethrowFromSystemServer();
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Clears audit log event callback. If a callback was set previously, it may still get invoked
+     * after this call returns if it was already scheduled.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
+    @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
+    public void clearAuditLogEventCallback() {
+        throwIfParentInstance("clearAuditLogEventCallback");
+        try {
+            mService.setAuditLogEventsCallback(mContext.getPackageName(), null);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
         }
     }
 
@@ -17442,24 +17459,24 @@
     }
 
     /**
-     * Returns the subscription ids of all subscriptions which was downloaded by the calling
+     * Returns the subscription ids of all subscriptions which were downloaded by the calling
      * admin.
      *
      * <p> This returns only the subscriptions which were downloaded by the calling admin via
      *      {@link android.telephony.euicc.EuiccManager#downloadSubscription}.
-     *      If a susbcription is returned by this method then in it subject to management controls
+     *      If a subscription is returned by this method then in it subject to management controls
      *      and cannot be removed by users.
      *
      * <p> Callable by device owners and profile owners.
      *
-     * @throws SecurityException if the caller is not authorized to call this method
-     * @return ids of all managed subscriptions currently downloaded by an admin on the device
+     * @throws SecurityException if the caller is not authorized to call this method.
+     * @return ids of all managed subscriptions currently downloaded by an admin on the device.
      */
     @FlaggedApi(FLAG_ESIM_MANAGEMENT_ENABLED)
     @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS)
     @NonNull
-    public Set<Integer> getSubscriptionsIds() {
-        throwIfParentInstance("getSubscriptionsIds");
+    public Set<Integer> getSubscriptionIds() {
+        throwIfParentInstance("getSubscriptionIds");
         if (mService != null) {
             try {
                 return intArrayToSet(mService.getSubscriptionIds(mContext.getPackageName()));
@@ -17511,4 +17528,25 @@
         }
         return -1;
     }
+
+    /**
+     * @return The headless device owner mode for the current set DO, returns
+     * {@link DeviceAdminInfo#HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED} if no DO is set.
+     *
+     * @hide
+     */
+    @DeviceAdminInfo.HeadlessDeviceOwnerMode
+    public int getHeadlessDeviceOwnerMode() {
+        if (!Flags.headlessDeviceOwnerProvisioningFixEnabled()) {
+            return HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
+        }
+        if (mService != null) {
+            try {
+                return mService.getHeadlessDeviceOwnerMode(mContext.getPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
+    }
 }
\ No newline at end of file
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 3a7a891c..03d0b0f 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -625,4 +625,6 @@
 
     void setMaxPolicyStorageLimit(String packageName, int storageLimit);
     int getMaxPolicyStorageLimit(String packageName);
+
+    int getHeadlessDeviceOwnerMode(String callerPackageName);
 }
diff --git a/core/java/android/app/admin/IntentFilterPolicyKey.java b/core/java/android/app/admin/IntentFilterPolicyKey.java
index de7ff9f..7526a7b 100644
--- a/core/java/android/app/admin/IntentFilterPolicyKey.java
+++ b/core/java/android/app/admin/IntentFilterPolicyKey.java
@@ -19,7 +19,6 @@
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_INTENT_FILTER;
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_BUNDLE_KEY;
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -60,9 +59,6 @@
     @TestApi
     public IntentFilterPolicyKey(@NonNull String identifier, @NonNull IntentFilter filter) {
         super(identifier);
-        if (devicePolicySizeTrackingEnabled()) {
-            PolicySizeVerifier.enforceMaxParcelableFieldsLength(filter);
-        }
         mFilter = Objects.requireNonNull(filter);
     }
 
diff --git a/core/java/android/app/admin/LockTaskPolicy.java b/core/java/android/app/admin/LockTaskPolicy.java
index 9d6ce24..a36ea05 100644
--- a/core/java/android/app/admin/LockTaskPolicy.java
+++ b/core/java/android/app/admin/LockTaskPolicy.java
@@ -16,11 +16,10 @@
 
 package android.app.admin;
 
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.app.admin.flags.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -136,7 +135,7 @@
     }
 
     private void setPackagesInternal(Set<String> packages) {
-        if (devicePolicySizeTrackingEnabled()) {
+        if (Flags.devicePolicySizeTrackingInternalEnabled()) {
             for (String p : packages) {
                 PolicySizeVerifier.enforceMaxPackageNameLength(p);
             }
diff --git a/core/java/android/app/admin/PackagePermissionPolicyKey.java b/core/java/android/app/admin/PackagePermissionPolicyKey.java
index 2241fdd..389585f 100644
--- a/core/java/android/app/admin/PackagePermissionPolicyKey.java
+++ b/core/java/android/app/admin/PackagePermissionPolicyKey.java
@@ -20,12 +20,12 @@
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_PERMISSION_NAME;
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_BUNDLE_KEY;
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -59,7 +59,7 @@
     public PackagePermissionPolicyKey(@NonNull String identifier, @NonNull String packageName,
             @NonNull String permissionName) {
         super(identifier);
-        if (devicePolicySizeTrackingEnabled()) {
+        if (Flags.devicePolicySizeTrackingInternalEnabled()) {
             PolicySizeVerifier.enforceMaxPackageNameLength(packageName);
             PolicySizeVerifier.enforceMaxStringLength(permissionName, "permissionName");
         }
diff --git a/core/java/android/app/admin/PackagePolicyKey.java b/core/java/android/app/admin/PackagePolicyKey.java
index 2ea17a1..68dc797 100644
--- a/core/java/android/app/admin/PackagePolicyKey.java
+++ b/core/java/android/app/admin/PackagePolicyKey.java
@@ -19,12 +19,12 @@
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_PACKAGE_NAME;
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_BUNDLE_KEY;
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -55,7 +55,7 @@
     @TestApi
     public PackagePolicyKey(@NonNull String key, @NonNull String packageName) {
         super(key);
-        if (devicePolicySizeTrackingEnabled()) {
+        if (Flags.devicePolicySizeTrackingInternalEnabled()) {
             PolicySizeVerifier.enforceMaxPackageNameLength(packageName);
         }
         mPackageName = Objects.requireNonNull((packageName));
diff --git a/core/java/android/app/admin/PolicySizeVerifier.java b/core/java/android/app/admin/PolicySizeVerifier.java
index 792ebc6..7f8e50e 100644
--- a/core/java/android/app/admin/PolicySizeVerifier.java
+++ b/core/java/android/app/admin/PolicySizeVerifier.java
@@ -17,12 +17,12 @@
 package android.app.admin;
 
 import android.content.ComponentName;
+import android.os.Bundle;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
 
 import com.android.internal.util.Preconditions;
 
-import java.lang.reflect.Field;
 import java.util.ArrayDeque;
 import java.util.Queue;
 
@@ -71,44 +71,51 @@
             for (String key : current.keySet()) {
                 enforceMaxStringLength(key, "key in " + argName);
                 Object value = current.get(key);
-                if (value instanceof String) {
-                    enforceMaxStringLength((String) value, "string value in " + argName);
-                } else if (value instanceof String[]) {
-                    for (String str : (String[]) value) {
+                if (value instanceof String str) {
+                    enforceMaxStringLength(str, "string value in " + argName);
+                } else if (value instanceof String[] strArray) {
+                    for (String str : strArray) {
                         enforceMaxStringLength(str, "string value in " + argName);
                     }
-                } else if (value instanceof PersistableBundle) {
-                    queue.add((PersistableBundle) value);
+                } else if (value instanceof PersistableBundle persistableBundle) {
+                    queue.add(persistableBundle);
                 }
             }
         }
     }
 
     /**
-     * Throw if Parcelable contains any string that's too long to be serialized.
+     * Throw if bundle contains any string that's too long to be serialized. This follows the
+     * serialization logic in BundlePolicySerializer#writeBundle.
      */
-    public static void enforceMaxParcelableFieldsLength(Parcelable parcelable) {
-        // TODO(b/326662716) rework to protect against infinite recursion.
-        if (true) {
-            return;
-        }
-        Class<?> clazz = parcelable.getClass();
-
-        Field[] fields = clazz.getDeclaredFields();
-        for (Field field : fields) {
-            field.setAccessible(true);
-            try {
-                Object value = field.get(parcelable);
-                if (value instanceof String) {
-                    String stringValue = (String) value;
-                    enforceMaxStringLength(stringValue, field.getName());
+    public static void enforceMaxBundleFieldsLength(Bundle bundle) {
+        Queue<Bundle> queue = new ArrayDeque<>();
+        queue.add(bundle);
+        while (!queue.isEmpty()) {
+            Bundle current = queue.remove();
+            for (String key : current.keySet()) {
+                enforceMaxStringLength(key, "key in Bundle");
+                Object value = current.get(key);
+                if (value instanceof String str) {
+                    enforceMaxStringLength(str, "string value in Bundle with "
+                            + "key" + key);
+                } else if (value instanceof String[] strArray) {
+                    for (String str : strArray) {
+                        enforceMaxStringLength(str, "string value in Bundle with"
+                                + " key" + key);
+                    }
+                } else if (value instanceof Bundle b) {
+                    queue.add(b);
                 }
-
-                if (value instanceof Parcelable) {
-                    enforceMaxParcelableFieldsLength((Parcelable) value);
+                else if (value instanceof Parcelable[] parcelableArray) {
+                    for (Parcelable parcelable : parcelableArray) {
+                        if (!(parcelable instanceof Bundle)) {
+                            throw new IllegalArgumentException("bundle-array can only hold "
+                                    + "Bundles");
+                        }
+                        queue.add((Bundle) parcelable);
+                    }
                 }
-            } catch (IllegalAccessException e) {
-                e.printStackTrace();
             }
         }
     }
diff --git a/core/java/android/app/admin/StringPolicyValue.java b/core/java/android/app/admin/StringPolicyValue.java
index f4d4adc..8995c0f 100644
--- a/core/java/android/app/admin/StringPolicyValue.java
+++ b/core/java/android/app/admin/StringPolicyValue.java
@@ -16,10 +16,9 @@
 
 package android.app.admin;
 
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.admin.flags.Flags;
 import android.os.Parcel;
 
 import java.util.Objects;
@@ -31,7 +30,7 @@
 
     public StringPolicyValue(@NonNull String value) {
         super(value);
-        if (devicePolicySizeTrackingEnabled()) {
+        if (Flags.devicePolicySizeTrackingInternalEnabled()) {
             PolicySizeVerifier.enforceMaxStringLength(value, "policyValue");
         }
     }
diff --git a/core/java/android/app/admin/StringSetPolicyValue.java b/core/java/android/app/admin/StringSetPolicyValue.java
index 82fe761..f37dfee 100644
--- a/core/java/android/app/admin/StringSetPolicyValue.java
+++ b/core/java/android/app/admin/StringSetPolicyValue.java
@@ -16,10 +16,9 @@
 
 package android.app.admin;
 
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.admin.flags.Flags;
 import android.os.Parcel;
 
 import java.util.HashSet;
@@ -33,7 +32,7 @@
 
     public StringSetPolicyValue(@NonNull Set<String> value) {
         super(value);
-        if (devicePolicySizeTrackingEnabled()) {
+        if (Flags.devicePolicySizeTrackingInternalEnabled()) {
             for (String str : value) {
                 PolicySizeVerifier.enforceMaxStringLength(str, "policyValue");
             }
diff --git a/core/java/android/app/admin/UserRestrictionPolicyKey.java b/core/java/android/app/admin/UserRestrictionPolicyKey.java
index d69a5f0..ee90ccd 100644
--- a/core/java/android/app/admin/UserRestrictionPolicyKey.java
+++ b/core/java/android/app/admin/UserRestrictionPolicyKey.java
@@ -17,11 +17,11 @@
 package android.app.admin;
 
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
 
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
 import android.os.Bundle;
 import android.os.Parcel;
 
@@ -45,7 +45,7 @@
     @TestApi
     public UserRestrictionPolicyKey(@NonNull String identifier, @NonNull String restriction) {
         super(identifier);
-        if (devicePolicySizeTrackingEnabled()) {
+        if (Flags.devicePolicySizeTrackingInternalEnabled()) {
             PolicySizeVerifier.enforceMaxStringLength(restriction, "restriction");
         }
         mRestriction = Objects.requireNonNull(restriction);
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 10954ab..c29ea6d 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -10,7 +10,14 @@
 flag {
   name: "device_policy_size_tracking_enabled"
   namespace: "enterprise"
-  description: "Add feature to track the total policy size and have a max threshold."
+  description: "Add feature to track the total policy size and have a max threshold - public API changes"
+  bug: "281543351"
+}
+
+flag {
+  name: "device_policy_size_tracking_internal_enabled"
+  namespace: "enterprise"
+  description: "Add feature to track the total policy size and have a max threshold - internal changes"
   bug: "281543351"
 }
 
@@ -149,3 +156,20 @@
   description: "Allow to query whether MTE is enabled or not to check for compliance for enterprise policy"
   bug: "322777918"
 }
+
+flag {
+  name: "esim_management_ux_enabled"
+  namespace: "enterprise"
+  description: "Enable UX changes for esim management"
+  bug: "295301164"
+}
+
+flag {
+  name: "headless_device_owner_provisioning_fix_enabled"
+  namespace: "enterprise"
+  description: "Fix provisioning for single-user headless DO"
+  bug: "289515470"
+  metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/app/ambient_context.aconfig b/core/java/android/app/ambient_context.aconfig
deleted file mode 100644
index 3f73da2..0000000
--- a/core/java/android/app/ambient_context.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "android.app"
-
-flag {
-  namespace: "biometrics_integration"
-  name: "ambient_heart_rate"
-  description: "Feature flag for adding heart rate api to ambient context."
-  bug: "318309481"
-}
diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.java b/core/java/android/app/ambientcontext/AmbientContextEvent.java
index 5ab7991..13d959c 100644
--- a/core/java/android/app/ambientcontext/AmbientContextEvent.java
+++ b/core/java/android/app/ambientcontext/AmbientContextEvent.java
@@ -16,9 +16,7 @@
 
 package android.app.ambientcontext;
 
-import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
-import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcelable;
@@ -70,14 +68,6 @@
     public static final int EVENT_BACK_DOUBLE_TAP = 3;
 
     /**
-     * The integer indicating a heart rate measurement was done.
-     *
-     * @see #getRatePerMinute
-     */
-    @Event @FlaggedApi(android.app.Flags.FLAG_AMBIENT_HEART_RATE)
-    public static final int EVENT_HEART_RATE = 4;
-
-    /**
      * Integer indicating the start of wearable vendor defined events that can be detected.
      * These depend on the vendor implementation.
      */
@@ -89,19 +79,12 @@
      */
     public static final String KEY_VENDOR_WEARABLE_EVENT_NAME = "wearable_event_name";
 
-    /**
-     * Default value for {@link #getRatePerMinute}. Indicates that the rate of the event is unknown.
-     */
-    @FlaggedApi(android.app.Flags.FLAG_AMBIENT_HEART_RATE)
-    public static final int RATE_PER_MINUTE_UNKNOWN = -1;
-
     /** @hide */
     @IntDef(prefix = { "EVENT_" }, value = {
             EVENT_UNKNOWN,
             EVENT_COUGH,
             EVENT_SNORE,
             EVENT_BACK_DOUBLE_TAP,
-            EVENT_HEART_RATE,
             EVENT_VENDOR_WEARABLE_START,
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -187,16 +170,6 @@
         return new PersistableBundle();
     }
 
-    /**
-     * Rate per minute of the event during the start to end time.
-     *
-     * @return the rate per minute, or {@link #RATE_PER_MINUTE_UNKNOWN} if the rate is unknown.
-     */
-    private final @IntRange(from = -1) int mRatePerMinute;
-    private static int defaultRatePerMinute() {
-        return RATE_PER_MINUTE_UNKNOWN;
-    }
-
 
 
     // Code below generated by codegen v1.0.23.
@@ -206,8 +179,6 @@
     //
     // To regenerate run:
     // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/ambientcontext/AmbientContextEvent.java
-    // then manually add @FlaggedApi(android.app.Flags.FLAG_AMBIENT_HEART_RATE) back to flagged
-    // APIs.
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -220,7 +191,6 @@
         EVENT_COUGH,
         EVENT_SNORE,
         EVENT_BACK_DOUBLE_TAP,
-        EVENT_HEART_RATE,
         EVENT_VENDOR_WEARABLE_START
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -239,8 +209,6 @@
                     return "EVENT_SNORE";
             case EVENT_BACK_DOUBLE_TAP:
                     return "EVENT_BACK_DOUBLE_TAP";
-            case EVENT_HEART_RATE:
-                    return "EVENT_HEART_RATE";
             case EVENT_VENDOR_WEARABLE_START:
                     return "EVENT_VENDOR_WEARABLE_START";
             default: return Integer.toHexString(value);
@@ -287,8 +255,7 @@
             @NonNull Instant endTime,
             @LevelValue int confidenceLevel,
             @LevelValue int densityLevel,
-            @NonNull PersistableBundle vendorData,
-            @IntRange(from = -1) int ratePerMinute) {
+            @NonNull PersistableBundle vendorData) {
         this.mEventType = eventType;
         com.android.internal.util.AnnotationValidations.validate(
                 EventCode.class, null, mEventType);
@@ -307,10 +274,6 @@
         this.mVendorData = vendorData;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mVendorData);
-        this.mRatePerMinute = ratePerMinute;
-        com.android.internal.util.AnnotationValidations.validate(
-                IntRange.class, null, mRatePerMinute,
-                "from", -1);
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -367,17 +330,6 @@
         return mVendorData;
     }
 
-    /**
-     * Rate per minute of the event during the start to end time.
-     *
-     * @return the rate per minute, or {@link #RATE_PER_MINUTE_UNKNOWN} if the rate is unknown.
-     */
-    @DataClass.Generated.Member
-    @FlaggedApi(android.app.Flags.FLAG_AMBIENT_HEART_RATE)
-    public @IntRange(from = -1) int getRatePerMinute() {
-        return mRatePerMinute;
-    }
-
     @Override
     @DataClass.Generated.Member
     public String toString() {
@@ -390,8 +342,7 @@
                 "endTime = " + mEndTime + ", " +
                 "confidenceLevel = " + mConfidenceLevel + ", " +
                 "densityLevel = " + mDensityLevel + ", " +
-                "vendorData = " + mVendorData + ", " +
-                "ratePerMinute = " + mRatePerMinute +
+                "vendorData = " + mVendorData +
         " }";
     }
 
@@ -429,7 +380,6 @@
         dest.writeInt(mConfidenceLevel);
         dest.writeInt(mDensityLevel);
         dest.writeTypedObject(mVendorData, flags);
-        dest.writeInt(mRatePerMinute);
     }
 
     @Override
@@ -449,7 +399,6 @@
         int confidenceLevel = in.readInt();
         int densityLevel = in.readInt();
         PersistableBundle vendorData = (PersistableBundle) in.readTypedObject(PersistableBundle.CREATOR);
-        int ratePerMinute = in.readInt();
 
         this.mEventType = eventType;
         com.android.internal.util.AnnotationValidations.validate(
@@ -469,10 +418,6 @@
         this.mVendorData = vendorData;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mVendorData);
-        this.mRatePerMinute = ratePerMinute;
-        com.android.internal.util.AnnotationValidations.validate(
-                IntRange.class, null, mRatePerMinute,
-                "from", -1);
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -504,7 +449,6 @@
         private @LevelValue int mConfidenceLevel;
         private @LevelValue int mDensityLevel;
         private @NonNull PersistableBundle mVendorData;
-        private @IntRange(from = -1) int mRatePerMinute;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -581,22 +525,10 @@
             return this;
         }
 
-        /**
-         * Rate per minute of the event during the start to end time.
-         */
-        @DataClass.Generated.Member
-        @FlaggedApi(android.app.Flags.FLAG_AMBIENT_HEART_RATE)
-        public @NonNull Builder setRatePerMinute(@IntRange(from = -1) int value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x40;
-            mRatePerMinute = value;
-            return this;
-        }
-
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull AmbientContextEvent build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x80; // Mark builder used
+            mBuilderFieldsSet |= 0x40; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
                 mEventType = defaultEventType();
@@ -616,22 +548,18 @@
             if ((mBuilderFieldsSet & 0x20) == 0) {
                 mVendorData = defaultVendorData();
             }
-            if ((mBuilderFieldsSet & 0x40) == 0) {
-                mRatePerMinute = defaultRatePerMinute();
-            }
             AmbientContextEvent o = new AmbientContextEvent(
                     mEventType,
                     mStartTime,
                     mEndTime,
                     mConfidenceLevel,
                     mDensityLevel,
-                    mVendorData,
-                    mRatePerMinute);
+                    mVendorData);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x80) != 0) {
+            if ((mBuilderFieldsSet & 0x40) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -639,10 +567,10 @@
     }
 
     @DataClass.Generated(
-            time = 1705575046107L,
+            time = 1709014715064L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/app/ambientcontext/AmbientContextEvent.java",
-            inputSignatures = "public static final  int EVENT_UNKNOWN\npublic static final  int EVENT_COUGH\npublic static final  int EVENT_SNORE\npublic static final  int EVENT_BACK_DOUBLE_TAP\npublic static final @android.app.ambientcontext.AmbientContextEvent.Event @android.annotation.FlaggedApi int EVENT_HEART_RATE\npublic static final  int EVENT_VENDOR_WEARABLE_START\npublic static final  java.lang.String KEY_VENDOR_WEARABLE_EVENT_NAME\npublic static final @android.annotation.FlaggedApi int RATE_PER_MINUTE_UNKNOWN\npublic static final  int LEVEL_UNKNOWN\npublic static final  int LEVEL_LOW\npublic static final  int LEVEL_MEDIUM_LOW\npublic static final  int LEVEL_MEDIUM\npublic static final  int LEVEL_MEDIUM_HIGH\npublic static final  int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate final @android.annotation.NonNull android.os.PersistableBundle mVendorData\nprivate final @android.annotation.IntRange int mRatePerMinute\nprivate static  int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultDensityLevel()\nprivate static  android.os.PersistableBundle defaultVendorData()\nprivate static  int defaultRatePerMinute()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+            inputSignatures = "public static final  int EVENT_UNKNOWN\npublic static final  int EVENT_COUGH\npublic static final  int EVENT_SNORE\npublic static final  int EVENT_BACK_DOUBLE_TAP\npublic static final  int EVENT_VENDOR_WEARABLE_START\npublic static final  java.lang.String KEY_VENDOR_WEARABLE_EVENT_NAME\npublic static final  int LEVEL_UNKNOWN\npublic static final  int LEVEL_LOW\npublic static final  int LEVEL_MEDIUM_LOW\npublic static final  int LEVEL_MEDIUM\npublic static final  int LEVEL_MEDIUM_HIGH\npublic static final  int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate final @android.annotation.NonNull android.os.PersistableBundle mVendorData\nprivate static  int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultDensityLevel()\nprivate static  android.os.PersistableBundle defaultVendorData()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index fb1b17b..7548562 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -1,5 +1,7 @@
 package android.app.assist;
 
+import static android.credentials.Constants.FAILURE_CREDMAN_SELECTOR;
+import static android.credentials.Constants.SUCCESS_CREDMAN_SELECTOR;
 import static android.service.autofill.Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION;
 
 import android.annotation.FlaggedApi;
@@ -20,14 +22,17 @@
 import android.os.BadParcelableException;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.LocaleList;
+import android.os.Looper;
 import android.os.OutcomeReceiver;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PooledStringReader;
 import android.os.PooledStringWriter;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.SystemClock;
 import android.service.autofill.FillRequest;
 import android.service.credentials.CredentialProviderService;
@@ -37,6 +42,7 @@
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Slog;
 import android.view.View;
 import android.view.View.AutofillImportance;
 import android.view.ViewRootImpl;
@@ -652,6 +658,9 @@
         @Nullable OutcomeReceiver<GetCredentialResponse, GetCredentialException>
                 mGetCredentialCallback;
 
+        @Nullable ResultReceiver mGetCredentialResultReceiver;
+
+
         AutofillValue mAutofillValue;
         CharSequence[] mAutofillOptions;
         boolean mSanitized;
@@ -916,6 +925,7 @@
                 mExtras = in.readBundle();
             }
             mGetCredentialRequest = in.readTypedObject(GetCredentialRequest.CREATOR);
+            mGetCredentialResultReceiver = in.readTypedObject(ResultReceiver.CREATOR);
         }
 
         /**
@@ -1153,6 +1163,7 @@
                 out.writeBundle(mExtras);
             }
             out.writeTypedObject(mGetCredentialRequest, flags);
+            out.writeTypedObject(mGetCredentialResultReceiver, flags);
             return flags;
         }
 
@@ -1286,7 +1297,7 @@
          */
         @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
         @Nullable
-        public GetCredentialRequest getCredentialManagerRequest() {
+        public GetCredentialRequest getPendingCredentialRequest() {
             return mGetCredentialRequest;
         }
 
@@ -1295,9 +1306,8 @@
          */
         @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
         @Nullable
-        public OutcomeReceiver<GetCredentialResponse,
-                GetCredentialException> getCredentialManagerCallback() {
-            return mGetCredentialCallback;
+        public ResultReceiver getPendingCredentialCallback() {
+            return mGetCredentialResultReceiver;
         }
 
         /**
@@ -1894,6 +1904,7 @@
         final AssistStructure mAssist;
         final ViewNode mNode;
         final boolean mAsync;
+        private Handler mHandler;
 
         /**
          * Used to instantiate a builder for a stand-alone {@link ViewNode} which is not associated
@@ -2180,14 +2191,14 @@
 
         @Nullable
         @Override
-        public GetCredentialRequest getCredentialManagerRequest() {
+        public GetCredentialRequest getPendingCredentialRequest() {
             return mNode.mGetCredentialRequest;
         }
 
         @Nullable
         @Override
         public OutcomeReceiver<
-                GetCredentialResponse, GetCredentialException> getCredentialManagerCallback() {
+                GetCredentialResponse, GetCredentialException> getPendingCredentialCallback() {
             return mNode.mGetCredentialCallback;
         }
 
@@ -2256,7 +2267,7 @@
         }
 
         @Override
-        public void setCredentialManagerRequest(@NonNull GetCredentialRequest request,
+        public void setPendingCredentialRequest(@NonNull GetCredentialRequest request,
                 @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
             mNode.mGetCredentialRequest = request;
             mNode.mGetCredentialCallback = callback;
@@ -2271,6 +2282,56 @@
                 option.getCandidateQueryData()
                         .putParcelableArrayList(CredentialProviderService.EXTRA_AUTOFILL_ID, ids);
             }
+            setUpResultReceiver(callback);
+        }
+
+        private void setUpResultReceiver(
+                OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
+
+            if (mHandler == null) {
+                mHandler = new Handler(Looper.getMainLooper(), null, true);
+            }
+            final ResultReceiver resultReceiver = new ResultReceiver(mHandler) {
+                @Override
+                protected void onReceiveResult(int resultCode, Bundle resultData) {
+                    if (resultCode == SUCCESS_CREDMAN_SELECTOR) {
+                        Slog.d(TAG, "onReceiveResult from Credential Manager");
+                        GetCredentialResponse getCredentialResponse =
+                                resultData.getParcelable(
+                                        CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
+                                        GetCredentialResponse.class);
+
+                        callback.onResult(getCredentialResponse);
+                    } else if (resultCode == FAILURE_CREDMAN_SELECTOR) {
+                        String[] exception =  resultData.getStringArray(
+                                CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION);
+                        if (exception != null && exception.length >= 2) {
+                            Slog.w(TAG, "Credman bottom sheet from pinned "
+                                    + "entry failed with: + " + exception[0] + " , "
+                                    + exception[1]);
+                            callback.onError(new GetCredentialException(
+                                    exception[0], exception[1]));
+                        }
+                    } else {
+                        Slog.d(TAG, "Unknown resultCode from credential "
+                                + "manager bottom sheet: " + resultCode);
+                    }
+                }
+            };
+            ResultReceiver ipcFriendlyResultReceiver =
+                    toIpcFriendlyResultReceiver(resultReceiver);
+            mNode.mGetCredentialResultReceiver = ipcFriendlyResultReceiver;
+        }
+
+        private ResultReceiver toIpcFriendlyResultReceiver(ResultReceiver resultReceiver) {
+            final Parcel parcel = Parcel.obtain();
+            resultReceiver.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+
+            final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel);
+            parcel.recycle();
+
+            return ipcFriendly;
         }
 
         @Override
@@ -2593,7 +2654,7 @@
                     + ", isCredential=" + node.isCredential()
             );
         }
-        GetCredentialRequest getCredentialRequest = node.getCredentialManagerRequest();
+        GetCredentialRequest getCredentialRequest = node.getPendingCredentialRequest();
         if (getCredentialRequest == null) {
             Log.i(TAG, prefix + " No Credential Manager Request");
         } else {
diff --git a/core/java/android/app/backup/BackupManagerMonitor.java b/core/java/android/app/backup/BackupManagerMonitor.java
index 812bf8e..c66478f 100644
--- a/core/java/android/app/backup/BackupManagerMonitor.java
+++ b/core/java/android/app/backup/BackupManagerMonitor.java
@@ -145,6 +145,25 @@
    */
   public static final String EXTRA_LOG_OPERATION_TYPE = "android.app.backup.extra.OPERATION_TYPE";
 
+  /**
+   * List of system components that do not support restore in a  V-> U OS downgrade, even if
+   * restoreAnyVersion is set to true.
+   * Read from Settings.Secure.V_TO_U_RESTORE_DENYLIST
+   *
+   * @hide
+   */
+  public static final String EXTRA_LOG_V_TO_U_DENYLIST = "android.app.backup.extra.V_TO_U_DENYLIST";
+
+  /**
+   * List of system components that support restore in a  V-> U OS downgrade, even if
+   * restoreAnyVersion is set to false.
+   * Read from Settings.Secure.V_TO_U_RESTORE_ALLOWLIST
+   *
+   * @hide
+   */
+  public static final String EXTRA_LOG_V_TO_U_ALLOWLIST =
+          "android.app.backup.extra.V_TO_U_ALLOWLIST";
+
   // TODO complete this list with all log messages. And document properly.
   public static final int LOG_EVENT_ID_FULL_BACKUP_CANCEL = 4;
   public static final int LOG_EVENT_ID_ILLEGAL_KEY = 5;
@@ -241,6 +260,15 @@
   /** Agent error during {@link PerformUnifiedRestoreTask#restoreFinished()}
    @hide */
   public static final int LOG_EVENT_ID_AGENT_FAILURE = 69;
+  /** V to U restore attempt, pkg is eligible
+   @hide */
+  public static final int LOG_EVENT_ID_V_TO_U_RESTORE_PKG_ELIGIBLE = 70;
+  /** V to U restore attempt, pkg is not eligible
+   @hide */
+  public static final int LOG_EVENT_ID_V_TO_U_RESTORE_PKG_NOT_ELIGIBLE = 71;
+  /** V to U restore attempt, allowlist and denlist are set
+   @hide */
+  public static final int LOG_EVENT_ID_V_TO_U_RESTORE_SET_LIST = 72;
 
   /**
    * This method will be called each time something important happens on BackupManager.
diff --git a/core/java/android/app/network-policy.aconfig b/core/java/android/app/network-policy.aconfig
new file mode 100644
index 0000000..88f386f
--- /dev/null
+++ b/core/java/android/app/network-policy.aconfig
@@ -0,0 +1,11 @@
+package: "android.app"
+
+flag {
+     namespace: "backstage_power"
+     name: "clear_dns_cache_on_network_rules_update"
+     description: "Clears the DNS cache when the network rules update"
+     bug: "237556596"
+     metadata {
+       purpose: PURPOSE_BUGFIX
+     }
+}
\ No newline at end of file
diff --git a/core/java/android/app/ondeviceintelligence/Feature.java b/core/java/android/app/ondeviceintelligence/Feature.java
index 5107354..4a38c92 100644
--- a/core/java/android/app/ondeviceintelligence/Feature.java
+++ b/core/java/android/app/ondeviceintelligence/Feature.java
@@ -199,24 +199,13 @@
 
         private long mBuilderFieldsSet = 0L;
 
-        public Builder(
-                int id,
-                int type,
-                int variant,
-                @NonNull PersistableBundle featureParams) {
+        /**
+         * Provides a builder instance to create a feature for given id.
+         * @param id the unique identifier for the feature.
+         */
+        public Builder(int id) {
             mId = id;
-            mType = type;
-            mVariant = variant;
-            mFeatureParams = featureParams;
-            com.android.internal.util.AnnotationValidations.validate(
-                    NonNull.class, null, mFeatureParams);
-        }
-
-        public @NonNull Builder setId(int value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x1;
-            mId = value;
-            return this;
+            mFeatureParams = new PersistableBundle();
         }
 
         public @NonNull Builder setName(@NonNull String value) {
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.java b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
index 92f3513..f3cbd26 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.java
+++ b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
@@ -41,7 +41,7 @@
 @FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
 public final class FeatureDetails implements Parcelable {
     @Status
-    private final int mStatus;
+    private final int mFeatureStatus;
     @NonNull
     private final PersistableBundle mFeatureDetailParams;
 
@@ -73,21 +73,21 @@
     }
 
     public FeatureDetails(
-            @Status int status,
+            @Status int featureStatus,
             @NonNull PersistableBundle featureDetailParams) {
-        this.mStatus = status;
+        this.mFeatureStatus = featureStatus;
         com.android.internal.util.AnnotationValidations.validate(
-                Status.class, null, mStatus);
+                Status.class, null, mFeatureStatus);
         this.mFeatureDetailParams = featureDetailParams;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mFeatureDetailParams);
     }
 
     public FeatureDetails(
-            @Status int status) {
-        this.mStatus = status;
+            @Status int featureStatus) {
+        this.mFeatureStatus = featureStatus;
         com.android.internal.util.AnnotationValidations.validate(
-                Status.class, null, mStatus);
+                Status.class, null, mFeatureStatus);
         this.mFeatureDetailParams = new PersistableBundle();
     }
 
@@ -95,8 +95,8 @@
     /**
      * Returns an integer value associated with the feature status.
      */
-    public @Status int getStatus() {
-        return mStatus;
+    public @Status int getFeatureStatus() {
+        return mFeatureStatus;
     }
 
 
@@ -111,7 +111,7 @@
     public String toString() {
         return MessageFormat.format("FeatureDetails '{' status = {0}, "
                         + "persistableBundle = {1} '}'",
-                mStatus,
+                mFeatureStatus,
                 mFeatureDetailParams);
     }
 
@@ -121,21 +121,21 @@
         if (o == null || getClass() != o.getClass()) return false;
         @SuppressWarnings("unchecked")
         FeatureDetails that = (FeatureDetails) o;
-        return mStatus == that.mStatus
+        return mFeatureStatus == that.mFeatureStatus
                 && java.util.Objects.equals(mFeatureDetailParams, that.mFeatureDetailParams);
     }
 
     @Override
     public int hashCode() {
         int _hash = 1;
-        _hash = 31 * _hash + mStatus;
+        _hash = 31 * _hash + mFeatureStatus;
         _hash = 31 * _hash + java.util.Objects.hashCode(mFeatureDetailParams);
         return _hash;
     }
 
     @Override
     public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
-        dest.writeInt(mStatus);
+        dest.writeInt(mFeatureStatus);
         dest.writeTypedObject(mFeatureDetailParams, flags);
     }
 
@@ -151,9 +151,9 @@
         PersistableBundle persistableBundle = (PersistableBundle) in.readTypedObject(
                 PersistableBundle.CREATOR);
 
-        this.mStatus = status;
+        this.mFeatureStatus = status;
         com.android.internal.util.AnnotationValidations.validate(
-                Status.class, null, mStatus);
+                Status.class, null, mFeatureStatus);
         this.mFeatureDetailParams = persistableBundle;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mFeatureDetailParams);
diff --git a/core/java/android/app/ondeviceintelligence/FilePart.java b/core/java/android/app/ondeviceintelligence/FilePart.java
deleted file mode 100644
index e9fb5f2..0000000
--- a/core/java/android/app/ondeviceintelligence/FilePart.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.ondeviceintelligence;
-
-import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
-
-import android.annotation.FlaggedApi;
-import android.annotation.SystemApi;
-import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
-import android.os.Parcelable;
-import android.os.PersistableBundle;
-
-import android.annotation.NonNull;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.Objects;
-
-/**
- * Represents file data with an associated file descriptor sent to and received from remote
- * processing. The interface ensures that the underlying file-descriptor is always opened in
- * read-only mode.
- *
- * @hide
- */
-@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-@SystemApi
-public final class FilePart implements Parcelable {
-    private final String mPartKey;
-    private final PersistableBundle mPartParams;
-    private final ParcelFileDescriptor mParcelFileDescriptor;
-
-    private FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams,
-            @NonNull ParcelFileDescriptor parcelFileDescriptor) {
-        Objects.requireNonNull(partKey);
-        Objects.requireNonNull(partParams);
-        this.mPartKey = partKey;
-        this.mPartParams = partParams;
-        this.mParcelFileDescriptor = Objects.requireNonNull(parcelFileDescriptor);
-    }
-
-    /**
-     * Create a file part using a filePath and any additional params.
-     */
-    public FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams,
-            @NonNull String filePath)
-            throws FileNotFoundException {
-        this(partKey, partParams, Objects.requireNonNull(ParcelFileDescriptor.open(
-                new File(Objects.requireNonNull(filePath)), ParcelFileDescriptor.MODE_READ_ONLY)));
-    }
-
-    /**
-     * Create a file part using a file input stream and any additional params.
-     * It is the caller's responsibility to close the stream. It is safe to do so as soon as this
-     * call returns.
-     */
-    public FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams,
-            @NonNull FileInputStream fileInputStream)
-            throws IOException {
-        this(partKey, partParams, ParcelFileDescriptor.dup(fileInputStream.getFD()));
-    }
-
-    /**
-     * Returns a FileInputStream for the associated File.
-     * Caller must close the associated stream when done reading from it.
-     *
-     * @return the FileInputStream associated with the FilePart.
-     */
-    @NonNull
-    public FileInputStream getFileInputStream() {
-        return new FileInputStream(mParcelFileDescriptor.getFileDescriptor());
-    }
-
-    /**
-     * Returns the unique key associated with the part. Each Part key added to a content object
-     * should be ensured to be unique.
-     */
-    @NonNull
-    public String getFilePartKey() {
-        return mPartKey;
-    }
-
-    /**
-     * Returns the params associated with Part.
-     */
-    @NonNull
-    public PersistableBundle getFilePartParams() {
-        return mPartParams;
-    }
-
-
-    @Override
-    public int describeContents() {
-        return CONTENTS_FILE_DESCRIPTOR;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeString8(getFilePartKey());
-        dest.writePersistableBundle(getFilePartParams());
-        mParcelFileDescriptor.writeToParcel(dest, flags
-                | Parcelable.PARCELABLE_WRITE_RETURN_VALUE); // This flag ensures that the sender's
-        // copy of the Pfd is closed as soon as the Binder call succeeds.
-    }
-
-    @NonNull
-    public static final Creator<FilePart> CREATOR = new Creator<>() {
-        @Override
-        public FilePart createFromParcel(Parcel in) {
-            return new FilePart(in.readString(), in.readTypedObject(PersistableBundle.CREATOR),
-                    in.readParcelable(
-                            getClass().getClassLoader(), ParcelFileDescriptor.class));
-        }
-
-        @Override
-        public FilePart[] newArray(int size) {
-            return new FilePart[size];
-        }
-    };
-}
diff --git a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
index b925f48..360a809 100644
--- a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
+++ b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
@@ -31,7 +31,7 @@
  import android.app.ondeviceintelligence.IResponseCallback;
  import android.app.ondeviceintelligence.IStreamingResponseCallback;
  import android.app.ondeviceintelligence.IProcessingSignal;
- import android.app.ondeviceintelligence.ITokenCountCallback;
+ import android.app.ondeviceintelligence.ITokenInfoCallback;
 
 
  /**
@@ -56,8 +56,8 @@
       void requestFeatureDownload(in Feature feature, ICancellationSignal signal, in IDownloadCallback callback) = 5;
 
       @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
-      void requestTokenCount(in Feature feature, in Content request, in  ICancellationSignal signal,
-                                                        in ITokenCountCallback tokenCountcallback) = 6;
+      void requestTokenInfo(in Feature feature, in Content request, in  ICancellationSignal signal,
+                                                        in ITokenInfoCallback tokenInfocallback) = 6;
 
       @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
       void processRequest(in Feature feature, in Content request, int requestType, in  ICancellationSignal cancellationSignal, in IProcessingSignal signal,
diff --git a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
index 9848e1d..0adf305 100644
--- a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
@@ -3,6 +3,7 @@
 import android.app.ondeviceintelligence.Content;
 import android.app.ondeviceintelligence.IProcessingSignal;
 import android.os.PersistableBundle;
+import android.os.RemoteCallback;
 
 /**
   * Interface for a IResponseCallback for receiving response from on-device intelligence service.
@@ -12,4 +13,5 @@
 interface IResponseCallback {
     void onSuccess(in Content result) = 1;
     void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+    void onDataAugmentRequest(in Content content, in RemoteCallback contentCallback) = 3;
 }
diff --git a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
index a680574..132e53e 100644
--- a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
@@ -4,6 +4,7 @@
 import android.app.ondeviceintelligence.IResponseCallback;
 import android.app.ondeviceintelligence.IProcessingSignal;
 import android.os.PersistableBundle;
+import android.os.RemoteCallback;
 
 
 /**
@@ -15,4 +16,5 @@
     void onNewContent(in Content result) = 1;
     void onSuccess(in Content result) = 2;
     void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 3;
+    void onDataAugmentRequest(in Content content, in RemoteCallback contentCallback) = 4;
 }
diff --git a/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl b/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl
deleted file mode 100644
index b724e03..0000000
--- a/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl
+++ /dev/null
@@ -1,13 +0,0 @@
-package android.app.ondeviceintelligence;
-
-import android.os.PersistableBundle;
-
-/**
-  * Interface for receiving the token count of a request for a given features.
-  *
-  * @hide
-  */
-interface ITokenCountCallback {
-    void onSuccess(long tokenCount) = 1;
-    void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
-}
diff --git a/core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl b/core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl
new file mode 100644
index 0000000..9219a89
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl
@@ -0,0 +1,14 @@
+package android.app.ondeviceintelligence;
+
+import android.os.PersistableBundle;
+import android.app.ondeviceintelligence.TokenInfo;
+
+/**
+  * Interface for receiving the token info of a request for a given feature.
+  *
+  * @hide
+  */
+interface ITokenInfoCallback {
+    void onSuccess(in TokenInfo tokenInfo) = 1;
+    void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+}
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
index 4d8e0d5..d195c4d 100644
--- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
+++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
@@ -26,8 +26,10 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.content.ComponentName;
 import android.content.Context;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.ICancellationSignal;
 import android.os.OutcomeReceiver;
@@ -37,6 +39,8 @@
 
 import androidx.annotation.IntDef;
 
+import com.android.internal.R;
+
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -60,7 +64,16 @@
 @SystemService(Context.ON_DEVICE_INTELLIGENCE_SERVICE)
 @FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
 public class OnDeviceIntelligenceManager {
+    /**
+     * @hide
+     */
     public static final String API_VERSION_BUNDLE_KEY = "ApiVersionBundleKey";
+
+    /**
+     * @hide
+     */
+    public static final String AUGMENT_REQUEST_CONTENT_BUNDLE_KEY =
+            "AugmentRequestContentBundleKey";
     private final Context mContext;
     private final IOnDeviceIntelligenceManager mService;
 
@@ -82,8 +95,6 @@
     public void getVersion(
             @NonNull @CallbackExecutor Executor callbackExecutor,
             @NonNull LongConsumer versionConsumer) {
-        // TODO explore modifying this method into getServicePackageDetails and return both
-        //  version and package name of the remote service implementing this.
         try {
             RemoteCallback callback = new RemoteCallback(result -> {
                 if (result == null) {
@@ -100,6 +111,23 @@
         }
     }
 
+
+    /**
+     * Get package name configured for providing the remote implementation for this system service.
+     */
+    @Nullable
+    @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+    public String getRemoteServicePackageName() {
+        String serviceConfigValue = mContext.getResources().getString(
+                R.string.config_defaultOnDeviceSandboxedInferenceService);
+        ComponentName componentName = ComponentName.unflattenFromString(serviceConfigValue);
+        if (componentName != null) {
+            return componentName.getPackageName();
+        }
+
+        return null;
+    }
+
     /**
      * Asynchronously get feature for a given id.
      *
@@ -273,29 +301,29 @@
     }
 
     /**
-     * The methods computes the token-count for a given request payload using the provided Feature
-     * details.
+     * The methods computes the token related information for a given request payload using the
+     * provided {@link Feature}.
      *
      * @param feature            feature associated with the request.
      * @param request            request that contains the content data and associated params.
-     * @param outcomeReceiver    callback to populate the token count or exception in case of
+     * @param outcomeReceiver    callback to populate the token info or exception in case of
      *                           failure.
      * @param cancellationSignal signal to invoke cancellation on the operation in the remote
      *                           implementation.
      * @param callbackExecutor   executor to run the callback on.
      */
     @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
-    public void requestTokenCount(@NonNull Feature feature, @NonNull Content request,
+    public void requestTokenInfo(@NonNull Feature feature, @NonNull Content request,
             @Nullable CancellationSignal cancellationSignal,
             @NonNull @CallbackExecutor Executor callbackExecutor,
-            @NonNull OutcomeReceiver<Long,
+            @NonNull OutcomeReceiver<TokenInfo,
                     OnDeviceIntelligenceManagerException> outcomeReceiver) {
         try {
-            ITokenCountCallback callback = new ITokenCountCallback.Stub() {
+            ITokenInfoCallback callback = new ITokenInfoCallback.Stub() {
                 @Override
-                public void onSuccess(long tokenCount) {
+                public void onSuccess(TokenInfo tokenInfo) {
                     Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
-                            () -> outcomeReceiver.onResult(tokenCount)));
+                            () -> outcomeReceiver.onResult(tokenInfo)));
                 }
 
                 @Override
@@ -314,7 +342,7 @@
                 cancellationSignal.setRemote(transport);
             }
 
-            mService.requestTokenCount(feature, request, transport, callback);
+            mService.requestTokenInfo(feature, request, transport, callback);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -328,33 +356,31 @@
      * was a
      * failure.
      *
-     * @param feature                 feature associated with the request.
-     * @param request                 request that contains the Content data and
-     *                                associated params.
-     * @param requestType             type of request being sent for processing the content.
-     * @param responseOutcomeReceiver callback to populate the response content and
-     *                                associated
-     *                                params.
-     * @param processingSignal        signal to invoke custom actions in the
-     *                                remote implementation.
-     * @param cancellationSignal      signal to invoke cancellation or
-     * @param callbackExecutor        executor to run the callback on.
+     * @param feature            feature associated with the request.
+     * @param request            request that contains the Content data and
+     *                           associated params.
+     * @param requestType        type of request being sent for processing the content.
+     * @param cancellationSignal signal to invoke cancellation.
+     * @param processingSignal   signal to send custom signals in the
+     *                           remote implementation.
+     * @param callbackExecutor   executor to run the callback on.
+     * @param responseCallback   callback to populate the response content and
+     *                           associated params.
      */
     @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
 
-    public void processRequest(@NonNull Feature feature, @NonNull Content request,
+    public void processRequest(@NonNull Feature feature, @Nullable Content request,
             @RequestType int requestType,
             @Nullable CancellationSignal cancellationSignal,
             @Nullable ProcessingSignal processingSignal,
             @NonNull @CallbackExecutor Executor callbackExecutor,
-            @NonNull OutcomeReceiver<Content,
-                    OnDeviceIntelligenceManagerProcessingException> responseOutcomeReceiver) {
+            @NonNull ProcessingOutcomeReceiver responseCallback) {
         try {
             IResponseCallback callback = new IResponseCallback.Stub() {
                 @Override
                 public void onSuccess(Content result) {
                     Binder.withCleanCallingIdentity(() -> {
-                        callbackExecutor.execute(() -> responseOutcomeReceiver.onResult(result));
+                        callbackExecutor.execute(() -> responseCallback.onResult(result));
                     });
                 }
 
@@ -362,12 +388,24 @@
                 public void onFailure(int errorCode, String errorMessage,
                         PersistableBundle errorParams) {
                     Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
-                            () -> responseOutcomeReceiver.onError(
+                            () -> responseCallback.onError(
                                     new OnDeviceIntelligenceManagerProcessingException(
                                             errorCode, errorMessage, errorParams))));
                 }
+
+                @Override
+                public void onDataAugmentRequest(@NonNull Content content,
+                        @NonNull RemoteCallback contentCallback) {
+                    Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+                            () -> responseCallback.onDataAugmentRequest(content, result -> {
+                                Bundle bundle = new Bundle();
+                                bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY, result);
+                                callbackExecutor.execute(() -> contentCallback.sendResult(bundle));
+                            })));
+                }
             };
 
+
             IProcessingSignal transport = null;
             if (processingSignal != null) {
                 transport = ProcessingSignal.createTransport();
@@ -389,46 +427,48 @@
     }
 
     /**
-     * Variation of {@link #processRequest} that asynchronously processes a request in a streaming
+     * Variation of {@link #processRequest} that asynchronously processes a request in a
+     * streaming
      * fashion, where new content is pushed to caller in chunks via the
-     * {@link StreamingResponseReceiver#onNewContent}. After the streaming is complete,
-     * the service should call {@link StreamingResponseReceiver#onResult} and can optionally
-     * populate the complete {@link Response}'s Content as part of the callback when the final
-     * {@link Response} contains an enhanced aggregation of the Contents already streamed.
+     * {@link StreamedProcessingOutcomeReceiver#onNewContent}. After the streaming is complete,
+     * the service should call {@link StreamedProcessingOutcomeReceiver#onResult} and can optionally
+     * populate the complete the full response {@link Content} as part of the callback in cases
+     * when the final response contains an enhanced aggregation of the Contents already
+     * streamed.
      *
      * @param feature                   feature associated with the request.
      * @param request                   request that contains the Content data and associated
      *                                  params.
      * @param requestType               type of request being sent for processing the content.
-     * @param processingSignal          signal to invoke  other custom actions in the
+     * @param cancellationSignal        signal to invoke cancellation.
+     * @param processingSignal          signal to send custom signals in the
      *                                  remote implementation.
-     * @param cancellationSignal        signal to invoke cancellation
-     * @param streamingResponseReceiver streaming callback to populate the response content and
+     * @param streamingResponseCallback streaming callback to populate the response content and
      *                                  associated params.
      * @param callbackExecutor          executor to run the callback on.
      */
     @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
-    public void processRequestStreaming(@NonNull Feature feature, @NonNull Content request,
+    public void processRequestStreaming(@NonNull Feature feature, @Nullable Content request,
             @RequestType int requestType,
             @Nullable CancellationSignal cancellationSignal,
             @Nullable ProcessingSignal processingSignal,
             @NonNull @CallbackExecutor Executor callbackExecutor,
-            @NonNull StreamingResponseReceiver<Content, Content,
-                    OnDeviceIntelligenceManagerProcessingException> streamingResponseReceiver) {
+            @NonNull StreamedProcessingOutcomeReceiver streamingResponseCallback) {
         try {
             IStreamingResponseCallback callback = new IStreamingResponseCallback.Stub() {
                 @Override
                 public void onNewContent(Content result) {
                     Binder.withCleanCallingIdentity(() -> {
                         callbackExecutor.execute(
-                                () -> streamingResponseReceiver.onNewContent(result));
+                                () -> streamingResponseCallback.onNewContent(result));
                     });
                 }
 
                 @Override
                 public void onSuccess(Content result) {
                     Binder.withCleanCallingIdentity(() -> {
-                        callbackExecutor.execute(() -> streamingResponseReceiver.onResult(result));
+                        callbackExecutor.execute(
+                                () -> streamingResponseCallback.onResult(result));
                     });
                 }
 
@@ -437,11 +477,26 @@
                         PersistableBundle errorParams) {
                     Binder.withCleanCallingIdentity(() -> {
                         callbackExecutor.execute(
-                                () -> streamingResponseReceiver.onError(
+                                () -> streamingResponseCallback.onError(
                                         new OnDeviceIntelligenceManagerProcessingException(
                                                 errorCode, errorMessage, errorParams)));
                     });
                 }
+
+
+                @Override
+                public void onDataAugmentRequest(@NonNull Content content,
+                        @NonNull RemoteCallback contentCallback) {
+                    Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+                            () -> streamingResponseCallback.onDataAugmentRequest(content,
+                                    contentResponse -> {
+                                        Bundle bundle = new Bundle();
+                                        bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY,
+                                                contentResponse);
+                                        callbackExecutor.execute(
+                                                () -> contentCallback.sendResult(bundle));
+                                    })));
+                }
             };
 
             IProcessingSignal transport = null;
@@ -468,7 +523,8 @@
     public static final int REQUEST_TYPE_INFERENCE = 0;
 
     /**
-     * Prepares the remote implementation environment for e.g.loading inference runtime etc.which
+     * Prepares the remote implementation environment for e.g.loading inference runtime etc
+     * .which
      * are time consuming beforehand to remove overhead and allow quick processing of requests
      * thereof.
      */
@@ -485,7 +541,8 @@
             REQUEST_TYPE_PREPARE,
             REQUEST_TYPE_EMBEDDINGS
     }, open = true)
-    @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+    @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER,
+            ElementType.FIELD})
     @Retention(RetentionPolicy.SOURCE)
     public @interface RequestType {
     }
@@ -501,17 +558,32 @@
          */
         public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 1000;
 
+        /**
+         * Error code to be used for on device intelligence manager failures.
+         *
+         * @hide
+         */
+        @IntDef(
+                value = {
+                        ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE
+                }, open = true)
+        @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+        @interface OnDeviceIntelligenceManagerErrorCode {
+        }
+
         private final int mErrorCode;
         private final PersistableBundle errorParams;
 
-        public OnDeviceIntelligenceManagerException(int errorCode, @NonNull String errorMessage,
+        public OnDeviceIntelligenceManagerException(
+                @OnDeviceIntelligenceManagerErrorCode int errorCode, @NonNull String errorMessage,
                 @NonNull PersistableBundle errorParams) {
             super(errorMessage);
             this.mErrorCode = errorCode;
             this.errorParams = errorParams;
         }
 
-        public OnDeviceIntelligenceManagerException(int errorCode,
+        public OnDeviceIntelligenceManagerException(
+                @OnDeviceIntelligenceManagerErrorCode int errorCode,
                 @NonNull PersistableBundle errorParams) {
             this.mErrorCode = errorCode;
             this.errorParams = errorParams;
@@ -573,11 +645,15 @@
         /** Inference suspended so that higher-priority inference can run. */
         public static final int PROCESSING_ERROR_SUSPENDED = 13;
 
-        /** Underlying processing encountered an internal error, like a violated precondition. */
+        /**
+         * Underlying processing encountered an internal error, like a violated precondition
+         * .
+         */
         public static final int PROCESSING_ERROR_INTERNAL = 14;
 
         /**
-         * The processing was not able to be passed on to the remote implementation, as the service
+         * The processing was not able to be passed on to the remote implementation, as the
+         * service
          * was unavailable.
          */
         public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15;
diff --git a/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java b/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java
new file mode 100644
index 0000000..b0b6e19
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.OutcomeReceiver;
+
+import java.util.function.Consumer;
+
+/**
+ * Response Callback to populate the processed response or any error that occurred during the
+ * request processing. This callback also provides a method to request additional data to be
+ * augmented to the request-processing, using  the partial {@link Content} that was already
+ * processed in the remote implementation.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public interface ProcessingOutcomeReceiver extends
+        OutcomeReceiver<Content,
+                OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> {
+    /**
+     * Callback to be invoked in cases where the remote service needs to perform retrieval or
+     * transformation operations based on a partially processed request, in order to augment the
+     * final response, by using the additional context sent via this callback.
+     *
+     * @param content         The content payload that should be used to augment ongoing request.
+     * @param contentConsumer The augmentation data that should be sent to remote
+     *                        service for further processing a request.
+     */
+    default void onDataAugmentRequest(@NonNull Content content,
+            @NonNull Consumer<Content> contentConsumer) {
+        contentConsumer.accept(null);
+    }
+}
diff --git a/core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java b/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
similarity index 71%
rename from core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java
rename to core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
index ebcf61c..ac2b032 100644
--- a/core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java
+++ b/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
@@ -21,23 +21,19 @@
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
-import android.os.OutcomeReceiver;
 
 /**
  * Streaming variant of outcome receiver to populate response while processing a given request,
- * possibly in
- * chunks to provide a async processing behaviour to the caller.
+ * possibly in chunks to provide a async processing behaviour to the caller.
  *
  * @hide
  */
 @SystemApi
 @FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public interface StreamingResponseReceiver<R, T, E extends Throwable> extends
-        OutcomeReceiver<R, E> {
+public interface StreamedProcessingOutcomeReceiver extends ProcessingOutcomeReceiver {
     /**
-     * Callback to be invoked when a part of the response i.e. some {@link Content} is already
-     * processed and
-     * needs to be passed onto the caller.
+     * Callback that would be invoked when a part of the response i.e. some {@link Content} is
+     * already processed and needs to be passed onto the caller.
      */
-    void onNewContent(@NonNull T content);
+    void onNewContent(@NonNull Content content);
 }
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/captioning/CaptioningModule.kt b/core/java/android/app/ondeviceintelligence/TokenInfo.aidl
similarity index 76%
rename from packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/captioning/CaptioningModule.kt
rename to core/java/android/app/ondeviceintelligence/TokenInfo.aidl
index aeb5c5d..2c19c1e 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/captioning/CaptioningModule.kt
+++ b/core/java/android/app/ondeviceintelligence/TokenInfo.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,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel.component.captioning
+package android.app.ondeviceintelligence;
 
-import dagger.Module
-
-@Module interface CaptioningModule
+/**
+  * @hide
+  */
+parcelable TokenInfo;
diff --git a/core/java/android/app/ondeviceintelligence/TokenInfo.java b/core/java/android/app/ondeviceintelligence/TokenInfo.java
new file mode 100644
index 0000000..035cc4b
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/TokenInfo.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+
+/**
+ * This class is used to provide a token count response for the
+ * {@link OnDeviceIntelligenceManager#requestTokenInfo} outcome receiver.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public final class TokenInfo implements Parcelable {
+    private final long mCount;
+    private final PersistableBundle mInfoParams;
+
+    /**
+     * Construct a token count using the count value and associated params.
+     */
+    public TokenInfo(long count, @NonNull PersistableBundle persistableBundle) {
+        this.mCount = count;
+        mInfoParams = persistableBundle;
+    }
+
+    /**
+     * Construct a token count using the count value.
+     */
+    public TokenInfo(long count) {
+        this.mCount = count;
+        this.mInfoParams = new PersistableBundle();
+    }
+
+    /**
+     * Returns the token count associated with a request payload.
+     */
+    public long getCount() {
+        return mCount;
+    }
+
+    /**
+     * Returns the params representing token info.
+     */
+    @NonNull
+    public PersistableBundle getInfoParams() {
+        return mInfoParams;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeLong(mCount);
+        dest.writePersistableBundle(mInfoParams);
+    }
+
+    public static final @NonNull Parcelable.Creator<TokenInfo> CREATOR
+            = new Parcelable.Creator<>() {
+        @Override
+        public TokenInfo[] newArray(int size) {
+            return new TokenInfo[size];
+        }
+
+        @Override
+        public TokenInfo createFromParcel(@NonNull Parcel in) {
+            return new TokenInfo(in.readLong(), in.readPersistableBundle());
+        }
+    };
+}
diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
index bc8fac5..6317725 100644
--- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -29,6 +29,7 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Trace;
+import android.window.ActivityWindowInfo;
 
 import java.util.Objects;
 
@@ -39,6 +40,7 @@
 public class ActivityConfigurationChangeItem extends ActivityTransactionItem {
 
     private Configuration mConfiguration;
+    private ActivityWindowInfo mActivityWindowInfo;
 
     @Override
     public void preExecute(@NonNull ClientTransactionHandler client) {
@@ -49,11 +51,12 @@
     }
 
     @Override
-    public void execute(@NonNull ClientTransactionHandler client, @Nullable ActivityClientRecord r,
+    public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
             @NonNull PendingTransactionActions pendingActions) {
         // TODO(lifecycler): detect if PIP or multi-window mode changed and report it here.
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged");
-        client.handleActivityConfigurationChanged(r, mConfiguration, INVALID_DISPLAY);
+        client.handleActivityConfigurationChanged(r, mConfiguration, INVALID_DISPLAY,
+                mActivityWindowInfo);
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
@@ -70,7 +73,7 @@
     /** Obtain an instance initialized with provided params. */
     @NonNull
     public static ActivityConfigurationChangeItem obtain(@NonNull IBinder activityToken,
-            @NonNull Configuration config) {
+            @NonNull Configuration config, @NonNull ActivityWindowInfo activityWindowInfo) {
         ActivityConfigurationChangeItem instance =
                 ObjectPool.obtain(ActivityConfigurationChangeItem.class);
         if (instance == null) {
@@ -78,6 +81,7 @@
         }
         instance.setActivityToken(activityToken);
         instance.mConfiguration = new Configuration(config);
+        instance.mActivityWindowInfo = new ActivityWindowInfo(activityWindowInfo);
 
         return instance;
     }
@@ -86,6 +90,7 @@
     public void recycle() {
         super.recycle();
         mConfiguration = null;
+        mActivityWindowInfo = null;
         ObjectPool.recycle(this);
     }
 
@@ -97,12 +102,14 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
         dest.writeTypedObject(mConfiguration, flags);
+        dest.writeTypedObject(mActivityWindowInfo, flags);
     }
 
     /** Read from Parcel. */
     private ActivityConfigurationChangeItem(@NonNull Parcel in) {
         super(in);
         mConfiguration = in.readTypedObject(Configuration.CREATOR);
+        mActivityWindowInfo = in.readTypedObject(ActivityWindowInfo.CREATOR);
     }
 
     public static final @NonNull Creator<ActivityConfigurationChangeItem> CREATOR =
@@ -125,7 +132,8 @@
             return false;
         }
         final ActivityConfigurationChangeItem other = (ActivityConfigurationChangeItem) o;
-        return Objects.equals(mConfiguration, other.mConfiguration);
+        return Objects.equals(mConfiguration, other.mConfiguration)
+                && Objects.equals(mActivityWindowInfo, other.mActivityWindowInfo);
     }
 
     @Override
@@ -133,12 +141,14 @@
         int result = 17;
         result = 31 * result + super.hashCode();
         result = 31 * result + Objects.hashCode(mConfiguration);
+        result = 31 * result + Objects.hashCode(mActivityWindowInfo);
         return result;
     }
 
     @Override
     public String toString() {
         return "ActivityConfigurationChange{" + super.toString()
-                + ",config=" + mConfiguration + "}";
+                + ",config=" + mConfiguration
+                + ",activityWindowInfo=" + mActivityWindowInfo + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 79696e0..48081bb 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -23,6 +23,7 @@
 import android.app.ClientTransactionHandler;
 import android.app.IApplicationThread;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -54,10 +55,12 @@
     @Nullable
     private List<ClientTransactionItem> mTransactionItems;
 
-    /** A list of individual callbacks to a client. */
-    // TODO(b/324203798): cleanup after remove UnsupportedAppUsage
-    @UnsupportedAppUsage
+    /** @deprecated use {@link #getTransactionItems} instead. */
     @Nullable
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+            trackingBug = 324203798,
+            publicAlternatives = "Use {@code #getTransactionItems()}")
+    @Deprecated
     private List<ClientTransactionItem> mActivityCallbacks;
 
     /**
@@ -126,42 +129,42 @@
         setActivityTokenIfNotSet(activityCallback);
     }
 
-    /**
-     * Gets the list of callbacks.
-     * @deprecated use {@link #getTransactionItems()} instead.
-     */
-    // TODO(b/324203798): cleanup after remove UnsupportedAppUsage
-    @Nullable
+    /** @deprecated use {@link #getTransactionItems()} instead. */
     @VisibleForTesting
-    @UnsupportedAppUsage
+    @Nullable
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+            trackingBug = 324203798,
+            publicAlternatives = "Use {@code #getTransactionItems()}")
     @Deprecated
     public List<ClientTransactionItem> getCallbacks() {
         return mActivityCallbacks;
     }
 
     /**
-     * @deprecated a transaction can contain {@link ClientTransactionItem} of different activities,
+     * A transaction can contain {@link ClientTransactionItem} of different activities,
      * this must not be used. For any unsupported app usages, please be aware that this is set to
      * the activity of the first item in {@link #getTransactionItems()}.
+     *
+     * @deprecated use {@link ClientTransactionItem#getActivityToken()} instead.
      */
-    // TODO(b/324203798): cleanup after remove UnsupportedAppUsage
     @VisibleForTesting
     @Nullable
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+            trackingBug = 324203798,
+            publicAlternatives = "Use {@code android.app.servertransaction"
+                    + ".ClientTransactionItem#getActivityToken()}")
     @Deprecated
     public IBinder getActivityToken() {
         return mActivityToken;
     }
 
-    /**
-     * Gets the target state lifecycle request.
-     * @deprecated use {@link #getTransactionItems()} instead.
-     */
-    // TODO(b/324203798): cleanup after remove UnsupportedAppUsage
+    /** @deprecated use {@link #getTransactionItems()} instead. */
     @VisibleForTesting(visibility = PACKAGE)
-    @UnsupportedAppUsage
-    @Deprecated
     @Nullable
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+            trackingBug = 324203798,
+            publicAlternatives = "Use {@code #getTransactionItems()}")
+    @Deprecated
     public ActivityLifecycleItem getLifecycleStateRequest() {
         return mLifecycleStateRequest;
     }
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index 1a8136e..7383d07 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -16,16 +16,24 @@
 
 package android.app.servertransaction;
 
+import static com.android.window.flags.Flags.activityWindowInfoFlag;
 import static com.android.window.flags.Flags.bundleClientTransactionFlag;
 
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.NonNull;
+import android.app.Activity;
 import android.app.ActivityThread;
 import android.hardware.display.DisplayManagerGlobal;
+import android.os.IBinder;
+import android.util.ArraySet;
+import android.window.ActivityWindowInfo;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.function.BiConsumer;
+
 /**
  * Singleton controller to manage listeners to individual {@link ClientTransaction}.
  *
@@ -35,8 +43,14 @@
 
     private static ClientTransactionListenerController sController;
 
+    private final Object mLock = new Object();
     private final DisplayManagerGlobal mDisplayManager;
 
+    /** Listeners registered via {@link #registerActivityWindowInfoChangedListener(BiConsumer)}. */
+    @GuardedBy("mLock")
+    private final ArraySet<BiConsumer<IBinder, ActivityWindowInfo>>
+            mActivityWindowInfoChangedListeners = new ArraySet<>();
+
     /** Gets the singleton controller. */
     @NonNull
     public static ClientTransactionListenerController getInstance() {
@@ -62,6 +76,57 @@
     }
 
     /**
+     * Registers to listen on activity {@link ActivityWindowInfo} change.
+     * The listener will be invoked with two parameters: {@link Activity#getActivityToken()} and
+     * {@link ActivityWindowInfo}.
+     */
+    public void registerActivityWindowInfoChangedListener(
+            @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
+        if (!activityWindowInfoFlag()) {
+            return;
+        }
+        synchronized (mLock) {
+            mActivityWindowInfoChangedListeners.add(listener);
+        }
+    }
+
+    /**
+     * Unregisters the listener that was previously registered via
+     * {@link #registerActivityWindowInfoChangedListener(BiConsumer)}
+     */
+    public void unregisterActivityWindowInfoChangedListener(
+            @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
+        if (!activityWindowInfoFlag()) {
+            return;
+        }
+        synchronized (mLock) {
+            mActivityWindowInfoChangedListeners.remove(listener);
+        }
+    }
+
+    /**
+     * Called when receives a {@link ClientTransaction} that is updating an activity's
+     * {@link ActivityWindowInfo}.
+     */
+    public void onActivityWindowInfoChanged(@NonNull IBinder activityToken,
+            @NonNull ActivityWindowInfo activityWindowInfo) {
+        if (!activityWindowInfoFlag()) {
+            return;
+        }
+        final Object[] activityWindowInfoChangedListeners;
+        synchronized (mLock) {
+            if (mActivityWindowInfoChangedListeners.isEmpty()) {
+                return;
+            }
+            activityWindowInfoChangedListeners = mActivityWindowInfoChangedListeners.toArray();
+        }
+        for (Object activityWindowInfoChangedListener : activityWindowInfoChangedListeners) {
+            ((BiConsumer<IBinder, ActivityWindowInfo>) activityWindowInfoChangedListener)
+                    .accept(activityToken, activityWindowInfo);
+        }
+    }
+
+    /**
      * Called when receives a {@link ClientTransaction} that is updating display-related
      * window configuration.
      */
diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java
index f9cf075..b0213d7 100644
--- a/core/java/android/app/servertransaction/DestroyActivityItem.java
+++ b/core/java/android/app/servertransaction/DestroyActivityItem.java
@@ -33,7 +33,6 @@
 public class DestroyActivityItem extends ActivityLifecycleItem {
 
     private boolean mFinished;
-    private int mConfigChanges;
 
     @Override
     public void preExecute(@NonNull ClientTransactionHandler client) {
@@ -44,7 +43,7 @@
     public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
             @NonNull PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
-        client.handleDestroyActivity(r, mFinished, mConfigChanges,
+        client.handleDestroyActivity(r, mFinished,
                 false /* getNonConfigInstance */, "DestroyActivityItem");
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
@@ -67,15 +66,13 @@
 
     /** Obtain an instance initialized with provided params. */
     @NonNull
-    public static DestroyActivityItem obtain(@NonNull IBinder activityToken, boolean finished,
-            int configChanges) {
+    public static DestroyActivityItem obtain(@NonNull IBinder activityToken, boolean finished) {
         DestroyActivityItem instance = ObjectPool.obtain(DestroyActivityItem.class);
         if (instance == null) {
             instance = new DestroyActivityItem();
         }
         instance.setActivityToken(activityToken);
         instance.mFinished = finished;
-        instance.mConfigChanges = configChanges;
 
         return instance;
     }
@@ -84,7 +81,6 @@
     public void recycle() {
         super.recycle();
         mFinished = false;
-        mConfigChanges = 0;
         ObjectPool.recycle(this);
     }
 
@@ -95,14 +91,12 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
         dest.writeBoolean(mFinished);
-        dest.writeInt(mConfigChanges);
     }
 
     /** Read from Parcel. */
     private DestroyActivityItem(@NonNull Parcel in) {
         super(in);
         mFinished = in.readBoolean();
-        mConfigChanges = in.readInt();
     }
 
     public static final @NonNull Creator<DestroyActivityItem> CREATOR = new Creator<>() {
@@ -124,7 +118,7 @@
             return false;
         }
         final DestroyActivityItem other = (DestroyActivityItem) o;
-        return mFinished == other.mFinished && mConfigChanges == other.mConfigChanges;
+        return mFinished == other.mFinished;
     }
 
     @Override
@@ -132,14 +126,12 @@
         int result = 17;
         result = 31 * result + super.hashCode();
         result = 31 * result + (mFinished ? 1 : 0);
-        result = 31 * result + mConfigChanges;
         return result;
     }
 
     @Override
     public String toString() {
         return "DestroyActivityItem{" + super.toString()
-                + ",finished=" + mFinished
-                + ",mConfigChanges=" + mConfigChanges + "}";
+                + ",finished=" + mFinished + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java
index 1353d16..0702c45 100644
--- a/core/java/android/app/servertransaction/MoveToDisplayItem.java
+++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java
@@ -28,6 +28,7 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Trace;
+import android.window.ActivityWindowInfo;
 
 import java.util.Objects;
 
@@ -39,6 +40,7 @@
 
     private int mTargetDisplayId;
     private Configuration mConfiguration;
+    private ActivityWindowInfo mActivityWindowInfo;
 
     @Override
     public void preExecute(@NonNull ClientTransactionHandler client) {
@@ -52,7 +54,8 @@
     public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
             @NonNull PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityMovedToDisplay");
-        client.handleActivityConfigurationChanged(r, mConfiguration, mTargetDisplayId);
+        client.handleActivityConfigurationChanged(r, mConfiguration, mTargetDisplayId,
+                mActivityWindowInfo);
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
@@ -69,7 +72,7 @@
     /** Obtain an instance initialized with provided params. */
     @NonNull
     public static MoveToDisplayItem obtain(@NonNull IBinder activityToken, int targetDisplayId,
-            @NonNull Configuration configuration) {
+            @NonNull Configuration configuration, @NonNull ActivityWindowInfo activityWindowInfo) {
         MoveToDisplayItem instance = ObjectPool.obtain(MoveToDisplayItem.class);
         if (instance == null) {
             instance = new MoveToDisplayItem();
@@ -77,6 +80,7 @@
         instance.setActivityToken(activityToken);
         instance.mTargetDisplayId = targetDisplayId;
         instance.mConfiguration = new Configuration(configuration);
+        instance.mActivityWindowInfo = new ActivityWindowInfo(activityWindowInfo);
 
         return instance;
     }
@@ -86,6 +90,7 @@
         super.recycle();
         mTargetDisplayId = 0;
         mConfiguration = null;
+        mActivityWindowInfo = null;
         ObjectPool.recycle(this);
     }
 
@@ -97,6 +102,7 @@
         super.writeToParcel(dest, flags);
         dest.writeInt(mTargetDisplayId);
         dest.writeTypedObject(mConfiguration, flags);
+        dest.writeTypedObject(mActivityWindowInfo, flags);
     }
 
     /** Read from Parcel. */
@@ -104,6 +110,7 @@
         super(in);
         mTargetDisplayId = in.readInt();
         mConfiguration = in.readTypedObject(Configuration.CREATOR);
+        mActivityWindowInfo = in.readTypedObject(ActivityWindowInfo.CREATOR);
     }
 
     public static final @NonNull Creator<MoveToDisplayItem> CREATOR = new Creator<>() {
@@ -126,7 +133,8 @@
         }
         final MoveToDisplayItem other = (MoveToDisplayItem) o;
         return mTargetDisplayId == other.mTargetDisplayId
-                && Objects.equals(mConfiguration, other.mConfiguration);
+                && Objects.equals(mConfiguration, other.mConfiguration)
+                && Objects.equals(mActivityWindowInfo, other.mActivityWindowInfo);
     }
 
     @Override
@@ -135,6 +143,7 @@
         result = 31 * result + super.hashCode();
         result = 31 * result + mTargetDisplayId;
         result = 31 * result + mConfiguration.hashCode();
+        result = 31 * result + Objects.hashCode(mActivityWindowInfo);
         return result;
     }
 
@@ -142,6 +151,7 @@
     public String toString() {
         return "MoveToDisplayItem{" + super.toString()
                 + ",targetDisplayId=" + mTargetDisplayId
-                + ",configuration=" + mConfiguration + "}";
+                + ",configuration=" + mConfiguration
+                + ",activityWindowInfo=" + mActivityWindowInfo + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java
index 8f1e90b..d230284 100644
--- a/core/java/android/app/servertransaction/PauseActivityItem.java
+++ b/core/java/android/app/servertransaction/PauseActivityItem.java
@@ -37,7 +37,6 @@
 
     private boolean mFinished;
     private boolean mUserLeaving;
-    private int mConfigChanges;
     private boolean mDontReport;
     private boolean mAutoEnteringPip;
 
@@ -45,7 +44,7 @@
     public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
             @NonNull PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
-        client.handlePauseActivity(r, mFinished, mUserLeaving, mConfigChanges, mAutoEnteringPip,
+        client.handlePauseActivity(r, mFinished, mUserLeaving, mAutoEnteringPip,
                 pendingActions, "PAUSE_ACTIVITY_ITEM");
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
@@ -72,7 +71,7 @@
     /** Obtain an instance initialized with provided params. */
     @NonNull
     public static PauseActivityItem obtain(@NonNull IBinder activityToken, boolean finished,
-            boolean userLeaving, int configChanges, boolean dontReport, boolean autoEnteringPip) {
+            boolean userLeaving, boolean dontReport, boolean autoEnteringPip) {
         PauseActivityItem instance = ObjectPool.obtain(PauseActivityItem.class);
         if (instance == null) {
             instance = new PauseActivityItem();
@@ -80,7 +79,6 @@
         instance.setActivityToken(activityToken);
         instance.mFinished = finished;
         instance.mUserLeaving = userLeaving;
-        instance.mConfigChanges = configChanges;
         instance.mDontReport = dontReport;
         instance.mAutoEnteringPip = autoEnteringPip;
 
@@ -91,7 +89,7 @@
     @NonNull
     public static PauseActivityItem obtain(@NonNull IBinder activityToken) {
         return obtain(activityToken, false /* finished */, false /* userLeaving */,
-                0 /* configChanges */, true /* dontReport */, false /* autoEnteringPip*/);
+                true /* dontReport */, false /* autoEnteringPip*/);
     }
 
     @Override
@@ -99,7 +97,6 @@
         super.recycle();
         mFinished = false;
         mUserLeaving = false;
-        mConfigChanges = 0;
         mDontReport = false;
         mAutoEnteringPip = false;
         ObjectPool.recycle(this);
@@ -113,7 +110,6 @@
         super.writeToParcel(dest, flags);
         dest.writeBoolean(mFinished);
         dest.writeBoolean(mUserLeaving);
-        dest.writeInt(mConfigChanges);
         dest.writeBoolean(mDontReport);
         dest.writeBoolean(mAutoEnteringPip);
     }
@@ -123,7 +119,6 @@
         super(in);
         mFinished = in.readBoolean();
         mUserLeaving = in.readBoolean();
-        mConfigChanges = in.readInt();
         mDontReport = in.readBoolean();
         mAutoEnteringPip = in.readBoolean();
     }
@@ -148,7 +143,7 @@
         }
         final PauseActivityItem other = (PauseActivityItem) o;
         return mFinished == other.mFinished && mUserLeaving == other.mUserLeaving
-                && mConfigChanges == other.mConfigChanges && mDontReport == other.mDontReport
+                && mDontReport == other.mDontReport
                 && mAutoEnteringPip == other.mAutoEnteringPip;
     }
 
@@ -158,7 +153,6 @@
         result = 31 * result + super.hashCode();
         result = 31 * result + (mFinished ? 1 : 0);
         result = 31 * result + (mUserLeaving ? 1 : 0);
-        result = 31 * result + mConfigChanges;
         result = 31 * result + (mDontReport ? 1 : 0);
         result = 31 * result + (mAutoEnteringPip ? 1 : 0);
         return result;
@@ -169,7 +163,6 @@
         return "PauseActivityItem{" + super.toString()
                 + ",finished=" + mFinished
                 + ",userLeaving=" + mUserLeaving
-                + ",configChanges=" + mConfigChanges
                 + ",dontReport=" + mDontReport
                 + ",autoEnteringPip=" + mAutoEnteringPip + "}";
     }
diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java
index b8ce52d..def7b3f 100644
--- a/core/java/android/app/servertransaction/StopActivityItem.java
+++ b/core/java/android/app/servertransaction/StopActivityItem.java
@@ -19,7 +19,6 @@
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.os.IBinder;
@@ -34,13 +33,11 @@
 
     private static final String TAG = "StopActivityItem";
 
-    private int mConfigChanges;
-
     @Override
     public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
             @NonNull PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
-        client.handleStopActivity(r, mConfigChanges, pendingActions,
+        client.handleStopActivity(r, pendingActions,
                 true /* finalStateRequest */, "STOP_ACTIVITY_ITEM");
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
@@ -63,16 +60,14 @@
     /**
      * Obtain an instance initialized with provided params.
      * @param activityToken the activity that stops.
-     * @param configChanges Configuration pieces that changed.
      */
     @NonNull
-    public static StopActivityItem obtain(@NonNull IBinder activityToken, int configChanges) {
+    public static StopActivityItem obtain(@NonNull IBinder activityToken) {
         StopActivityItem instance = ObjectPool.obtain(StopActivityItem.class);
         if (instance == null) {
             instance = new StopActivityItem();
         }
         instance.setActivityToken(activityToken);
-        instance.mConfigChanges = configChanges;
 
         return instance;
     }
@@ -80,23 +75,14 @@
     @Override
     public void recycle() {
         super.recycle();
-        mConfigChanges = 0;
         ObjectPool.recycle(this);
     }
 
     // Parcelable implementation
 
-    /** Write to Parcel. */
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        super.writeToParcel(dest, flags);
-        dest.writeInt(mConfigChanges);
-    }
-
     /** Read from Parcel. */
     private StopActivityItem(@NonNull Parcel in) {
         super(in);
-        mConfigChanges = in.readInt();
     }
 
     public static final @NonNull Creator<StopActivityItem> CREATOR = new Creator<>() {
@@ -110,28 +96,7 @@
     };
 
     @Override
-    public boolean equals(@Nullable Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (!super.equals(o)) {
-            return false;
-        }
-        final StopActivityItem other = (StopActivityItem) o;
-        return mConfigChanges == other.mConfigChanges;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = 17;
-        result = 31 * result + super.hashCode();
-        result = 31 * result + mConfigChanges;
-        return result;
-    }
-
-    @Override
     public String toString() {
-        return "StopActivityItem{" + super.toString()
-                + ",configChanges=" + mConfigChanges + "}";
+        return "StopActivityItem{" + super.toString() + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index fa73c99..c837191 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -334,18 +334,18 @@
                     break;
                 case ON_PAUSE:
                     mTransactionHandler.handlePauseActivity(r, false /* finished */,
-                            false /* userLeaving */, 0 /* configChanges */,
+                            false /* userLeaving */,
                             false /* autoEnteringPip */, mPendingActions,
                             "LIFECYCLER_PAUSE_ACTIVITY");
                     break;
                 case ON_STOP:
-                    mTransactionHandler.handleStopActivity(r, 0 /* configChanges */,
+                    mTransactionHandler.handleStopActivity(r,
                             mPendingActions, false /* finalStateRequest */,
                             "LIFECYCLER_STOP_ACTIVITY");
                     break;
                 case ON_DESTROY:
                     mTransactionHandler.handleDestroyActivity(r, false /* finishing */,
-                            0 /* configChanges */, false /* getNonConfigInstance */,
+                            false /* getNonConfigInstance */,
                             "performLifecycleSequence. cycling to:" + path.get(size - 1));
                     break;
                 case ON_RESTART:
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index 475c6fb..710261a 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -200,7 +200,7 @@
                 lifecycleItem = PauseActivityItem.obtain(r.token);
                 break;
             case ON_STOP:
-                lifecycleItem = StopActivityItem.obtain(r.token, 0 /* configChanges */);
+                lifecycleItem = StopActivityItem.obtain(r.token);
                 break;
             default:
                 lifecycleItem = ResumeActivityItem.obtain(r.token, false /* isForward */,
diff --git a/core/java/android/app/usage/OWNERS b/core/java/android/app/usage/OWNERS
index a4bf985..57d958f 100644
--- a/core/java/android/app/usage/OWNERS
+++ b/core/java/android/app/usage/OWNERS
@@ -3,6 +3,7 @@
 yamasani@google.com
 mwachens@google.com
 varunshah@google.com
+guanxin@google.com
 
 per-file *StorageStats* = file:/core/java/android/os/storage/OWNERS
 per-file *Broadcast* = sudheersai@google.com
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index eb82e1f..2c0e035 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -822,7 +822,18 @@
      *
      * @param appWidgetIds  The AppWidget instances to notify of view data changes.
      * @param viewId        The collection view id.
+     * @deprecated The corresponding API
+     * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been
+     * deprecated. Moving forward please use
+     * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)}
+     * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote
+     * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)},
+     * {@link #updateAppWidget(int, RemoteViews)},
+     * {@link #updateAppWidget(ComponentName, RemoteViews)},
+     * {@link #partiallyUpdateAppWidget(int[], RemoteViews)},
+     * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable.
      */
+    @Deprecated
     public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) {
         if (mService == null) {
             return;
@@ -873,7 +884,18 @@
      *
      * @param appWidgetId  The AppWidget instance to notify of view data changes.
      * @param viewId       The collection view id.
+     * @deprecated The corresponding API
+     * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been
+     * deprecated. Moving forward please use
+     * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)}
+     * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote
+     * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)},
+     * {@link #updateAppWidget(int, RemoteViews)},
+     * {@link #updateAppWidget(ComponentName, RemoteViews)},
+     * {@link #partiallyUpdateAppWidget(int[], RemoteViews)},
+     * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable.
      */
+    @Deprecated
     public void notifyAppWidgetViewDataChanged(int appWidgetId, int viewId) {
         if (mService == null) {
             return;
@@ -1417,13 +1439,15 @@
      * @see AppWidgetProviderInfo#WIDGET_CATEGORY_HOME_SCREEN
      * @see AppWidgetProviderInfo#WIDGET_CATEGORY_KEYGUARD
      * @see AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX
+     *
+     * @return true if the call was successful, false if it was rate-limited.
      */
     @FlaggedApi(Flags.FLAG_GENERATED_PREVIEWS)
-    public void setWidgetPreview(@NonNull ComponentName provider,
+    public boolean setWidgetPreview(@NonNull ComponentName provider,
             @AppWidgetProviderInfo.CategoryFlags int widgetCategories,
             @NonNull RemoteViews preview) {
         try {
-            mService.setWidgetPreview(provider, widgetCategories, preview);
+            return mService.setWidgetPreview(provider, widgetCategories, preview);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index 39f6de7..00d5343 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -16,6 +16,9 @@
 
 package android.companion.virtual;
 
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -352,12 +355,20 @@
             @Nullable Executor executor,
             @Nullable VirtualAudioDevice.AudioConfigurationChangeCallback callback) {
         if (mVirtualAudioDevice == null) {
-            Context context = mContext;
-            if (Flags.deviceAwareRecordAudioPermission()) {
-                context = mContext.createDeviceContext(getDeviceId());
+            try {
+                Context context = mContext;
+                if (Flags.deviceAwareRecordAudioPermission()) {
+                    // When using a default policy for audio device-aware RECORD_AUDIO permission
+                    // should not take effect, thus register policies with the default context.
+                    if (mVirtualDevice.getDevicePolicy(POLICY_TYPE_AUDIO) == DEVICE_POLICY_CUSTOM) {
+                        context = mContext.createDeviceContext(getDeviceId());
+                    }
+                }
+                mVirtualAudioDevice = new VirtualAudioDevice(context, mVirtualDevice, display,
+                        executor, callback, () -> mVirtualAudioDevice = null);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
             }
-            mVirtualAudioDevice = new VirtualAudioDevice(context, mVirtualDevice, display,
-                    executor, callback, () -> mVirtualAudioDevice = null);
         }
         return mVirtualAudioDevice;
     }
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index 728c350..b421339 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -169,6 +169,8 @@
  */
 @android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ClipData implements Parcelable {
+    private static final String TAG = "ClipData";
+
     static final String[] MIMETYPES_TEXT_PLAIN = new String[] {
         ClipDescription.MIMETYPE_TEXT_PLAIN };
     static final String[] MIMETYPES_TEXT_HTML = new String[] {
@@ -476,7 +478,6 @@
          * @return Returns the item's textual representation.
          */
 //BEGIN_INCLUDE(coerceToText)
-        @android.ravenwood.annotation.RavenwoodThrow
         public CharSequence coerceToText(Context context) {
             // If this Item has an explicit textual value, simply return that.
             CharSequence text = getText();
@@ -484,13 +485,20 @@
                 return text;
             }
 
+            // Gracefully handle cases where resolver isn't available
+            ContentResolver resolver = null;
+            try {
+                resolver = context.getContentResolver();
+            } catch (Exception e) {
+                Log.w(TAG, "Failed to obtain ContentResolver: " + e);
+            }
+
             // If this Item has a URI value, try using that.
             Uri uri = getUri();
-            if (uri != null) {
+            if (uri != null && resolver != null) {
                 // First see if the URI can be opened as a plain text stream
                 // (of any sub-type).  If so, this is the best textual
                 // representation for it.
-                final ContentResolver resolver = context.getContentResolver();
                 AssetFileDescriptor descr = null;
                 FileInputStream stream = null;
                 InputStreamReader reader = null;
@@ -499,7 +507,7 @@
                         // Ask for a stream of the desired type.
                         descr = resolver.openTypedAssetFileDescriptor(uri, "text/*", null);
                     } catch (SecurityException e) {
-                        Log.w("ClipData", "Failure opening stream", e);
+                        Log.w(TAG, "Failure opening stream", e);
                     } catch (FileNotFoundException|RuntimeException e) {
                         // Unable to open content URI as text...  not really an
                         // error, just something to ignore.
@@ -519,7 +527,7 @@
                             return builder.toString();
                         } catch (IOException e) {
                             // Something bad has happened.
-                            Log.w("ClipData", "Failure loading text", e);
+                            Log.w(TAG, "Failure loading text", e);
                             return e.toString();
                         }
                     }
@@ -528,7 +536,8 @@
                     IoUtils.closeQuietly(stream);
                     IoUtils.closeQuietly(reader);
                 }
-
+            }
+            if (uri != null) {
                 // If we couldn't open the URI as a stream, use the URI itself as a textual
                 // representation (but not for "content", "android.resource" or "file" schemes).
                 final String scheme = uri.getScheme();
@@ -704,7 +713,7 @@
                         }
 
                     } catch (SecurityException e) {
-                        Log.w("ClipData", "Failure opening stream", e);
+                        Log.w(TAG, "Failure opening stream", e);
 
                     } catch (FileNotFoundException e) {
                         // Unable to open content URI as text...  not really an
@@ -712,7 +721,7 @@
 
                     } catch (IOException e) {
                         // Something bad has happened.
-                        Log.w("ClipData", "Failure loading text", e);
+                        Log.w(TAG, "Failure loading text", e);
                         return Html.escapeHtml(e.toString());
 
                     } finally {
@@ -1123,7 +1132,7 @@
      *
      * @hide
      */
-    @android.ravenwood.annotation.RavenwoodThrow
+    @android.ravenwood.annotation.RavenwoodKeep
     public void prepareToLeaveProcess(boolean leavingPackage) {
         // Assume that callers are going to be granting permissions
         prepareToLeaveProcess(leavingPackage, Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -1134,7 +1143,7 @@
      *
      * @hide
      */
-    @android.ravenwood.annotation.RavenwoodThrow
+    @android.ravenwood.annotation.RavenwoodReplace
     public void prepareToLeaveProcess(boolean leavingPackage, int intentFlags) {
         final int size = mItems.size();
         for (int i = 0; i < size; i++) {
@@ -1154,6 +1163,11 @@
         }
     }
 
+    /** @hide */
+    public void prepareToLeaveProcess$ravenwood(boolean leavingPackage, int intentFlags) {
+        // No process boundaries on Ravenwood; ignored
+    }
+
     /** {@hide} */
     @android.ravenwood.annotation.RavenwoodThrow
     public void prepareToEnterProcess(AttributionSource source) {
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index 107f107..2fabcba 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -50,6 +50,7 @@
  * </div>
  */
 @SystemService(Context.CLIPBOARD_SERVICE)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ClipboardManager extends android.text.ClipboardManager {
 
     /**
@@ -143,6 +144,7 @@
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION)
+    @android.ravenwood.annotation.RavenwoodThrow
     public boolean areClipboardAccessNotificationsEnabled() {
         try {
             return mService.areClipboardAccessNotificationsEnabledForUser(mContext.getUserId());
@@ -159,6 +161,7 @@
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION)
+    @android.ravenwood.annotation.RavenwoodThrow
     public void setClipboardAccessNotificationsEnabled(boolean enable) {
         try {
             mService.setClipboardAccessNotificationsEnabledForUser(enable, mContext.getUserId());
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index f77ebc1..7f2ec53 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -83,6 +83,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
+import android.provider.E2eeContactKeysManager;
 import android.provider.MediaStore;
 import android.telephony.TelephonyRegistryManager;
 import android.util.AttributeSet;
@@ -4815,9 +4816,7 @@
      * @see android.net.thread.ThreadNetworkManager
      * @hide
      */
-    // TODO (b/325886480): update the flag to
-    // "com.android.net.thread.platform.flags.Flags.FLAG_THREAD_ENABLED_PLATFORM"
-    @FlaggedApi("com.android.net.thread.flags.thread_enabled_platform")
+    @FlaggedApi(com.android.net.thread.platform.flags.Flags.FLAG_THREAD_ENABLED_PLATFORM)
     @SystemApi
     public static final String THREAD_NETWORK_SERVICE = "thread_network";
 
@@ -5068,6 +5067,7 @@
      * {@link android.hardware.fingerprint.FingerprintManager} for handling management
      * of fingerprints.
      *
+     * @removed See {@link android.hardware.biometrics.BiometricPrompt}
      * @see #getSystemService(String)
      * @see android.hardware.fingerprint.FingerprintManager
      */
@@ -5082,8 +5082,6 @@
      * @see #getSystemService
      * @see android.hardware.face.FaceManager
      */
-    @FlaggedApi(android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION)
-    @SystemApi
     public static final String FACE_SERVICE = "face";
 
     /**
@@ -6566,10 +6564,10 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
-     * {@link android.provider.ContactKeysManager} to managing contact keys.
+     * {@link E2eeContactKeysManager} to managing contact keys.
      *
      * @see #getSystemService(String)
-     * @see android.provider.ContactKeysManager
+     * @see E2eeContactKeysManager
      */
     @FlaggedApi(android.provider.Flags.FLAG_USER_KEYS)
     public static final String CONTACT_KEYS_SERVICE = "contact_keys";
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index fd2af99..443aadd 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1064,7 +1064,11 @@
         }
 
         if (sender != null) {
-            intent.putExtra(EXTRA_CHOOSER_RESULT_INTENT_SENDER, sender);
+            if (android.service.chooser.Flags.enableChooserResult()) {
+                intent.putExtra(EXTRA_CHOOSER_RESULT_INTENT_SENDER, sender);
+            } else {
+                intent.putExtra(EXTRA_CHOSEN_COMPONENT_INTENT_SENDER, sender);
+            }
         }
 
         // Migrate any clip data and flags from target.
@@ -8179,7 +8183,7 @@
 
                 // launch flags
                 else if (uri.startsWith("launchFlags=", i)) {
-                    intent.mFlags = Integer.decode(value);
+                    intent.mFlags = decodeInteger(value);
                     if ((flags& URI_ALLOW_UNSAFE) == 0) {
                         intent.mFlags &= ~IMMUTABLE_FLAGS;
                     }
@@ -8187,7 +8191,7 @@
 
                 // extended flags
                 else if (uri.startsWith("extendedLaunchFlags=", i)) {
-                    intent.mExtendedFlags = Integer.decode(value);
+                    intent.mExtendedFlags = decodeInteger(value);
                 }
 
                 // package
@@ -8397,7 +8401,7 @@
                 isIntentFragment = true;
                 i += 12;
                 int j = uri.indexOf(')', i);
-                intent.mFlags = Integer.decode(uri.substring(i, j));
+                intent.mFlags = decodeInteger(uri.substring(i, j));
                 if ((flags& URI_ALLOW_UNSAFE) == 0) {
                     intent.mFlags &= ~IMMUTABLE_FLAGS;
                 }
@@ -8508,6 +8512,23 @@
         return intent;
     }
 
+    private static Integer decodeInteger(String value) {
+        try {
+            return Integer.decode(value);
+        } catch (NumberFormatException e) {
+            try {
+                if (value != null && value.startsWith("0x")) {
+                    // In toUriInner, we do "0x".append(Integer.toHexString).
+                    // Sometimes "decode" fails to parse, e.g. 0x90000000.
+                    return Integer.parseUnsignedInt(value.substring(2), 16);
+                }
+            } catch (NumberFormatException ignored) {
+                // ignored, throw the original exception
+            }
+            throw e;
+        }
+    }
+
     /** @hide */
     public interface CommandOptionHandler {
         boolean handleOption(String opt, ShellCommand cmd);
@@ -8573,7 +8594,7 @@
                 case "--ei": {
                     String key = cmd.getNextArgRequired();
                     String value = cmd.getNextArgRequired();
-                    intent.putExtra(key, Integer.decode(value));
+                    intent.putExtra(key, decodeInteger(value));
                 }
                 break;
                 case "--eu": {
@@ -8597,7 +8618,7 @@
                     String[] strings = value.split(",");
                     int[] list = new int[strings.length];
                     for (int i = 0; i < strings.length; i++) {
-                        list[i] = Integer.decode(strings[i]);
+                        list[i] = decodeInteger(strings[i]);
                     }
                     intent.putExtra(key, list);
                 }
@@ -8608,7 +8629,7 @@
                     String[] strings = value.split(",");
                     ArrayList<Integer> list = new ArrayList<>(strings.length);
                     for (int i = 0; i < strings.length; i++) {
-                        list.add(Integer.decode(strings[i]));
+                        list.add(decodeInteger(strings[i]));
                     }
                     intent.putExtra(key, list);
                 }
@@ -8743,7 +8764,7 @@
                         arg = false;
                     } else {
                         try {
-                            arg = Integer.decode(value) != 0;
+                            arg = decodeInteger(value) != 0;
                         } catch (NumberFormatException ex) {
                             throw new IllegalArgumentException("Invalid boolean value: " + value);
                         }
@@ -8773,7 +8794,7 @@
                 break;
                 case "-f":
                     String str = cmd.getNextArgRequired();
-                    intent.setFlags(Integer.decode(str).intValue());
+                    intent.setFlags(decodeInteger(str).intValue());
                     break;
                 case "--grant-read-uri-permission":
                     intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index a64ee5b..8852705 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1519,6 +1519,16 @@
     private static final long CHECK_MIN_WIDTH_HEIGHT_FOR_MULTI_WINDOW = 197654537L;
 
     /**
+     * The activity is targeting a SDK version that should receive the changed behavior of
+     * configuration insets decouple.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public static final long INSETS_DECOUPLED_CONFIGURATION_ENFORCED = 151861875L;
+
+    /**
      * Optional set of a certificates identifying apps that are allowed to embed this activity. From
      * the "knownActivityEmbeddingCerts" attribute.
      */
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index cec49c7..55957bf 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -67,6 +67,7 @@
     List<String> getPreInstalledSystemPackages(in UserHandle user);
     IntentSender getAppMarketActivityIntent(String callingPackage, String packageName,
             in UserHandle user);
+    IntentSender getPrivateSpaceSettingsIntent();
     void showAppDetailsAsUser(in IApplicationThread caller, String callingPackage,
             String callingFeatureId, in ComponentName component, in Rect sourceBounds,
             in Bundle opts, in UserHandle user);
@@ -132,4 +133,7 @@
     void setArchiveCompatibilityOptions(boolean enableIconOverlay, boolean enableUnarchivalConfirmation);
 
     List<UserHandle> getUserProfiles();
+
+    /** Saves view capture data to the wm trace directory. */
+    void saveViewCaptureData();
 }
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index e437925..3a5383d 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -697,8 +697,9 @@
     public List<UserHandle> getProfiles() {
         if (mUserManager.isManagedProfile()
                 || (android.multiuser.Flags.enableLauncherAppsHiddenProfileChecks()
-                        && android.os.Flags.allowPrivateProfile()
-                        && mUserManager.isPrivateProfile())) {
+                    && android.os.Flags.allowPrivateProfile()
+                    && android.multiuser.Flags.enablePrivateSpaceFeatures()
+                    && mUserManager.isPrivateProfile())) {
             // If it's a managed or private profile, only return the current profile.
             final List result = new ArrayList(1);
             result.add(android.os.Process.myUserHandle());
@@ -899,6 +900,28 @@
     }
 
     /**
+     * Returns {@link IntentSender} which can be used to start the Private Space Settings Activity.
+     *
+     * <p> Caller should have {@link android.app.role.RoleManager.ROLE_HOME} and either of the
+     * permissions required.</p>
+     *
+     * @return {@link IntentSender} object which launches the Private Space Settings Activity, if
+     * successful, null otherwise.
+     * @hide
+     */
+    @Nullable
+    @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+    @RequiresPermission(conditional = true,
+            anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
+    public IntentSender getPrivateSpaceSettingsIntent() {
+        try {
+            return mService.getPrivateSpaceSettingsIntent();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns the activity info for a given intent and user handle, if it resolves. Otherwise it
      * returns null.
      *
@@ -1305,6 +1328,19 @@
     }
 
     /**
+     * Saves view capture data to the default location.
+     * @hide
+     */
+    @RequiresPermission(READ_FRAME_BUFFER)
+    public void saveViewCaptureData() {
+        try {
+            mService.saveViewCaptureData();
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Unregister a callback, so that it won't be called when LauncherApps dumps.
      * @hide
      */
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 7a015cd..9f2f74b 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3974,9 +3974,7 @@
      * The device is capable of communicating with other devices via
      * <a href="https://www.threadgroup.org">Thread</a> networking protocol.
      */
-    // TODO (b/325886480): update the flag to
-    // "com.android.net.thread.platform.flags.Flags.FLAG_THREAD_ENABLED_PLATFORM"
-    @FlaggedApi("com.android.net.thread.flags.thread_enabled_platform")
+    @FlaggedApi(com.android.net.thread.platform.flags.Flags.FLAG_THREAD_ENABLED_PLATFORM)
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network";
 
@@ -4204,15 +4202,6 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
-     * {@link #hasSystemFeature}: The device supports multiple concurrent IME sessions.
-     */
-    @FlaggedApi("android.view.inputmethod.concurrent_input_methods")
-    @SdkConstant(SdkConstantType.FEATURE)
-    public static final String FEATURE_CONCURRENT_INPUT_METHODS =
-            "android.software.concurrent_input_methods";
-
-    /**
-     * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device supports device policy enforcement via device admins.
      */
     @SdkConstant(SdkConstantType.FEATURE)
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index cdda12e..3f941da 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -273,6 +273,10 @@
      * to the <code>retailDemo</code> value of
      * {@link android.R.attr#protectionLevel}.
      *
+     * @deprecated This flag has been replaced by the
+     *             {@link android.R.string#config_defaultRetailDemo retail demo role} and is a
+     *             no-op since {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}.
+     *
      * @hide
      */
     @SystemApi
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 9c6aab4..5b0cee7 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -163,25 +163,12 @@
      * Because of this, developers must make sure to stop the foreground service even if
      * {@link android.app.Service#onTimeout(int, int)} is not called on such versions.
      *
-     * <p>Apps targeting API level {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} and
-     * later should <b>NOT</b> use this type: calling
-     * {@link android.app.Service#startForeground(int, android.app.Notification, int)} with
-     * this type on devices running {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} is
-     * still allowed, but it may throw an {@link android.app.InvalidForegroundServiceTypeException}
-     * in future platform releases.
-     *
-     * <p class="note">
-     * Use the {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean)} API for
-     * user-initiated, network data transfers.
-     *
-     * @deprecated Use {@link android.app.job.JobInfo.Builder} APIs or alternate FGS types
-     * (like {@link #FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING}) applicable to your use-case.
+     * @see android.app.Service#onTimeout(int, int)
      */
     @RequiresPermission(
             value = Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC,
             conditional = true
     )
-    @Deprecated
     public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1 << 0;
 
     /**
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 3e9f260..8a3a3ad 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -112,6 +112,12 @@
     /**
      * Indicates that this user is disabled.
      *
+     * <p> This is currently used to indicate that a Managed Profile, when created via
+     * DevicePolicyManager, has not yet been provisioned; once the DPC provisions it, a DPM call
+     * will manually set it to enabled.
+     *
+     * <p>Users that are slated for deletion are also generally set to disabled.
+     *
      * <p>Note: If an ephemeral user is disabled, it shouldn't be later re-enabled. Ephemeral users
      * are disabled as their removal is in progress to indicate that they shouldn't be re-entered.
      */
@@ -398,6 +404,7 @@
         return UserManager.isUserTypePrivateProfile(userType);
     }
 
+    /** See {@link #FLAG_DISABLED}*/
     @UnsupportedAppUsage
     public boolean isEnabled() {
         return (flags & FLAG_DISABLED) != FLAG_DISABLED;
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 5e9d8f0..92cb9cc 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -46,6 +46,7 @@
 
 flag {
     name: "use_art_service_v2"
+    is_exported: true
     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"
@@ -61,6 +62,7 @@
 
 flag {
     name: "rollback_lifetime"
+    is_exported: true
     namespace: "package_manager_service"
     description: "Feature flag to enable custom rollback lifetime during install."
     bug: "299670324"
@@ -156,6 +158,7 @@
 
 flag {
     name: "recoverability_detection"
+    is_exported: true
     namespace: "package_manager_service"
     description: "Feature flag to enable recoverability detection feature. It includes GMS core rollback and improvements to rescue party."
     bug: "291135724"
@@ -208,6 +211,14 @@
 }
 
 flag {
+    name: "restrict_nonpreloads_system_shareduids"
+    namespace: "package_manager_service"
+    description: "Feature flag to restrict apps from joining system shared uids"
+    bug: "308573169"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "min_target_sdk_24"
     namespace: "responsible_apis"
     description: "Feature flag to bump min target sdk to 24"
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index ac80561..48a7cc9 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -184,3 +184,11 @@
     description: "Enable Private Space telephony and SMS intent redirection to the main user"
     bug: "325576602"
 }
+
+flag {
+    name: "block_private_space_creation"
+    namespace: "profile_experiences"
+    description: "Allow blocking private space creation based on specific conditions"
+    bug: "290333800"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
index 4dcc517..a908456 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -163,14 +163,31 @@
     }
 
     /**
-     * Update the URI relative filter groups for a package. All previously existing groups
-     * will be cleared before the new groups will be applied.
+     * Update the URI relative filter groups for a package. The groups set using this API acts
+     * as an additional filtering layer during intent resolution. It does not replace any
+     * existing groups that have been added to the package's intent filters either using the
+     * {@link android.content.IntentFilter#addUriRelativeFilterGroup(UriRelativeFilterGroup)}
+     * API or defined in the manifest.
+     * <p>
+     * Groups can be indexed to any domain or can be indexed for all subdomains by prefixing the
+     * hostname with a wildcard (i.e. "*.example.com"). Priority will be first given to groups
+     * that are indexed to the specific subdomain of the intent's data URI followed by any groups
+     * indexed to wildcard subdomains. If the subdomain consists of more than one label, priority
+     * will decrease corresponding to the decreasing number of subdomain labels after the wildcard.
+     * For example "a.b.c.d" will match "*.b.c.d" before "*.c.d".
+     * <p>
+     * All previously existing groups set for a domain index using this API will be cleared when
+     * new groups are set.
      *
      * @param packageName The name of the package.
      * @param domainToGroupsMap A map of domains to a list of {@link UriRelativeFilterGroup}s that
      *                         should apply to them. Groups for each domain will replace any groups
-     *                         provided for that domain in a prior call to this method. Groups will
+     *                         provided for that domain in a prior call to this method. To clear
+     *                         existing groups, set the list to null or a empty list. Groups will
      *                         be evaluated in the order they are provided.
+     *
+     * @see UriRelativeFilterGroup
+     * @see android.content.IntentFilter
      * @hide
      */
     @SystemApi
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index d259e97..273e40a 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -471,6 +471,16 @@
         return addAssetPathInternal(path, true /*overlay*/, false /*appAsLib*/);
     }
 
+    /**
+     * @hide
+     */
+    public void addSharedLibraryPaths(@NonNull String[] paths) {
+        final int length = paths.length;
+        for (int i = 0; i < length; i++) {
+            addAssetPathInternal(paths[i], false, true);
+        }
+    }
+
     private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) {
         Objects.requireNonNull(path, "path");
         synchronized (this) {
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 7fba3e8..1f5f88f 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -43,6 +43,7 @@
 import android.annotation.StyleableRes;
 import android.annotation.XmlRes;
 import android.app.Application;
+import android.app.ResourcesManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -2854,6 +2855,11 @@
     @FlaggedApi(android.content.res.Flags.FLAG_REGISTER_RESOURCE_PATHS)
     public static void registerResourcePaths(@NonNull String uniqueId,
             @NonNull ApplicationInfo appInfo) {
-        throw new UnsupportedOperationException("The implementation has not been done yet.");
+        if (Flags.registerResourcePaths()) {
+            ResourcesManager.getInstance().registerResourcePaths(uniqueId, appInfo);
+        } else {
+            throw new UnsupportedOperationException("Flag " + Flags.FLAG_REGISTER_RESOURCE_PATHS
+                    + " is disabled.");
+        }
     }
 }
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 079c2c1..8d045aa 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -29,6 +29,7 @@
 import android.annotation.StyleableRes;
 import android.app.LocaleConfig;
 import android.app.ResourcesManager;
+import android.app.ResourcesManager.SharedLibraryAssets;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.Config;
@@ -47,6 +48,7 @@
 import android.os.LocaleList;
 import android.os.ParcelFileDescriptor;
 import android.os.Trace;
+import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -197,6 +199,14 @@
     public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
             @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
         mAssets = assets;
+        if (Flags.registerResourcePaths()) {
+            ArrayMap<String, SharedLibraryAssets> sharedLibMap =
+                    ResourcesManager.getInstance().getSharedLibAssetsMap();
+            final int size = sharedLibMap.size();
+            for (int i = 0; i < size; i++) {
+                assets.addSharedLibraryPaths(sharedLibMap.valueAt(i).getAllAssetPaths());
+            }
+        }
         mMetrics.setToDefaults();
         mDisplayAdjustments = displayAdjustments;
         mConfiguration.setToDefaults();
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index f660770..7fd0b03 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -38,7 +38,7 @@
     name: "nine_patch_frro"
     namespace: "resource_manager"
     description: "Feature flag for creating an frro from a 9-patch"
-    bug: "309232726"
+    bug: "296324826"
 }
 
 flag {
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 2e63664..3a9a0f91 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -23,6 +23,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -31,6 +32,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.IntentSender;
+import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.CancellationSignal;
@@ -57,6 +59,7 @@
  * to authenticate to the app.
  */
 @SystemService(Context.CREDENTIAL_SERVICE)
+@RequiresFeature(PackageManager.FEATURE_CREDENTIALS)
 public final class CredentialManager {
     private static final String TAG = "CredentialManager";
     private static final Bundle OPTIONS_SENDER_BAL_OPTIN = ActivityOptions.makeBasic()
diff --git a/core/java/android/credentials/GetCandidateCredentialsResponse.java b/core/java/android/credentials/GetCandidateCredentialsResponse.java
index 6e53fd9..3d8ccaa 100644
--- a/core/java/android/credentials/GetCandidateCredentialsResponse.java
+++ b/core/java/android/credentials/GetCandidateCredentialsResponse.java
@@ -18,7 +18,7 @@
 
 import android.annotation.Hide;
 import android.annotation.NonNull;
-import android.app.PendingIntent;
+import android.content.Intent;
 import android.credentials.selection.GetCredentialProviderData;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -39,32 +39,22 @@
     @NonNull
     private final List<GetCredentialProviderData> mCandidateProviderDataList;
 
-    private final PendingIntent mPendingIntent;
+    @NonNull
+    private final Intent mIntent;
 
     /**
      * @hide
      */
     @Hide
     public GetCandidateCredentialsResponse(
-            GetCredentialResponse getCredentialResponse
-    ) {
-        mCandidateProviderDataList = null;
-        mPendingIntent = null;
-    }
-
-    /**
-     * @hide
-     */
-    @Hide
-    public GetCandidateCredentialsResponse(
-            List<GetCredentialProviderData> candidateProviderDataList,
-            PendingIntent pendingIntent
+            @NonNull List<GetCredentialProviderData> candidateProviderDataList,
+            @NonNull Intent intent
     ) {
         Preconditions.checkCollectionNotEmpty(
                 candidateProviderDataList,
                 /*valueName=*/ "candidateProviderDataList");
         mCandidateProviderDataList = new ArrayList<>(candidateProviderDataList);
-        mPendingIntent = pendingIntent;
+        mIntent = intent;
     }
 
     /**
@@ -81,8 +71,9 @@
      *
      * @hide
      */
-    public PendingIntent getPendingIntent() {
-        return mPendingIntent;
+    @NonNull
+    public Intent getIntent() {
+        return mIntent;
     }
 
     protected GetCandidateCredentialsResponse(Parcel in) {
@@ -91,14 +82,13 @@
         mCandidateProviderDataList = candidateProviderDataList;
 
         AnnotationValidations.validate(NonNull.class, null, mCandidateProviderDataList);
-
-        mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
+        mIntent = in.readTypedObject(Intent.CREATOR);
     }
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeTypedList(mCandidateProviderDataList);
-        dest.writeTypedObject(mPendingIntent, flags);
+        dest.writeTypedObject(mIntent, flags);
     }
 
     @Override
diff --git a/core/java/android/credentials/selection/IntentFactory.java b/core/java/android/credentials/selection/IntentFactory.java
index 4b0fa6d..79fba9b 100644
--- a/core/java/android/credentials/selection/IntentFactory.java
+++ b/core/java/android/credentials/selection/IntentFactory.java
@@ -80,17 +80,7 @@
             ArrayList<DisabledProviderData> disabledProviderDataList,
             @NonNull ResultReceiver resultReceiver) {
         Intent intent = new Intent();
-        ComponentName componentName =
-                ComponentName.unflattenFromString(
-                        Resources.getSystem()
-                                .getString(
-                                        com.android.internal.R.string
-                                                .config_credentialManagerDialogComponent));
-        ComponentName oemOverrideComponentName = getOemOverrideComponentName(context);
-        if (oemOverrideComponentName != null) {
-            componentName = oemOverrideComponentName;
-        }
-        intent.setComponent(componentName);
+        setCredentialSelectorUiComponentName(context, intent);
         intent.putParcelableArrayListExtra(
                 ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, disabledProviderDataList);
         intent.putExtra(RequestInfo.EXTRA_REQUEST_INFO, requestInfo);
@@ -100,6 +90,24 @@
         return intent;
     }
 
+    private static void setCredentialSelectorUiComponentName(@NonNull Context context,
+            @NonNull Intent intent) {
+        if (configurableSelectorUiEnabled()) {
+            ComponentName componentName = getOemOverrideComponentName(context);
+            if (componentName == null) {
+                componentName = ComponentName.unflattenFromString(Resources.getSystem().getString(
+                        com.android.internal.R.string
+                                .config_fallbackCredentialManagerDialogComponent));
+            }
+            intent.setComponent(componentName);
+        } else {
+            ComponentName componentName = ComponentName.unflattenFromString(Resources.getSystem()
+                    .getString(com.android.internal.R.string
+                            .config_fallbackCredentialManagerDialogComponent));
+            intent.setComponent(componentName);
+        }
+    }
+
     /**
      * Returns null if there is not an enabled and valid oem override component. It means the
      * default platform UI component name should be used instead.
@@ -107,44 +115,39 @@
     @Nullable
     private static ComponentName getOemOverrideComponentName(@NonNull Context context) {
         ComponentName result = null;
-        if (configurableSelectorUiEnabled()) {
-            if (Resources.getSystem().getBoolean(
-                    com.android.internal.R.bool.config_enableOemCredentialManagerDialogComponent)) {
-                String oemComponentString =
-                        Resources.getSystem()
-                                .getString(
-                                        com.android.internal.R.string
-                                                .config_oemCredentialManagerDialogComponent);
-                if (!TextUtils.isEmpty(oemComponentString)) {
-                    ComponentName oemComponentName = ComponentName.unflattenFromString(
-                            oemComponentString);
-                    if (oemComponentName != null) {
-                        try {
-                            ActivityInfo info = context.getPackageManager().getActivityInfo(
-                                    oemComponentName,
-                                    PackageManager.ComponentInfoFlags.of(
-                                            PackageManager.MATCH_SYSTEM_ONLY));
-                            if (info.enabled && info.exported) {
-                                Slog.i(TAG,
-                                        "Found enabled oem CredMan UI component."
-                                                + oemComponentString);
-                                result = oemComponentName;
-                            } else {
-                                Slog.i(TAG,
-                                        "Found enabled oem CredMan UI component but it was not "
-                                                + "enabled.");
-                            }
-                        } catch (PackageManager.NameNotFoundException e) {
-                            Slog.i(TAG, "Unable to find oem CredMan UI component: "
-                                    + oemComponentString + ".");
-                        }
+        String oemComponentString =
+                Resources.getSystem()
+                        .getString(
+                                com.android.internal.R.string
+                                        .config_oemCredentialManagerDialogComponent);
+        if (!TextUtils.isEmpty(oemComponentString)) {
+            ComponentName oemComponentName = ComponentName.unflattenFromString(
+                    oemComponentString);
+            if (oemComponentName != null) {
+                try {
+                    ActivityInfo info = context.getPackageManager().getActivityInfo(
+                            oemComponentName,
+                            PackageManager.ComponentInfoFlags.of(
+                                    PackageManager.MATCH_SYSTEM_ONLY));
+                    if (info.enabled && info.exported) {
+                        Slog.i(TAG,
+                                "Found enabled oem CredMan UI component."
+                                        + oemComponentString);
+                        result = oemComponentName;
                     } else {
-                        Slog.i(TAG, "Invalid OEM ComponentName format.");
+                        Slog.i(TAG,
+                                "Found enabled oem CredMan UI component but it was not "
+                                        + "enabled.");
                     }
-                } else {
-                    Slog.i(TAG, "Invalid empty OEM component name.");
+                } catch (PackageManager.NameNotFoundException e) {
+                    Slog.i(TAG, "Unable to find oem CredMan UI component: "
+                            + oemComponentString + ".");
                 }
+            } else {
+                Slog.i(TAG, "Invalid OEM ComponentName format.");
             }
+        } else {
+            Slog.i(TAG, "Invalid empty OEM component name.");
         }
         return result;
     }
@@ -186,16 +189,11 @@
      * Creates an Intent that cancels any UI matching the given request token id.
      */
     @NonNull
-    public static Intent createCancelUiIntent(@NonNull IBinder requestToken,
-            boolean shouldShowCancellationUi, @NonNull String appPackageName) {
+    public static Intent createCancelUiIntent(@NonNull Context context,
+            @NonNull IBinder requestToken, boolean shouldShowCancellationUi,
+            @NonNull String appPackageName) {
         Intent intent = new Intent();
-        ComponentName componentName =
-                ComponentName.unflattenFromString(
-                        Resources.getSystem()
-                                .getString(
-                                        com.android.internal.R.string
-                                                .config_credentialManagerDialogComponent));
-        intent.setComponent(componentName);
+        setCredentialSelectorUiComponentName(context, intent);
         intent.putExtra(CancelSelectionRequest.EXTRA_CANCEL_UI_REQUEST,
                 new CancelSelectionRequest(new RequestToken(requestToken), shouldShowCancellationUi,
                         appPackageName));
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index ecffe9e..dfb77c0 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -34,17 +34,22 @@
 import android.util.LruCache;
 import android.util.Pair;
 import android.util.Printer;
+import com.android.internal.util.RingBuffer;
 import dalvik.system.BlockGuard;
 import dalvik.system.CloseGuard;
+
 import java.io.File;
 import java.io.IOException;
 import java.lang.ref.Reference;
 import java.nio.file.FileSystems;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.Date;
+import java.util.Locale;
 import java.util.Map;
 import java.util.function.BinaryOperator;
 import java.util.function.UnaryOperator;
@@ -185,7 +190,7 @@
             SQLiteDatabaseConfiguration configuration,
             int connectionId, boolean primaryConnection) {
         mPool = pool;
-        mRecentOperations = new OperationLog(mPool);
+        mRecentOperations = new OperationLog();
         mConfiguration = new SQLiteDatabaseConfiguration(configuration);
         mConnectionId = connectionId;
         mIsPrimaryConnection = primaryConnection;
@@ -307,6 +312,16 @@
         }
     }
 
+    /** Record the start of a transaction for logging and debugging. */
+    void recordBeginTransaction(String mode) {
+        mRecentOperations.beginTransaction(mode);
+    }
+
+    /** Record the end of a transaction for logging and debugging. */
+    void recordEndTransaction(boolean successful) {
+        mRecentOperations.endTransaction(successful);
+    }
+
     private void setPageSize() {
         if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
             final long newValue = SQLiteGlobal.getDefaultPageSize();
@@ -392,8 +407,6 @@
             return;
         }
 
-        Log.i(TAG, walFile.getAbsolutePath() + " " + size + " bytes: Bigger than "
-                + threshold + "; truncating");
         try {
             executeForString("PRAGMA wal_checkpoint(TRUNCATE)", null, null);
             mConfiguration.shouldTruncateWalFile = false;
@@ -1339,6 +1352,7 @@
         }
         printer.println("  isPrimaryConnection: " + mIsPrimaryConnection);
         printer.println("  onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations);
+        printer.println("  totalLongOperations: " + mRecentOperations.getTotalLongOperations());
 
         mRecentOperations.dump(printer);
 
@@ -1597,51 +1611,39 @@
         }
     }
 
-    private static final class OperationLog {
+    private final class OperationLog {
         private static final int MAX_RECENT_OPERATIONS = 20;
         private static final int COOKIE_GENERATION_SHIFT = 8;
         private static final int COOKIE_INDEX_MASK = 0xff;
 
+        // Operations over 2s are long.  Save the last ten.
+        private static final long LONG_OPERATION_THRESHOLD_MS = 2_000;
+        private static final int MAX_LONG_OPERATIONS = 10;
+
         private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS];
-        private int mIndex;
-        private int mGeneration;
-        private final SQLiteConnectionPool mPool;
+        private int mIndex = -1;
+        private int mGeneration = 0;
+        private final Operation mTransaction = new Operation();
         private long mResultLong = Long.MIN_VALUE;
         private String mResultString;
 
-        OperationLog(SQLiteConnectionPool pool) {
-            mPool = pool;
-        }
+        private final RingBuffer<Operation> mLongOperations =
+                new RingBuffer<>(()->{return new Operation();},
+                        (n) ->{return new Operation[n];},
+                        MAX_LONG_OPERATIONS);
+        private int mTotalLongOperations = 0;
 
         public int beginOperation(String kind, String sql, Object[] bindArgs) {
             mResultLong = Long.MIN_VALUE;
             mResultString = null;
 
             synchronized (mOperations) {
-                final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
-                Operation operation = mOperations[index];
-                if (operation == null) {
-                    operation = new Operation();
-                    mOperations[index] = operation;
-                } else {
-                    operation.mFinished = false;
-                    operation.mException = null;
-                    if (operation.mBindArgs != null) {
-                        operation.mBindArgs.clear();
-                    }
-                }
-                operation.mStartWallTime = System.currentTimeMillis();
-                operation.mStartTime = SystemClock.uptimeMillis();
+                Operation operation = newOperationLocked();
                 operation.mKind = kind;
                 operation.mSql = sql;
-                operation.mPath = mPool.getPath();
-                operation.mResultLong = Long.MIN_VALUE;
-                operation.mResultString = null;
                 if (bindArgs != null) {
                     if (operation.mBindArgs == null) {
                         operation.mBindArgs = new ArrayList<Object>();
-                    } else {
-                        operation.mBindArgs.clear();
                     }
                     for (int i = 0; i < bindArgs.length; i++) {
                         final Object arg = bindArgs[i];
@@ -1653,16 +1655,44 @@
                         }
                     }
                 }
-                operation.mCookie = newOperationCookieLocked(index);
                 if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
                     Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
                             operation.mCookie);
                 }
-                mIndex = index;
                 return operation.mCookie;
             }
         }
 
+        public void beginTransaction(String kind) {
+            synchronized (mOperations) {
+                Operation operation = newOperationLocked();
+                operation.mKind = kind;
+                mTransaction.copyFrom(operation);
+
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
+                    Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
+                            operation.mCookie);
+                }
+            }
+        }
+
+        /**
+         * Fetch a new operation from the ring buffer.  The operation is properly initialized.
+         * This advances mIndex to point to the next element.
+         */
+        private Operation newOperationLocked() {
+            final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
+            Operation operation = mOperations[index];
+            if (operation == null) {
+                mOperations[index] = new Operation();
+                operation = mOperations[index];
+            }
+            operation.start();
+            operation.mCookie = newOperationCookieLocked(index);
+            mIndex = index;
+            return operation;
+        }
+
         public void failOperation(int cookie, Exception ex) {
             synchronized (mOperations) {
                 final Operation operation = getOperationLocked(cookie);
@@ -1686,6 +1716,20 @@
             }
         }
 
+        public boolean endTransaction(boolean success) {
+            synchronized (mOperations) {
+                mTransaction.mResultLong = success ? 1 : 0;
+                final long execTime = finishOperationLocked(mTransaction);
+                final Operation operation = getOperationLocked(mTransaction.mCookie);
+                if (operation != null) {
+                    operation.copyFrom(mTransaction);
+                }
+                mTransaction.setEmpty();
+                return NoPreloadHolder.DEBUG_LOG_SLOW_QUERIES
+                        && SQLiteDebug.shouldLogSlowQuery(execTime);
+            }
+        }
+
         public void logOperation(int cookie, String detail) {
             synchronized (mOperations) {
                 logOperationLocked(cookie, detail);
@@ -1707,9 +1751,7 @@
                     Trace.asyncTraceEnd(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
                             operation.mCookie);
                 }
-                operation.mEndTime = SystemClock.uptimeMillis();
-                operation.mFinished = true;
-                final long execTime = operation.mEndTime - operation.mStartTime;
+                final long execTime = finishOperationLocked(operation);
                 mPool.onStatementExecuted(execTime);
                 return NoPreloadHolder.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery(
                         execTime);
@@ -1734,10 +1776,22 @@
             return generation << COOKIE_GENERATION_SHIFT | index;
         }
 
+        /** Close out the operation and return the elapsed time. */
+        private long finishOperationLocked(Operation operation) {
+            operation.mEndTime = SystemClock.uptimeMillis();
+            operation.mFinished = true;
+            final long elapsed = operation.mEndTime - operation.mStartTime;
+            if (elapsed > LONG_OPERATION_THRESHOLD_MS) {
+                mLongOperations.getNextSlot().copyFrom(operation);
+                mTotalLongOperations++;
+            }
+            return elapsed;
+        }
+
         private Operation getOperationLocked(int cookie) {
             final int index = cookie & COOKIE_INDEX_MASK;
             final Operation operation = mOperations[index];
-            return operation.mCookie == cookie ? operation : null;
+            return (operation != null && operation.mCookie == cookie) ? operation : null;
         }
 
         public String describeCurrentOperation() {
@@ -1752,48 +1806,87 @@
             }
         }
 
-        public void dump(Printer printer) {
+        /**
+         * Dump an Operation if it is not in the recent operations list.  Return 1 if the
+         * operation was dumped and 0 if not.
+         */
+        private int dumpIfNotRecentLocked(Printer pw, Operation op, int counter) {
+            if (op == null || op.isEmpty() || getOperationLocked(op.mCookie) != null) {
+                return 0;
+            }
+            pw.println(op.describe(counter));
+            return 1;
+        }
+
+        private void dumpRecentLocked(Printer printer) {
             synchronized (mOperations) {
                 printer.println("  Most recently executed operations:");
                 int index = mIndex;
-                Operation operation = mOperations[index];
-                if (operation != null) {
-                    // Note: SimpleDateFormat is not thread-safe, cannot be compile-time created,
-                    // and is relatively expensive to create during preloading. This method is only
-                    // used when dumping a connection, which is a rare (mainly error) case.
-                    SimpleDateFormat opDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
-                    int n = 0;
-                    do {
-                        StringBuilder msg = new StringBuilder();
-                        msg.append("    ").append(n).append(": [");
-                        String formattedStartTime = opDF.format(new Date(operation.mStartWallTime));
-                        msg.append(formattedStartTime);
-                        msg.append("] ");
-                        operation.describe(msg, false); // Never dump bingargs in a bugreport
-                        printer.println(msg.toString());
-
-                        if (index > 0) {
-                            index -= 1;
-                        } else {
-                            index = MAX_RECENT_OPERATIONS - 1;
-                        }
-                        n += 1;
-                        operation = mOperations[index];
-                    } while (operation != null && n < MAX_RECENT_OPERATIONS);
-                } else {
+                if (index == 0) {
                     printer.println("    <none>");
+                    return;
                 }
+
+                // Operations are dumped in order of most recent first.
+                int counter = 0;
+                int n = 0;
+                Operation operation = mOperations[index];
+                do {
+                    printer.println(operation.describe(counter));
+
+                    if (index > 0) {
+                        index -= 1;
+                    } else {
+                        index = MAX_RECENT_OPERATIONS - 1;
+                    }
+                    n++;
+                    counter++;
+                    operation = mOperations[index];
+                } while (operation != null && n < MAX_RECENT_OPERATIONS);
+                counter += dumpIfNotRecentLocked(printer, mTransaction, counter);
+            }
+        }
+
+        private void dumpLongLocked(Printer printer) {
+            printer.println("  Operations exceeding " + LONG_OPERATION_THRESHOLD_MS + "ms:");
+            if (mLongOperations.isEmpty()) {
+                printer.println("    <none>");
+                return;
+            }
+            Operation[] longOps = mLongOperations.toArray();
+            for (int i = 0; i < longOps.length; i++) {
+                if (longOps[i] != null) {
+                    printer.println(longOps[i].describe(i));
+                }
+            }
+        }
+
+        public long getTotalLongOperations() {
+            return mTotalLongOperations;
+        }
+
+        public void dump(Printer printer) {
+            synchronized (mOperations) {
+                dumpRecentLocked(printer);
+                dumpLongLocked(printer);
             }
         }
     }
 
-    private static final class Operation {
+    private final class Operation {
         // Trim all SQL statements to 256 characters inside the trace marker.
         // This limit gives plenty of context while leaving space for other
         // entries in the trace buffer (and ensures atrace doesn't truncate the
         // marker for us, potentially losing metadata in the process).
         private static final int MAX_TRACE_METHOD_NAME_LEN = 256;
 
+        // The reserved start time that indicates the Operation is empty.
+        private static final long EMPTY_OPERATION = -1;
+
+        // The formatter for the timestamp.
+        private static final DateTimeFormatter sDateTime =
+                DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS", Locale.US);
+
         public long mStartWallTime; // in System.currentTimeMillis()
         public long mStartTime; // in SystemClock.uptimeMillis();
         public long mEndTime; // in SystemClock.uptimeMillis();
@@ -1803,16 +1896,58 @@
         public boolean mFinished;
         public Exception mException;
         public int mCookie;
-        public String mPath;
         public long mResultLong; // MIN_VALUE means "value not set".
         public String mResultString;
 
+        /** Reset the object to begin a new operation. */
+        void start() {
+            mStartWallTime = System.currentTimeMillis();
+            mStartTime = SystemClock.uptimeMillis();
+            mEndTime = Long.MIN_VALUE;
+            mKind = null;
+            mSql = null;
+            if (mBindArgs != null) mBindArgs.clear();
+            mFinished = false;
+            mException = null;
+            mCookie = -1;
+            mResultLong = Long.MIN_VALUE;
+            mResultString = null;
+        }
+
+        /**
+         * Initialize from the source object.  This is meant to clone the object for use in a
+         * transaction operation.  To that end, the local bind args are set to null.
+         */
+        void copyFrom(Operation r) {
+            mStartWallTime = r.mStartWallTime;
+            mStartTime = r.mStartTime;
+            mEndTime = r.mEndTime;
+            mKind = r.mKind;
+            mSql = r.mSql;
+            mBindArgs = null;
+            mFinished = r.mFinished;
+            mException = r.mException;
+            mCookie = r.mCookie;
+            mResultLong = r.mResultLong;
+            mResultString = r.mResultString;
+        }
+
+        /** Mark the operation empty. */
+        void setEmpty() {
+            mStartWallTime = EMPTY_OPERATION;
+        }
+
+        /** Return true if the operation is empty. */
+        boolean isEmpty() {
+            return mStartWallTime == EMPTY_OPERATION;
+        }
+
         public void describe(StringBuilder msg, boolean allowDetailedLog) {
             msg.append(mKind);
             if (mFinished) {
                 msg.append(" took ").append(mEndTime - mStartTime).append("ms");
             } else {
-                msg.append(" started ").append(System.currentTimeMillis() - mStartWallTime)
+                msg.append(" started ").append(SystemClock.uptimeMillis() - mStartTime)
                         .append("ms ago");
             }
             msg.append(" - ").append(getStatus());
@@ -1841,7 +1976,7 @@
                 }
                 msg.append("]");
             }
-            msg.append(", path=").append(mPath);
+            msg.append(", path=").append(mPool.getPath());
             if (mException != null) {
                 msg.append(", exception=\"").append(mException.getMessage()).append("\"");
             }
@@ -1853,6 +1988,21 @@
             }
         }
 
+        /**
+         * Convert a wall-clock time in milliseconds to logcat format.
+         */
+        private String timeString(long millis) {
+            return sDateTime.withZone(ZoneId.systemDefault()).format(Instant.ofEpochMilli(millis));
+        }
+
+        public String describe(int n) {
+            final StringBuilder msg = new StringBuilder();
+            final String start = timeString(mStartWallTime);
+            msg.append("    ").append(n).append(": [").append(start).append("] ");
+            describe(msg, false); // Never dump bingargs in a bugreport
+            return msg.toString();
+        }
+
         private String getStatus() {
             if (!mFinished) {
                 return "running";
@@ -1866,7 +2016,6 @@
                 return methodName.substring(0, MAX_TRACE_METHOD_NAME_LEN);
             return methodName;
         }
-
     }
 
     /**
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index ad335b6..15d7d66 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -1175,7 +1175,7 @@
                     + ", isLegacyCompatibilityWalEnabled=" + isCompatibilityWalEnabled
                     + ", journalMode=" + TextUtils.emptyIfNull(mConfiguration.resolveJournalMode())
                     + ", syncMode=" + TextUtils.emptyIfNull(mConfiguration.resolveSyncMode()));
-            printer.println("  IsReadOnlyDatabase=" + mConfiguration.isReadOnlyDatabase());
+            printer.println("  IsReadOnlyDatabase: " + mConfiguration.isReadOnlyDatabase());
 
             if (isCompatibilityWalEnabled) {
                 printer.println("  Compatibility WAL enabled: wal_syncmode="
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 5dfeac7..d683d72 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -40,15 +40,12 @@
 import android.os.OperationCanceledException;
 import android.os.SystemProperties;
 import android.text.TextUtils;
-import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Printer;
 
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
 import dalvik.annotation.optimization.NeverCompile;
@@ -106,14 +103,8 @@
     // Stores reference to all databases opened in the current process.
     // (The referent Object is not used at this time.)
     // INVARIANT: Guarded by sActiveDatabases.
-    @GuardedBy("sActiveDatabases")
     private static WeakHashMap<SQLiteDatabase, Object> sActiveDatabases = new WeakHashMap<>();
 
-    // Tracks which database files are currently open.  If a database file is opened more than
-    // once at any given moment, the associated databases are marked as "concurrent".
-    @GuardedBy("sActiveDatabases")
-    private static final OpenTracker sOpenTracker = new OpenTracker();
-
     // Thread-local for database sessions that belong to this database.
     // Each thread has its own database session.
     // INVARIANT: Immutable.
@@ -519,7 +510,6 @@
 
     private void dispose(boolean finalized) {
         final SQLiteConnectionPool pool;
-        final String path;
         synchronized (mLock) {
             if (mCloseGuardLocked != null) {
                 if (finalized) {
@@ -530,12 +520,10 @@
 
             pool = mConnectionPoolLocked;
             mConnectionPoolLocked = null;
-            path = isInMemoryDatabase() ? null : getPath();
         }
 
         if (!finalized) {
             synchronized (sActiveDatabases) {
-                sOpenTracker.close(path);
                 sActiveDatabases.remove(this);
             }
 
@@ -1144,74 +1132,6 @@
         }
     }
 
-    /**
-     * Track the number of times a database file has been opened.  There is a primary connection
-     * associated with every open database, and these can contend with each other, leading to
-     * unexpected SQLiteDatabaseLockedException exceptions.  The tracking here is only advisory:
-     * multiply-opened databases are logged but no other action is taken.
-     *
-     * This class is not thread-safe.
-     */
-    private static class OpenTracker {
-        // The list of currently-open databases.  This maps the database file to the number of
-        // currently-active opens.
-        private final ArrayMap<String, Integer> mOpens = new ArrayMap<>();
-
-        // The maximum number of concurrently open database paths that will be stored.  Once this
-        // many paths have been recorded, further paths are logged but not saved.
-        private static final int MAX_RECORDED_PATHS = 20;
-
-        // The list of databases that were ever concurrently opened.
-        private final ArraySet<String> mConcurrent = new ArraySet<>();
-
-        /** Return the canonical path.  On error, just return the input path. */
-        private static String normalize(String path) {
-            try {
-                return new File(path).toPath().toRealPath().toString();
-            } catch (Exception e) {
-                // If there is an IO or security exception, just continue, using the input path.
-                return path;
-            }
-        }
-
-        /** Return true if the path is currently open in another SQLiteDatabase instance. */
-        void open(@Nullable String path) {
-            if (path == null) return;
-            path = normalize(path);
-
-            Integer count = mOpens.get(path);
-            if (count == null || count == 0) {
-                mOpens.put(path, 1);
-                return;
-            } else {
-                mOpens.put(path, count + 1);
-                if (mConcurrent.size() < MAX_RECORDED_PATHS) {
-                    mConcurrent.add(path);
-                }
-                Log.w(TAG, "multiple primary connections on " + path);
-                return;
-            }
-        }
-
-        void close(@Nullable String path) {
-            if (path == null) return;
-            path = normalize(path);
-            Integer count = mOpens.get(path);
-            if (count == null || count <= 0) {
-                Log.e(TAG, "open database counting failure on " + path);
-            } else if (count == 1) {
-                // Implicitly set the count to zero, and make mOpens smaller.
-                mOpens.remove(path);
-            } else {
-                mOpens.put(path, count - 1);
-            }
-        }
-
-        ArraySet<String> getConcurrentDatabasePaths() {
-            return new ArraySet<>(mConcurrent);
-        }
-    }
-
     private void open() {
         try {
             try {
@@ -1233,17 +1153,14 @@
     }
 
     private void openInner() {
-        final String path;
         synchronized (mLock) {
             assert mConnectionPoolLocked == null;
             mConnectionPoolLocked = SQLiteConnectionPool.open(mConfigurationLocked);
             mCloseGuardLocked.open("close");
-            path = isInMemoryDatabase() ? null : getPath();
         }
 
         synchronized (sActiveDatabases) {
             sActiveDatabases.put(this, null);
-            sOpenTracker.open(path);
         }
     }
 
@@ -2428,17 +2345,6 @@
     }
 
     /**
-     * Return list of databases that have been concurrently opened.
-     * @hide
-     */
-    @VisibleForTesting
-    public static ArraySet<String> getConcurrentDatabasePaths() {
-        synchronized (sActiveDatabases) {
-            return sOpenTracker.getConcurrentDatabasePaths();
-        }
-    }
-
-    /**
      * Returns true if the new version code is greater than the current database version.
      *
      * @param newVersion The new version code.
@@ -2860,19 +2766,6 @@
                 dumpDatabaseDirectory(printer, new File(dir), isSystem);
             }
         }
-
-        // Dump concurrently-opened database files, if any
-        final ArraySet<String> concurrent;
-        synchronized (sActiveDatabases) {
-            concurrent = sOpenTracker.getConcurrentDatabasePaths();
-        }
-        if (concurrent.size() > 0) {
-            printer.println("");
-            printer.println("Concurrently opened database files");
-            for (String f : concurrent) {
-                printer.println("  " + f);
-            }
-        }
     }
 
     private static void dumpDatabaseDirectory(Printer pw, File dir, boolean isSystem) {
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
index 5e523c0..78c8954 100644
--- a/core/java/android/database/sqlite/SQLiteOpenHelper.java
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -377,8 +377,7 @@
                     if (writable) {
                         throw ex;
                     }
-                    Log.e(TAG, "Couldn't open " + mName
-                            + " for writing (will try read-only):", ex);
+                    Log.e(TAG, "Couldn't open database for writing (will try read-only):", ex);
                     params = params.toBuilder().addOpenFlags(SQLiteDatabase.OPEN_READONLY).build();
                     db = SQLiteDatabase.openDatabase(filePath, params);
                 }
@@ -425,11 +424,6 @@
             }
 
             onOpen(db);
-
-            if (db.isReadOnly()) {
-                Log.w(TAG, "Opened " + mName + " in read-only mode");
-            }
-
             mDatabase = db;
             return db;
         } finally {
diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java
index 7d9f02d..3b14d9d 100644
--- a/core/java/android/database/sqlite/SQLiteSession.java
+++ b/core/java/android/database/sqlite/SQLiteSession.java
@@ -312,6 +312,15 @@
                 cancellationSignal);
     }
 
+    private String modeString(int transactionMode) {
+        switch (transactionMode) {
+            case TRANSACTION_MODE_IMMEDIATE: return "TRANSACTION-IMMEDIATE";
+            case TRANSACTION_MODE_EXCLUSIVE: return "TRANSACTION-EXCLUSIVE";
+            case TRANSACTION_MODE_DEFERRED:  return "TRANSACTION-DEFERRED";
+            default: return "TRANSACTION";
+        }
+    }
+
     private void beginTransactionUnchecked(int transactionMode,
             SQLiteTransactionListener transactionListener, int connectionFlags,
             CancellationSignal cancellationSignal) {
@@ -321,6 +330,7 @@
 
         if (mTransactionStack == null) {
             acquireConnection(null, connectionFlags, cancellationSignal); // might throw
+            mConnection.recordBeginTransaction(modeString(transactionMode));
         }
         try {
             // Set up the transaction such that we can back out safely
@@ -465,6 +475,7 @@
                     mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw
                 }
             } finally {
+                mConnection.recordEndTransaction(successful);
                 releaseConnection(); // might throw
             }
         }
diff --git a/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl b/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
deleted file mode 100644
index 838e41e..0000000
--- a/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware;
-
-/** @hide */
-parcelable CameraPrivacyAllowlistEntry {
-    String packageName;
-    boolean isMandatory;
-}
-
diff --git a/core/java/android/hardware/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl
index 851ce2a..19d1029 100644
--- a/core/java/android/hardware/ISensorPrivacyManager.aidl
+++ b/core/java/android/hardware/ISensorPrivacyManager.aidl
@@ -16,7 +16,6 @@
 
 package android.hardware;
 
-import android.hardware.CameraPrivacyAllowlistEntry;
 import android.hardware.ISensorPrivacyListener;
 
 /** @hide */
@@ -48,7 +47,7 @@
     void setToggleSensorPrivacyForProfileGroup(int userId, int source, int sensor, boolean enable);
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)")
-    List<CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist();
+    List<String> getCameraPrivacyAllowlist();
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)")
     int getToggleSensorPrivacyState(int toggleType, int sensor);
@@ -62,6 +61,10 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)")
     boolean isCameraPrivacyEnabled(String packageName);
 
+    /** @hide */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY)")
+    void setCameraPrivacyAllowlist(in List<String> allowlist);
+
     // =============== End of transactions used on native side as well ============================
 
     void suppressToggleSensorPrivacyReminders(int userId, int sensor, IBinder token,
diff --git a/core/java/android/hardware/OverlayProperties.java b/core/java/android/hardware/OverlayProperties.java
index 88089ee..4a4d451 100644
--- a/core/java/android/hardware/OverlayProperties.java
+++ b/core/java/android/hardware/OverlayProperties.java
@@ -70,19 +70,6 @@
     }
 
     /**
-     * @return True if the device can support fp16, false otherwise.
-     * TODO: Move this to isCombinationSupported once the flag flips
-     * @hide
-     */
-    public boolean isFp16SupportedForHdr() {
-        if (mNativeObject == 0) {
-            return false;
-        }
-        return nIsCombinationSupported(
-                mNativeObject, DataSpace.DATASPACE_SCRGB, HardwareBuffer.RGBA_FP16);
-    }
-
-    /**
      * Indicates that hardware composition of a buffer encoded with the provided {@link DataSpace}
      * and {@link HardwareBuffer.Format} is supported on the device.
      *
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index 6294a8d..4cdaaddd 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -43,7 +43,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Map;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -204,6 +204,8 @@
      * Types of state which can exist for the sensor privacy toggle
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
     public static class StateTypes {
         private StateTypes() {}
 
@@ -217,30 +219,12 @@
          */
         public static final int DISABLED = SensorPrivacyIndividualEnabledSensorProto.DISABLED;
 
-        /**
-         * Constant indicating privacy is enabled except for the automotive driver assistance apps
-         * which are helpful for driving.
-         */
-        @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
-        public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS =
-                SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_HELPFUL_APPS;
-
          /**
          * Constant indicating privacy is enabled except for the automotive driver assistance apps
          * which are required by car manufacturer for driving.
          */
-        @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
-        public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS =
-                SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_REQUIRED_APPS;
-
-        /**
-         * Constant indicating privacy is enabled except for the automotive driver assistance apps
-         * which are both helpful for driving and also apps required by car manufacturer for
-         * driving.
-         */
-        @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
-        public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_APPS =
-                SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_APPS;
+        public static final int ENABLED_EXCEPT_ALLOWLISTED_APPS =
+                SensorPrivacyIndividualEnabledSensorProto.ENABLED_EXCEPT_ALLOWLISTED_APPS;
 
         /**
          * Types of state which can exist for a sensor privacy toggle
@@ -250,9 +234,7 @@
         @IntDef(value = {
                 ENABLED,
                 DISABLED,
-                AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS,
-                AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS,
-                AUTOMOTIVE_DRIVER_ASSISTANCE_APPS
+                ENABLED_EXCEPT_ALLOWLISTED_APPS
         })
         @Retention(RetentionPolicy.SOURCE)
         public @interface StateType {}
@@ -369,9 +351,6 @@
     private final ArrayMap<Pair<Integer, OnSensorPrivacyChangedListener>,
             OnSensorPrivacyChangedListener> mLegacyToggleListeners = new ArrayMap<>();
 
-    @GuardedBy("mLock")
-    private ArrayMap<String, Boolean> mCameraPrivacyAllowlist = null;
-
     /** The singleton ISensorPrivacyListener for IPC which will be used to dispatch to local
      * listeners */
     @NonNull
@@ -397,7 +376,8 @@
 
         @Override
         @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
-        public void onSensorPrivacyStateChanged(int toggleType, int sensor, int state) {
+        public void onSensorPrivacyStateChanged(@ToggleType int toggleType,
+                @Sensors.Sensor int sensor, @StateTypes.StateType int state) {
             synchronized (mLock) {
                 for (int i = 0; i < mToggleListeners.size(); i++) {
                     OnSensorPrivacyChangedListener listener = mToggleListeners.keyAt(i);
@@ -725,6 +705,8 @@
     /**
      * Returns sensor privacy state for a specific sensor.
      *
+     * @param toggleType The type of toggle to use
+     * @param sensor The sensor to check
      * @return int sensor privacy state.
      *
      * @hide
@@ -741,10 +723,11 @@
         }
     }
 
-  /**
+   /**
      * Returns if camera privacy is enabled for a specific package.
      *
-     * @return boolean sensor privacy state.
+     * @param packageName The package to check
+     * @return boolean camera privacy state.
      *
      * @hide
      */
@@ -763,29 +746,41 @@
      * Returns camera privacy allowlist.
      *
      * @return List of automotive driver assistance packages for
-     * privacy allowlisting. The returned map includes the package
-     * name as key and the value is a Boolean which tells if that package
-     * is required by the car manufacturer as mandatory package for driving.
+     * privacy allowlisting.
      *
      * @hide
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
     @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
-    public @NonNull Map<String, Boolean>  getCameraPrivacyAllowlist() {
+    public @NonNull List<String>  getCameraPrivacyAllowlist() {
         synchronized (mLock) {
-            if (mCameraPrivacyAllowlist == null) {
-                mCameraPrivacyAllowlist = new ArrayMap<>();
-                try {
-                    for (CameraPrivacyAllowlistEntry entry :
-                            mService.getCameraPrivacyAllowlist()) {
-                        mCameraPrivacyAllowlist.put(entry.packageName, entry.isMandatory);
-                    }
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
-                }
+            try {
+                return mService.getCameraPrivacyAllowlist();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
             }
-            return mCameraPrivacyAllowlist;
+        }
+    }
+
+    /**
+     * Sets camera privacy allowlist.
+     *
+     * @param allowlist List of automotive driver assistance packages for
+     * privacy allowlisting.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
+    @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
+    public void setCameraPrivacyAllowlist(@NonNull List<String> allowlist) {
+        synchronized (mLock) {
+            try {
+                mService.setCameraPrivacyAllowlist(allowlist);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
         }
     }
 
@@ -867,6 +862,7 @@
     /**
      * Sets sensor privacy to the specified state for an individual sensor.
      *
+     * @param source the source using which the sensor is toggled
      * @param sensor the sensor which to change the state for
      * @param state the state to which sensor privacy should be set.
      *
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 8165d44..ff07498 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -2,6 +2,7 @@
 
 flag {
     name: "last_authentication_time"
+    is_exported: true
     namespace: "wallet_integration"
     description: "Feature flag for adding getLastAuthenticationTime API to BiometricManager"
     bug: "301979982"
@@ -9,6 +10,7 @@
 
 flag {
   name: "add_key_agreement_crypto_object"
+  is_exported: true
   namespace: "biometrics"
   description: "Feature flag for adding KeyAgreement api to CryptoObject."
   bug: "282058146"
@@ -28,10 +30,3 @@
   bug: "302735104"
 }
 
-flag {
-  name: "face_background_authentication"
-  namespace: "biometrics_framework"
-  description: "Feature flag for allowing face background authentication with USE_BACKGROUND_FACE_AUTHENTICATION."
-  bug: "318584190"
-}
-
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 57b437f..dc8f4b4 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -3521,7 +3521,7 @@
      * <p>When the key is present, only a PRIVATE/YUV output of the specified size is guaranteed
      * to be supported by the camera HAL in the secure camera mode. Any other format or
      * resolutions might not be supported. Use
-     * {@link CameraManager#isSessionConfigurationWithParametersSupported }
+     * {@link CameraDevice#isSessionConfigurationSupported }
      * API to query if a secure session configuration is supported if the device supports this
      * API.</p>
      * <p>If this key returns null on a device with SECURE_IMAGE_DATA capability, the application
@@ -5046,18 +5046,18 @@
 
     /**
      * <p>The version of the session configuration query
-     * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported }
+     * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
      * API</p>
      * <p>The possible values in this key correspond to the values defined in
      * android.os.Build.VERSION_CODES. Each version defines a set of feature combinations the
      * camera device must reliably report whether they are supported via
-     * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported }
+     * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
      * API. And the version is always less or equal to android.os.Build.VERSION.SDK_INT.</p>
      * <p>If set to UPSIDE_DOWN_CAKE, this camera device doesn't support
-     * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported }.
+     * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }.
      * Calling the method for this camera ID throws an UnsupportedOperationException.</p>
      * <p>If set to VANILLA_ICE_CREAM, the application can call
-     * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported }
+     * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
      * to check if the combinations of below features are supported.</p>
      * <ul>
      * <li>A subset of LIMITED-level device stream combinations.</li>
@@ -6082,11 +6082,11 @@
 
     /**
      * <p>Minimum and maximum padding zoom factors supported by this camera device for
-     * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used for the
+     * android.efv.paddingZoomFactor used for the
      * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
      * extension.</p>
      * <p>The minimum and maximum padding zoom factors supported by the device for
-     * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used as part of the
+     * android.efv.paddingZoomFactor used as part of the
      * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
      * extension feature. This extension specific camera characteristic can be queried using
      * {@link android.hardware.camera2.CameraExtensionCharacteristics#get }.</p>
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 749f218..083d49f 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -180,6 +180,16 @@
             EXTENSION_HDR,
             EXTENSION_NIGHT};
 
+    /**
+     * List of synthetic CameraCharacteristics keys that are supported in the extensions.
+     */
+    private static final List<CameraCharacteristics.Key>
+            SUPPORTED_SYNTHETIC_CAMERA_CHARACTERISTICS =
+            Arrays.asList(
+                    CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES,
+                    CameraCharacteristics.REQUEST_AVAILABLE_COLOR_SPACE_PROFILES
+            );
+
     private final Context mContext;
     private final String mCameraId;
     private final Map<String, CameraCharacteristics> mCharacteristicsMap;
@@ -874,11 +884,17 @@
                 Class<CameraCharacteristics.Key<?>> keyTyped =
                         (Class<CameraCharacteristics.Key<?>>) key;
 
-                // Do not include synthetic keys. Including synthetic keys leads to undefined
-                // behavior. This causes inclusion of capabilities that may not be supported in
-                // camera extensions.
                 ret.addAll(chars.getAvailableKeyList(CameraCharacteristics.class, keyTyped, keys,
                         /*includeSynthetic*/ false));
+
+                // Add synthetic keys to the available key list if they are part of the supported
+                // synthetic camera characteristic key list
+                for (CameraCharacteristics.Key charKey :
+                        SUPPORTED_SYNTHETIC_CAMERA_CHARACTERISTICS) {
+                    if (chars.get(charKey) != null) {
+                        ret.add(charKey);
+                    }
+                }
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to query the extension for all available keys! Extension "
@@ -990,6 +1006,7 @@
                     case ImageFormat.YUV_420_888:
                     case ImageFormat.JPEG:
                     case ImageFormat.JPEG_R:
+                    case ImageFormat.YCBCR_P010:
                         break;
                     default:
                         throw new IllegalArgumentException("Unsupported format: " + format);
@@ -1021,8 +1038,9 @@
                     return generateJpegSupportedSizes(
                             extenders.second.getSupportedPostviewResolutions(sz),
                                     streamMap);
-                }  else if (format == ImageFormat.JPEG_R) {
-                    // Jpeg_R/UltraHDR is currently not supported in the basic extension case
+                }  else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
+                    // Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the basic
+                    // extension case
                     return new ArrayList<>();
                 } else {
                     throw new IllegalArgumentException("Unsupported format: " + format);
@@ -1118,16 +1136,16 @@
      *
      * <p>Device-specific extensions currently support at most three
      * multi-frame capture surface formats. ImageFormat.JPEG will be supported by all
-     * extensions while ImageFormat.YUV_420_888 and ImageFormat.JPEG_R may or may not be
-     * supported.</p>
+     * extensions while ImageFormat.YUV_420_888, ImageFormat.JPEG_R, or ImageFormat.YCBCR_P010
+     * may or may not be supported.</p>
      *
      * @param extension the extension type
      * @param format    device-specific extension output format
      * @return non-modifiable list of available sizes or an empty list if the format is not
      * supported.
      * @throws IllegalArgumentException in case of format different from ImageFormat.JPEG,
-     *                                  ImageFormat.YUV_420_888, ImageFormat.JPEG_R; or
-     *                                  unsupported extension.
+     *                                  ImageFormat.YUV_420_888, ImageFormat.JPEG_R,
+     *                                  ImageFormat.YCBCR_P010; or unsupported extension.
      */
     public @NonNull
     List<Size> getExtensionSupportedSizes(@Extension int extension, int format) {
@@ -1151,6 +1169,7 @@
                         case ImageFormat.YUV_420_888:
                         case ImageFormat.JPEG:
                         case ImageFormat.JPEG_R:
+                        case ImageFormat.YCBCR_P010:
                             break;
                         default:
                             throw new IllegalArgumentException("Unsupported format: " + format);
@@ -1183,8 +1202,9 @@
                         } else {
                             return generateSupportedSizes(null, format, streamMap);
                         }
-                    } else if (format == ImageFormat.JPEG_R) {
-                        // Jpeg_R/UltraHDR is currently not supported in the basic extension case
+                    } else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
+                        // Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the
+                        // basic extension case
                         return new ArrayList<>();
                     } else {
                         throw new IllegalArgumentException("Unsupported format: " + format);
@@ -1213,7 +1233,8 @@
      * @return the range of estimated minimal and maximal capture latency in milliseconds
      * or null if no capture latency info can be provided
      * @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG},
-     *                                  {@link ImageFormat#YUV_420_888}, {@link ImageFormat#JPEG_R};
+     *                                  {@link ImageFormat#YUV_420_888}, {@link ImageFormat#JPEG_R}
+     *                                  {@link ImageFormat#YCBCR_P010};
      *                                  or unsupported extension.
      */
     public @Nullable Range<Long> getEstimatedCaptureLatencyRangeMillis(@Extension int extension,
@@ -1222,6 +1243,7 @@
             case ImageFormat.YUV_420_888:
             case ImageFormat.JPEG:
             case ImageFormat.JPEG_R:
+            case ImageFormat.YCBCR_P010:
                 //No op
                 break;
             default:
@@ -1269,8 +1291,8 @@
                     // specific and cannot be estimated accurately enough.
                     return  null;
                 }
-                if (format == ImageFormat.JPEG_R) {
-                    // JpegR/UltraHDR is not supported for basic extensions
+                if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
+                    // JpegR/UltraHDR + YCBCR_P010 is not supported for basic extensions
                     return null;
                 }
 
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 04e3dab..d2e4a61 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -753,25 +753,22 @@
     @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP)
     public CameraDevice.CameraDeviceSetup getCameraDeviceSetup(@NonNull String cameraId)
             throws CameraAccessException {
-        if (cameraId == null) {
-            throw new IllegalArgumentException("cameraId was null");
-        }
-
-        if (CameraManagerGlobal.sCameraServiceDisabled) {
-            throw new CameraAccessException(CameraAccessException.CAMERA_DISABLED,
-                    "No cameras available on device");
-        }
-
-        if (!Arrays.asList(CameraManagerGlobal.get().getCameraIdList()).contains(cameraId)) {
-            throw new IllegalArgumentException(
-                    "Camera ID '" + cameraId + "' not available on device.");
-        }
-
+        // isCameraDeviceSetup does all the error checking we need.
         if (!isCameraDeviceSetupSupported(cameraId)) {
             throw new UnsupportedOperationException(
                     "CameraDeviceSetup is not supported for Camera ID: " + cameraId);
         }
 
+        return getCameraDeviceSetupUnsafe(cameraId);
+
+    }
+
+    /**
+     * Creates and returns a {@link CameraDeviceSetup} instance without any error checking. To
+     * be used (carefully) by callers who are sure that CameraDeviceSetup instance can be legally
+     * created and don't want to pay the latency cost of calling {@link #getCameraDeviceSetup}.
+     */
+    private CameraDevice.CameraDeviceSetup getCameraDeviceSetupUnsafe(@NonNull String cameraId) {
         return new CameraDeviceSetupImpl(cameraId, /*cameraManager=*/ this, mContext);
     }
 
@@ -809,12 +806,8 @@
             throw new IllegalArgumentException("Camera ID was null");
         }
 
-        if (CameraManagerGlobal.sCameraServiceDisabled) {
-            throw new CameraAccessException(CameraAccessException.CAMERA_DISABLED,
-                    "No cameras available on device");
-        }
-
-        if (!Arrays.asList(CameraManagerGlobal.get().getCameraIdList()).contains(cameraId)) {
+        if (CameraManagerGlobal.sCameraServiceDisabled
+                || !Arrays.asList(CameraManagerGlobal.get().getCameraIdList()).contains(cameraId)) {
             throw new IllegalArgumentException(
                     "Camera ID '" + cameraId + "' not available on device.");
         }
@@ -857,8 +850,9 @@
 
             ICameraDeviceUser cameraUser = null;
             CameraDevice.CameraDeviceSetup cameraDeviceSetup = null;
-            if (Flags.cameraDeviceSetup() && isCameraDeviceSetupSupported(cameraId)) {
-                cameraDeviceSetup = getCameraDeviceSetup(cameraId);
+            if (Flags.cameraDeviceSetup()
+                    && CameraDeviceSetupImpl.isCameraDeviceSetupSupported(characteristics)) {
+                cameraDeviceSetup = getCameraDeviceSetupUnsafe(cameraId);
             }
 
             android.hardware.camera2.impl.CameraDeviceImpl deviceImpl =
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index e24c98e..9fb561b 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -911,10 +911,10 @@
      * </ul>
      * <p>Combinations of logical and physical streams, or physical streams from different
      * physical cameras are not guaranteed. However, if the camera device supports
-     * {@link CameraManager#isSessionConfigurationWithParametersSupported },
+     * {@link CameraDevice#isSessionConfigurationSupported },
      * application must be able to query whether a stream combination involving physical
      * streams is supported by calling
-     * {@link CameraManager#isSessionConfigurationWithParametersSupported }.</p>
+     * {@link CameraDevice#isSessionConfigurationSupported }.</p>
      * <p>Camera application shouldn't assume that there are at most 1 rear camera and 1 front
      * camera in the system. For an application that switches between front and back cameras,
      * the recommendation is to switch between the first rear camera and the first front
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 66efccd1..c0db77c 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -309,6 +309,8 @@
 
     private Object mUserTag;
 
+    private boolean mReleaseSurfaces = false;
+
     /**
      * Construct empty request.
      *
@@ -610,6 +612,9 @@
             Parcelable[] parcelableArray = in.readParcelableArray(Surface.class.getClassLoader(),
                     Surface.class);
             if (parcelableArray != null) {
+                if (Flags.surfaceLeakFix()) {
+                    mReleaseSurfaces = true;
+                }
                 for (Parcelable p : parcelableArray) {
                     Surface s = (Surface) p;
                     mSurfaceSet.add(s);
@@ -792,6 +797,17 @@
         }
     }
 
+    @SuppressWarnings("Finalize")
+    @FlaggedApi(Flags.FLAG_SURFACE_LEAK_FIX)
+    @Override
+    protected void finalize() {
+        if (mReleaseSurfaces) {
+            for (Surface s : mSurfaceSet) {
+                s.release();
+            }
+        }
+    }
+
     /**
      * A builder for capture requests.
      *
diff --git a/core/java/android/hardware/camera2/extension/AdvancedExtender.java b/core/java/android/hardware/camera2/extension/AdvancedExtender.java
index 4895f38..8fa09a8 100644
--- a/core/java/android/hardware/camera2/extension/AdvancedExtender.java
+++ b/core/java/android/hardware/camera2/extension/AdvancedExtender.java
@@ -61,7 +61,6 @@
     private CameraUsageTracker mCameraUsageTracker;
     private static final String TAG = "AdvancedExtender";
 
-
     /**
      * Initialize a camera extension advanced extender instance.
      *
@@ -263,6 +262,13 @@
      *
      * <p>For example, an extension may limit the zoom ratio range. In this case, an OEM can return
      * a new zoom ratio range for the key {@link CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE}.
+     *
+     * <p> Currently, the only synthetic keys supported for override are
+     * {@link CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES} and
+     * {@link CameraCharacteristics#REQUEST_AVAILABLE_COLOR_SPACE_PROFILES}. To enable them, an OEM
+     * should override the respective native keys
+     * {@link CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP} and
+     *  {@link CameraCharacteristics#REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP}.
      */
     @FlaggedApi(Flags.FLAG_CAMERA_EXTENSIONS_CHARACTERISTICS_GET)
     @NonNull
diff --git a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
index 509bcb8..5567bed 100644
--- a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
+++ b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
@@ -27,6 +27,7 @@
     int imageFormat;
     int capacity;
     long usage;
+    long dynamicRangeProfile;
 
     const int TYPE_SURFACE = 0;
     const int TYPE_IMAGEREADER = 1;
diff --git a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
index 53f56bc..001b794 100644
--- a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
+++ b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
@@ -133,15 +133,4 @@
             @DynamicRangeProfiles.Profile long dynamicRangeProfile) {
         mOutputSurface.dynamicRangeProfile = dynamicRangeProfile;
     }
-
-    /**
-     * Set the color space. The default colorSpace
-     * will be
-     * {@link android.hardware.camera2.params.ColorSpaceProfiles.UNSPECIFIED}
-     * unless explicitly set using this method.
-     */
-    @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT)
-    public void setColorSpace(int colorSpace) {
-        mOutputSurface.colorSpace = colorSpace;
-    }
 }
diff --git a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
index 84ca2b6..c4f653c 100644
--- a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
+++ b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
@@ -25,4 +25,5 @@
     CameraMetadataNative sessionParameter;
     int sessionTemplateId;
     int sessionType;
+    int colorSpace;
 }
diff --git a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
index 96c88e6..84b7a7f 100644
--- a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
+++ b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
@@ -22,6 +22,7 @@
 import android.annotation.SystemApi;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.ColorSpaceProfiles;
 import android.os.IBinder;
 
 import com.android.internal.camera.flags.Flags;
@@ -48,6 +49,7 @@
     private final int mSessionTemplateId;
     private final List<ExtensionOutputConfiguration> mOutputs;
     private final CaptureRequest mSessionParameters;
+    private int mColorSpace;
 
     /**
      * Initialize an extension configuration instance
@@ -72,6 +74,18 @@
         mSessionTemplateId = sessionTemplateId;
         mOutputs = outputs;
         mSessionParameters = sessionParams;
+        mColorSpace = ColorSpaceProfiles.UNSPECIFIED;
+    }
+
+    /**
+     * Set the color space using the ordinal value of a
+     * {@link android.graphics.ColorSpace.Named}.
+     * The default will be -1, indicating an unspecified ColorSpace,
+     * unless explicitly set using this method.
+     */
+    @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT)
+    public void setColorSpace(int colorSpace) {
+        mColorSpace = colorSpace;
     }
 
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
@@ -84,6 +98,11 @@
         ret.sessionTemplateId = mSessionTemplateId;
         ret.sessionType = mSessionType;
         ret.outputConfigs = new ArrayList<>(mOutputs.size());
+        if (Flags.extension10Bit()) {
+            ret.colorSpace = mColorSpace;
+        } else {
+            ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+        }
         for (ExtensionOutputConfiguration outputConfig : mOutputs) {
             ret.outputConfigs.add(outputConfig.getOutputConfig());
         }
diff --git a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
index 9dc6d7b..3a67d61 100644
--- a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
+++ b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.hardware.camera2.params.DynamicRangeProfiles;
 
 import com.android.internal.camera.flags.Flags;
 
@@ -79,6 +80,11 @@
         config.outputId = new OutputConfigId();
         config.outputId.id = mOutputConfigId;
         config.surfaceGroupId = mSurfaceGroupId;
+        if (Flags.extension10Bit()) {
+            config.dynamicRangeProfile = surface.getDynamicRangeProfile();
+        } else {
+            config.dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+        }
     }
 
     @Nullable CameraOutputConfig getOutputConfig() {
diff --git a/core/java/android/hardware/camera2/extension/SessionProcessor.java b/core/java/android/hardware/camera2/extension/SessionProcessor.java
index 2e428e5..0ec5c0a 100644
--- a/core/java/android/hardware/camera2/extension/SessionProcessor.java
+++ b/core/java/android/hardware/camera2/extension/SessionProcessor.java
@@ -363,11 +363,20 @@
 
     private final class SessionProcessorImpl extends ISessionProcessorImpl.Stub {
         private long mVendorId = -1;
+        OutputSurface mImageCaptureSurface;
+        OutputSurface mPreviewSurface;
+        OutputSurface mPostviewSurface;
+
         @Override
         public CameraSessionConfig initSession(IBinder token, String cameraId,
                 Map<String, CameraMetadataNative> charsMap, OutputSurface previewSurface,
                 OutputSurface imageCaptureSurface, OutputSurface postviewSurface)
                 throws RemoteException {
+            if (Flags.surfaceLeakFix()) {
+                mPreviewSurface = previewSurface;
+                mPostviewSurface = postviewSurface;
+                mImageCaptureSurface = imageCaptureSurface;
+            }
             ExtensionConfiguration config = SessionProcessor.this.initSession(token, cameraId,
                     new CharacteristicsMap(charsMap),
                     new CameraOutputSurface(previewSurface),
@@ -390,6 +399,17 @@
         @Override
         public void deInitSession(IBinder token) throws RemoteException {
             SessionProcessor.this.deInitSession(token);
+            if (Flags.surfaceLeakFix()) {
+                if ((mPreviewSurface != null) && (mPreviewSurface.surface != null)) {
+                    mPreviewSurface.surface.release();
+                }
+                if ((mImageCaptureSurface != null) && (mImageCaptureSurface.surface != null)) {
+                    mImageCaptureSurface.surface.release();
+                }
+                if ((mPostviewSurface != null) && (mPostviewSurface.surface != null)) {
+                    mPostviewSurface.surface.release();
+                }
+            }
         }
 
         @Override
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index a7d6caf..5b7f8bb 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.content.Context;
+import android.graphics.ColorSpace;
 import android.graphics.ImageFormat;
 import android.graphics.SurfaceTexture;
 import android.hardware.SyncFence;
@@ -49,6 +50,7 @@
 import android.hardware.camera2.extension.ParcelImage;
 import android.hardware.camera2.extension.ParcelTotalCaptureResult;
 import android.hardware.camera2.extension.Request;
+import android.hardware.camera2.params.ColorSpaceProfiles;
 import android.hardware.camera2.params.DynamicRangeProfiles;
 import android.hardware.camera2.params.ExtensionSessionConfiguration;
 import android.hardware.camera2.params.OutputConfiguration;
@@ -62,6 +64,7 @@
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.Size;
 import android.view.Surface;
@@ -97,6 +100,9 @@
     private Surface mClientRepeatingRequestSurface;
     private Surface mClientCaptureSurface;
     private Surface mClientPostviewSurface;
+    private OutputConfiguration mClientRepeatingRequestOutputConfig;
+    private OutputConfiguration mClientCaptureOutputConfig;
+    private OutputConfiguration mClientPostviewOutputConfig;
     private CameraCaptureSession mCaptureSession = null;
     private ISessionProcessorImpl mSessionProcessor = null;
     private final InitializeSessionHandler mInitializeHandler;
@@ -142,8 +148,19 @@
 
         for (OutputConfiguration c : config.getOutputConfigurations()) {
             if (c.getDynamicRangeProfile() != DynamicRangeProfiles.STANDARD) {
-                throw new IllegalArgumentException("Unsupported dynamic range profile: " +
-                        c.getDynamicRangeProfile());
+                if (Flags.extension10Bit() && Flags.cameraExtensionsCharacteristicsGet()) {
+                    DynamicRangeProfiles dynamicProfiles = extensionChars.get(
+                            config.getExtension(),
+                            CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES);
+                    if (dynamicProfiles == null || !dynamicProfiles.getSupportedProfiles()
+                            .contains(c.getDynamicRangeProfile())) {
+                        throw new IllegalArgumentException("Unsupported dynamic range profile: "
+                                + c.getDynamicRangeProfile());
+                    }
+                } else {
+                    throw new IllegalArgumentException("Unsupported dynamic range profile: "
+                            + c.getDynamicRangeProfile());
+                }
             }
             if (c.getStreamUseCase() !=
                     CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT) {
@@ -157,12 +174,26 @@
                 config.getExtension(), SurfaceTexture.class);
         Surface repeatingRequestSurface = CameraExtensionUtils.getRepeatingRequestSurface(
                 config.getOutputConfigurations(), supportedPreviewSizes);
+        OutputConfiguration repeatingRequestOutputConfig = null;
         if (repeatingRequestSurface != null) {
+            for (OutputConfiguration outputConfig : config.getOutputConfigurations()) {
+                if (outputConfig.getSurface() == repeatingRequestSurface) {
+                    repeatingRequestOutputConfig = outputConfig;
+                }
+            }
             suitableSurfaceCount++;
         }
 
         HashMap<Integer, List<Size>> supportedCaptureSizes = new HashMap<>();
-        for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+
+        IntArray supportedCaptureOutputFormats =
+                new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length);
+        supportedCaptureOutputFormats.addAll(
+                CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS);
+        if (Flags.extension10Bit()) {
+            supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
+        }
+        for (int format : supportedCaptureOutputFormats.toArray()) {
             List<Size> supportedSizes = extensionChars.getExtensionSupportedSizes(
                     config.getExtension(), format);
             if (supportedSizes != null) {
@@ -171,7 +202,13 @@
         }
         Surface burstCaptureSurface = CameraExtensionUtils.getBurstCaptureSurface(
                 config.getOutputConfigurations(), supportedCaptureSizes);
+        OutputConfiguration burstCaptureOutputConfig = null;
         if (burstCaptureSurface != null) {
+            for (OutputConfiguration outputConfig : config.getOutputConfigurations()) {
+                if (outputConfig.getSurface() == burstCaptureSurface) {
+                    burstCaptureOutputConfig = outputConfig;
+                }
+            }
             suitableSurfaceCount++;
         }
 
@@ -180,13 +217,14 @@
         }
 
         Surface postviewSurface = null;
+        OutputConfiguration postviewOutputConfig = config.getPostviewOutputConfiguration();
         if (burstCaptureSurface != null && config.getPostviewOutputConfiguration() != null) {
             CameraExtensionUtils.SurfaceInfo burstCaptureSurfaceInfo =
                     CameraExtensionUtils.querySurface(burstCaptureSurface);
             Size burstCaptureSurfaceSize =
                     new Size(burstCaptureSurfaceInfo.mWidth, burstCaptureSurfaceInfo.mHeight);
             HashMap<Integer, List<Size>> supportedPostviewSizes = new HashMap<>();
-            for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+            for (int format : supportedCaptureOutputFormats.toArray()) {
                 List<Size> supportedSizesPostview = extensionChars.getPostviewSupportedSizes(
                         config.getExtension(), burstCaptureSurfaceSize, format);
                 if (supportedSizesPostview != null) {
@@ -207,8 +245,8 @@
         extender.init(cameraId, characteristicsMapNative);
 
         CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(ctx,
-                extender, cameraDevice, characteristicsMapNative, repeatingRequestSurface,
-                burstCaptureSurface, postviewSurface, config.getStateCallback(),
+                extender, cameraDevice, characteristicsMapNative, repeatingRequestOutputConfig,
+                burstCaptureOutputConfig, postviewOutputConfig, config.getStateCallback(),
                 config.getExecutor(), sessionId, token, config.getExtension());
 
         ret.mStatsAggregator.setClientName(ctx.getOpPackageName());
@@ -223,8 +261,9 @@
             @NonNull IAdvancedExtenderImpl extender,
             @NonNull CameraDeviceImpl cameraDevice,
             Map<String, CameraMetadataNative> characteristicsMap,
-            @Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface,
-            @Nullable Surface postviewSurface,
+            @Nullable OutputConfiguration repeatingRequestOutputConfig,
+            @Nullable OutputConfiguration burstCaptureOutputConfig,
+            @Nullable OutputConfiguration postviewOutputConfig,
             @NonNull StateCallback callback, @NonNull Executor executor,
             int sessionId,
             @NonNull IBinder token,
@@ -235,9 +274,18 @@
         mCharacteristicsMap = characteristicsMap;
         mCallbacks = callback;
         mExecutor = executor;
-        mClientRepeatingRequestSurface = repeatingRequestSurface;
-        mClientCaptureSurface = burstCaptureSurface;
-        mClientPostviewSurface = postviewSurface;
+        mClientRepeatingRequestOutputConfig = repeatingRequestOutputConfig;
+        mClientCaptureOutputConfig = burstCaptureOutputConfig;
+        mClientPostviewOutputConfig = postviewOutputConfig;
+        if (repeatingRequestOutputConfig != null) {
+            mClientRepeatingRequestSurface = repeatingRequestOutputConfig.getSurface();
+        }
+        if (burstCaptureOutputConfig != null) {
+            mClientCaptureSurface = burstCaptureOutputConfig.getSurface();
+        }
+        if (postviewOutputConfig != null) {
+            mClientPostviewSurface = postviewOutputConfig.getSurface();
+        }
         mHandlerThread = new HandlerThread(TAG);
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
@@ -262,9 +310,9 @@
             return;
         }
 
-        OutputSurface previewSurface = initializeParcelable(mClientRepeatingRequestSurface);
-        OutputSurface captureSurface = initializeParcelable(mClientCaptureSurface);
-        OutputSurface postviewSurface = initializeParcelable(mClientPostviewSurface);
+        OutputSurface previewSurface = initializeParcelable(mClientRepeatingRequestOutputConfig);
+        OutputSurface captureSurface = initializeParcelable(mClientCaptureOutputConfig);
+        OutputSurface postviewSurface = initializeParcelable(mClientPostviewOutputConfig);
 
         mSessionProcessor = mAdvancedExtender.getSessionProcessor();
         CameraSessionConfig sessionConfig = mSessionProcessor.initSession(mToken,
@@ -300,6 +348,7 @@
             cameraOutput.setTimestampBase(OutputConfiguration.TIMESTAMP_BASE_SENSOR);
             cameraOutput.setReadoutTimestampEnabled(false);
             cameraOutput.setPhysicalCameraId(output.physicalCameraId);
+            cameraOutput.setDynamicRangeProfile(output.dynamicRangeProfile);
             outputList.add(cameraOutput);
             mCameraConfigMap.put(cameraOutput.getSurface(), output);
         }
@@ -314,7 +363,10 @@
         SessionConfiguration sessionConfiguration = new SessionConfiguration(sessionType,
                 outputList, new CameraExtensionUtils.HandlerExecutor(mHandler),
                 new SessionStateHandler());
-
+        if (sessionConfig.colorSpace != ColorSpaceProfiles.UNSPECIFIED) {
+            sessionConfiguration.setColorSpace(
+                    ColorSpace.Named.values()[sessionConfig.colorSpace]);
+        }
         if ((sessionConfig.sessionParameter != null) &&
                 (!sessionConfig.sessionParameter.isEmpty())) {
             CaptureRequest.Builder requestBuilder = mCameraDevice.createCaptureRequest(
@@ -362,21 +414,38 @@
         return ret;
     }
 
-    private static OutputSurface initializeParcelable(Surface s) {
+    private static OutputSurface initializeParcelable(OutputConfiguration o) {
         OutputSurface ret = new OutputSurface();
-        if (s != null) {
+
+        if (o != null && o.getSurface() != null) {
+            Surface s = o.getSurface();
             ret.surface = s;
             ret.size = new android.hardware.camera2.extension.Size();
             Size surfaceSize = SurfaceUtils.getSurfaceSize(s);
             ret.size.width = surfaceSize.getWidth();
             ret.size.height = surfaceSize.getHeight();
             ret.imageFormat = SurfaceUtils.getSurfaceFormat(s);
+
+            if (Flags.extension10Bit()) {
+                ret.dynamicRangeProfile = o.getDynamicRangeProfile();
+                ColorSpace colorSpace = o.getColorSpace();
+                if (colorSpace != null) {
+                    ret.colorSpace = colorSpace.getId();
+                } else {
+                    ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+                }
+            } else {
+                ret.dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+                ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+            }
         } else {
             ret.surface = null;
             ret.size = new android.hardware.camera2.extension.Size();
             ret.size.width = -1;
             ret.size.height = -1;
             ret.imageFormat = ImageFormat.UNKNOWN;
+            ret.dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+            ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
         }
 
         return ret;
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 725b413..5b32f33 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -58,12 +58,15 @@
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.Pair;
 import android.util.Size;
 import android.view.Surface;
 
+import com.android.internal.camera.flags.Flags;
+
 import java.io.Closeable;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -183,7 +186,14 @@
         }
 
         HashMap<Integer, List<Size>> supportedCaptureSizes = new HashMap<>();
-        for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+        IntArray supportedCaptureOutputFormats =
+                new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length);
+        supportedCaptureOutputFormats.addAll(
+                CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS);
+        if (Flags.extension10Bit()) {
+            supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
+        }
+        for (int format : supportedCaptureOutputFormats.toArray()) {
             List<Size> supportedSizes = extensionChars.getExtensionSupportedSizes(
                     config.getExtension(), format);
             if (supportedSizes != null) {
@@ -207,7 +217,7 @@
             Size burstCaptureSurfaceSize =
                     new Size(burstCaptureSurfaceInfo.mWidth, burstCaptureSurfaceInfo.mHeight);
             HashMap<Integer, List<Size>> supportedPostviewSizes = new HashMap<>();
-            for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+            for (int format : supportedCaptureOutputFormats.toArray()) {
                 List<Size> supportedSizesPostview = extensionChars.getPostviewSupportedSizes(
                         config.getExtension(), burstCaptureSurfaceSize, format);
                 if (supportedSizesPostview != null) {
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
index a8066aa..f0c6e2e 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
@@ -29,10 +29,13 @@
 import android.media.Image;
 import android.media.ImageWriter;
 import android.os.Handler;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.Size;
 import android.view.Surface;
 
+import com.android.internal.camera.flags.Flags;
+
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -130,9 +133,16 @@
     public static Surface getBurstCaptureSurface(
             @NonNull List<OutputConfiguration> outputConfigs,
             @NonNull HashMap<Integer, List<Size>> supportedCaptureSizes) {
+        IntArray supportedCaptureOutputFormats =
+                new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length);
+        supportedCaptureOutputFormats.addAll(
+                CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS);
+        if (Flags.extension10Bit()) {
+            supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
+        }
         for (OutputConfiguration config : outputConfigs) {
             SurfaceInfo surfaceInfo = querySurface(config.getSurface());
-            for (int supportedFormat : SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+            for (int supportedFormat : supportedCaptureOutputFormats.toArray()) {
                 if (surfaceInfo.mFormat == supportedFormat) {
                     Size captureSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
                     if (supportedCaptureSizes.containsKey(supportedFormat)) {
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index 9fbe348..f3b7b91 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -260,7 +260,7 @@
          * smaller sizes, then the resulting
          * {@link android.hardware.camera2.params.SessionConfiguration session configuration} can
          * be tested either by calling {@link CameraDevice#createCaptureSession} or
-         * {@link CameraManager#isSessionConfigurationWithParametersSupported}.
+         * {@link CameraDeviceSetup#isSessionConfigurationSupported}.
          *
          * @return non-modifiable ascending list of available sizes.
          */
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 95526a8..8aacd5e 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -1379,7 +1379,6 @@
                     mSurfaceType != other.mSurfaceType ||
                     mIsDeferredConfig != other.mIsDeferredConfig ||
                     mIsShared != other.mIsShared ||
-                    mConfiguredFormat != other.mConfiguredFormat ||
                     mConfiguredDataspace != other.mConfiguredDataspace ||
                     mConfiguredGenerationId != other.mConfiguredGenerationId ||
                     !Objects.equals(mPhysicalCameraId, other.mPhysicalCameraId) ||
diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java
index e35e801..76888f3 100644
--- a/core/java/android/hardware/devicestate/DeviceState.java
+++ b/core/java/android/hardware/devicestate/DeviceState.java
@@ -25,8 +25,9 @@
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-
-import com.android.internal.util.Preconditions;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArraySet;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -37,8 +38,7 @@
 import java.util.Set;
 
 /**
- * A state of the device defined by the {@link DeviceStateProvider} and managed by the
- * {@link DeviceStateManagerService}.
+ * A state of the device managed by {@link DeviceStateManager}.
  * <p>
  * Device state is an abstract concept that allows mapping the current state of the device to the
  * state of the system. This is useful for variable-state devices, like foldable or rollable
@@ -268,68 +268,88 @@
     @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
     public @interface DeviceStateProperties {}
 
-    /** Unique identifier for the device state. */
-    @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to = MAXIMUM_DEVICE_STATE_IDENTIFIER)
-    private final int mIdentifier;
+    /** @hide */
+    @IntDef(prefix = {"PROPERTY_"}, flag = true, value = {
+            PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED,
+            PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN,
+            PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+    public @interface PhysicalDeviceStateProperties {}
 
-    /** String description of the device state. */
+    /** @hide */
+    @IntDef(prefix = {"PROPERTY_"}, flag = true, value = {
+            PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
+            PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP,
+            PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL,
+            PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE,
+            PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST,
+            PROPERTY_APP_INACCESSIBLE,
+            PROPERTY_EMULATED_ONLY,
+            PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY,
+            PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY,
+            PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP,
+            PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE,
+            PROPERTY_EXTENDED_DEVICE_STATE_EXTERNAL_DISPLAY,
+            PROPERTY_FEATURE_REAR_DISPLAY,
+            PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+    public @interface SystemDeviceStateProperties {}
+
     @NonNull
-    private final String mName;
+    private final DeviceState.Configuration mDeviceStateConfiguration;
 
     @DeviceStateFlags
     private final int mFlags;
 
-    private final Set<@DeviceStateProperties Integer> mProperties;
+    /** @hide */
+    public DeviceState(@NonNull DeviceState.Configuration deviceStateConfiguration) {
+        Objects.requireNonNull(deviceStateConfiguration, "Device StateConfiguration is null");
+        mDeviceStateConfiguration = deviceStateConfiguration;
+        mFlags = 0;
+    }
+
+    /** @hide */
+    public DeviceState(
+            @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to =
+                    MAXIMUM_DEVICE_STATE_IDENTIFIER) int identifier,
+            @NonNull String name,
+            @NonNull Set<@DeviceStateProperties Integer> properties) {
+        mDeviceStateConfiguration = new DeviceState.Configuration(identifier, name, properties,
+                Collections.emptySet());
+        mFlags = 0;
+    }
 
     /**
      * @deprecated Deprecated in favor of {@link #DeviceState(int, String, Set)}
      * @hide
      */
     // TODO(b/325124054): Make non-default and remove deprecated callback methods.
-    @TestApi
     @Deprecated
     public DeviceState(
             @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to =
                     MAXIMUM_DEVICE_STATE_IDENTIFIER) int identifier,
             @NonNull String name,
             @DeviceStateFlags int flags) {
-        Preconditions.checkArgumentInRange(identifier, MINIMUM_DEVICE_STATE_IDENTIFIER,
-                MAXIMUM_DEVICE_STATE_IDENTIFIER,
-                "identifier");
 
-        mIdentifier = identifier;
-        mName = name;
+        mDeviceStateConfiguration = new DeviceState.Configuration(identifier, name,
+                Collections.emptySet(), Collections.emptySet());
         mFlags = flags;
-        mProperties = Collections.emptySet();
-    }
-
-    /** @hide */
-    @TestApi
-    public DeviceState(
-            @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to =
-                    MAXIMUM_DEVICE_STATE_IDENTIFIER) int identifier,
-            @NonNull String name,
-            @NonNull Set<@DeviceStateProperties Integer> properties) {
-        Preconditions.checkArgumentInRange(identifier, MINIMUM_DEVICE_STATE_IDENTIFIER,
-                MAXIMUM_DEVICE_STATE_IDENTIFIER,
-                "identifier");
-
-        mIdentifier = identifier;
-        mName = name;
-        mProperties = Set.copyOf(properties);
-        mFlags = 0;
     }
 
     /** Returns the unique identifier for the device state. */
     @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER)
     public int getIdentifier() {
-        return mIdentifier;
+        return mDeviceStateConfiguration.getIdentifier();
     }
 
     /** Returns a string description of the device state. */
     @NonNull
     public String getName() {
-        return mName;
+        return mDeviceStateConfiguration.getName();
     }
 
     /**
@@ -345,10 +365,13 @@
 
     @Override
     public String toString() {
-        return "DeviceState{" + "identifier=" + mIdentifier + ", name='" + mName + '\''
-                + ", app_accessible=" + !hasProperty(PROPERTY_APP_INACCESSIBLE)
+        return "DeviceState{" + "identifier=" + mDeviceStateConfiguration.getIdentifier()
+                + ", name='" + mDeviceStateConfiguration.getName() + '\''
+                + ", app_accessible=" + !mDeviceStateConfiguration.getSystemProperties().contains(
+                PROPERTY_APP_INACCESSIBLE)
                 + ", cancel_when_requester_not_on_top="
-                + hasProperty(PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP)
+                + mDeviceStateConfiguration.getSystemProperties().contains(
+                PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP)
                 + "}";
     }
 
@@ -357,14 +380,12 @@
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         DeviceState that = (DeviceState) o;
-        return mIdentifier == that.mIdentifier
-                && Objects.equals(mName, that.mName)
-                && Objects.equals(mProperties, that.mProperties);
+        return Objects.equals(mDeviceStateConfiguration, that.mDeviceStateConfiguration);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mIdentifier, mName, mProperties);
+        return Objects.hash(mDeviceStateConfiguration);
     }
 
     /** Checks if a specific flag is set
@@ -381,7 +402,8 @@
      * Checks if a specific property is set on this state
      */
     public boolean hasProperty(@DeviceStateProperties int propertyToCheckFor) {
-        return mProperties.contains(propertyToCheckFor);
+        return mDeviceStateConfiguration.mSystemProperties.contains(propertyToCheckFor)
+                || mDeviceStateConfiguration.mPhysicalProperties.contains(propertyToCheckFor);
     }
 
     /**
@@ -389,10 +411,193 @@
      */
     public boolean hasProperties(@NonNull @DeviceStateProperties int... properties) {
         for (int i = 0; i < properties.length; i++) {
-            if (mProperties.contains(properties[i])) {
+            if (!hasProperty(properties[i])) {
                 return false;
             }
         }
         return true;
     }
+
+    /**
+     * Returns the underlying {@link DeviceState.Configuration} object used to model the
+     * device state.
+     * @hide
+     */
+    public Configuration getConfiguration() {
+        return mDeviceStateConfiguration;
+    }
+
+    /**
+     * Detailed description of a {@link DeviceState} that includes separated sets of
+     * {@link DeviceStateProperties} for properties that correspond to the state of the system when
+     * the device is in this state, as well as physical properties that describe this state.
+     *
+     * Instantiation of this class should only be done by the system server, and clients of
+     * {@link DeviceStateManager} will receive {@link DeviceState} objects.
+     *
+     * @see DeviceStateManager
+     * @hide
+     */
+    public static final class Configuration implements Parcelable {
+        /** Unique identifier for the device state. */
+        @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to = MAXIMUM_DEVICE_STATE_IDENTIFIER)
+        private final int mIdentifier;
+
+        /** String description of the device state. */
+        @NonNull
+        private final String mName;
+
+        /** {@link ArraySet} of system properties that apply to this state. */
+        @NonNull
+        private final ArraySet<@SystemDeviceStateProperties Integer> mSystemProperties;
+
+        /** {@link ArraySet} of physical device properties that apply to this state. */
+        @NonNull
+        private final ArraySet<@PhysicalDeviceStateProperties Integer> mPhysicalProperties;
+
+        private Configuration(int identifier, @NonNull String name,
+                @NonNull Set<@SystemDeviceStateProperties Integer> systemProperties,
+                @NonNull Set<@PhysicalDeviceStateProperties Integer> physicalProperties) {
+            mIdentifier = identifier;
+            mName = name;
+            mSystemProperties = new ArraySet<@SystemDeviceStateProperties Integer>(
+                    systemProperties);
+            mPhysicalProperties = new ArraySet<@PhysicalDeviceStateProperties Integer>(
+                    physicalProperties);
+        }
+
+        /** Returns the unique identifier for the device state. */
+        public int getIdentifier() {
+            return mIdentifier;
+        }
+
+        /** Returns a string description of the device state. */
+        @NonNull
+        public String getName() {
+            return mName;
+        }
+
+        /** Returns the {@link Set} of system properties that apply to this state. */
+        @NonNull
+        public Set<@SystemDeviceStateProperties Integer> getSystemProperties() {
+            return mSystemProperties;
+        }
+
+        /** Returns the {@link Set} of physical device properties that apply to this state. */
+        @NonNull
+        public Set<@DeviceStateProperties Integer> getPhysicalProperties() {
+            return mPhysicalProperties;
+        }
+
+        @Override
+        public String toString() {
+            return "DeviceState{" + "identifier=" + mIdentifier
+                    + ", name='" + mName + '\''
+                    + ", app_accessible=" + mSystemProperties.contains(PROPERTY_APP_INACCESSIBLE)
+                    + ", cancel_when_requester_not_on_top="
+                    + mSystemProperties.contains(PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP)
+                    + "}";
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            DeviceState.Configuration that = (DeviceState.Configuration) o;
+            return mIdentifier == that.mIdentifier
+                    && Objects.equals(mName, that.mName)
+                    && Objects.equals(mSystemProperties, that.mSystemProperties)
+                    && Objects.equals(mPhysicalProperties, that.mPhysicalProperties);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mIdentifier, mName, mSystemProperties, mPhysicalProperties);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeInt(mIdentifier);
+            dest.writeString8(mName);
+
+            dest.writeInt(mSystemProperties.size());
+            for (int i = 0; i < mSystemProperties.size(); i++) {
+                dest.writeInt(mSystemProperties.valueAt(i));
+            }
+
+            dest.writeInt(mPhysicalProperties.size());
+            for (int i = 0; i < mPhysicalProperties.size(); i++) {
+                dest.writeInt(mPhysicalProperties.valueAt(i));
+            }
+        }
+
+        @NonNull
+        public static final Creator<DeviceState.Configuration> CREATOR = new Creator<>() {
+            @Override
+            public DeviceState.Configuration createFromParcel(Parcel source) {
+                int identifier = source.readInt();
+                String name = source.readString8();
+                ArraySet<@DeviceStateProperties Integer> systemProperties = new ArraySet<>();
+                int systemPropertySize = source.readInt();
+                for (int i = 0; i < systemPropertySize; i++) {
+                    systemProperties.add(source.readInt());
+                }
+                ArraySet<@DeviceStateProperties Integer> physicalProperties = new ArraySet<>();
+                int physicalPropertySize = source.readInt();
+                for (int j = 0; j < physicalPropertySize; j++) {
+                    physicalProperties.add(source.readInt());
+                }
+                return new DeviceState.Configuration(identifier, name, systemProperties,
+                        physicalProperties);
+            }
+
+            @Override
+            public DeviceState.Configuration[] newArray(int size) {
+                return new DeviceState.Configuration[size];
+            }
+        };
+
+        /** @hide */
+        public static class Builder {
+            private final int mIdentifier;
+            private final String mName;
+            private Set<@SystemDeviceStateProperties Integer> mSystemProperties =
+                    Collections.emptySet();
+            private Set<@PhysicalDeviceStateProperties Integer> mPhysicalProperties =
+                    Collections.emptySet();
+
+            public Builder(int identifier, String name) {
+                mIdentifier = identifier;
+                mName = name;
+            }
+
+            /** Sets the system properties for this {@link DeviceState.Configuration.Builder} */
+            public Builder setSystemProperties(
+                    Set<@SystemDeviceStateProperties Integer> systemProperties) {
+                mSystemProperties = systemProperties;
+                return this;
+            }
+
+            /** Sets the system properties for this {@link DeviceState.Configuration.Builder} */
+            public Builder setPhysicalProperties(
+                    Set<@PhysicalDeviceStateProperties Integer> physicalProperties) {
+                mPhysicalProperties = physicalProperties;
+                return this;
+            }
+
+            /**
+             * Returns a new {@link DeviceState.Configuration} whose values match the values set on
+             * the builder.
+             */
+            public DeviceState.Configuration build() {
+                return new DeviceState.Configuration(mIdentifier, mName, mSystemProperties,
+                        mPhysicalProperties);
+            }
+        }
+    }
 }
diff --git a/core/java/android/hardware/devicestate/DeviceStateInfo.java b/core/java/android/hardware/devicestate/DeviceStateInfo.java
index bc6af37..c319c89 100644
--- a/core/java/android/hardware/devicestate/DeviceStateInfo.java
+++ b/core/java/android/hardware/devicestate/DeviceStateInfo.java
@@ -24,7 +24,8 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -56,19 +57,19 @@
      * The list of states supported by the device.
      */
     @NonNull
-    public final int[] supportedStates;
+    public final ArrayList<DeviceState> supportedStates;
 
     /**
      * The base (non-override) state of the device. The base state is the state of the device
      * ignoring any override requests made through a call to {@link DeviceStateManager#requestState(
      * DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
      */
-    public final int baseState;
+    public final DeviceState baseState;
 
     /**
      * The state of the device.
      */
-    public final int currentState;
+    public final DeviceState currentState;
 
     /**
      * Creates a new instance of {@link DeviceStateInfo}.
@@ -76,8 +77,9 @@
      * NOTE: Unlike {@link #DeviceStateInfo(DeviceStateInfo)}, this constructor does not copy the
      * supplied parameters.
      */
-    public DeviceStateInfo(@NonNull int[] supportedStates, int baseState, int state) {
-        this.supportedStates = supportedStates;
+    public DeviceStateInfo(@NonNull List<DeviceState> supportedStates, DeviceState baseState,
+            DeviceState state) {
+        this.supportedStates = new ArrayList<>(supportedStates);
         this.baseState = baseState;
         this.currentState = state;
     }
@@ -87,8 +89,7 @@
      * the fields of the returned instance.
      */
     public DeviceStateInfo(@NonNull DeviceStateInfo info) {
-        this(Arrays.copyOf(info.supportedStates, info.supportedStates.length),
-                info.baseState, info.currentState);
+        this(List.copyOf(info.supportedStates), info.baseState, info.currentState);
     }
 
     @Override
@@ -96,15 +97,15 @@
         if (this == other) return true;
         if (other == null || getClass() != other.getClass()) return false;
         DeviceStateInfo that = (DeviceStateInfo) other;
-        return baseState == that.baseState
-                &&  currentState == that.currentState
-                && Arrays.equals(supportedStates, that.supportedStates);
+        return baseState.equals(that.baseState)
+                &&  currentState.equals(that.currentState)
+                && Objects.equals(supportedStates, that.supportedStates);
     }
 
     @Override
     public int hashCode() {
         int result = Objects.hash(baseState, currentState);
-        result = 31 * result + Arrays.hashCode(supportedStates);
+        result = 31 * result + supportedStates.hashCode();
         return result;
     }
 
@@ -112,13 +113,13 @@
     @ChangeFlags
     public int diff(@NonNull DeviceStateInfo other) {
         int diff = 0;
-        if (!Arrays.equals(supportedStates, other.supportedStates)) {
+        if (!supportedStates.equals(other.supportedStates)) {
             diff |= CHANGED_SUPPORTED_STATES;
         }
-        if (baseState != other.baseState) {
+        if (!baseState.equals(other.baseState)) {
             diff |= CHANGED_BASE_STATE;
         }
-        if (currentState != other.currentState) {
+        if (!currentState.equals(other.currentState)) {
             diff |= CHANGED_CURRENT_STATE;
         }
         return diff;
@@ -126,13 +127,13 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(supportedStates.length);
-        for (int i = 0; i < supportedStates.length; i++) {
-            dest.writeInt(supportedStates[i]);
+        dest.writeInt(supportedStates.size());
+        for (int i = 0; i < supportedStates.size(); i++) {
+            dest.writeTypedObject(supportedStates.get(i).getConfiguration(), flags);
         }
 
-        dest.writeInt(baseState);
-        dest.writeInt(currentState);
+        dest.writeTypedObject(baseState.getConfiguration(), flags);
+        dest.writeTypedObject(currentState.getConfiguration(), flags);
     }
 
     @Override
@@ -140,16 +141,21 @@
         return 0;
     }
 
-    public static final @NonNull Creator<DeviceStateInfo> CREATOR = new Creator<DeviceStateInfo>() {
+    public static final @NonNull Creator<DeviceStateInfo> CREATOR = new Creator<>() {
         @Override
         public DeviceStateInfo createFromParcel(Parcel source) {
             final int numberOfSupportedStates = source.readInt();
-            final int[] supportedStates = new int[numberOfSupportedStates];
+            final ArrayList<DeviceState> supportedStates = new ArrayList<>(numberOfSupportedStates);
             for (int i = 0; i < numberOfSupportedStates; i++) {
-                supportedStates[i] = source.readInt();
+                DeviceState.Configuration configuration = source.readTypedObject(
+                        DeviceState.Configuration.CREATOR);
+                supportedStates.add(i, new DeviceState(configuration));
             }
-            final int baseState = source.readInt();
-            final int currentState = source.readInt();
+
+            final DeviceState baseState = new DeviceState(
+                    source.readTypedObject(DeviceState.Configuration.CREATOR));
+            final DeviceState currentState = new DeviceState(
+                    source.readTypedObject(DeviceState.Configuration.CREATOR));
 
             return new DeviceStateInfo(supportedStates, baseState, currentState);
         }
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index 8b4d43e..a4c3833 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -49,7 +49,7 @@
      *
      * @hide
      */
-    public static final int INVALID_DEVICE_STATE = -1;
+    public static final int INVALID_DEVICE_STATE_IDENTIFIER = -1;
 
     /**
      * The minimum allowed device state identifier.
diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
index 6db5aee..d6cc00d 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
-import android.app.ActivityThread;
 import android.content.Context;
 import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
 import android.os.Binder;
@@ -33,13 +32,9 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.util.ArrayUtils;
 
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.Executor;
 
 /**
@@ -55,18 +50,12 @@
     private static final String TAG = "DeviceStateManagerGlobal";
     private static final boolean DEBUG = Build.IS_DEBUGGABLE;
 
-    // TODO(b/325124054): Remove when system server refactor is completed
-    private static int[] sFoldedDeviceStates = new int[0];
-
     /**
      * Returns an instance of {@link DeviceStateManagerGlobal}. May return {@code null} if a
      * connection with the device state service couldn't be established.
      */
     @Nullable
     public static DeviceStateManagerGlobal getInstance() {
-        // TODO(b/325124054): Remove when system server refactor is completed
-        instantiateFoldedStateArray();
-
         synchronized (DeviceStateManagerGlobal.class) {
             if (sInstance == null) {
                 IBinder b = ServiceManager.getService(Context.DEVICE_STATE_SERVICE);
@@ -79,16 +68,6 @@
         }
     }
 
-    // TODO(b/325124054): Remove when system server refactor is completed
-    // TODO(b/325330654): Investigate if we need a Context passed in to DSMGlobal
-    private static void instantiateFoldedStateArray() {
-        Context context = ActivityThread.currentApplication();
-        if (context != null) {
-            sFoldedDeviceStates = context.getResources().getIntArray(
-                    com.android.internal.R.array.config_foldedDeviceStates);
-        }
-    }
-
     private final Object mLock = new Object();
     @NonNull
     private final IDeviceStateManager mDeviceStateManager;
@@ -115,6 +94,7 @@
      *
      * @see DeviceStateManager#getSupportedStates()
      */
+    // TODO(b/325124054): Remove unused methods when clients are migrated.
     public int[] getSupportedStates() {
         synchronized (mLock) {
             final DeviceStateInfo currentInfo;
@@ -132,12 +112,12 @@
                 }
             }
 
-            return Arrays.copyOf(currentInfo.supportedStates, currentInfo.supportedStates.length);
+            return getSupportedStateIdentifiersLocked(currentInfo.supportedStates);
         }
     }
 
     /**
-     * Returns the {@link List} of supported device states.
+     * Returns {@link List} of supported {@link DeviceState}s.
      *
      * @see DeviceStateManager#getSupportedDeviceStates()
      */
@@ -158,7 +138,7 @@
                 }
             }
 
-            return createDeviceStateList(currentInfo.supportedStates);
+            return List.copyOf(currentInfo.supportedStates);
         }
     }
 
@@ -285,13 +265,14 @@
 
             if (mLastReceivedInfo != null) {
                 // Copy the array to prevent the callback from modifying the internal state.
-                final int[] supportedStates = Arrays.copyOf(mLastReceivedInfo.supportedStates,
-                        mLastReceivedInfo.supportedStates.length);
+                final int[] supportedStates = getSupportedStateIdentifiersLocked(
+                        mLastReceivedInfo.supportedStates);
                 wrapper.notifySupportedStatesChanged(supportedStates);
-                wrapper.notifySupportedDeviceStatesChanged(createDeviceStateList(supportedStates));
-                wrapper.notifyBaseStateChanged(mLastReceivedInfo.baseState);
-                wrapper.notifyStateChanged(mLastReceivedInfo.currentState);
-                wrapper.notifyDeviceStateChanged(createDeviceState(mLastReceivedInfo.currentState));
+                wrapper.notifySupportedDeviceStatesChanged(
+                        List.copyOf(mLastReceivedInfo.supportedStates));
+                wrapper.notifyBaseStateChanged(mLastReceivedInfo.baseState.getIdentifier());
+                wrapper.notifyStateChanged(mLastReceivedInfo.currentState.getIdentifier());
+                wrapper.notifyDeviceStateChanged(mLastReceivedInfo.currentState);
             }
         }
     }
@@ -349,6 +330,15 @@
         return -1;
     }
 
+    @GuardedBy("mLock")
+    private int[] getSupportedStateIdentifiersLocked(List<DeviceState> states) {
+        int[] identifiers = new int[states.size()];
+        for (int i = 0; i < states.size(); i++) {
+            identifiers[i] = states.get(i).getIdentifier();
+        }
+        return identifiers;
+    }
+
     @Nullable
     private IBinder findRequestTokenLocked(@NonNull DeviceStateRequest request) {
         for (int i = 0; i < mRequests.size(); i++) {
@@ -363,32 +353,31 @@
     private void handleDeviceStateInfoChanged(@NonNull DeviceStateInfo info) {
         ArrayList<DeviceStateCallbackWrapper> callbacks;
         DeviceStateInfo oldInfo;
+        int[] supportedStateIdentifiers;
         synchronized (mLock) {
             oldInfo = mLastReceivedInfo;
             mLastReceivedInfo = info;
             callbacks = new ArrayList<>(mCallbacks);
+            supportedStateIdentifiers = getSupportedStateIdentifiersLocked(info.supportedStates);
         }
 
         final int diff = oldInfo == null ? ~0 : info.diff(oldInfo);
         if ((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0) {
             for (int i = 0; i < callbacks.size(); i++) {
-                // Copy the array to prevent callbacks from modifying the internal state.
-                final int[] supportedStates = Arrays.copyOf(info.supportedStates,
-                        info.supportedStates.length);
-                callbacks.get(i).notifySupportedStatesChanged(supportedStates);
                 callbacks.get(i).notifySupportedDeviceStatesChanged(
-                        createDeviceStateList(supportedStates));
+                        List.copyOf(info.supportedStates));
+                callbacks.get(i).notifySupportedStatesChanged(supportedStateIdentifiers);
             }
         }
         if ((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0) {
             for (int i = 0; i < callbacks.size(); i++) {
-                callbacks.get(i).notifyBaseStateChanged(info.baseState);
+                callbacks.get(i).notifyBaseStateChanged(info.baseState.getIdentifier());
             }
         }
         if ((diff & DeviceStateInfo.CHANGED_CURRENT_STATE) > 0) {
             for (int i = 0; i < callbacks.size(); i++) {
-                callbacks.get(i).notifyStateChanged(info.currentState);
-                callbacks.get(i).notifyDeviceStateChanged(createDeviceState(info.currentState));
+                callbacks.get(i).notifyDeviceStateChanged(info.currentState);
+                callbacks.get(i).notifyStateChanged(info.currentState.getIdentifier());
             }
         }
     }
@@ -421,36 +410,6 @@
         }
     }
 
-    /**
-     * Creates a {@link DeviceState} object from a device state identifier, with the
-     * {@link DeviceState} property that corresponds to what display is primary.
-     *
-     */
-    // TODO(b/325124054): Remove when system server refactor is completed
-    @NonNull
-    private DeviceState createDeviceState(int stateIdentifier) {
-        final Set<@DeviceState.DeviceStateProperties Integer> properties = new HashSet<>();
-        if (ArrayUtils.contains(sFoldedDeviceStates, stateIdentifier)) {
-            properties.add(DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY);
-        } else {
-            properties.add(DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY);
-        }
-        return new DeviceState(stateIdentifier, "" /* name */, properties);
-    }
-
-    /**
-     * Creates a list of {@link DeviceState} objects from an array of state identifiers.
-     */
-    // TODO(b/325124054): Remove when system server refactor is completed
-    @NonNull
-    private List<DeviceState> createDeviceStateList(int[] supportedStates) {
-        List<DeviceState> deviceStateList = new ArrayList<>();
-        for (int i = 0; i < supportedStates.length; i++) {
-            deviceStateList.add(createDeviceState(supportedStates[i]));
-        }
-        return deviceStateList;
-    }
-
     private final class DeviceStateManagerCallback extends IDeviceStateManagerCallback.Stub {
         @Override
         public void onDeviceStateInfoChanged(DeviceStateInfo info) {
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 6b2814e..58aafbc 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -778,6 +778,16 @@
          */
         float[] getAutoBrightnessLuxLevels(int mode);
 
+        /**
+         * @return The current brightness setting
+         */
+        float getBrightness();
+
+        /**
+         * @return The brightness value that is used when the device is in doze
+         */
+        float getDozeBrightness();
+
         /** Returns whether displayoffload supports the given display state. */
         static boolean isSupportedOffloadState(int displayState) {
             return Display.isSuspendedState(displayState);
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 066c45f..210ce2b 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -18,23 +18,18 @@
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.MANAGE_BIOMETRIC;
-import static android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
-import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION;
 
-import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.BiometricStateListener;
 import android.hardware.biometrics.CryptoObject;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
@@ -42,9 +37,9 @@
 import android.os.CancellationSignal;
 import android.os.CancellationSignal.OnCancelListener;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
+import android.os.Looper;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.Trace;
@@ -54,21 +49,15 @@
 import android.view.Surface;
 
 import com.android.internal.R;
+import com.android.internal.os.SomeArgs;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.Executor;
 
 /**
  * A class that coordinates access to the face authentication hardware.
- *
- * <p>Please use {@link BiometricPrompt} for face authentication unless the experience must be
- * customized for unique system-level utilities, like the lock screen or ambient background usage.
- *
  * @hide
  */
-@FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
-@SystemApi
 @SystemService(Context.FACE_SERVICE)
 public class FaceManager implements BiometricAuthenticator, BiometricFaceConstants {
 
@@ -99,76 +88,81 @@
     @Nullable private GenerateChallengeCallback mGenerateChallengeCallback;
     private CryptoObject mCryptoObject;
     private Face mRemovalFace;
-    private Executor mExecutor;
+    private Handler mHandler;
     private List<FaceSensorPropertiesInternal> mProps = new ArrayList<>();
 
     private final IFaceServiceReceiver mServiceReceiver = new IFaceServiceReceiver.Stub() {
 
         @Override // binder call
         public void onEnrollResult(Face face, int remaining) {
-            mExecutor.execute(() -> sendEnrollResult(face, remaining));
+            mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0, face).sendToTarget();
         }
 
         @Override // binder call
         public void onAcquired(int acquireInfo, int vendorCode) {
-            mExecutor.execute(() -> sendAcquiredResult(acquireInfo, vendorCode));
+            mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode).sendToTarget();
         }
 
         @Override // binder call
         public void onAuthenticationSucceeded(Face face, int userId, boolean isStrongBiometric) {
-            mExecutor.execute(() -> sendAuthenticatedSucceeded(face, userId, isStrongBiometric));
+            mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId,
+                    isStrongBiometric ? 1 : 0, face).sendToTarget();
         }
 
         @Override // binder call
         public void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
-            mExecutor.execute(() -> sendFaceDetected(sensorId, userId, isStrongBiometric));
+            mHandler.obtainMessage(MSG_FACE_DETECTED, sensorId, userId, isStrongBiometric)
+                    .sendToTarget();
         }
 
         @Override // binder call
         public void onAuthenticationFailed() {
-            mExecutor.execute(() -> sendAuthenticatedFailed());
+            mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
         }
 
         @Override // binder call
         public void onError(int error, int vendorCode) {
-            mExecutor.execute(() -> sendErrorResult(error, vendorCode));
+            mHandler.obtainMessage(MSG_ERROR, error, vendorCode).sendToTarget();
         }
 
         @Override // binder call
         public void onRemoved(Face face, int remaining) {
-            mExecutor.execute(() -> {
-                sendRemovedResult(face, remaining);
-                if (remaining == 0) {
-                    Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                            Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0,
-                            UserHandle.USER_CURRENT);
-                }
-            });
+            mHandler.obtainMessage(MSG_REMOVED, remaining, 0, face).sendToTarget();
+            if (remaining == 0) {
+                Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                        Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0,
+                        UserHandle.USER_CURRENT);
+            }
         }
 
         @Override
         public void onFeatureSet(boolean success, int feature) {
-            mExecutor.execute(() -> sendSetFeatureCompleted(success, feature));
+            mHandler.obtainMessage(MSG_SET_FEATURE_COMPLETED, feature, 0, success).sendToTarget();
         }
 
         @Override
         public void onFeatureGet(boolean success, int[] features, boolean[] featureState) {
-            mExecutor.execute(() -> sendGetFeatureCompleted(success, features, featureState));
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = success;
+            args.arg2 = features;
+            args.arg3 = featureState;
+            mHandler.obtainMessage(MSG_GET_FEATURE_COMPLETED, args).sendToTarget();
         }
 
         @Override
         public void onChallengeGenerated(int sensorId, int userId, long challenge) {
-            mExecutor.execute(() -> sendChallengeGenerated(sensorId, userId, challenge));
+            mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, userId, challenge)
+                    .sendToTarget();
         }
 
         @Override
         public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
-            mExecutor.execute(() -> sendAuthenticationFrame(frame));
+            mHandler.obtainMessage(MSG_AUTHENTICATION_FRAME, frame).sendToTarget();
         }
 
         @Override
         public void onEnrollmentFrame(FaceEnrollFrame frame) {
-            mExecutor.execute(() -> sendEnrollmentFrame(frame));
+            mHandler.obtainMessage(MSG_ENROLLMENT_FRAME, frame).sendToTarget();
         }
     };
 
@@ -181,7 +175,7 @@
         if (mService == null) {
             Slog.v(TAG, "FaceAuthenticationManagerService was null");
         }
-        mExecutor = context.getMainExecutor();
+        mHandler = new MyHandler(context);
         if (context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
                 == PackageManager.PERMISSION_GRANTED) {
             addAuthenticatorsRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
@@ -195,16 +189,18 @@
     }
 
     /**
-     * Returns an {@link Executor} for the given {@link Handler} or the main {@link Executor} if
-     * {@code handler} is {@code null}.
+     * Use the provided handler thread for events.
      */
-    private @NonNull Executor createExecutorForHandlerIfNeeded(@Nullable Handler handler) {
-        return handler != null ? new HandlerExecutor(handler) : mContext.getMainExecutor();
+    private void useHandler(Handler handler) {
+        if (handler != null) {
+            mHandler = new MyHandler(handler.getLooper());
+        } else if (mHandler.getLooper() != mContext.getMainLooper()) {
+            mHandler = new MyHandler(mContext.getMainLooper());
+        }
     }
 
     /**
      * @deprecated use {@link #authenticate(CryptoObject, CancellationSignal, AuthenticationCallback, Handler, FaceAuthenticateOptions)}.
-     * @hide
      */
     @Deprecated
     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -216,22 +212,17 @@
     }
 
     /**
-     * Request authentication.
-     *
-     * <p>This call operates the face recognition hardware and starts capturing images.
+     * Request authentication. This call operates the face recognition hardware and starts capturing images.
      * It terminates when
      * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
      * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, at
      * which point the object is no longer valid. The operation can be canceled by using the
-     * provided {@code cancel} object.
+     * provided cancel object.
      *
-     * @param crypto   the cryptographic operations to use for authentication or {@code null} if
-     *                 none required
-     * @param cancel   an object that can be used to cancel authentication or {@code null} if not
-     *                 needed
+     * @param crypto   object associated with the call or null if none required
+     * @param cancel   an object that can be used to cancel authentication
      * @param callback an object to receive authentication events
-     * @param handler  an optional handler to handle callback events or {@code null} to obtain main
-     *                 {@link Executor} from {@link Context}
+     * @param handler  an optional handler to handle callback events
      * @param options  additional options to customize this request
      * @throws IllegalArgumentException if the crypto operation is not supported or is not backed
      *                                  by
@@ -244,14 +235,6 @@
     public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
             @NonNull AuthenticationCallback callback, @Nullable Handler handler,
             @NonNull FaceAuthenticateOptions options) {
-        authenticate(crypto, cancel, callback, createExecutorForHandlerIfNeeded(handler),
-                options, false /* allowBackgroundAuthentication */);
-    }
-
-    @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL, USE_BACKGROUND_FACE_AUTHENTICATION})
-    private void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
-            @NonNull AuthenticationCallback callback, @NonNull Executor executor,
-            @NonNull FaceAuthenticateOptions options, boolean allowBackgroundAuthentication) {
         if (callback == null) {
             throw new IllegalArgumentException("Must supply an authentication callback");
         }
@@ -266,15 +249,13 @@
 
         if (mService != null) {
             try {
-                mExecutor = executor;
+                useHandler(handler);
                 mAuthenticationCallback = callback;
                 mCryptoObject = crypto;
                 final long operationId = crypto != null ? crypto.getOpId() : 0;
                 Trace.beginSection("FaceManager#authenticate");
-                final long authId = allowBackgroundAuthentication
-                        ? mService.authenticateInBackground(
-                                mToken, operationId, mServiceReceiver, options)
-                        : mService.authenticate(mToken, operationId, mServiceReceiver, options);
+                final long authId = mService.authenticate(
+                        mToken, operationId, mServiceReceiver, options);
                 if (cancel != null) {
                     cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
                 }
@@ -292,67 +273,6 @@
     }
 
     /**
-     * Request background face authentication.
-     *
-     * <p>This call operates the face recognition hardware and starts capturing images.
-     * It terminates when
-     * {@link BiometricPrompt.AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
-     * {@link BiometricPrompt.AuthenticationCallback#onAuthenticationSucceeded(
-     * BiometricPrompt.AuthenticationResult)} is called, at which point the object is no longer
-     * valid. The operation can be canceled by using the provided cancel object.
-     *
-     * <p>See {@link BiometricPrompt#authenticate} for more details. Please use
-     * {@link BiometricPrompt} for face authentication unless the experience must be customized for
-     * unique system-level utilities, like the lock screen or ambient background usage.
-     *
-     * @param executor the specified {@link Executor} to handle callback events; if {@code null},
-     *                 the callback will be executed on the main {@link Executor}.
-     * @param crypto   the cryptographic operations to use for authentication or {@code null} if
-     *                 none required.
-     * @param cancel   an object that can be used to cancel authentication or {@code null} if not
-     *                 needed.
-     * @param callback an object to receive authentication events.
-     * @throws IllegalArgumentException if the crypto operation is not supported or is not backed
-     *                                  by
-     *                                  <a href="{@docRoot}training/articles/keystore.html">Android
-     *                                  Keystore facility</a>.
-     * @hide
-     */
-    @RequiresPermission(USE_BACKGROUND_FACE_AUTHENTICATION)
-    @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
-    @SystemApi
-    public void authenticateInBackground(@Nullable Executor executor,
-            @Nullable BiometricPrompt.CryptoObject crypto, @Nullable CancellationSignal cancel,
-            @NonNull BiometricPrompt.AuthenticationCallback callback) {
-        authenticate(crypto, cancel, new AuthenticationCallback() {
-                    @Override
-                    public void onAuthenticationError(int errorCode, CharSequence errString) {
-                        callback.onAuthenticationError(errorCode, errString);
-                    }
-
-                    @Override
-                    public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
-                        callback.onAuthenticationHelp(helpCode, helpString);
-                    }
-
-                    @Override
-                    public void onAuthenticationSucceeded(AuthenticationResult result) {
-                        callback.onAuthenticationSucceeded(
-                                new BiometricPrompt.AuthenticationResult(
-                                        crypto,
-                                        BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC));
-                    }
-
-                    @Override
-                    public void onAuthenticationFailed() {
-                        callback.onAuthenticationFailed();
-                    }
-                }, executor == null ? mContext.getMainExecutor() : executor,
-                new FaceAuthenticateOptions.Builder().build(),
-                true /* allowBackgroundAuthentication */);
-    }
-
-    /**
      * Uses the face hardware to detect for the presence of a face, without giving details about
      * accept/reject/lockout.
      * @hide
@@ -710,14 +630,12 @@
     }
 
     /**
-     * Determine if there are enrolled {@link Face} templates.
+     * Determine if there is a face enrolled.
      *
-     * @return {@code true} if there are enrolled {@link Face} templates, {@code false} otherwise
+     * @return true if a face is enrolled, false otherwise
      * @hide
      */
-    @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL, USE_BACKGROUND_FACE_AUTHENTICATION})
-    @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
-    @SystemApi
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
     public boolean hasEnrolledTemplates() {
         return hasEnrolledTemplates(UserHandle.myUserId());
     }
@@ -882,7 +800,7 @@
                                             PowerManager.PARTIAL_WAKE_LOCK,
                                             "faceLockoutResetCallback");
                                     wakeLock.acquire();
-                                    mExecutor.execute(() -> {
+                                    mHandler.post(() -> {
                                         try {
                                             callback.onLockoutReset(sensorId);
                                         } finally {
@@ -1352,6 +1270,70 @@
         }
     }
 
+    private class MyHandler extends Handler {
+        private MyHandler(Context context) {
+            super(context.getMainLooper());
+        }
+
+        private MyHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(android.os.Message msg) {
+            Trace.beginSection("FaceManager#handleMessage: " + Integer.toString(msg.what));
+            switch (msg.what) {
+                case MSG_ENROLL_RESULT:
+                    sendEnrollResult((Face) msg.obj, msg.arg1 /* remaining */);
+                    break;
+                case MSG_ACQUIRED:
+                    sendAcquiredResult(msg.arg1 /* acquire info */, msg.arg2 /* vendorCode */);
+                    break;
+                case MSG_AUTHENTICATION_SUCCEEDED:
+                    sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */,
+                            msg.arg2 == 1 /* isStrongBiometric */);
+                    break;
+                case MSG_AUTHENTICATION_FAILED:
+                    sendAuthenticatedFailed();
+                    break;
+                case MSG_ERROR:
+                    sendErrorResult(msg.arg1 /* errMsgId */, msg.arg2 /* vendorCode */);
+                    break;
+                case MSG_REMOVED:
+                    sendRemovedResult((Face) msg.obj, msg.arg1 /* remaining */);
+                    break;
+                case MSG_SET_FEATURE_COMPLETED:
+                    sendSetFeatureCompleted((boolean) msg.obj /* success */,
+                            msg.arg1 /* feature */);
+                    break;
+                case MSG_GET_FEATURE_COMPLETED:
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    sendGetFeatureCompleted((boolean) args.arg1 /* success */,
+                            (int[]) args.arg2 /* features */,
+                            (boolean[]) args.arg3 /* featureState */);
+                    args.recycle();
+                    break;
+                case MSG_CHALLENGE_GENERATED:
+                    sendChallengeGenerated(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
+                            (long) msg.obj /* challenge */);
+                    break;
+                case MSG_FACE_DETECTED:
+                    sendFaceDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
+                            (boolean) msg.obj /* isStrongBiometric */);
+                    break;
+                case MSG_AUTHENTICATION_FRAME:
+                    sendAuthenticationFrame((FaceAuthenticationFrame) msg.obj /* frame */);
+                    break;
+                case MSG_ENROLLMENT_FRAME:
+                    sendEnrollmentFrame((FaceEnrollFrame) msg.obj /* frame */);
+                    break;
+                default:
+                    Slog.w(TAG, "Unknown message: " + msg.what);
+            }
+            Trace.endSection();
+        }
+    }
+
     private void sendSetFeatureCompleted(boolean success, int feature) {
         if (mSetFeatureCallback == null) {
             return;
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index b98c0cb..553d9f7 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -47,7 +47,7 @@
     byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer);
 
     // Retrieve static sensor properties for all face sensors
-    @EnforcePermission(anyOf = {"USE_BIOMETRIC_INTERNAL", "USE_BACKGROUND_FACE_AUTHENTICATION"})
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(String opPackageName);
 
     // Retrieve static sensor properties for the specified sensor
@@ -59,11 +59,6 @@
     long authenticate(IBinder token, long operationId, IFaceServiceReceiver receiver,
             in FaceAuthenticateOptions options);
 
-    // Authenticate with a face. A requestId is returned that can be used to cancel this operation.
-    @EnforcePermission("USE_BACKGROUND_FACE_AUTHENTICATION")
-    long authenticateInBackground(IBinder token, long operationId, IFaceServiceReceiver receiver,
-            in FaceAuthenticateOptions options);
-
     // Uses the face hardware to detect for the presence of a face, without giving details
     // about accept/reject/lockout. A requestId is returned that can be used to cancel this
     // operation.
@@ -138,7 +133,7 @@
     void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName, long challenge);
 
     // Determine if a user has at least one enrolled face
-    @EnforcePermission(anyOf = {"USE_BIOMETRIC_INTERNAL", "USE_BACKGROUND_FACE_AUTHENTICATION"})
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName);
 
     // Return the LockoutTracker status for the specified user
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 81e321d..b0f69f5 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -83,7 +83,8 @@
 
 /**
  * A class that coordinates access to the fingerprint hardware.
- * @deprecated See {@link BiometricPrompt} which shows a system-provided dialog upon starting
+ *
+ * @removed See {@link BiometricPrompt} which shows a system-provided dialog upon starting
  * authentication. In a world where devices may have different types of biometric authentication,
  * it's much more realistic to have a system-provided authentication dialog since the method may
  * vary by vendor/device.
@@ -94,7 +95,6 @@
 @RequiresFeature(PackageManager.FEATURE_FINGERPRINT)
 public class FingerprintManager implements BiometricAuthenticator, BiometricFingerprintConstants {
     private static final String TAG = "FingerprintManager";
-    private static final boolean DEBUG = true;
     private static final int MSG_ENROLL_RESULT = 100;
     private static final int MSG_ACQUIRED = 101;
     private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
@@ -196,6 +196,7 @@
 
     /**
      * Retrieves a test session for FingerprintManager.
+     *
      * @hide
      */
     @TestApi
@@ -254,9 +255,10 @@
     }
 
     /**
-     * A wrapper class for the crypto objects supported by FingerprintManager. Currently the
+     * A wrapper class for the crypto objects supported by FingerprintManager. Currently, the
      * framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects.
-     * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}
+     *
+     * @removed See {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}
      */
     @Deprecated
     public static final class CryptoObject extends android.hardware.biometrics.CryptoObject {
@@ -330,7 +332,8 @@
     /**
      * Container for callback data from {@link FingerprintManager#authenticate(CryptoObject,
      *     CancellationSignal, int, AuthenticationCallback, Handler)}.
-     * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationResult}
+     *
+     * @removed See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationResult}
      */
     @Deprecated
     public static class AuthenticationResult {
@@ -392,7 +395,8 @@
      * FingerprintManager#authenticate(CryptoObject, CancellationSignal,
      * int, AuthenticationCallback, Handler) } must provide an implementation of this for listening to
      * fingerprint events.
-     * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback}
+     *
+     * @removed See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback}
      */
     @Deprecated
     public static abstract class AuthenticationCallback
@@ -455,6 +459,7 @@
     /**
      * Callback structure provided for {@link #detectFingerprint(CancellationSignal,
      * FingerprintDetectionCallback, int, Surface)}.
+     *
      * @hide
      */
     public interface FingerprintDetectionCallback {
@@ -608,7 +613,8 @@
      *         by <a href="{@docRoot}training/articles/keystore.html">Android Keystore
      *         facility</a>.
      * @throws IllegalStateException if the crypto primitive is not initialized.
-     * @deprecated See {@link BiometricPrompt#authenticate(CancellationSignal, Executor,
+     *
+     * @removed See {@link BiometricPrompt#authenticate(CancellationSignal, Executor,
      * BiometricPrompt.AuthenticationCallback)} and {@link BiometricPrompt#authenticate(
      * BiometricPrompt.CryptoObject, CancellationSignal, Executor,
      * BiometricPrompt.AuthenticationCallback)}
@@ -623,6 +629,7 @@
     /**
      * Per-user version of authenticate.
      * @deprecated use {@link #authenticate(CryptoObject, CancellationSignal, AuthenticationCallback, Handler, FingerprintAuthenticateOptions)}.
+     *
      * @hide
      */
     @Deprecated
@@ -635,6 +642,7 @@
     /**
      * Per-user and per-sensor version of authenticate.
      * @deprecated use {@link #authenticate(CryptoObject, CancellationSignal, AuthenticationCallback, Handler, FingerprintAuthenticateOptions)}.
+     *
      * @hide
      */
     @Deprecated
@@ -651,6 +659,7 @@
 
     /**
      * Version of authenticate with additional options.
+     *
      * @hide
      */
     @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT})
@@ -698,6 +707,7 @@
     /**
      * Uses the fingerprint hardware to detect for the presence of a finger, without giving details
      * about accept/reject/lockout.
+     *
      * @hide
      */
     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -740,6 +750,7 @@
      * @param callback an object to receive enrollment events
      * @param shouldLogMetrics a flag that indicates if enrollment failure/success metrics
      * should be logged.
+     *
      * @hide
      */
     @RequiresPermission(MANAGE_FINGERPRINT)
@@ -810,6 +821,7 @@
     /**
      * Same as {@link #generateChallenge(int, GenerateChallengeCallback)}, but assumes the first
      * enumerated sensor.
+     *
      * @hide
      */
     @RequiresPermission(MANAGE_FINGERPRINT)
@@ -824,6 +836,7 @@
 
     /**
      * Revokes the specified challenge.
+     *
      * @hide
      */
     @RequiresPermission(MANAGE_FINGERPRINT)
@@ -849,6 +862,7 @@
      * @param sensorId Sensor ID that this operation takes effect for
      * @param userId User ID that this operation takes effect for.
      * @param hardwareAuthToken An opaque token returned by password confirmation.
+     *
      * @hide
      */
     @RequiresPermission(RESET_FINGERPRINT_LOCKOUT)
@@ -886,6 +900,7 @@
 
     /**
      * Removes all fingerprint templates for the given user.
+     *
      * @hide
      */
     @RequiresPermission(MANAGE_FINGERPRINT)
@@ -1005,6 +1020,7 @@
     /**
      * Forwards BiometricStateListener to FingerprintService
      * @param listener new BiometricStateListener being added
+     *
      * @hide
      */
     public void registerBiometricStateListener(@NonNull BiometricStateListener listener) {
@@ -1156,7 +1172,8 @@
     }
 
     /**
-     * This is triggered by SideFpsEventHandler
+     * This is triggered by SideFpsEventHandler.
+     *
      * @hide
      */
     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -1169,7 +1186,8 @@
      * Determine if there is at least one fingerprint enrolled.
      *
      * @return true if at least one fingerprint is enrolled, false otherwise
-     * @deprecated See {@link BiometricPrompt} and
+     *
+     * @removed See {@link BiometricPrompt} and
      * {@link FingerprintManager#FINGERPRINT_ERROR_NO_FINGERPRINTS}
      */
     @Deprecated
@@ -1203,7 +1221,8 @@
      * Determine if fingerprint hardware is present and functional.
      *
      * @return true if hardware is present and functional, false otherwise.
-     * @deprecated See {@link BiometricPrompt} and
+     *
+     * @removed See {@link BiometricPrompt} and
      * {@link FingerprintManager#FINGERPRINT_ERROR_HW_UNAVAILABLE}
      */
     @Deprecated
@@ -1229,6 +1248,7 @@
 
     /**
      * Get statically configured sensor properties.
+     *
      * @hide
      */
     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -1247,6 +1267,7 @@
     /**
      * Returns whether the device has a power button fingerprint sensor.
      * @return boolean indicating whether power button is fingerprint sensor
+     *
      * @hide
      */
     public boolean isPowerbuttonFps() {
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 1f54959..2816f77 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -27,6 +27,7 @@
 import android.hardware.input.IKeyboardBacklightState;
 import android.hardware.input.IStickyModifierStateListener;
 import android.hardware.input.ITabletModeChangedListener;
+import android.hardware.input.KeyboardLayoutSelectionResult;
 import android.hardware.input.TouchCalibration;
 import android.os.CombinedVibration;
 import android.hardware.input.IInputSensorEventListener;
@@ -120,8 +121,9 @@
             String keyboardLayoutDescriptor);
 
     // New Keyboard layout config APIs
-    String getKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier, int userId,
-            in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype);
+    KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
+            in InputDeviceIdentifier identifier, int userId, in InputMethodInfo imeInfo,
+            in InputMethodSubtype imeSubtype);
 
     @EnforcePermission("SET_KEYBOARD_LAYOUT")
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 744dfae9..a1242fb 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -784,10 +784,10 @@
      *
      * @hide
      */
-    @Nullable
-    public String getKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
-            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
-            @Nullable InputMethodSubtype imeSubtype) {
+    @NonNull
+    public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
+            @NonNull InputDeviceIdentifier identifier, @UserIdInt int userId,
+            @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) {
         try {
             return mIm.getKeyboardLayoutForInputDevice(identifier, userId, imeInfo, imeSubtype);
         } catch (RemoteException ex) {
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 62473c5..4328d9f 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -28,6 +28,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.TestApi;
+import android.app.AppGlobals;
 import android.content.Context;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -393,15 +394,34 @@
      *
      * @hide
      */
-    public static boolean isStylusPointerIconEnabled(@NonNull Context context) {
+    public static boolean isStylusPointerIconEnabled(@NonNull Context context,
+            boolean forceReloadSetting) {
         if (InputProperties.force_enable_stylus_pointer_icon().orElse(false)) {
+            // Sysprop override is set
             return true;
         }
-        return context.getResources()
-                        .getBoolean(com.android.internal.R.bool.config_enableStylusPointerIcon)
-                && Settings.Secure.getIntForUser(context.getContentResolver(),
-                        Settings.Secure.STYLUS_POINTER_ICON_ENABLED,
-                        DEFAULT_STYLUS_POINTER_ICON_ENABLED, UserHandle.USER_CURRENT_OR_SELF) != 0;
+        if (!context.getResources().getBoolean(
+                com.android.internal.R.bool.config_enableStylusPointerIcon)) {
+            // Stylus pointer icons are disabled for the build
+            return false;
+        }
+        if (forceReloadSetting) {
+            return Settings.Secure.getIntForUser(context.getContentResolver(),
+                    Settings.Secure.STYLUS_POINTER_ICON_ENABLED,
+                    DEFAULT_STYLUS_POINTER_ICON_ENABLED, UserHandle.USER_CURRENT_OR_SELF) != 0;
+        }
+        return AppGlobals.getIntCoreSetting(Settings.Secure.STYLUS_POINTER_ICON_ENABLED,
+                DEFAULT_STYLUS_POINTER_ICON_ENABLED) != 0;
+    }
+
+    /**
+     * Whether a pointer icon will be shown over the location of a stylus pointer.
+     *
+     * @hide
+     * @see #isStylusPointerIconEnabled(Context, boolean)
+     */
+    public static boolean isStylusPointerIconEnabled(@NonNull Context context) {
+        return isStylusPointerIconEnabled(context, false /* forceReloadSetting */);
     }
 
     /**
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ShadeSceneModule.kt b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.aidl
similarity index 79%
rename from packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ShadeSceneModule.kt
rename to core/java/android/hardware/input/KeyboardLayoutSelectionResult.aidl
index 232c421..13be2ff 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ShadeSceneModule.kt
+++ b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023 The Android Open Source Project
+ * Copyright 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.systemui.scene
+package android.hardware.input;
 
-import dagger.Module
-
-@Module interface ShadeSceneModule
+parcelable KeyboardLayoutSelectionResult;
diff --git a/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java
new file mode 100644
index 0000000..5a1c947
--- /dev/null
+++ b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Provides information about the selected layout and the selection criteria when the caller calls
+ * {@link InputManager#getKeyboardLayoutForInputDevice(InputDeviceIdentifier, int, InputMethodInfo,
+ * InputMethodSubtype)}
+ *
+ * @hide
+ */
+
+@DataClass(genParcelable = true, genToString = true, genEqualsHashCode = true)
+public final class KeyboardLayoutSelectionResult implements Parcelable {
+    @Nullable
+    private final String mLayoutDescriptor;
+
+    /** Unspecified layout selection criteria */
+    public static final int LAYOUT_SELECTION_CRITERIA_UNSPECIFIED = 0;
+
+    /** Manual selection by user */
+    public static final int LAYOUT_SELECTION_CRITERIA_USER = 1;
+
+    /** Auto-detection based on device provided language tag and layout type */
+    public static final int LAYOUT_SELECTION_CRITERIA_DEVICE = 2;
+
+    /** Auto-detection based on IME provided language tag and layout type */
+    public static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD = 3;
+
+    /** Default selection */
+    public static final int LAYOUT_SELECTION_CRITERIA_DEFAULT = 4;
+
+    /** Failed layout selection */
+    public static final KeyboardLayoutSelectionResult FAILED = new KeyboardLayoutSelectionResult(
+            null, LAYOUT_SELECTION_CRITERIA_UNSPECIFIED);
+
+    @LayoutSelectionCriteria
+    private final int mSelectionCriteria;
+
+
+
+    // 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/hardware/input/KeyboardLayoutSelectionResult.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @IntDef(prefix = "LAYOUT_SELECTION_CRITERIA_", value = {
+        LAYOUT_SELECTION_CRITERIA_UNSPECIFIED,
+        LAYOUT_SELECTION_CRITERIA_USER,
+        LAYOUT_SELECTION_CRITERIA_DEVICE,
+        LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+        LAYOUT_SELECTION_CRITERIA_DEFAULT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface LayoutSelectionCriteria {}
+
+    @DataClass.Generated.Member
+    public static String layoutSelectionCriteriaToString(@LayoutSelectionCriteria int value) {
+        switch (value) {
+            case LAYOUT_SELECTION_CRITERIA_UNSPECIFIED:
+                    return "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED";
+            case LAYOUT_SELECTION_CRITERIA_USER:
+                    return "LAYOUT_SELECTION_CRITERIA_USER";
+            case LAYOUT_SELECTION_CRITERIA_DEVICE:
+                    return "LAYOUT_SELECTION_CRITERIA_DEVICE";
+            case LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD:
+                    return "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD";
+            case LAYOUT_SELECTION_CRITERIA_DEFAULT:
+                    return "LAYOUT_SELECTION_CRITERIA_DEFAULT";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    @DataClass.Generated.Member
+    public KeyboardLayoutSelectionResult(
+            @Nullable String layoutDescriptor,
+            @LayoutSelectionCriteria int selectionCriteria) {
+        this.mLayoutDescriptor = layoutDescriptor;
+        this.mSelectionCriteria = selectionCriteria;
+
+        if (!(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_UNSPECIFIED)
+                && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_USER)
+                && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEVICE)
+                && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD)
+                && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEFAULT)) {
+            throw new java.lang.IllegalArgumentException(
+                    "selectionCriteria was " + mSelectionCriteria + " but must be one of: "
+                            + "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED(" + LAYOUT_SELECTION_CRITERIA_UNSPECIFIED + "), "
+                            + "LAYOUT_SELECTION_CRITERIA_USER(" + LAYOUT_SELECTION_CRITERIA_USER + "), "
+                            + "LAYOUT_SELECTION_CRITERIA_DEVICE(" + LAYOUT_SELECTION_CRITERIA_DEVICE + "), "
+                            + "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD(" + LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD + "), "
+                            + "LAYOUT_SELECTION_CRITERIA_DEFAULT(" + LAYOUT_SELECTION_CRITERIA_DEFAULT + ")");
+        }
+
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public @Nullable String getLayoutDescriptor() {
+        return mLayoutDescriptor;
+    }
+
+    @DataClass.Generated.Member
+    public @LayoutSelectionCriteria int getSelectionCriteria() {
+        return mSelectionCriteria;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "KeyboardLayoutSelectionResult { " +
+                "layoutDescriptor = " + mLayoutDescriptor + ", " +
+                "selectionCriteria = " + layoutSelectionCriteriaToString(mSelectionCriteria) +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(KeyboardLayoutSelectionResult other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        KeyboardLayoutSelectionResult that = (KeyboardLayoutSelectionResult) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && java.util.Objects.equals(mLayoutDescriptor, that.mLayoutDescriptor)
+                && mSelectionCriteria == that.mSelectionCriteria;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mLayoutDescriptor);
+        _hash = 31 * _hash + mSelectionCriteria;
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mLayoutDescriptor != null) flg |= 0x1;
+        dest.writeByte(flg);
+        if (mLayoutDescriptor != null) dest.writeString(mLayoutDescriptor);
+        dest.writeInt(mSelectionCriteria);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ KeyboardLayoutSelectionResult(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        String layoutDescriptor = (flg & 0x1) == 0 ? null : in.readString();
+        int selectionCriteria = in.readInt();
+
+        this.mLayoutDescriptor = layoutDescriptor;
+        this.mSelectionCriteria = selectionCriteria;
+
+        if (!(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_UNSPECIFIED)
+                && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_USER)
+                && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEVICE)
+                && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD)
+                && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEFAULT)) {
+            throw new java.lang.IllegalArgumentException(
+                    "selectionCriteria was " + mSelectionCriteria + " but must be one of: "
+                            + "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED(" + LAYOUT_SELECTION_CRITERIA_UNSPECIFIED + "), "
+                            + "LAYOUT_SELECTION_CRITERIA_USER(" + LAYOUT_SELECTION_CRITERIA_USER + "), "
+                            + "LAYOUT_SELECTION_CRITERIA_DEVICE(" + LAYOUT_SELECTION_CRITERIA_DEVICE + "), "
+                            + "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD(" + LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD + "), "
+                            + "LAYOUT_SELECTION_CRITERIA_DEFAULT(" + LAYOUT_SELECTION_CRITERIA_DEFAULT + ")");
+        }
+
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<KeyboardLayoutSelectionResult> CREATOR
+            = new Parcelable.Creator<KeyboardLayoutSelectionResult>() {
+        @Override
+        public KeyboardLayoutSelectionResult[] newArray(int size) {
+            return new KeyboardLayoutSelectionResult[size];
+        }
+
+        @Override
+        public KeyboardLayoutSelectionResult createFromParcel(@NonNull android.os.Parcel in) {
+            return new KeyboardLayoutSelectionResult(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1709568115865L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java",
+            inputSignatures = "private final @android.annotation.Nullable java.lang.String mLayoutDescriptor\npublic static final  int LAYOUT_SELECTION_CRITERIA_UNSPECIFIED\npublic static final  int LAYOUT_SELECTION_CRITERIA_USER\npublic static final  int LAYOUT_SELECTION_CRITERIA_DEVICE\npublic static final  int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD\npublic static final  int LAYOUT_SELECTION_CRITERIA_DEFAULT\npublic static final  android.hardware.input.KeyboardLayoutSelectionResult FAILED\nprivate final @android.hardware.input.KeyboardLayoutSelectionResult.LayoutSelectionCriteria int mSelectionCriteria\nclass KeyboardLayoutSelectionResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genToString=true, genEqualsHashCode=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index a3a2a2e..c5167db 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -304,7 +304,11 @@
      *
      * @param id primary identifier of a program to fetch
      * @return the program info, or null if there is no such program on the list
+     *
+     * @deprecated Use {@link #getProgramInfos(ProgramSelector.Identifier)} to get all programs
+     * with the given primary identifier
      */
+    @Deprecated
     public @Nullable RadioManager.ProgramInfo get(@NonNull ProgramSelector.Identifier id) {
         Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries;
         synchronized (mLock) {
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index a968c6f..0740374 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -312,15 +312,23 @@
     public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10;
     /**
      * 1: AM, 2:FM
+     * @deprecated use {@link #IDENTIFIER_TYPE_DRMO_FREQUENCY} instead
      */
+    @Deprecated
     public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11;
     /**
      * 32bit primary identifier for SiriusXM Satellite Radio.
+     *
+     * @deprecated SiriusXM Satellite Radio is not supported
      */
+    @Deprecated
     public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12;
     /**
      * 0-999 range
+     *
+     * @deprecated SiriusXM Satellite Radio is not supported
      */
+    @Deprecated
     public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13;
     /**
      * 44bit compound primary identifier for Digital Audio Broadcasting and
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 61cf8901..da6c686 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -166,7 +166,12 @@
      * analog handover state managed from the HAL implementation side.
      *
      * <p>Some radio technologies may not support this, i.e. DAB.
+     *
+     * @deprecated Use {@link #CONFIG_FORCE_ANALOG_FM} instead. If {@link #CONFIG_FORCE_ANALOG_FM}
+     * is supported in HAL, {@link RadioTuner#setConfigFlag} and {@link RadioTuner#isConfigFlagSet}
+     * with CONFIG_FORCE_ANALOG will set/get the value of {@link #CONFIG_FORCE_ANALOG_FM}.
      */
+    @Deprecated
     public static final int CONFIG_FORCE_ANALOG = 2;
     /**
      * Forces the digital playback for the supporting radio technology.
diff --git a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
index 63ae28f..9e487e1 100644
--- a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
@@ -20,3 +20,10 @@
     description: "Enable reporting USB data compliance warnings from HAL when set"
     bug: "296119135"
 }
+
+flag {
+    name: "enable_usb_data_signal_staking"
+    namespace: "preload_safety"
+    description: "Enables signal API with staking"
+    bug: "287498482"
+}
\ No newline at end of file
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index f5b58b9..9dc8c5d 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -453,7 +453,7 @@
 
     @BinderThread
     @Override
-    public void showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
+    public void showSoftInput(IBinder showInputToken, @NonNull ImeTracker.Token statsToken,
             @InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) {
         ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
         mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_SHOW_SOFT_INPUT,
@@ -462,7 +462,7 @@
 
     @BinderThread
     @Override
-    public void hideSoftInput(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken,
+    public void hideSoftInput(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken,
             int flags, ResultReceiver resultReceiver) {
         ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
         mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_HIDE_SOFT_INPUT,
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 2c7ca27..4dbdd91 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -701,7 +701,13 @@
      */
     private IBinder mCurHideInputToken;
 
-    /** The token tracking the current IME request or {@code null} otherwise. */
+    /**
+     * The token tracking the current IME request.
+     *
+     * <p> This exists as a workaround to changing the signatures of public methods. It will get
+     * set to a {@code non-null} value before every call that uses it, stored locally inside the
+     * callee, and immediately after reset to {@code null} from the callee.
+     */
     @Nullable
     private ImeTracker.Token mCurStatsToken;
 
@@ -907,14 +913,13 @@
         @MainThread
         @Override
         public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver,
-                IBinder hideInputToken, @Nullable ImeTracker.Token statsToken) {
+                IBinder hideInputToken, @NonNull ImeTracker.Token statsToken) {
             mSystemCallingHideSoftInput = true;
             mCurHideInputToken = hideInputToken;
             mCurStatsToken = statsToken;
             try {
                 hideSoftInput(flags, resultReceiver);
             } finally {
-                mCurStatsToken = null;
                 mCurHideInputToken = null;
                 mSystemCallingHideSoftInput = false;
             }
@@ -926,23 +931,33 @@
         @MainThread
         @Override
         public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
-            ImeTracker.forLogging().onProgress(
-                    mCurStatsToken, ImeTracker.PHASE_IME_HIDE_SOFT_INPUT);
             if (DEBUG) Log.v(TAG, "hideSoftInput()");
+
+            final var statsToken = mCurStatsToken != null ? mCurStatsToken
+                    : createStatsToken(false /* show */,
+                            SoftInputShowHideReason.HIDE_SOFT_INPUT_LEGACY_DIRECT,
+                            ImeTracker.isFromUser(mRootView));
+            mCurStatsToken = null;
+
+            // TODO(b/148086656): Disallow IME developers from calling InputMethodImpl methods.
             if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R
                     && !mSystemCallingHideSoftInput) {
                 Log.e(TAG, "IME shouldn't call hideSoftInput on itself."
                         + " Use requestHideSelf(int) itself");
+                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_IME_HIDE_SOFT_INPUT);
                 return;
             }
+            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_HIDE_SOFT_INPUT);
+
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.hideSoftInput");
             ImeTracing.getInstance().triggerServiceDump(
                     "InputMethodService.InputMethodImpl#hideSoftInput", mDumper,
                     null /* icProto */);
             final boolean wasVisible = isInputViewShown();
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.hideSoftInput");
 
             mShowInputFlags = 0;
             mShowInputRequested = false;
+            mCurStatsToken = statsToken;
             hideWindow();
             final boolean isVisible = isInputViewShown();
             final boolean visibilityChanged = isVisible != wasVisible;
@@ -963,14 +978,13 @@
         @Override
         public void showSoftInputWithToken(@InputMethod.ShowFlags int flags,
                 ResultReceiver resultReceiver, IBinder showInputToken,
-                @Nullable ImeTracker.Token statsToken) {
+                @NonNull ImeTracker.Token statsToken) {
             mSystemCallingShowSoftInput = true;
             mCurShowInputToken = showInputToken;
             mCurStatsToken = statsToken;
             try {
                 showSoftInput(flags, resultReceiver);
             } finally {
-                mCurStatsToken = null;
                 mCurShowInputToken = null;
                 mSystemCallingShowSoftInput = false;
             }
@@ -982,16 +996,23 @@
         @MainThread
         @Override
         public void showSoftInput(@InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) {
-            ImeTracker.forLogging().onProgress(
-                    mCurStatsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT);
             if (DEBUG) Log.v(TAG, "showSoftInput()");
+
+            final var statsToken = mCurStatsToken != null ? mCurStatsToken
+                    : createStatsToken(true /* show */,
+                            SoftInputShowHideReason.SHOW_SOFT_INPUT_LEGACY_DIRECT,
+                            ImeTracker.isFromUser(mRootView));
+            mCurStatsToken = null;
+
             // TODO(b/148086656): Disallow IME developers from calling InputMethodImpl methods.
             if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R
                     && !mSystemCallingShowSoftInput) {
-                Log.e(TAG," IME shouldn't call showSoftInput on itself."
+                Log.e(TAG, "IME shouldn't call showSoftInput on itself."
                         + " Use requestShowSelf(int) itself");
+                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT);
                 return;
             }
+            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT);
 
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.showSoftInput");
             ImeTracing.getInstance().triggerServiceDump(
@@ -999,11 +1020,12 @@
                     null /* icProto */);
             final boolean wasVisible = isInputViewShown();
             if (dispatchOnShowInputRequested(flags, false)) {
-                ImeTracker.forLogging().onProgress(mCurStatsToken,
+                ImeTracker.forLogging().onProgress(statsToken,
                         ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE);
-                showWindow(true);
+                mCurStatsToken = statsToken;
+                showWindow(true /* showInput */);
             } else {
-                ImeTracker.forLogging().onFailed(mCurStatsToken,
+                ImeTracker.forLogging().onFailed(statsToken,
                         ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE);
             }
             setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition);
@@ -1895,21 +1917,23 @@
             if (showingInput) {
                 // If we were last showing the soft keyboard, try to do so again.
                 if (dispatchOnShowInputRequested(showFlags, true)) {
-                    showWindow(true);
+                    showWindowWithToken(true /* showInput */,
+                            SoftInputShowHideReason.RESET_NEW_CONFIGURATION);
                     if (completions != null) {
                         mCurCompletions = completions;
                         onDisplayCompletions(completions);
                     }
                 } else {
-                    hideWindow();
+                    hideWindowWithToken(SoftInputShowHideReason.RESET_NEW_CONFIGURATION);
                 }
             } else if (mCandidatesVisibility == View.VISIBLE) {
                 // If the candidates are currently visible, make sure the
                 // window is shown for them.
-                showWindow(false);
+                showWindowWithToken(false /* showInput */,
+                        SoftInputShowHideReason.RESET_NEW_CONFIGURATION);
             } else {
                 // Otherwise hide the window.
-                hideWindow();
+                hideWindowWithToken(SoftInputShowHideReason.RESET_NEW_CONFIGURATION);
             }
             // If user uses hard keyboard, IME button should always be shown.
             boolean showing = onEvaluateInputViewShown();
@@ -2368,13 +2392,15 @@
             // has not asked for the input view to be shown, then we need
             // to update whether the window is shown.
             if (shown) {
-                showWindow(false);
+                showWindowWithToken(false /* showInput */,
+                        SoftInputShowHideReason.UPDATE_CANDIDATES_VIEW_VISIBILITY);
             } else {
-                hideWindow();
+                hideWindowWithToken(
+                        SoftInputShowHideReason.UPDATE_CANDIDATES_VIEW_VISIBILITY);
             }
         }
     }
-    
+
     void updateCandidatesVisibility(boolean shown) {
         int vis = shown ? View.VISIBLE : getCandidatesHiddenVisibility();
         if (mCandidatesVisibility != vis) {
@@ -3009,6 +3035,19 @@
         return result;
     }
 
+    /**
+     * Utility function that creates an IME request tracking token before
+     * calling {@link #showWindow}.
+     *
+     * @param showInput whether the input window should be shown.
+     * @param reason the reason why the IME request was created.
+     */
+    private void showWindowWithToken(boolean showInput, @SoftInputShowHideReason int reason) {
+        mCurStatsToken = createStatsToken(true /* show */, reason,
+                ImeTracker.isFromUser(mRootView));
+        showWindow(showInput);
+    }
+
     public void showWindow(boolean showInput) {
         if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput
                 + " mShowInputRequested=" + mShowInputRequested
@@ -3018,11 +3057,20 @@
                 + " mInputStarted=" + mInputStarted
                 + " mShowInputFlags=" + mShowInputFlags);
 
+        final var statsToken = mCurStatsToken != null ? mCurStatsToken
+                : createStatsToken(true /* show */,
+                        SoftInputShowHideReason.SHOW_WINDOW_LEGACY_DIRECT,
+                        ImeTracker.isFromUser(mRootView));
+        mCurStatsToken = null;
+
         if (mInShowWindow) {
             Log.w(TAG, "Re-entrance in to showWindow");
+            ImeTracker.forLogging().onCancelled(statsToken, ImeTracker.PHASE_IME_SHOW_WINDOW);
             return;
         }
 
+        ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_SHOW_WINDOW);
+
         ImeTracing.getInstance().triggerServiceDump("InputMethodService#showWindow", mDumper,
                 null /* icProto */);
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.showWindow");
@@ -3046,7 +3094,7 @@
         if (DEBUG) Log.v(TAG, "showWindow: draw decorView!");
         mWindow.show();
         mDecorViewWasVisible = true;
-        applyVisibilityInInsetsConsumerIfNecessary(true);
+        applyVisibilityInInsetsConsumerIfNecessary(true /* setVisible */, statsToken);
         cancelImeSurfaceRemoval();
         mInShowWindow = false;
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -3137,13 +3185,15 @@
      * Applies the IME visibility in {@link android.view.ImeInsetsSourceConsumer}.
      *
      * @param setVisible {@code true} to make it visible, false to hide it.
+     * @param statsToken the token tracking the current IME request.
      */
-    private void applyVisibilityInInsetsConsumerIfNecessary(boolean setVisible) {
+    private void applyVisibilityInInsetsConsumerIfNecessary(boolean setVisible,
+            @NonNull ImeTracker.Token statsToken) {
         ImeTracing.getInstance().triggerServiceDump(
                 "InputMethodService#applyVisibilityInInsetsConsumerIfNecessary", mDumper,
                 null /* icProto */);
         mPrivOps.applyImeVisibilityAsync(setVisible
-                ? mCurShowInputToken : mCurHideInputToken, setVisible, mCurStatsToken);
+                ? mCurShowInputToken : mCurHideInputToken, setVisible, statsToken);
     }
 
     private void finishViews(boolean finishingInput) {
@@ -3159,12 +3209,35 @@
         mCandidatesViewStarted = false;
     }
 
+    /**
+     * Utility function that creates an IME request tracking token before
+     * calling {@link #hideWindow}.
+     *
+     * @param reason the reason why the IME request was created.
+     */
+    private void hideWindowWithToken(@SoftInputShowHideReason int reason) {
+        // TODO(b/303041796): this should be handled by ImeTracker.isFromUser after fixing it
+        //  to work with onClickListeners
+        final boolean isFromUser = ImeTracker.isFromUser(mRootView)
+                || reason == SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_BACK_KEY;
+        mCurStatsToken = createStatsToken(false /* show */, reason, isFromUser);
+        hideWindow();
+    }
+
     public void hideWindow() {
         if (DEBUG) Log.v(TAG, "CALL: hideWindow");
+
+        final var statsToken = mCurStatsToken != null ? mCurStatsToken
+                : createStatsToken(false /* show */,
+                        SoftInputShowHideReason.HIDE_WINDOW_LEGACY_DIRECT,
+                        ImeTracker.isFromUser(mRootView));
+        mCurStatsToken = null;
+
+        ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_HIDE_WINDOW);
         ImeTracing.getInstance().triggerServiceDump("InputMethodService#hideWindow", mDumper,
                 null /* icProto */);
         setImeWindowStatus(0, mBackDisposition);
-        applyVisibilityInInsetsConsumerIfNecessary(false);
+        applyVisibilityInInsetsConsumerIfNecessary(false /* setVisible */, statsToken);
         mWindowVisible = false;
         finishViews(false /* finishingInput */);
         if (mDecorViewVisible) {
@@ -3440,9 +3513,14 @@
 
     private void requestHideSelf(@InputMethodManager.HideFlags int flags,
             @SoftInputShowHideReason int reason) {
+        // TODO(b/303041796): this should be handled by ImeTracker.isFromUser after fixing it
+        //  to work with onClickListeners
+        final boolean isFromUser = ImeTracker.isFromUser(mRootView)
+                || reason == SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_BACK_KEY;
+        final var statsToken = createStatsToken(false /* show */, reason, isFromUser);
         ImeTracing.getInstance().triggerServiceDump("InputMethodService#requestHideSelf", mDumper,
                 null /* icProto */);
-        mPrivOps.hideMySoftInput(flags, reason);
+        mPrivOps.hideMySoftInput(statsToken, flags, reason);
     }
 
     /**
@@ -3450,9 +3528,16 @@
      * interact with it.
      */
     public final void requestShowSelf(@InputMethodManager.ShowFlags int flags) {
+        requestShowSelf(flags, SoftInputShowHideReason.SHOW_SOFT_INPUT_FROM_IME);
+    }
+
+    private void requestShowSelf(@InputMethodManager.ShowFlags int flags,
+            @SoftInputShowHideReason int reason) {
+        final var statsToken = createStatsToken(true /* show */, reason,
+                ImeTracker.isFromUser(mRootView));
         ImeTracing.getInstance().triggerServiceDump("InputMethodService#requestShowSelf", mDumper,
                 null /* icProto */);
-        mPrivOps.showMySoftInput(flags);
+        mPrivOps.showMySoftInput(statsToken, flags, reason);
     }
 
     private boolean handleBack(boolean doIt) {
@@ -3472,7 +3557,7 @@
                 // If we have the window visible for some other reason --
                 // most likely to show candidates -- then just get rid
                 // of it.  This really shouldn't happen, but just in case...
-                if (doIt) hideWindow();
+                if (doIt) hideWindowWithToken(SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_BACK_KEY);
             }
             return true;
         }
@@ -3627,10 +3712,11 @@
             @InputMethodManager.HideFlags int hideFlags) {
         if (DEBUG) Log.v(TAG, "toggleSoftInput()");
         if (isInputViewShown()) {
-            requestHideSelf(
-                    hideFlags, SoftInputShowHideReason.HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT);
+            requestHideSelf(hideFlags,
+                    SoftInputShowHideReason.HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT);
         } else {
-            requestShowSelf(showFlags);
+            requestShowSelf(showFlags,
+                    SoftInputShowHideReason.SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT);
         }
     }
     
@@ -4272,6 +4358,20 @@
     }
 
     /**
+     * Creates an IME request tracking token.
+     *
+     * @param show whether this is a show or a hide request.
+     * @param reason the reason why the IME request was created.
+     * @param isFromUser whether this request was created directly from user interaction.
+     */
+    @NonNull
+    private ImeTracker.Token createStatsToken(boolean show, @SoftInputShowHideReason int reason,
+            boolean isFromUser) {
+        return ImeTracker.forLogging().onStart(show ? ImeTracker.TYPE_SHOW : ImeTracker.TYPE_HIDE,
+                ImeTracker.ORIGIN_IME, reason, isFromUser);
+    }
+
+    /**
      * Performs a dump of the InputMethodService's internal state.  Override
      * to add your own information to the dump.
      */
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
index 510b14e..e28f345 100644
--- a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
@@ -99,7 +99,11 @@
 
         mDeadZone = new android.inputmethodservice.navigationbar.DeadZone(this);
 
-        getImeSwitchButton().setOnClickListener(view -> view.getContext()
+        getBackButton().setLongClickable(false);
+
+        final ButtonDispatcher imeSwitchButton = getImeSwitchButton();
+        imeSwitchButton.setLongClickable(false);
+        imeSwitchButton.setOnClickListener(view -> view.getContext()
                 .getSystemService(InputMethodManager.class).showInputMethodPicker());
     }
 
diff --git a/core/java/android/net/flags.aconfig b/core/java/android/net/flags.aconfig
index 9f9aef8..3544a69 100644
--- a/core/java/android/net/flags.aconfig
+++ b/core/java/android/net/flags.aconfig
@@ -19,6 +19,7 @@
 
 flag {
   name: "register_nsd_offload_engine"
+  is_exported: true
   namespace: "android_core_networking"
   description: "Flag for registerOffloadEngine API in NsdManager"
   bug: "294777050"
diff --git a/core/java/android/net/thread/flags.aconfig b/core/java/android/net/thread/flags.aconfig
index d679f9c..ef798ad 100644
--- a/core/java/android/net/thread/flags.aconfig
+++ b/core/java/android/net/thread/flags.aconfig
@@ -5,6 +5,7 @@
 
 flag {
     name: "thread_user_restriction_enabled"
+    is_exported: true
     namespace: "thread_network"
     description: "Controls whether user restriction on thread networks is enabled"
     bug: "307679182"
@@ -12,6 +13,7 @@
 
 flag {
     name: "thread_enabled_platform"
+    is_exported: true
     namespace: "thread_network"
     description: "Controls whether the Android Thread feature is enabled"
     bug: "301473012"
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
index 7afd721..97b773e 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -2,6 +2,7 @@
 
 flag {
     name: "safe_mode_config"
+    is_exported: true
     namespace: "vcn"
     description: "Feature flag for safe mode configurability"
     bug: "276358140"
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 05a1abea..b417534 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -240,7 +240,7 @@
             new Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_ANY);
 
     /**
-     * Identifies power attribution dimensions that are captured by an data element of
+     * Identifies power attribution dimensions that are captured by a data element of
      * a BatteryConsumer. These Keys are used to access those values and to set them using
      * Builders.  See for example {@link #getConsumedPower(Key)}.
      *
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index e090942..c611cb9 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1587,7 +1587,7 @@
                 outNumOfInterest[0] = numOfInterest;
             }
 
-            // The estimated time is the average time we spend in each level, multipled
+            // The estimated time is the average time we spend in each level, multiplied
             // by 100 -- the total number of battery levels
             return (total / numOfInterest) * 100;
         }
@@ -2019,7 +2019,7 @@
         public static final int EVENT_TOP = 0x0003;
         // Event is about active sync operations.
         public static final int EVENT_SYNC = 0x0004;
-        // Events for all additional wake locks aquired/release within a wake block.
+        // Events for all additional wake locks acquired/release within a wake block.
         // These are not generated by default.
         public static final int EVENT_WAKE_LOCK = 0x0005;
         // Event is about an application executing a scheduled job.
@@ -3419,7 +3419,7 @@
      * incoming service calls from apps.  The result is returned as an array of longs,
      * organized as a sequence like this:
      * <pre>
-     *     cluster1-speeed1, cluster1-speed2, ..., cluster2-speed1, cluster2-speed2, ...
+     *     cluster1-speed1, cluster1-speed2, ..., cluster2-speed1, cluster2-speed2, ...
      * </pre>
      *
      * @see com.android.internal.os.CpuScalingPolicies#getPolicies
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 3149de4..beb9a93 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -120,7 +120,6 @@
     private ClassLoader mClassLoader;
     private String mLibrarySearchPaths;
     private String mLibraryPermittedPaths;
-    private GameManager mGameManager;
 
     private int mAngleOptInIndex = -1;
     private boolean mShouldUseAngle = false;
@@ -134,8 +133,6 @@
         final ApplicationInfo appInfoWithMetaData =
                 getAppInfoWithMetadata(context, pm, packageName);
 
-        mGameManager = context.getSystemService(GameManager.class);
-
         Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupGpuLayers");
         setupGpuLayers(context, coreSettings, pm, packageName, appInfoWithMetaData);
         Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
@@ -161,9 +158,11 @@
         Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
 
         Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "notifyGraphicsEnvironmentSetup");
-        if (mGameManager != null
-                && appInfoWithMetaData.category == ApplicationInfo.CATEGORY_GAME) {
-            mGameManager.notifyGraphicsEnvironmentSetup();
+        if (appInfoWithMetaData.category == ApplicationInfo.CATEGORY_GAME) {
+            final GameManager gameManager = context.getSystemService(GameManager.class);
+            if (gameManager != null) {
+                gameManager.notifyGraphicsEnvironmentSetup();
+            }
         }
         Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
     }
diff --git a/core/java/android/os/HandlerThread.java b/core/java/android/os/HandlerThread.java
index 36730cb..f852d3c 100644
--- a/core/java/android/os/HandlerThread.java
+++ b/core/java/android/os/HandlerThread.java
@@ -19,6 +19,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
+import java.util.concurrent.Executor;
+
 /**
  * A {@link Thread} that has a {@link Looper}.
  * The {@link Looper} can then be used to create {@link Handler}s.
@@ -30,7 +32,8 @@
     int mPriority;
     int mTid = -1;
     Looper mLooper;
-    private @Nullable Handler mHandler;
+    private volatile @Nullable Handler mHandler;
+    private volatile @Nullable Executor mExecutor;
 
     public HandlerThread(String name) {
         super(name);
@@ -131,6 +134,18 @@
     }
 
     /**
+     * @return a shared {@link Executor} associated with this thread
+     * @hide
+     */
+    @NonNull
+    public Executor getThreadExecutor() {
+        if (mExecutor == null) {
+            mExecutor = new HandlerExecutor(getThreadHandler());
+        }
+        return mExecutor;
+    }
+
+    /**
      * Quits the handler thread's looper.
      * <p>
      * Causes the handler thread's looper to terminate without processing any
diff --git a/core/java/android/os/HwParcel.java b/core/java/android/os/HwParcel.java
index 9fd37d4..fb500a9 100644
--- a/core/java/android/os/HwParcel.java
+++ b/core/java/android/os/HwParcel.java
@@ -618,7 +618,7 @@
      */
     @FastNative
     @NonNull
-    public native final @Nullable
+    public native final
     HidlMemory readEmbeddedHidlMemory(long fieldHandle, long parentHandle, long offset);
 
     /**
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 800ba6d..23d6007 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -85,6 +85,7 @@
     boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable, int mUserId);
     boolean isRestricted(int userId);
     boolean canHaveRestrictedProfile(int userId);
+    boolean canAddPrivateProfile(int userId);
     int getUserSerialNumber(int userId);
     int getUserHandle(int userSerialNumber);
     int getUserRestrictionSource(String restrictionKey, int userId);
diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl
index 65e498e..8b1577c 100644
--- a/core/java/android/os/IVibratorManagerService.aidl
+++ b/core/java/android/os/IVibratorManagerService.aidl
@@ -41,5 +41,5 @@
     // There is no order guarantee with respect to the two-way APIs above like
     // vibrate/isVibrating/cancel.
     oneway void performHapticFeedback(int uid, int deviceId, String opPkg, int constant,
-            boolean always, String reason);
+            boolean always, String reason, boolean fromIme);
 }
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index 3950c25..2fe115f 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -623,14 +623,14 @@
                 // Message is to be inserted at tail or middle of queue. Usually we don't have to
                 // wake up the event queue unless there is a barrier at the head of the queue and
                 // the message is the earliest asynchronous message in the queue.
-                //
+                needWake = mBlocked && p.target == null && msg.isAsynchronous();
+
                 // For readability, we split this portion of the function into two blocks based on
                 // whether tail tracking is enabled. This has a minor implication for the case
                 // where tail tracking is disabled. See the comment below.
                 if (Flags.messageQueueTailTracking()) {
-                    needWake = mBlocked && p.target == null && msg.isAsynchronous()
-                        && mAsyncMessageCount == 0;
                     if (when >= mLast.when) {
+                        needWake = needWake && mAsyncMessageCount == 0;
                         msg.next = null;
                         mLast.next = msg;
                         mLast = msg;
@@ -643,6 +643,9 @@
                             if (p == null || when < p.when) {
                                 break;
                             }
+                            if (needWake && p.isAsynchronous()) {
+                                needWake = false;
+                            }
                         }
                         if (p == null) {
                             /* Inserting at tail of queue */
@@ -652,7 +655,6 @@
                         prev.next = msg;
                     }
                 } else {
-                    needWake = mBlocked && p.target == null && msg.isAsynchronous();
                     Message prev;
                     for (;;) {
                         prev = p;
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index b1c24a7..90279c6 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -103,3 +103,6 @@
 
 # ProfilingService
 per-file ProfilingServiceManager.java = file:/PERFORMANCE_OWNERS
+
+# Memory
+per-file OomKillRecord.java = file:/MEMORY_OWNERS
\ No newline at end of file
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index b669814..8a17742 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -1259,6 +1259,24 @@
     }
 
     /**
+     * Reboot into recovery and wipe the data partition with ext4
+     *
+     * @throws IOException if something goes wrong.
+     *
+     * @hide
+     */
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.RECOVERY,
+            android.Manifest.permission.REBOOT
+    })
+    public void wipePartitionToExt4()
+            throws IOException {
+        // Reformat /data partition with ext4
+        String command = "--wipe_data\n--reformat_data=ext4";
+        rebootRecoveryWithCommand(command);
+    }
+
+    /**
      * Reboot into the recovery system with the supplied argument.
      * @param args to pass to the recovery utility.
      * @throws IOException if something goes wrong.
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 04c257b..2a62c24 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -206,12 +206,13 @@
     }
 
     @Override
-    public void performHapticFeedback(int constant, boolean always, String reason) {
+    public void performHapticFeedback(
+            int constant, boolean always, String reason, boolean fromIme) {
         if (mVibratorManager == null) {
             Log.w(TAG, "Failed to perform haptic feedback; no vibrator manager.");
             return;
         }
-        mVibratorManager.performHapticFeedback(constant, always, reason);
+        mVibratorManager.performHapticFeedback(constant, always, reason, fromIme);
     }
 
     @Override
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
index 8e83923..c80bcac 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -147,14 +147,15 @@
     }
 
     @Override
-    public void performHapticFeedback(int constant, boolean always, String reason) {
+    public void performHapticFeedback(int constant, boolean always, String reason,
+            boolean fromIme) {
         if (mService == null) {
             Log.w(TAG, "Failed to perform haptic feedback; no vibrator manager service.");
             return;
         }
         try {
             mService.performHapticFeedback(
-                    mUid, mContext.getDeviceId(), mPackageName, constant, always, reason);
+                    mUid, mContext.getDeviceId(), mPackageName, constant, always, reason, fromIme);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to perform haptic feedback.", e);
         }
@@ -244,8 +245,9 @@
         }
 
         @Override
-        public void performHapticFeedback(int effectId, boolean always, String reason) {
-            SystemVibratorManager.this.performHapticFeedback(effectId, always, reason);
+        public void performHapticFeedback(int effectId, boolean always, String reason,
+                boolean fromIme) {
+            SystemVibratorManager.this.performHapticFeedback(effectId, always, reason, fromIme);
         }
 
         @Override
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index c0b4909..bebb912 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -164,6 +164,8 @@
     private static native void nativeInstant(long tag, String name);
     @FastNative
     private static native void nativeInstantForTrack(long tag, String trackName, String name);
+    @FastNative
+    private static native void nativeRegisterWithPerfetto();
 
     private Trace() {
     }
@@ -523,4 +525,15 @@
             nativeTraceCounter(TRACE_TAG_APP, counterName, counterValue);
         }
     }
+
+    /**
+     * Initialize the perfetto SDK. This must be called before any tracing
+     * calls so that perfetto SDK can be used, otherwise libcutils would be
+     * used.
+     *
+     * @hide
+     */
+    public static void registerWithPerfetto() {
+        nativeRegisterWithPerfetto();
+    }
 }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index be17d7c..fdaa0b4 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1920,9 +1920,7 @@
      * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
-    // TODO (b/325886480): update the flag to
-    // "com.android.net.thread.platform.flags.Flags.FLAG_THREAD_USER_RESTRICTION_ENABLED"
-    @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled")
+    @FlaggedApi(com.android.net.thread.platform.flags.Flags.FLAG_THREAD_USER_RESTRICTION_ENABLED)
     public static final String DISALLOW_THREAD_NETWORK = "no_thread_network";
 
     /**
@@ -2355,6 +2353,17 @@
     public static final int USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS = 7;
 
     /**
+     * Indicates user operation failed because user is disabled on the device.
+     * @hide
+     */
+    public static final int USER_OPERATION_ERROR_DISABLED_USER = 8;
+    /**
+     * Indicates user operation failed because user is disabled on the device.
+     * @hide
+     */
+    public static final int USER_OPERATION_ERROR_PRIVATE_PROFILE = 9;
+
+    /**
      * Result returned from various user operations.
      *
      * @hide
@@ -2368,7 +2377,9 @@
             USER_OPERATION_ERROR_CURRENT_USER,
             USER_OPERATION_ERROR_LOW_STORAGE,
             USER_OPERATION_ERROR_MAX_USERS,
-            USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS
+            USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS,
+            USER_OPERATION_ERROR_DISABLED_USER,
+            USER_OPERATION_ERROR_PRIVATE_PROFILE,
     })
     public @interface UserOperationResult {}
 
@@ -2565,6 +2576,17 @@
     }
 
     /**
+     * Returns whether the device supports Private Profile
+     * @hide
+     */
+    public static boolean isPrivateProfileEnabled() {
+        if (android.multiuser.Flags.blockPrivateSpaceCreation()) {
+            return !ActivityManager.isLowRamDeviceStatic();
+        }
+        return true;
+    }
+
+    /**
      * Returns whether multiple admins are enabled on the device
      * @hide
      */
@@ -3157,6 +3179,27 @@
     }
 
     /**
+     * Checks if it's possible to add a private profile to the context user
+     * @return whether the context user can add a private profile.
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS},
+            conditional = true)
+    @UserHandleAware
+    public boolean canAddPrivateProfile() {
+        if (android.multiuser.Flags.blockPrivateSpaceCreation()) {
+            try {
+                return mService.canAddPrivateProfile(mUserId);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+        }
+        return true;
+    }
+
+    /**
      * Returns whether the context user has at least one restricted profile associated with it.
      * @return whether the user has a restricted profile associated with it
      * @hide
@@ -4710,6 +4753,9 @@
      * Sets the user as enabled, if such an user exists.
      *
      * <p>Note that the default is true, it's only that managed profiles might not be enabled.
+     * (Managed profiles created by DevicePolicyManager will start out disabled, and DPM will later
+     * toggle them to enabled once they are provisioned. This is the primary purpose of the
+     * {@link UserInfo#FLAG_DISABLED} flag.)
      * Also ephemeral users can be disabled to indicate that their removal is in progress and they
      * shouldn't be re-entered. Therefore ephemeral users should not be re-enabled once disabled.
      *
@@ -5261,7 +5307,7 @@
 
     /**
      * Returns list of the profiles of userId including userId itself.
-     * Note that this returns only enabled.
+     * Note that this returns only {@link UserInfo#isEnabled() enabled} profiles.
      * <p>Note that this includes all profile types (not including Restricted profiles).
      *
      * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} or
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 46705a3..9df5b85 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -289,6 +289,15 @@
     }
 
     /**
+     * Return the original {@link AudioAttributes} used to create the vibration attributes.
+     * @hide
+     */
+    @AudioAttributes.AttributeUsage
+    public int getOriginalAudioUsage() {
+        return mOriginalAudioUsage;
+    }
+
+    /**
      * Return the flags.
      * @return a combined mask of all flags
      */
@@ -405,8 +414,8 @@
         return "VibrationAttributes{"
                 + "mUsage=" + usageToString()
                 + ", mAudioUsage= " + AudioAttributes.usageToString(mOriginalAudioUsage)
-                + ", mFlags=" + mFlags
                 + ", mCategory=" + categoryToString()
+                + ", mFlags=" + mFlags
                 + '}';
     }
 
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 2fc2414..4b2d4eb 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -534,10 +534,12 @@
      *          {@code false} if the vibration for the haptic feedback should respect the applicable
      *          vibration intensity settings.
      * @param reason the reason for this haptic feedback.
+     * @param fromIme the haptic feedback is performed from an IME.
      *
      * @hide
      */
-    public void performHapticFeedback(int constant, boolean always, String reason) {
+    public void performHapticFeedback(int constant, boolean always, String reason,
+            boolean fromIme) {
         Log.w(TAG, "performHapticFeedback is not supported");
     }
 
diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java
index e0b6a9f..513c4bd 100644
--- a/core/java/android/os/VibratorManager.java
+++ b/core/java/android/os/VibratorManager.java
@@ -146,9 +146,11 @@
      *          vibration intensity settings applicable to the corresponding vibration.
      *          {@code false} otherwise.
      * @param reason the reason for this haptic feedback.
+     * @param fromIme the haptic feedback is performed from an IME.
      * @hide
      */
-    public void performHapticFeedback(int constant, boolean always, String reason) {
+    public void performHapticFeedback(int constant, boolean always, String reason,
+            boolean fromIme) {
         Log.w(TAG, "performHapticFeedback is not supported");
     }
 
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index abfa4e3..943014c 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -1,7 +1,9 @@
 package: "android.os"
+container: "system"
 
 flag {
     name: "android_os_build_vanilla_ice_cream"
+    is_exported: true
     namespace: "build"
     description: "Feature flag for adding the VANILLA_ICE_CREAM constant."
     bug: "264658905"
@@ -9,6 +11,7 @@
 
 flag {
     name: "state_of_health_public"
+    is_exported: true
     namespace: "system_sw_battery"
     description: "Feature flag for making state_of_health a public api."
     bug: "288842045"
@@ -40,6 +43,7 @@
     namespace: "profile_experiences"
     description: "Guards a new Private Profile type in UserManager - everything from its setup to config to deletion."
     bug: "299069460"
+    is_exported: true
 }
 
 flag {
@@ -130,3 +134,10 @@
      is_fixed_read_only: true
      bug: "324241334"
 }
+
+flag {
+     namespace: "system_performance"
+     name: "perfetto_sdk_tracing"
+     description: "Tracing using Perfetto SDK."
+     bug: "303199244"
+}
diff --git a/core/java/android/os/health/HealthStatsWriter.java b/core/java/android/os/health/HealthStatsWriter.java
index d4d10b0..4118775 100644
--- a/core/java/android/os/health/HealthStatsWriter.java
+++ b/core/java/android/os/health/HealthStatsWriter.java
@@ -58,7 +58,7 @@
      * Construct a HealthStatsWriter object with the given constants.
      *
      * The "getDataType()" of the resulting HealthStats object will be the
-     * short name of the java class that the Constants object was initalized
+     * short name of the java class that the Constants object was initialized
      * with.
      */
     public HealthStatsWriter(HealthKeys.Constants constants) {
diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java
index 88096ab..ada708b 100644
--- a/core/java/android/os/image/DynamicSystemClient.java
+++ b/core/java/android/os/image/DynamicSystemClient.java
@@ -52,7 +52,7 @@
  *
  * After the installation is completed, the device will be running in the new system on next the
  * reboot. Then, when the user reboots the device again, it will leave {@code DynamicSystem} and go
- * back to the original system. While running in {@code DynamicSystem}, persitent storage for
+ * back to the original system. While running in {@code DynamicSystem}, persistent storage for
  * factory reset protection (FRP) remains unchanged. Since the user is running the new system with
  * a temporarily created data partition, their original user data are kept unchanged.</p>
  *
diff --git a/core/java/android/os/image/DynamicSystemManager.java b/core/java/android/os/image/DynamicSystemManager.java
index 536795b..8ce87e9 100644
--- a/core/java/android/os/image/DynamicSystemManager.java
+++ b/core/java/android/os/image/DynamicSystemManager.java
@@ -172,7 +172,7 @@
         }
     }
     /**
-     * Finish a previously started installation. Installations without a cooresponding
+     * Finish a previously started installation. Installations without a corresponding
      * finishInstallation() will be cleaned up during device boot.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 5a09541..d45a17f 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1395,7 +1395,7 @@
                 // Package name can be null if the activity thread is running but the app
                 // hasn't bound yet. In this case we fall back to the first package in the
                 // current UID. This works for runtime permissions as permission state is
-                // per UID and permission realted app ops are updated for all UID packages.
+                // per UID and permission related app ops are updated for all UID packages.
                 String[] packageNames = ActivityThread.getPackageManager().getPackagesForUid(
                         android.os.Process.myUid());
                 if (packageNames == null || packageNames.length <= 0) {
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index e1f112a..4cf2fd4 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -62,7 +62,7 @@
  * <li>To get access to standard directories (like the {@link Environment#DIRECTORY_PICTURES}), they
  * can use the {@link #createAccessIntent(String)}. This is the recommend way, since it provides a
  * simpler API and narrows the access to the given directory (and its descendants).
- * <li>To get access to any directory (and its descendants), they can use the Storage Acess
+ * <li>To get access to any directory (and its descendants), they can use the Storage Access
  * Framework APIs (such as {@link Intent#ACTION_OPEN_DOCUMENT} and
  * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, although these APIs do not guarantee the user will
  * select this specific volume.
diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java
index bcdb982..555a120 100644
--- a/core/java/android/os/vibrator/VibrationConfig.java
+++ b/core/java/android/os/vibrator/VibrationConfig.java
@@ -70,6 +70,8 @@
 
     private final boolean mDefaultKeyboardVibrationEnabled;
 
+    private final boolean mHasFixedKeyboardAmplitude;
+
     /** @hide */
     public VibrationConfig(@Nullable Resources resources) {
         mHapticChannelMaxVibrationAmplitude = loadFloat(resources,
@@ -87,6 +89,8 @@
                 com.android.internal.R.bool.config_ignoreVibrationsOnWirelessCharger, false);
         mDefaultKeyboardVibrationEnabled = loadBoolean(resources,
                 com.android.internal.R.bool.config_defaultKeyboardVibrationEnabled, true);
+        mHasFixedKeyboardAmplitude = loadFloat(resources,
+                com.android.internal.R.dimen.config_keyboardHapticFeedbackFixedAmplitude, -1) > 0;
 
         mDefaultAlarmVibrationIntensity = loadDefaultIntensity(resources,
                 com.android.internal.R.integer.config_defaultAlarmVibrationIntensity);
@@ -197,6 +201,14 @@
         return mDefaultKeyboardVibrationEnabled;
     }
 
+    /**
+     * Whether the device has a fixed amplitude for keyboard.
+     * @hide
+     */
+    public boolean hasFixedKeyboardAmplitude() {
+        return mHasFixedKeyboardAmplitude;
+    }
+
     /** Get the default vibration intensity for given usage. */
     @VibrationIntensity
     public int getDefaultVibrationIntensity(@VibrationAttributes.Usage int usage) {
@@ -224,17 +236,19 @@
     @Override
     public String toString() {
         return "VibrationConfig{"
-                + "mHapticChannelMaxVibrationAmplitude=" + mHapticChannelMaxVibrationAmplitude
+                + "mIgnoreVibrationsOnWirelessCharger=" + mIgnoreVibrationsOnWirelessCharger
+                + ", mHapticChannelMaxVibrationAmplitude=" + mHapticChannelMaxVibrationAmplitude
                 + ", mRampStepDurationMs=" + mRampStepDurationMs
                 + ", mRampDownDurationMs=" + mRampDownDurationMs
+                + ", mRequestVibrationParamsForUsages="
+                + Arrays.toString(getRequestVibrationParamsForUsagesNames())
+                + ", mRequestVibrationParamsTimeoutMs=" + mRequestVibrationParamsTimeoutMs
                 + ", mDefaultAlarmIntensity=" + mDefaultAlarmVibrationIntensity
                 + ", mDefaultHapticFeedbackIntensity=" + mDefaultHapticFeedbackIntensity
                 + ", mDefaultMediaIntensity=" + mDefaultMediaVibrationIntensity
                 + ", mDefaultNotificationIntensity=" + mDefaultNotificationVibrationIntensity
                 + ", mDefaultRingIntensity=" + mDefaultRingVibrationIntensity
-                + ", mRequestVibrationParamsTimeoutMs=" + mRequestVibrationParamsTimeoutMs
-                + ", mRequestVibrationParamsForUsages=" + Arrays.toString(
-                getRequestVibrationParamsForUsagesNames())
+                + ", mDefaultKeyboardVibrationEnabled=" + mDefaultKeyboardVibrationEnabled
                 + "}";
     }
 
@@ -246,9 +260,13 @@
     public void dumpWithoutDefaultSettings(IndentingPrintWriter pw) {
         pw.println("VibrationConfig:");
         pw.increaseIndent();
+        pw.println("ignoreVibrationsOnWirelessCharger = " + mIgnoreVibrationsOnWirelessCharger);
         pw.println("hapticChannelMaxAmplitude = " + mHapticChannelMaxVibrationAmplitude);
         pw.println("rampStepDurationMs = " + mRampStepDurationMs);
         pw.println("rampDownDurationMs = " + mRampDownDurationMs);
+        pw.println("requestVibrationParamsForUsages = "
+                + Arrays.toString(getRequestVibrationParamsForUsagesNames()));
+        pw.println("requestVibrationParamsTimeoutMs = " + mRequestVibrationParamsTimeoutMs);
         pw.decreaseIndent();
     }
 
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 8495f37..3c7692d 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -247,8 +247,6 @@
 
     private final LegacyPermissionManager mLegacyPermissionManager;
 
-    private final VirtualDeviceManager mVirtualDeviceManager;
-
     private final ArrayMap<PackageManager.OnPermissionsChangedListener,
             IOnPermissionsChangeListener> mPermissionListeners = new ArrayMap<>();
     private PermissionUsageHelper mUsageHelper;
@@ -269,7 +267,6 @@
         mPermissionManager = IPermissionManager.Stub.asInterface(ServiceManager.getServiceOrThrow(
                 "permissionmgr"));
         mLegacyPermissionManager = context.getSystemService(LegacyPermissionManager.class);
-        mVirtualDeviceManager = context.getSystemService(VirtualDeviceManager.class);
     }
 
     /**
@@ -1791,6 +1788,9 @@
 
     /**
      * Gets the permission states for requested package and persistent device.
+     * <p>
+     * <strong>Note: </strong>Default device permissions are not inherited in this API. Returns the
+     * exact permission states for the requested device.
      *
      * @param packageName name of the package you are checking against
      * @param persistentDeviceId id of the persistent device you are checking against
@@ -1800,7 +1800,11 @@
      */
     @SystemApi
     @NonNull
-    @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+            android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+            android.Manifest.permission.GET_RUNTIME_PERMISSIONS
+    })
     @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public Map<String, PermissionState> getAllPermissionStates(@NonNull String packageName,
             @NonNull String persistentDeviceId) {
@@ -1918,14 +1922,18 @@
         if (deviceId == Context.DEVICE_ID_DEFAULT) {
             persistentDeviceId = VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
         } else if (android.companion.virtual.flags.Flags.vdmPublicApis()) {
-            VirtualDevice virtualDevice = mVirtualDeviceManager.getVirtualDevice(deviceId);
-            if (virtualDevice == null) {
-                Slog.e(LOG_TAG, "Virtual device is not found with device Id " + deviceId);
-                return null;
-            }
-            persistentDeviceId = virtualDevice.getPersistentDeviceId();
-            if (persistentDeviceId == null) {
-                Slog.e(LOG_TAG, "Cannot find persistent device Id for " + deviceId);
+            VirtualDeviceManager virtualDeviceManager = mContext.getSystemService(
+                    VirtualDeviceManager.class);
+            if (virtualDeviceManager != null) {
+                VirtualDevice virtualDevice = virtualDeviceManager.getVirtualDevice(deviceId);
+                if (virtualDevice == null) {
+                    Slog.e(LOG_TAG, "Virtual device is not found with device Id " + deviceId);
+                    return null;
+                }
+                persistentDeviceId = virtualDevice.getPersistentDeviceId();
+                if (persistentDeviceId == null) {
+                    Slog.e(LOG_TAG, "Cannot find persistent device Id for " + deviceId);
+                }
             }
         } else {
             Slog.e(LOG_TAG, "vdmPublicApis flag is not enabled when device Id " + deviceId
@@ -2072,5 +2080,29 @@
                 return new PermissionState[size];
             }
         };
+
+        /** @hide */
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            PermissionState that = (PermissionState) o;
+            return mGranted == that.mGranted && mFlags == that.mFlags;
+        }
+
+        /** @hide */
+        @Override
+        public int hashCode() {
+            return Objects.hash(mGranted, mFlags);
+        }
+
+        /** @hide */
+        @Override
+        public String toString() {
+            return "PermissionState{"
+                    + "mGranted=" + mGranted
+                    + ", mFlags=" + mFlags
+                    + '}';
+        }
     }
 }
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 460b4dd..141ffc9 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -359,8 +359,7 @@
                         new PermissionGroupUsage(usage.packageName, usage.uid, usage.lastAccessTime,
                                 permGroup,
                                 usage.isRunning, isPhone, usage.attributionTag, attributionLabel,
-                                usagesWithLabels.valueAt(usageNum),
-                                VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT));
+                                usagesWithLabels.valueAt(usageNum), deviceId));
             }
         }
 
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index de7008b..dc782d4 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -138,3 +138,11 @@
     bug: "325356776"
 }
 
+flag {
+    name: "runtime_permission_appops_mapping_enabled"
+    is_fixed_read_only: true
+    namespace: "permissions"
+    description: "Use runtime permission state to determine appop state"
+    bug: "266164193"
+}
+
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index c13dd36..120846c 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -406,6 +406,7 @@
          * Builder for the add-call parameters.
          */
         public static final class AddCallParametersBuilder {
+            public static final int MAX_NUMBER_OF_CHARACTERS = 256;
             private CallerInfo mCallerInfo;
             private String mNumber;
             private String mPostDialDigits;
@@ -431,7 +432,7 @@
             private Uri mPictureUri;
             private int mIsPhoneAccountMigrationPending;
             private boolean mIsBusinessCall;
-            private String mBusinessName;
+            private String mAssertedDisplayName;
 
             /**
              * @param callerInfo the CallerInfo object to get the target contact from.
@@ -659,11 +660,19 @@
             }
 
             /**
-             * @param businessName should be set if the caller is a business call
+             * @param assertedDisplayName the asserted display name associated with the business
+             *                            call
+             * @throws IllegalArgumentException if the assertedDisplayName is over 256 characters
              */
             @FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER)
-            public @NonNull AddCallParametersBuilder setBusinessName(String businessName) {
-                mBusinessName = businessName;
+            public @NonNull AddCallParametersBuilder setAssertedDisplayName(
+                    String assertedDisplayName) {
+                if (assertedDisplayName != null
+                        && assertedDisplayName.length() > MAX_NUMBER_OF_CHARACTERS) {
+                    throw new IllegalArgumentException("assertedDisplayName exceeds the character"
+                            + " limit of " + MAX_NUMBER_OF_CHARACTERS + ".");
+                }
+                mAssertedDisplayName = assertedDisplayName;
                 return this;
             }
 
@@ -678,7 +687,7 @@
                             mCallBlockReason,
                             mCallScreeningAppName, mCallScreeningComponentName, mMissedReason,
                             mPriority, mSubject, mLatitude, mLongitude, mPictureUri,
-                            mIsPhoneAccountMigrationPending, mIsBusinessCall, mBusinessName);
+                            mIsPhoneAccountMigrationPending, mIsBusinessCall, mAssertedDisplayName);
                 } else {
                     return new AddCallParams(mCallerInfo, mNumber, mPostDialDigits, mViaNumber,
                             mPresentation, mCallType, mFeatures, mAccountHandle, mStart, mDuration,
@@ -716,7 +725,7 @@
         private Uri mPictureUri;
         private int mIsPhoneAccountMigrationPending;
         private boolean mIsBusinessCall;
-        private String mBusinessName;
+        private String mAssertedDisplayName;
 
         private AddCallParams(CallerInfo callerInfo, String number, String postDialDigits,
                 String viaNumber, int presentation, int callType, int features,
@@ -761,7 +770,8 @@
                 CharSequence callScreeningAppName, String callScreeningComponentName,
                 long missedReason,
                 int priority, String subject, double latitude, double longitude, Uri pictureUri,
-                int isPhoneAccountMigrationPending, boolean isBusinessCall, String businessName) {
+                int isPhoneAccountMigrationPending, boolean isBusinessCall,
+                String assertedDisplayName) {
             mCallerInfo = callerInfo;
             mNumber = number;
             mPostDialDigits = postDialDigits;
@@ -787,7 +797,7 @@
             mPictureUri = pictureUri;
             mIsPhoneAccountMigrationPending = isPhoneAccountMigrationPending;
             mIsBusinessCall = isBusinessCall;
-            mBusinessName = businessName;
+            mAssertedDisplayName = assertedDisplayName;
         }
 
     }
@@ -1833,7 +1843,7 @@
             values.put(IS_PHONE_ACCOUNT_MIGRATION_PENDING, params.mIsPhoneAccountMigrationPending);
             if (Flags.businessCallComposer()) {
                 values.put(IS_BUSINESS_CALL, Integer.valueOf(params.mIsBusinessCall ? 1 : 0));
-                values.put(ASSERTED_DISPLAY_NAME, params.mBusinessName);
+                values.put(ASSERTED_DISPLAY_NAME, params.mAssertedDisplayName);
             }
             if ((params.mCallerInfo != null) && (params.mCallerInfo.getContactId() > 0)) {
                 // Update usage information for the number associated with the contact ID.
diff --git a/core/java/android/provider/ContactKeysManager.java b/core/java/android/provider/E2eeContactKeysManager.java
similarity index 60%
rename from core/java/android/provider/ContactKeysManager.java
rename to core/java/android/provider/E2eeContactKeysManager.java
index 01aaa3d..09c93e3 100644
--- a/core/java/android/provider/ContactKeysManager.java
+++ b/core/java/android/provider/E2eeContactKeysManager.java
@@ -39,18 +39,18 @@
 import java.util.Objects;
 
 /**
- * ContactKeysManager provides access to the provider of end-to-end encryption contact keys.
- * It manages two types of keys - {@link ContactKey} and {@link SelfKey}.
+ * E2eeContactKeysManager provides access to the provider of end-to-end encryption contact keys.
+ * It manages two types of keys - {@link E2eeContactKey} and {@link E2eeSelfKey}.
  * <ul>
  * <li>
- * A {@link ContactKey} is a public key associated with a contact. It's used to end-to-end
+ * A {@link E2eeContactKey} is a public key associated with a contact. It's used to end-to-end
  * encrypt the communications between a user and the contact. This API allows operations on
- * {@link ContactKey}s to insert/update, remove, change the verification state, and retrieving
+ * {@link E2eeContactKey}s to insert/update, remove, change the verification state, and retrieving
  * keys (either created by or visible to the caller app).
  * </li>
  * <li>
- * A {@link SelfKey} is a key for this device, so the key represents the owner of the device.
- * This API allows operations on {@link SelfKey}s to insert/update, remove, and retrieving
+ * A {@link E2eeSelfKey} is a key for this device, so the key represents the owner of the device.
+ * This API allows operations on {@link E2eeSelfKey}s to insert/update, remove, and retrieving
  * self keys (either created by or visible to the caller app).
  * </li>
  * </ul>
@@ -65,29 +65,32 @@
  * </li>
  * <li>
  * accountId - the app-specified identifier for the account for which the contact key can be used.
- * Usually a phone number.
+ * Using different account IDs allows for multiple key entries representing the same user.
+ * For most apps this would be a phone number.
  * </li>
  * </ul>
  * Contact keys also use lookupKey which is an opaque value used to identify a contact in
  * ContactsProvider.
  */
 @FlaggedApi(Flags.FLAG_USER_KEYS)
-public final class ContactKeysManager {
+public final class E2eeContactKeysManager {
     /**
-     * The authority for the contact keys provider.
+     * The authority for the end-to-end encryption contact keys provider.
+     *
      * @hide
      */
     public static final String AUTHORITY = "com.android.contactkeys.contactkeysprovider";
 
     /**
-     * A content:// style uri to the authority for the contact keys provider.
+     * A content:// style uri to the authority for the end-to-end encryption contact keys provider.
+     *
      * @hide
      */
     @NonNull
     public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
 
     /**
-     * Maximum size of a contact key.
+     * Maximum size of an end-to-end encryption contact key.
      */
     private static final int MAX_KEY_SIZE_BYTES = 5000;
 
@@ -100,91 +103,91 @@
     private final ContentResolver mContentResolver;
 
     /** @hide */
-    public ContactKeysManager(@NonNull Context context) {
+    public E2eeContactKeysManager(@NonNull Context context) {
         Objects.requireNonNull(context);
         mContentResolver = context.getContentResolver();
     }
 
     /**
-     * Inserts a new entry into the contact keys table or updates one if it already exists.
-     * The inserted/updated contact key is owned by the caller app.
+     * Inserts a new entry into the end-to-end encryption contact keys table or updates one if it
+     * already exists.
+     * The inserted/updated end-to-end encryption contact key is owned by the caller app.
      *
      * @param lookupKey value that references the contact
-     * @param deviceId an app-specified identifier for the device
+     * @param deviceId  an app-specified identifier for the device
      * @param accountId an app-specified identifier for the account
-     * @param keyValue the raw bytes for the key (max size is {@link #getMaxKeySizeBytes} bytes)
+     * @param keyValue  the raw bytes for the key (max size is {@link #getMaxKeySizeBytes} bytes)
      */
     @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS)
-    public void updateOrInsertContactKey(@NonNull String lookupKey,
+    public void updateOrInsertE2eeContactKey(@NonNull String lookupKey,
             @NonNull String deviceId,
             @NonNull String accountId,
             @NonNull byte[] keyValue) {
         validateKeyLength(keyValue);
 
         Bundle extras = new Bundle();
-        extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
-        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
-        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
-        extras.putByteArray(ContactKeys.KEY_VALUE, Objects.requireNonNull(keyValue));
+        extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+        extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+        extras.putByteArray(E2eeContactKeys.KEY_VALUE, Objects.requireNonNull(keyValue));
 
         nullSafeCall(mContentResolver,
-                ContactKeys.UPDATE_OR_INSERT_CONTACT_KEY_METHOD, extras);
+                E2eeContactKeys.UPDATE_OR_INSERT_CONTACT_KEY_METHOD, extras);
     }
 
     /**
-     * Retrieves a contact key entry given the lookup key, device ID, accountId and inferred
-     * caller package name.
+     * Retrieves an end-to-end encryption contact key entry given the lookup key, device ID,
+     * accountId and inferred caller package name.
      *
      * @param lookupKey the value that references the contact
-     * @param deviceId an app-specified identifier for the device
+     * @param deviceId  an app-specified identifier for the device
      * @param accountId an app-specified identifier for the account
-     *
-     * @return a {@link ContactKey} object containing the contact key information,
+     * @return a {@link E2eeContactKey} object containing the contact key information,
      * or null if no contact key is found.
      */
     @RequiresPermission(android.Manifest.permission.READ_CONTACTS)
     @Nullable
-    public ContactKey getContactKey(
+    public E2eeContactKey getE2eeContactKey(
             @NonNull String lookupKey,
             @NonNull String deviceId,
             @NonNull String accountId) {
         Bundle extras = new Bundle();
-        extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
-        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
-        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+        extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+        extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
 
         Bundle response = nullSafeCall(mContentResolver,
-                ContactKeys.GET_CONTACT_KEY_METHOD, extras);
+                E2eeContactKeys.GET_CONTACT_KEY_METHOD, extras);
 
         if (response == null) {
             return null;
         }
-        return response.getParcelable(ContactKeys.KEY_CONTACT_KEY, ContactKey.class);
+        return response.getParcelable(E2eeContactKeys.KEY_CONTACT_KEY, E2eeContactKey.class);
     }
 
     /**
-     * Retrieves all contact key entries that belong to apps visible to the caller.
+     * Retrieves all end-to-end encryption contact key entries that belong to apps visible to
+     * the caller.
      * The keys will be stripped of deviceId, timeUpdated and keyValue data.
      *
      * @param lookupKey the value that references the contact
-     *
-     * @return a list of {@link ContactKey} objects containing the contact key
+     * @return a list of {@link E2eeContactKey} objects containing the contact key
      * information, or an empty list if no keys are found.
      */
     @RequiresPermission(android.Manifest.permission.READ_CONTACTS)
     @NonNull
-    public List<ContactKey> getAllContactKeys(@NonNull String lookupKey) {
+    public List<E2eeContactKey> getAllE2eeContactKeys(@NonNull String lookupKey) {
         Bundle extras = new Bundle();
-        extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+        extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
 
         Bundle response = nullSafeCall(mContentResolver,
-                ContactKeys.GET_ALL_CONTACT_KEYS_METHOD, extras);
+                E2eeContactKeys.GET_ALL_CONTACT_KEYS_METHOD, extras);
 
         if (response == null) {
             return new ArrayList<>();
         }
-        List<ContactKey> value = response.getParcelableArrayList(ContactKeys.KEY_CONTACT_KEYS,
-                ContactKey.class);
+        List<E2eeContactKey> value = response.getParcelableArrayList(
+                E2eeContactKeys.KEY_CONTACT_KEYS, E2eeContactKey.class);
         if (value == null) {
             return new ArrayList<>();
         }
@@ -192,27 +195,27 @@
     }
 
     /**
-     * Retrieves all contact key entries for a given lookupKey that belong to the caller app.
+     * Retrieves all end-to-end encryption contact key entries for a given lookupKey that belong to
+     * the caller app.
      *
      * @param lookupKey the value that references the contact
-     *
-     * @return a list of {@link ContactKey} objects containing the contact key
-     * information, or an empty list if no keys are found.
+     * @return a list of {@link E2eeContactKey} objects containing the end-to-end encryption
+     * contact key information, or an empty list if no keys are found.
      */
     @RequiresPermission(android.Manifest.permission.READ_CONTACTS)
     @NonNull
-    public List<ContactKey> getOwnerContactKeys(@NonNull String lookupKey) {
+    public List<E2eeContactKey> getOwnerE2eeContactKeys(@NonNull String lookupKey) {
         Bundle extras = new Bundle();
-        extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+        extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
 
         Bundle response = nullSafeCall(mContentResolver,
-                ContactKeys.GET_OWNER_CONTACT_KEYS_METHOD, extras);
+                E2eeContactKeys.GET_OWNER_CONTACT_KEYS_METHOD, extras);
 
         if (response == null) {
             return new ArrayList<>();
         }
-        List<ContactKey> value = response.getParcelableArrayList(ContactKeys.KEY_CONTACT_KEYS,
-                ContactKey.class);
+        List<E2eeContactKey> value = response.getParcelableArrayList(
+                E2eeContactKeys.KEY_CONTACT_KEYS, E2eeContactKey.class);
         if (value == null) {
             return new ArrayList<>();
         }
@@ -220,53 +223,51 @@
     }
 
     /**
-     * Updates a contact key entry's local verification state that belongs to the caller app.
+     * Updates an end-to-end encryption contact key entry's local verification state that belongs
+     * to the caller app.
      *
-     * @param lookupKey the value that references the contact
-     * @param deviceId an app-specified identifier for the device
-     * @param accountId an app-specified identifier for the account
+     * @param lookupKey              the value that references the contact
+     * @param deviceId               an app-specified identifier for the device
+     * @param accountId              an app-specified identifier for the account
      * @param localVerificationState the new local verification state
-     *
      * @return true if the entry was updated, false otherwise.
      */
     @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS)
-    public boolean updateContactKeyLocalVerificationState(@NonNull String lookupKey,
+    public boolean updateE2eeContactKeyLocalVerificationState(@NonNull String lookupKey,
             @NonNull String deviceId,
             @NonNull String accountId,
             @VerificationState int localVerificationState) {
         validateVerificationState(localVerificationState);
 
         Bundle extras = new Bundle();
-        extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
-        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
-        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
-        extras.putInt(ContactKeys.LOCAL_VERIFICATION_STATE, localVerificationState);
+        extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+        extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+        extras.putInt(E2eeContactKeys.LOCAL_VERIFICATION_STATE, localVerificationState);
 
         Bundle response = nullSafeCall(mContentResolver,
-                ContactKeys.UPDATE_CONTACT_KEY_LOCAL_VERIFICATION_STATE_METHOD, extras);
+                E2eeContactKeys.UPDATE_CONTACT_KEY_LOCAL_VERIFICATION_STATE_METHOD, extras);
 
-        return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+        return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS);
     }
 
     /**
-     * Updates a contact key entry's local verification state that belongs to the app identified
-     * by ownerPackageName.
+     * Updates an end-to-end encryption contact key entry's local verification state that belongs
+     * to the app identified by ownerPackageName.
      *
-     * @param lookupKey the value that references the contact
-     * @param deviceId an app-specified identifier for the device
-     * @param accountId an app-specified identifier for the account
-     * @param ownerPackageName the package name of the app that owns the key
+     * @param lookupKey              the value that references the contact
+     * @param deviceId               an app-specified identifier for the device
+     * @param accountId              an app-specified identifier for the account
+     * @param ownerPackageName       the package name of the app that owns the key
      * @param localVerificationState the new local verification state
-     *
      * @return true if the entry was updated, false otherwise.
-     *
      * @hide
      */
     @SystemApi
     @RequiresPermission(allOf = {
             android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
             android.Manifest.permission.WRITE_CONTACTS})
-    public boolean updateContactKeyLocalVerificationState(@NonNull String lookupKey,
+    public boolean updateE2eeContactKeyLocalVerificationState(@NonNull String lookupKey,
             @NonNull String deviceId,
             @NonNull String accountId,
             @NonNull String ownerPackageName,
@@ -274,66 +275,65 @@
         validateVerificationState(localVerificationState);
 
         final Bundle extras = new Bundle();
-        extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
-        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
-        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
-        extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
-        extras.putInt(ContactKeys.LOCAL_VERIFICATION_STATE, localVerificationState);
+        extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+        extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+        extras.putString(E2eeContactKeys.OWNER_PACKAGE_NAME,
+                Objects.requireNonNull(ownerPackageName));
+        extras.putInt(E2eeContactKeys.LOCAL_VERIFICATION_STATE, localVerificationState);
 
         final Bundle response = nullSafeCall(mContentResolver,
-                ContactKeys.UPDATE_CONTACT_KEY_LOCAL_VERIFICATION_STATE_METHOD, extras);
+                E2eeContactKeys.UPDATE_CONTACT_KEY_LOCAL_VERIFICATION_STATE_METHOD, extras);
 
-        return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+        return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS);
     }
 
     /**
-     * Updates a contact key entry's remote verification state that belongs to the caller app.
+     * Updates an end-to-end encryption contact key entry's remote verification state that belongs
+     * to the caller app.
      *
-     * @param lookupKey the value that references the contact
-     * @param deviceId an app-specified identifier for the device
-     * @param accountId an app-specified identifier for the account
+     * @param lookupKey               the value that references the contact
+     * @param deviceId                an app-specified identifier for the device
+     * @param accountId               an app-specified identifier for the account
      * @param remoteVerificationState the new remote verification state
-     *
      * @return true if the entry was updated, false otherwise.
      */
     @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS)
-    public boolean updateContactKeyRemoteVerificationState(@NonNull String lookupKey,
+    public boolean updateE2eeContactKeyRemoteVerificationState(@NonNull String lookupKey,
             @NonNull String deviceId,
             @NonNull String accountId,
             @VerificationState int remoteVerificationState) {
         validateVerificationState(remoteVerificationState);
 
         Bundle extras = new Bundle();
-        extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
-        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
-        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
-        extras.putInt(ContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
+        extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+        extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+        extras.putInt(E2eeContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
 
         Bundle response = nullSafeCall(mContentResolver,
-                ContactKeys.UPDATE_CONTACT_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
+                E2eeContactKeys.UPDATE_CONTACT_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
 
-        return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+        return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS);
     }
 
     /**
-     * Updates a contact key entry's remote verification state that belongs to the app identified
-     * by ownerPackageName.
+     * Updates an end-to-end encryption contact key entry's remote verification state that belongs
+     * to the app identified by ownerPackageName.
      *
-     * @param lookupKey the value that references the contact
-     * @param deviceId an app-specified identifier for the device
-     * @param accountId an app-specified identifier for the account
-     * @param ownerPackageName the package name of the app that owns the key
+     * @param lookupKey               the value that references the contact
+     * @param deviceId                an app-specified identifier for the device
+     * @param accountId               an app-specified identifier for the account
+     * @param ownerPackageName        the package name of the app that owns the key
      * @param remoteVerificationState the new remote verification state
-     *
      * @return true if the entry was updated, false otherwise.
-     *
      * @hide
      */
     @SystemApi
     @RequiresPermission(allOf = {
             android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
             android.Manifest.permission.WRITE_CONTACTS})
-    public boolean updateContactKeyRemoteVerificationState(@NonNull String lookupKey,
+    public boolean updateE2eeContactKeyRemoteVerificationState(@NonNull String lookupKey,
             @NonNull String deviceId,
             @NonNull String accountId,
             @NonNull String ownerPackageName,
@@ -341,19 +341,19 @@
         validateVerificationState(remoteVerificationState);
 
         final Bundle extras = new Bundle();
-        extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
-        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
-        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
-        extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
-        extras.putInt(ContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
+        extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+        extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+        extras.putString(E2eeContactKeys.OWNER_PACKAGE_NAME,
+                Objects.requireNonNull(ownerPackageName));
+        extras.putInt(E2eeContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
 
         final Bundle response = nullSafeCall(mContentResolver,
-                ContactKeys.UPDATE_CONTACT_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
+                E2eeContactKeys.UPDATE_CONTACT_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
 
-        return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+        return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS);
     }
 
-
     private static void validateVerificationState(int verificationState) {
         if (verificationState != VERIFICATION_STATE_UNVERIFIED
                 && verificationState != VERIFICATION_STATE_VERIFICATION_FAILED
@@ -364,53 +364,52 @@
     }
 
     /**
-     * Removes a contact key entry that belongs to the caller app.
+     * Removes an end-to-end encryption contact key entry that belongs to the caller app.
      *
      * @param lookupKey the value that references the contact
-     * @param deviceId an app-specified identifier for the device
+     * @param deviceId  an app-specified identifier for the device
      * @param accountId an app-specified identifier for the account
-     *
      * @return true if the entry was removed, false otherwise.
      */
     @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS)
-    public boolean removeContactKey(@NonNull String lookupKey,
+    public boolean removeE2eeContactKey(@NonNull String lookupKey,
             @NonNull String deviceId,
             @NonNull String accountId) {
         final Bundle extras = new Bundle();
-        extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
-        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
-        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+        extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+        extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
 
         final Bundle response = nullSafeCall(mContentResolver,
-                ContactKeys.REMOVE_CONTACT_KEY_METHOD, extras);
+                E2eeContactKeys.REMOVE_CONTACT_KEY_METHOD, extras);
 
-        return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+        return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS);
     }
 
     /**
-     * Inserts a new entry into the self keys table or updates one if it already exists.
-
-     * @param deviceId an app-specified identifier for the device
-     * @param accountId an app-specified identifier for the account
-     * @param keyValue the raw bytes for the key (max size is {@link #getMaxKeySizeBytes} bytes)
+     * Inserts a new entry into the end-to-end encryption self keys table or updates one if it
+     * already exists.
      *
+     * @param deviceId  an app-specified identifier for the device
+     * @param accountId an app-specified identifier for the account
+     * @param keyValue  the raw bytes for the key (max size is {@link #getMaxKeySizeBytes} bytes)
      * @return true if the entry was added or updated, false otherwise.
      */
     @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS)
-    public boolean updateOrInsertSelfKey(@NonNull String deviceId,
+    public boolean updateOrInsertE2eeSelfKey(@NonNull String deviceId,
             @NonNull String accountId,
             @NonNull byte[] keyValue) {
         validateKeyLength(keyValue);
 
         Bundle extras = new Bundle();
-        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
-        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
-        extras.putByteArray(ContactKeys.KEY_VALUE, Objects.requireNonNull(keyValue));
+        extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+        extras.putByteArray(E2eeContactKeys.KEY_VALUE, Objects.requireNonNull(keyValue));
 
         Bundle response = nullSafeCall(mContentResolver,
-                ContactKeys.UPDATE_OR_INSERT_SELF_KEY_METHOD, extras);
+                E2eeContactKeys.UPDATE_OR_INSERT_SELF_KEY_METHOD, extras);
 
-        return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+        return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS);
     }
 
     private static void validateKeyLength(byte[] keyValue) {
@@ -422,119 +421,117 @@
     }
 
     /**
-     * Updates a self key entry's remote verification state.
+     * Updates an end-to-end encryption self key entry's remote verification state.
      *
-     * @param deviceId an app-specified identifier for the device
-     * @param accountId an app-specified identifier for the account
+     * @param deviceId                an app-specified identifier for the device
+     * @param accountId               an app-specified identifier for the account
      * @param remoteVerificationState the new remote verification state
-     *
      * @return true if the entry was updated, false otherwise.
      */
     @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS)
-    public boolean updateSelfKeyRemoteVerificationState(@NonNull String deviceId,
+    public boolean updateE2eeSelfKeyRemoteVerificationState(@NonNull String deviceId,
             @NonNull String accountId,
             @VerificationState int remoteVerificationState) {
         validateVerificationState(remoteVerificationState);
 
         Bundle extras = new Bundle();
-        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
-        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
-        extras.putInt(ContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
+        extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+        extras.putInt(E2eeContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
 
         Bundle response = nullSafeCall(mContentResolver,
-                ContactKeys.UPDATE_SELF_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
+                E2eeContactKeys.UPDATE_SELF_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
 
-        return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+        return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS);
     }
 
     /**
-     * Updates a self key entry's remote verification state that belongs to the app identified
-     * by ownerPackageName.
+     * Updates an end-to-end encryption self key entry's remote verification state that belongs to
+     * the app identified by ownerPackageName.
      *
-     * @param deviceId an app-specified identifier for the device
-     * @param accountId an app-specified identifier for the account
-     * @param ownerPackageName the package name of the app that owns the key
+     * @param deviceId                an app-specified identifier for the device
+     * @param accountId               an app-specified identifier for the account
+     * @param ownerPackageName        the package name of the app that owns the key
      * @param remoteVerificationState the new remote verification state
-     *
      * @return true if the entry was updated, false otherwise.
-     *
      * @hide
      */
     @SystemApi
     @RequiresPermission(allOf = {
             android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
             android.Manifest.permission.WRITE_CONTACTS})
-    public boolean updateSelfKeyRemoteVerificationState(@NonNull String deviceId,
+    public boolean updateE2eeSelfKeyRemoteVerificationState(@NonNull String deviceId,
             @NonNull String accountId,
             @NonNull String ownerPackageName,
             @VerificationState int remoteVerificationState) {
         validateVerificationState(remoteVerificationState);
 
         Bundle extras = new Bundle();
-        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
-        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
-        extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
-        extras.putInt(ContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
+        extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+        extras.putString(E2eeContactKeys.OWNER_PACKAGE_NAME,
+                Objects.requireNonNull(ownerPackageName));
+        extras.putInt(E2eeContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
 
         Bundle response = nullSafeCall(mContentResolver,
-                ContactKeys.UPDATE_SELF_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
+                E2eeContactKeys.UPDATE_SELF_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
 
-        return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+        return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS);
     }
 
     /**
-     * Maximum size of a contact key.
+     * Maximum size of an end-to-end encryption contact key.
      */
     public static int getMaxKeySizeBytes() {
         return MAX_KEY_SIZE_BYTES;
     }
 
     /**
-     * Returns a self key entry given the deviceId and the inferred package name of the caller.
+     * Returns an end-to-end encryption self key entry given the deviceId and the inferred package
+     * name of the caller.
      *
-     * @param deviceId an app-specified identifier for the device
+     * @param deviceId  an app-specified identifier for the device
      * @param accountId an app-specified identifier for the account
-     *
-     * @return a {@link SelfKey} object containing the self key information, or null if no self key
-     * is found.
+     * @return a {@link E2eeSelfKey} object containing the end-to-end encryption self key
+     * information, or null if no self key is found.
      */
     @RequiresPermission(android.Manifest.permission.READ_CONTACTS)
     @Nullable
-    public SelfKey getSelfKey(@NonNull String deviceId,
+    public E2eeSelfKey getE2eeSelfKey(@NonNull String deviceId,
             @NonNull String accountId) {
         Bundle extras = new Bundle();
-        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
-        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+        extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
 
         Bundle response = nullSafeCall(mContentResolver,
-                ContactKeys.GET_SELF_KEY_METHOD, extras);
+                E2eeContactKeys.GET_SELF_KEY_METHOD, extras);
 
         if (response == null) {
             return null;
         }
-        return response.getParcelable(ContactKeys.KEY_CONTACT_KEY, SelfKey.class);
+        return response.getParcelable(E2eeContactKeys.KEY_CONTACT_KEY, E2eeSelfKey.class);
     }
 
     /**
-     * Returns all self key entries that belong to apps visible to the caller.
+     * Returns all end-to-end encryption self key entries that belong to apps visible to the caller.
      * The keys will be stripped of deviceId, timeUpdated and keyValue data.
      *
-     * @return a list of {@link SelfKey} objects containing the self key information, or
-     * an empty list if no keys are found.
+     * @return a list of {@link E2eeSelfKey} objects containing the end-to-end encryption self key
+     * information, or an empty list if no self keys are found.
      */
     @RequiresPermission(android.Manifest.permission.READ_CONTACTS)
     @NonNull
-    public List<SelfKey> getAllSelfKeys() {
+    public List<E2eeSelfKey> getAllE2eeSelfKeys() {
         Bundle extras = new Bundle();
 
-        Bundle response = nullSafeCall(mContentResolver, ContactKeys.GET_ALL_SELF_KEYS_METHOD,
+        Bundle response = nullSafeCall(mContentResolver, E2eeContactKeys.GET_ALL_SELF_KEYS_METHOD,
                 extras);
 
         if (response == null) {
             return new ArrayList<>();
         }
-        List<SelfKey> value = response.getParcelableArrayList(ContactKeys.KEY_CONTACT_KEYS,
-                SelfKey.class);
+        List<E2eeSelfKey> value = response.getParcelableArrayList(E2eeContactKeys.KEY_CONTACT_KEYS,
+                E2eeSelfKey.class);
         if (value == null) {
             return new ArrayList<>();
         }
@@ -542,24 +539,24 @@
     }
 
     /**
-     * Returns all self key entries that are owned by the caller app.
+     * Returns all end-to-end encryption self key entries that are owned by the caller app.
      *
-     * @return a list of {@link SelfKey} objects containing the self key information, or
-     * an empty list if no keys are found.
+     * @return a list of {@link E2eeSelfKey} objects containing the end-to-end encryption self key
+     * information, or an empty list if no self keys are found.
      */
     @RequiresPermission(android.Manifest.permission.READ_CONTACTS)
     @NonNull
-    public List<SelfKey> getOwnerSelfKeys() {
+    public List<E2eeSelfKey> getOwnerE2eeSelfKeys() {
         Bundle extras = new Bundle();
 
-        Bundle response = nullSafeCall(mContentResolver, ContactKeys.GET_OWNER_SELF_KEYS_METHOD,
+        Bundle response = nullSafeCall(mContentResolver, E2eeContactKeys.GET_OWNER_SELF_KEYS_METHOD,
                 extras);
 
         if (response == null) {
             return new ArrayList<>();
         }
-        List<SelfKey> value = response.getParcelableArrayList(ContactKeys.KEY_CONTACT_KEYS,
-                SelfKey.class);
+        List<E2eeSelfKey> value = response.getParcelableArrayList(E2eeContactKeys.KEY_CONTACT_KEYS,
+                E2eeSelfKey.class);
         if (value == null) {
             return new ArrayList<>();
         }
@@ -567,24 +564,24 @@
     }
 
     /**
-     * Removes a self key entry given the deviceId and the inferred package name of the caller.
-
-     * @param deviceId an app-specified identifier for the device
+     * Removes an end-to-end encryption self key entry given the deviceId and the inferred
+     * package name of the caller.
+     *
+     * @param deviceId  an app-specified identifier for the device
      * @param accountId an app-specified identifier for the account
-     *
      * @return true if the entry was removed, false otherwise.
      */
     @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS)
-    public boolean removeSelfKey(@NonNull String deviceId,
+    public boolean removeE2eeSelfKey(@NonNull String deviceId,
             @NonNull String accountId) {
         Bundle extras = new Bundle();
-        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
-        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+        extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
 
         Bundle response = nullSafeCall(mContentResolver,
-                ContactKeys.REMOVE_SELF_KEY_METHOD, extras);
+                E2eeContactKeys.REMOVE_SELF_KEY_METHOD, extras);
 
-        return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+        return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS);
     }
 
     private Bundle nullSafeCall(@NonNull ContentResolver resolver, @NonNull String method,
@@ -598,6 +595,7 @@
 
     /**
      * Possible values of verification state.
+     *
      * @hide
      */
     @IntDef(prefix = {"VERIFICATION_STATE_"}, value = {
@@ -606,7 +604,8 @@
             VERIFICATION_STATE_VERIFIED
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface VerificationState {}
+    public @interface VerificationState {
+    }
 
     /**
      * Unverified state of a contact end to end encrypted key.
@@ -622,9 +621,10 @@
     public static final int VERIFICATION_STATE_VERIFIED = 2;
 
     /** @hide */
-    public static final class ContactKeys {
+    public static final class E2eeContactKeys {
 
-        private ContactKeys() {}
+        private E2eeContactKeys() {
+        }
 
         /**
          * <p>
@@ -636,14 +636,16 @@
 
         /**
          * <p>
-         * An app-specified identifier for the device for which the contact key can be used.
+         * An app-specified identifier for the device for which the end-to-end encryption
+         * contact key can be used.
          * </p>
          */
         public static final String DEVICE_ID = "device_id";
 
         /**
          * <p>
-         * An app-specified identifier for the account for which the contact key can be used.
+         * An app-specified identifier for the account for which the end-to-end encryption
+         * contact key can be used.
          * Usually a phone number.
          * </p>
          */
@@ -718,29 +720,32 @@
         public static final String GET_CONTACT_KEY_METHOD = "getContactKey";
 
         /**
-         * The method to invoke in order to retrieve all contact keys.
+         * The method to invoke in order to retrieve all end-to-end encryption contact keys.
          */
         public static final String GET_ALL_CONTACT_KEYS_METHOD = "getAllContactKeys";
 
         /**
-         * The method to invoke in order to retrieve contact keys that belong to the caller.
+         * The method to invoke in order to retrieve end-to-end encryption contact keys that belong
+         * to the caller.
          */
         public static final String GET_OWNER_CONTACT_KEYS_METHOD = "getOwnerContactKeys";
 
         /**
-         * The method to invoke in order to update a contact key local verification state.
+         * The method to invoke in order to update an end-to-end encryption contact key local
+         * verification state.
          */
         public static final String UPDATE_CONTACT_KEY_LOCAL_VERIFICATION_STATE_METHOD =
                 "updateContactKeyLocalVerificationState";
 
         /**
-         * The method to invoke in order to update a contact key remote verification state.
+         * The method to invoke in order to update an end-to-end encryption contact key remote
+         * verification state.
          */
         public static final String UPDATE_CONTACT_KEY_REMOTE_VERIFICATION_STATE_METHOD =
                 "updateContactKeyRemoteVerificationState";
 
         /**
-         * The method to invoke in order to remove a contact key.
+         * The method to invoke in order to remove a end-to-end encryption contact key.
          */
         public static final String REMOVE_CONTACT_KEY_METHOD = "removeContactKey";
 
@@ -776,12 +781,12 @@
         public static final String REMOVE_SELF_KEY_METHOD = "removeSelfKey";
 
         /**
-         * Key in the incoming Bundle for all the contact keys.
+         * Key in the incoming Bundle for all the end-to-end encryption contact keys.
          */
         public static final String KEY_CONTACT_KEYS = "key_contact_keys";
 
         /**
-         * Key in the incoming Bundle for a single contact key.
+         * Key in the incoming Bundle for a single end-to-end encryption contact key.
          */
         public static final String KEY_CONTACT_KEY = "key_contact_key";
 
@@ -790,36 +795,10 @@
          */
         public static final String KEY_UPDATED_ROWS = "key_updated_rows";
     }
-
     /**
      * A parcelable class encapsulating other users' end to end encrypted contact key.
      */
-    public static final class ContactKey implements Parcelable {
-        /**
-         * An app-specified identifier for the device for which the contact key can be used.
-         */
-        private final String mDeviceId;
-
-        /**
-         * An app-specified identifier for the account for which the contact key can be used.
-         * Usually a phone number.
-         */
-        private final String mAccountId;
-
-        /**
-         * Owner application package name.
-         */
-        private final String mOwnerPackageName;
-
-        /**
-         * Timestamp at which the key was updated.
-         */
-        private final long mTimeUpdated;
-
-        /**
-         * The raw bytes for the key.
-         */
-        private final byte[] mKeyValue;
+    public static final class E2eeContactKey extends E2eeBaseKey implements Parcelable {
 
         /**
          * Describes the local verification state for the key, for instance QR-code based
@@ -828,12 +807,6 @@
         private final int mLocalVerificationState;
 
         /**
-         * Describes the remote verification state for the key, for instance through a key
-         * transparency server.
-         */
-        private final int mRemoteVerificationState;
-
-        /**
          * The display name for the contact.
          */
         private final String mDisplayName;
@@ -851,79 +824,21 @@
         /**
          * @hide
          */
-        public ContactKey(@Nullable String deviceId, @NonNull String accountId,
+        public E2eeContactKey(@Nullable String deviceId, @NonNull String accountId,
                 @NonNull String ownerPackageName, long timeUpdated, @Nullable byte[] keyValue,
                 @VerificationState int localVerificationState,
                 @VerificationState int remoteVerificationState,
                 @Nullable String displayName,
                 @Nullable String phoneNumber, @Nullable String emailAddress) {
-            this.mDeviceId = deviceId;
-            this.mAccountId = accountId;
-            this.mOwnerPackageName = ownerPackageName;
-            this.mTimeUpdated = timeUpdated;
-            this.mKeyValue = keyValue == null ? null : Arrays.copyOf(keyValue, keyValue.length);
+            super(deviceId, accountId, ownerPackageName, timeUpdated, keyValue,
+                    remoteVerificationState);
             this.mLocalVerificationState = localVerificationState;
-            this.mRemoteVerificationState = remoteVerificationState;
             this.mDisplayName = displayName;
             this.mPhoneNumber = phoneNumber;
             this.mEmailAddress = emailAddress;
         }
 
         /**
-         * Gets the app-specified identifier for the device for which the contact key can be used.
-         * Returns null if the app doesn't have the required visibility into the contact key.
-         *
-         * @return An app-specified identifier for the device.
-         */
-        @Nullable
-        public String getDeviceId() {
-            return mDeviceId;
-        }
-
-        /**
-         * Gets the app-specified identifier for the account for which the contact key can be used.
-         * Usually a phone number.
-         *
-         * @return An app-specified identifier for the account.
-         */
-        @NonNull
-        public String getAccountId() {
-            return mAccountId;
-        }
-
-        /**
-         * Gets the owner application package name.
-         *
-         * @return The owner application package name.
-         */
-        @NonNull
-        public String getOwnerPackageName() {
-            return mOwnerPackageName;
-        }
-
-        /**
-         * Gets the timestamp at which the key was updated. Returns -1 if the app doesn't have the
-         * required visibility into the contact key.
-         *
-         * @return The timestamp at which the key was updated in the System.currentTimeMillis()
-         * base.
-         */
-        public long getTimeUpdated() {
-            return mTimeUpdated;
-        }
-
-        /**
-         * Gets the raw bytes for the key.
-         * Returns null if the app doesn't have the required visibility into the contact key.
-         *
-         * @return A copy of the raw bytes for the key.
-         */
-        @Nullable
-        public byte[] getKeyValue() {
-            return mKeyValue == null ? null : Arrays.copyOf(mKeyValue, mKeyValue.length);
-        }
-
-        /**
          * Gets the local verification state for the key, for instance QR-code based verification.
          *
          * @return The local verification state for the key.
@@ -933,16 +848,6 @@
         }
 
         /**
-         * Gets the remote verification state for the key, for instance through a key transparency
-         * server.
-         *
-         * @return The remote verification state for the key.
-         */
-        public @VerificationState int getRemoteVerificationState() {
-            return mRemoteVerificationState;
-        }
-
-        /**
          * Gets the display name for the contact.
          *
          * @return The display name for the contact.
@@ -984,7 +889,7 @@
             if (obj == null) return false;
             if (obj == this) return true;
 
-            if (!(obj instanceof ContactKey toCompare)) {
+            if (!(obj instanceof E2eeContactKey toCompare)) {
                 return false;
             }
 
@@ -1023,10 +928,10 @@
         }
 
         @NonNull
-        public static final Creator<ContactKey> CREATOR =
+        public static final Creator<E2eeContactKey> CREATOR =
                 new Creator<>() {
                     @Override
-                    public ContactKey createFromParcel(Parcel source) {
+                    public E2eeContactKey createFromParcel(Parcel source) {
                         String deviceId = source.readString8();
                         String accountId = source.readString8();
                         String ownerPackageName = source.readString8();
@@ -1044,14 +949,14 @@
                         String displayName = source.readString8();
                         String number = source.readString8();
                         String address = source.readString8();
-                        return new ContactKey(deviceId, accountId, ownerPackageName,
+                        return new E2eeContactKey(deviceId, accountId, ownerPackageName,
                                 timeUpdated, keyValue, localVerificationState,
                                 remoteVerificationState, displayName, number, address);
                     }
 
                     @Override
-                    public ContactKey[] newArray(int size) {
-                        return new ContactKey[size];
+                    public E2eeContactKey[] newArray(int size) {
+                        return new E2eeContactKey[size];
                     }
                 };
     }
@@ -1059,116 +964,15 @@
     /**
      * A parcelable class encapsulating self end to end encrypted contact key.
      */
-    public static final class SelfKey implements Parcelable {
-        /**
-         * An app-specified identifier for the device for which the contact key can be used.
-         */
-        private final String mDeviceId;
-
-        /**
-         * An app-specified identifier for the account for which the contact key can be used.
-         * Usually a phone number.
-         */
-        private final String mAccountId;
-
-        /**
-         * Owner application package name.
-         */
-        private final String mOwnerPackageName;
-
-        /**
-         * Timestamp at which the key was updated.
-         */
-        private final long mTimeUpdated;
-
-        /**
-         * The raw bytes for the key.
-         */
-        private final byte[] mKeyValue;
-
-
-        /**
-         * Describes the remote verification state for the key, for instance through a key
-         * transparency server.
-         */
-        private final int mRemoteVerificationState;
-
+    public static final class E2eeSelfKey extends E2eeBaseKey implements Parcelable {
         /**
          * @hide
          */
-        public SelfKey(@Nullable String deviceId, @NonNull String accountId,
+        public E2eeSelfKey(@Nullable String deviceId, @NonNull String accountId,
                 @NonNull String ownerPackageName, long timeUpdated, @Nullable byte[] keyValue,
                 @VerificationState int remoteVerificationState) {
-            this.mDeviceId = deviceId;
-            this.mAccountId = accountId;
-            this.mOwnerPackageName = ownerPackageName;
-            this.mTimeUpdated = timeUpdated;
-            this.mKeyValue = keyValue == null ? null : Arrays.copyOf(keyValue, keyValue.length);
-            this.mRemoteVerificationState = remoteVerificationState;
-        }
-
-        /**
-         * Gets the app-specified identifier for the device for which the contact key can be used.
-         * Returns null if the app doesn't have the required visibility into the contact key.
-         *
-         * @return An app-specified identifier for the device.
-         */
-        @Nullable
-        public String getDeviceId() {
-            return mDeviceId;
-        }
-
-        /**
-         * Gets the app-specified identifier for the account for which the contact key can be used.
-         * Usually a phone number.
-         *
-         * @return An app-specified identifier for the device.
-         */
-        @NonNull
-        public String getAccountId() {
-            return mAccountId;
-        }
-
-        /**
-         * Gets the owner application package name.
-         *
-         * @return The owner application package name.
-         */
-        @NonNull
-        public String getOwnerPackageName() {
-            return mOwnerPackageName;
-        }
-
-        /**
-         * Gets the timestamp at which the key was updated. Returns -1 if the app doesn't have the
-         * required visibility into the contact key.
-         *
-         * @return The timestamp at which the key was updated in the System.currentTimeMillis()
-         * base.
-         */
-        public long getTimeUpdated() {
-            return mTimeUpdated;
-        }
-
-        /**
-         * Gets the raw bytes for the key.
-         * Returns null if the app doesn't have the required visibility into the contact key.
-         *
-         * @return A copy of the raw bytes for the key.
-         */
-        @Nullable
-        public byte[] getKeyValue() {
-            return mKeyValue == null ? null : Arrays.copyOf(mKeyValue, mKeyValue.length);
-        }
-
-        /**
-         * Gets the remote verification state for the key, for instance through a key transparency
-         * server.
-         *
-         * @return The remote verification state for the key.
-         */
-        public @VerificationState int getRemoteVerificationState() {
-            return mRemoteVerificationState;
+            super(deviceId, accountId, ownerPackageName, timeUpdated, keyValue,
+                    remoteVerificationState);
         }
 
         @Override
@@ -1182,7 +986,7 @@
             if (obj == null) return false;
             if (obj == this) return true;
 
-            if (!(obj instanceof SelfKey toCompare)) {
+            if (!(obj instanceof E2eeSelfKey toCompare)) {
                 return false;
             }
 
@@ -1213,10 +1017,10 @@
         }
 
         @NonNull
-        public static final Creator<SelfKey> CREATOR =
+        public static final Creator<E2eeSelfKey> CREATOR =
                 new Creator<>() {
                     @Override
-                    public SelfKey createFromParcel(Parcel source) {
+                    public E2eeSelfKey createFromParcel(Parcel source) {
                         String deviceId = source.readString8();
                         String accountId = source.readString8();
                         String ownerPackageName = source.readString8();
@@ -1230,14 +1034,132 @@
                             keyValue = null;
                         }
                         int remoteVerificationState = source.readInt();
-                        return new SelfKey(deviceId, accountId, ownerPackageName,
+                        return new E2eeSelfKey(deviceId, accountId, ownerPackageName,
                                 timeUpdated, keyValue, remoteVerificationState);
                     }
 
                     @Override
-                    public SelfKey[] newArray(int size) {
-                        return new SelfKey[size];
+                    public E2eeSelfKey[] newArray(int size) {
+                        return new E2eeSelfKey[size];
                     }
                 };
     }
+
+    /**
+     * An abstract class that's extended by self and contact key classes.
+     *
+     * @hide
+     */
+    abstract static class E2eeBaseKey {
+        /**
+         * An app-specified identifier for the device for which the key can be used.
+         */
+        protected final String mDeviceId;
+
+        /**
+         * An app-specified identifier for the account for which the key can be used.
+         * Usually a phone number.
+         */
+        protected final String mAccountId;
+
+        /**
+         * Owner application package name.
+         */
+        protected final String mOwnerPackageName;
+
+        /**
+         * Timestamp at which the key was updated.
+         */
+        protected final long mTimeUpdated;
+
+        /**
+         * The raw bytes for the key.
+         */
+        protected final byte[] mKeyValue;
+
+        /**
+         * Describes the remote verification state for the end-to-end encryption key, for instance
+         * through a key transparency server.
+         */
+        protected final int mRemoteVerificationState;
+
+        protected E2eeBaseKey(@Nullable String deviceId, @NonNull String accountId,
+                @NonNull String ownerPackageName, long timeUpdated, @Nullable byte[] keyValue,
+                @VerificationState int remoteVerificationState) {
+            this.mDeviceId = deviceId;
+            this.mAccountId = accountId;
+            this.mOwnerPackageName = ownerPackageName;
+            this.mTimeUpdated = timeUpdated;
+            this.mKeyValue = keyValue == null ? null : Arrays.copyOf(keyValue, keyValue.length);
+            this.mRemoteVerificationState = remoteVerificationState;
+        }
+
+        /**
+         * Gets the app-specified identifier for the device for which the end-to-end encryption
+         * key can be used.
+         * Returns null if the app doesn't have the required visibility into
+         * the end-to-end encryption key.
+         *
+         * @return An app-specified identifier for the device.
+         */
+        @Nullable
+        public String getDeviceId() {
+            return mDeviceId;
+        }
+
+        /**
+         * Gets the app-specified identifier for the account for which the end-to-end encryption
+         * key can be used.
+         * Usually a phone number.
+         *
+         * @return An app-specified identifier for the account.
+         */
+        @NonNull
+        public String getAccountId() {
+            return mAccountId;
+        }
+
+        /**
+         * Gets the owner application package name.
+         *
+         * @return The owner application package name.
+         */
+        @NonNull
+        public String getOwnerPackageName() {
+            return mOwnerPackageName;
+        }
+
+        /**
+         * Gets the timestamp at which the end-to-end encryption key was updated. Returns -1 if
+         * the app doesn't have the required visibility into the key.
+         *
+         * @return The timestamp at which the key was updated in the System.currentTimeMillis()
+         * base.
+         */
+        public long getTimeUpdated() {
+            return mTimeUpdated;
+        }
+
+        /**
+         * Gets the raw bytes for the end-to-end encryption key.
+         * Returns null if the app doesn't have the required visibility into
+         * the end-to-end encryption key.
+         *
+         * @return A copy of the raw bytes for the end-to-end encryption key.
+         */
+        @Nullable
+        public byte[] getKeyValue() {
+            return mKeyValue == null ? null : Arrays.copyOf(mKeyValue, mKeyValue.length);
+        }
+
+        /**
+         * Gets the remote verification state for the end-to-end encryption key, for instance
+         * through a key transparency server.
+         *
+         * @return The remote verification state for the end-to-end encryption key.
+         */
+        public @VerificationState int getRemoteVerificationState() {
+            return mRemoteVerificationState;
+        }
+    }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 20b4857..e26dc73 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -471,6 +471,21 @@
             "android.settings.ACCESSIBILITY_COLOR_MOTION_SETTINGS";
 
     /**
+     * Activity Action: Show settings to allow configuration of accessibility color contrast.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_ACCESSIBILITY_COLOR_CONTRAST_SETTINGS =
+            "android.settings.ACCESSIBILITY_COLOR_CONTRAST_SETTINGS";
+
+    /**
      * Activity Action: Show settings to allow configuration of Reduce Bright Colors.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
@@ -3568,6 +3583,12 @@
                                                     + " and user:" + userHandle
                                                     + " with index:" + index);
                                         }
+                                        // Always make sure to close any pre-existing tracker before
+                                        // replacing it, to prevent memory leaks
+                                        var oldTracker = mGenerationTrackers.get(name);
+                                        if (oldTracker != null) {
+                                            oldTracker.destroy();
+                                        }
                                         mGenerationTrackers.put(name, new GenerationTracker(name,
                                                 array, index, generation,
                                                 mGenerationTrackerErrorHandler));
@@ -3790,6 +3811,12 @@
                                         + " in package:" + cr.getPackageName()
                                         + " with index:" + index);
                             }
+                            // Always make sure to close any pre-existing tracker before
+                            // replacing it, to prevent memory leaks
+                            var oldTracker = mGenerationTrackers.get(prefix);
+                            if (oldTracker != null) {
+                                oldTracker.destroy();
+                            }
                             mGenerationTrackers.put(prefix,
                                     new GenerationTracker(prefix, array, index, generation,
                                             mGenerationTrackerErrorHandler));
@@ -11561,6 +11588,15 @@
                 "extra_low_power_warning_acknowledged";
 
         /**
+         * Whether the emergency thermal alert would be disabled
+         * (0: default) or not (1).
+         *
+         * @hide
+         */
+        public static final String EMERGENCY_THERMAL_ALERT_DISABLED =
+                "emergency_thermal_alert_disabled";
+
+        /**
          * 0 (default) Auto battery saver suggestion has not been suppressed. 1) it has been
          * suppressed.
          * @hide
@@ -19968,6 +20004,13 @@
             @Readable
             public static final String CONSISTENT_NOTIFICATION_BLOCKING_ENABLED =
                     "consistent_notification_blocking_enabled";
+
+            /**
+             * Whether the Auto Bedtime Mode experience is enabled.
+             *
+             * @hide
+             */
+            public static final String AUTO_BEDTIME_MODE = "auto_bedtime_mode";
         }
     }
 
diff --git a/core/java/android/security/ConfirmationPrompt.java b/core/java/android/security/ConfirmationPrompt.java
index d8c44ad..f626149 100644
--- a/core/java/android/security/ConfirmationPrompt.java
+++ b/core/java/android/security/ConfirmationPrompt.java
@@ -92,7 +92,6 @@
     private Executor mExecutor;
     private Context mContext;
 
-    private final KeyStore mKeyStore = KeyStore.getInstance();
     private AndroidProtectedConfirmation mProtectedConfirmation;
 
     private AndroidProtectedConfirmation getService() {
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 7631454..3c77c44 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -2,6 +2,7 @@
 
 flag {
     name: "certificate_transparency_configuration"
+    is_exported: true
     namespace: "network_security"
     description: "Enable certificate transparency setting in the network security config"
     bug: "28746284"
@@ -16,6 +17,7 @@
 
 flag {
     name: "mgf1_digest_setter_v2"
+    is_exported: true
     namespace: "hardware_backed_security"
     description: "Feature flag for mgf1 digest setter in key generation and import parameters."
     bug: "308378912"
@@ -31,6 +33,14 @@
 }
 
 flag {
+    name: "keyinfo_unlocked_device_required"
+    is_exported: true
+    namespace: "hardware_backed_security"
+    description: "Add the API android.security.keystore.KeyInfo#isUnlockedDeviceRequired()"
+    bug: "296475382"
+}
+
+flag {
     name: "deprecate_fsv_sig"
     namespace: "hardware_backed_security"
     description: "Feature flag for deprecating .fsv_sig"
diff --git a/core/java/android/security/keystore/recovery/RecoveryController.java b/core/java/android/security/keystore/recovery/RecoveryController.java
index f1054ec..c171c1b 100644
--- a/core/java/android/security/keystore/recovery/RecoveryController.java
+++ b/core/java/android/security/keystore/recovery/RecoveryController.java
@@ -26,7 +26,6 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
-import android.security.KeyStore;
 import android.security.KeyStore2;
 import android.security.keystore.KeyPermanentlyInvalidatedException;
 import android.security.keystore2.AndroidKeyStoreProvider;
@@ -272,11 +271,9 @@
     public static final int ERROR_KEY_NOT_FOUND = 30;
 
     private final ILockSettings mBinder;
-    private final KeyStore mKeyStore;
 
-    private RecoveryController(ILockSettings binder, KeyStore keystore) {
+    private RecoveryController(ILockSettings binder) {
         mBinder = binder;
-        mKeyStore = keystore;
     }
 
     /**
@@ -296,7 +293,7 @@
         // lockSettings may be null.
         ILockSettings lockSettings =
                 ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings"));
-        return new RecoveryController(lockSettings, KeyStore.getInstance());
+        return new RecoveryController(lockSettings);
     }
 
     /**
diff --git a/core/java/android/service/chooser/flags.aconfig b/core/java/android/service/chooser/flags.aconfig
index 00236df..d72441f 100644
--- a/core/java/android/service/chooser/flags.aconfig
+++ b/core/java/android/service/chooser/flags.aconfig
@@ -27,3 +27,14 @@
   description: "Provides additional callbacks with information about user actions in ChooserResult"
   bug: "263474465"
 }
+
+flag {
+  name: "legacy_chooser_pinning_removal"
+  namespace: "intentresolver"
+  description: "Removing pinning functionality from the legacy chooser (used by partial screenshare)"
+  bug: "301068735"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index dd8b3de..c489c58 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -38,6 +38,7 @@
     boolean isDreaming();
     @UnsupportedAppUsage
     boolean isDreamingOrInPreview();
+    boolean canStartDreaming(boolean isScreenOn);
     void finishSelf(in IBinder token, boolean immediate);
     void startDozing(in IBinder token, int screenState, int screenBrightness);
     void stopDozing(in IBinder token);
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 7658af5..bd9ab86 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -22,6 +22,7 @@
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
+import android.annotation.UiThread;
 import android.app.ActivityManager;
 import android.app.INotificationManager;
 import android.app.Notification;
@@ -468,6 +469,7 @@
      *            object as well as its identifying information (tag and id) and source
      *            (package name).
      */
+    @UiThread
     public void onNotificationPosted(StatusBarNotification sbn) {
         // optional
     }
@@ -481,6 +483,7 @@
      * @param rankingMap The current ranking map that can be used to retrieve ranking information
      *                   for active notifications, including the newly posted one.
      */
+    @UiThread
     public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
         onNotificationPosted(sbn);
     }
@@ -499,6 +502,7 @@
      *            and source (package name) used to post the {@link android.app.Notification} that
      *            was just removed.
      */
+    @UiThread
     public void onNotificationRemoved(StatusBarNotification sbn) {
         // optional
     }
@@ -520,6 +524,7 @@
      *                   for active notifications.
      *
      */
+    @UiThread
     public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
         onNotificationRemoved(sbn);
     }
@@ -541,6 +546,7 @@
      * @param rankingMap The current ranking map that can be used to retrieve ranking information
      *                   for active notifications.
      */
+    @UiThread
     public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
             @NotificationCancelReason int reason) {
         onNotificationRemoved(sbn, rankingMap);
@@ -552,6 +558,7 @@
      *
      * @hide
      */
+    @UiThread
     @SystemApi
     public void onNotificationRemoved(@NonNull StatusBarNotification sbn,
             @NonNull RankingMap rankingMap, @NonNull NotificationStats stats, int reason) {
@@ -563,6 +570,7 @@
      * the notification manager.  You are safe to call {@link #getActiveNotifications()}
      * at this time.
      */
+    @UiThread
     public void onListenerConnected() {
         // optional
     }
@@ -572,6 +580,7 @@
      * notification manager.You will not receive any events after this call, and may only
      * call {@link #requestRebind(ComponentName)} at this time.
      */
+    @UiThread
     public void onListenerDisconnected() {
         // optional
     }
@@ -582,6 +591,7 @@
      * @param rankingMap The current ranking map that can be used to retrieve ranking information
      *                   for active notifications.
      */
+    @UiThread
     public void onNotificationRankingUpdate(RankingMap rankingMap) {
         // optional
     }
@@ -592,6 +602,7 @@
      *
      * @param hints The current {@link #getCurrentListenerHints() listener hints}.
      */
+    @UiThread
     public void onListenerHintsChanged(int hints) {
         // optional
     }
@@ -603,6 +614,7 @@
      * @param hideSilentStatusIcons whether or not status bar icons should be hidden for silent
      *                              notifications
      */
+    @UiThread
     public void onSilentStatusBarIconsVisibilityChanged(boolean hideSilentStatusIcons) {
         // optional
     }
@@ -620,6 +632,7 @@
      *                   {@link #NOTIFICATION_CHANNEL_OR_GROUP_UPDATED},
      *                   {@link #NOTIFICATION_CHANNEL_OR_GROUP_DELETED}.
      */
+    @UiThread
     public void onNotificationChannelModified(String pkg, UserHandle user,
             NotificationChannel channel, @ChannelOrGroupModificationTypes int modificationType) {
         // optional
@@ -638,6 +651,7 @@
      *                   {@link #NOTIFICATION_CHANNEL_OR_GROUP_UPDATED},
      *                   {@link #NOTIFICATION_CHANNEL_OR_GROUP_DELETED}.
      */
+    @UiThread
     public void onNotificationChannelGroupModified(String pkg, UserHandle user,
             NotificationChannelGroup group, @ChannelOrGroupModificationTypes int modificationType) {
         // optional
@@ -650,6 +664,7 @@
      * @param interruptionFilter The current
      *     {@link #getCurrentInterruptionFilter() interruption filter}.
      */
+    @UiThread
     public void onInterruptionFilterChanged(int interruptionFilter) {
         // optional
     }
@@ -1197,6 +1212,11 @@
      * <p>
      * Listen for updates using {@link #onInterruptionFilterChanged(int)}.
      *
+     * <p>Apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+     * exceptions, such as companion device managers) cannot modify the global interruption filter.
+     * Calling this method will instead activate or deactivate an
+     * {@link android.app.AutomaticZenRule} associated to the app.
+     *
      * <p>The service should wait for the {@link #onListenerConnected()} event
      * before performing this operation.
      *
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index 264b53c..d821074 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -174,6 +174,23 @@
         return sbnKey;
     }
 
+    /**
+     * @return Whether the Entry is a group child by the app or system
+     * @hide
+     */
+    public boolean isAppOrSystemGroupChild() {
+        return isGroup() && !getNotification().isGroupSummary();
+    }
+
+
+    /**
+     * @return Whether the Entry is a group summary by the app or system
+     * @hide
+     */
+    public boolean isAppOrSystemGroupSummary() {
+        return isGroup() && getNotification().isGroupSummary();
+    }
+
     private String groupKey() {
         if (overrideGroupKey != null) {
             return user.getIdentifier() + "|" + pkg + "|" + "g:" + overrideGroupKey;
diff --git a/core/java/android/service/notification/ZenAdapters.java b/core/java/android/service/notification/ZenAdapters.java
new file mode 100644
index 0000000..b249815
--- /dev/null
+++ b/core/java/android/service/notification/ZenAdapters.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.notification;
+
+import android.annotation.NonNull;
+import android.app.Flags;
+import android.app.NotificationManager.Policy;
+
+/**
+ * Converters between different Zen representations.
+ * @hide
+ */
+public class ZenAdapters {
+
+    /** Maps {@link Policy} to {@link ZenPolicy}. */
+    @NonNull
+    public static ZenPolicy notificationPolicyToZenPolicy(@NonNull Policy policy) {
+        ZenPolicy.Builder zenPolicyBuilder = new ZenPolicy.Builder()
+                .allowAlarms(policy.allowAlarms())
+                .allowCalls(
+                        policy.allowCalls()
+                                ? notificationPolicySendersToZenPolicyPeopleType(
+                                        policy.allowCallsFrom())
+                        : ZenPolicy.PEOPLE_TYPE_NONE)
+                .allowConversations(
+                        policy.allowConversations()
+                                ? notificationPolicyConversationSendersToZenPolicy(
+                                        policy.allowConversationsFrom())
+                                : ZenPolicy.CONVERSATION_SENDERS_NONE)
+                .allowEvents(policy.allowEvents())
+                .allowMedia(policy.allowMedia())
+                .allowMessages(
+                        policy.allowMessages()
+                                ? notificationPolicySendersToZenPolicyPeopleType(
+                                        policy.allowMessagesFrom())
+                                : ZenPolicy.PEOPLE_TYPE_NONE)
+                .allowReminders(policy.allowReminders())
+                .allowRepeatCallers(policy.allowRepeatCallers())
+                .allowSystem(policy.allowSystem());
+
+        if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) {
+            zenPolicyBuilder.showBadges(policy.showBadges())
+                    .showFullScreenIntent(policy.showFullScreenIntents())
+                    .showInAmbientDisplay(policy.showAmbient())
+                    .showInNotificationList(policy.showInNotificationList())
+                    .showLights(policy.showLights())
+                    .showPeeking(policy.showPeeking())
+                    .showStatusBarIcons(policy.showStatusBarIcons());
+        }
+
+        if (Flags.modesApi()) {
+            zenPolicyBuilder.allowPriorityChannels(policy.allowPriorityChannels());
+        }
+
+        return zenPolicyBuilder.build();
+    }
+
+    /** Maps {@link ZenPolicy.PeopleType} enum to {@link Policy.PrioritySenders}. */
+    @Policy.PrioritySenders
+    public static int zenPolicyPeopleTypeToNotificationPolicySenders(
+            @ZenPolicy.PeopleType int zpPeopleType, @Policy.PrioritySenders int defaultResult) {
+        switch (zpPeopleType) {
+            case ZenPolicy.PEOPLE_TYPE_ANYONE:
+                return Policy.PRIORITY_SENDERS_ANY;
+            case ZenPolicy.PEOPLE_TYPE_CONTACTS:
+                return Policy.PRIORITY_SENDERS_CONTACTS;
+            case ZenPolicy.PEOPLE_TYPE_STARRED:
+                return Policy.PRIORITY_SENDERS_STARRED;
+            default:
+                return defaultResult;
+        }
+    }
+
+    /** Maps {@link Policy.PrioritySenders} enum to {@link ZenPolicy.PeopleType}. */
+    @ZenPolicy.PeopleType
+    public static int notificationPolicySendersToZenPolicyPeopleType(
+            @Policy.PrioritySenders int npPrioritySenders) {
+        switch (npPrioritySenders) {
+            case Policy.PRIORITY_SENDERS_ANY:
+                return ZenPolicy.PEOPLE_TYPE_ANYONE;
+            case Policy.PRIORITY_SENDERS_CONTACTS:
+                return ZenPolicy.PEOPLE_TYPE_CONTACTS;
+            case Policy.PRIORITY_SENDERS_STARRED:
+            default:
+                return ZenPolicy.PEOPLE_TYPE_STARRED;
+        }
+    }
+
+    /** Maps {@link ZenPolicy.ConversationSenders} enum to {@link Policy.ConversationSenders}. */
+    @Policy.ConversationSenders
+    public static int zenPolicyConversationSendersToNotificationPolicy(
+            @ZenPolicy.ConversationSenders int zpConversationSenders,
+            @Policy.ConversationSenders int defaultResult) {
+        switch (zpConversationSenders) {
+            case ZenPolicy.CONVERSATION_SENDERS_ANYONE:
+                return Policy.CONVERSATION_SENDERS_ANYONE;
+            case ZenPolicy.CONVERSATION_SENDERS_IMPORTANT:
+                return Policy.CONVERSATION_SENDERS_IMPORTANT;
+            case ZenPolicy.CONVERSATION_SENDERS_NONE:
+                return Policy.CONVERSATION_SENDERS_NONE;
+            default:
+                return defaultResult;
+        }
+    }
+
+    /** Maps {@link Policy.ConversationSenders} enum to {@link ZenPolicy.ConversationSenders}. */
+    @ZenPolicy.ConversationSenders
+    private static int notificationPolicyConversationSendersToZenPolicy(
+            @Policy.ConversationSenders int npPriorityConversationSenders) {
+        switch (npPriorityConversationSenders) {
+            case Policy.CONVERSATION_SENDERS_ANYONE:
+                return ZenPolicy.CONVERSATION_SENDERS_ANYONE;
+            case Policy.CONVERSATION_SENDERS_IMPORTANT:
+                return ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
+            case Policy.CONVERSATION_SENDERS_NONE:
+                return ZenPolicy.CONVERSATION_SENDERS_NONE;
+            default: // including Policy.CONVERSATION_SENDERS_UNSET
+                return ZenPolicy.CONVERSATION_SENDERS_UNSET;
+        }
+    }
+}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index d9ca935..1d6dd1e 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -24,6 +24,9 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
+import static android.service.notification.ZenAdapters.notificationPolicySendersToZenPolicyPeopleType;
+import static android.service.notification.ZenAdapters.zenPolicyConversationSendersToNotificationPolicy;
+import static android.service.notification.ZenAdapters.zenPolicyPeopleTypeToNotificationPolicySenders;
 
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
@@ -1269,11 +1272,11 @@
     public ZenPolicy toZenPolicy() {
         ZenPolicy.Builder builder = new ZenPolicy.Builder()
                 .allowCalls(allowCalls
-                        ? ZenModeConfig.getZenPolicySenders(allowCallsFrom)
+                        ? notificationPolicySendersToZenPolicyPeopleType(allowCallsFrom)
                         : ZenPolicy.PEOPLE_TYPE_NONE)
                 .allowRepeatCallers(allowRepeatCallers)
                 .allowMessages(allowMessages
-                        ? ZenModeConfig.getZenPolicySenders(allowMessagesFrom)
+                        ? notificationPolicySendersToZenPolicyPeopleType(allowMessagesFrom)
                         : ZenPolicy.PEOPLE_TYPE_NONE)
                 .allowReminders(allowReminders)
                 .allowEvents(allowEvents)
@@ -1333,14 +1336,14 @@
         if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_MESSAGES,
                 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_MESSAGES, defaultPolicy))) {
             priorityCategories |= Policy.PRIORITY_CATEGORY_MESSAGES;
-            messageSenders = getNotificationPolicySenders(zenPolicy.getPriorityMessageSenders(),
-                    messageSenders);
+            messageSenders = zenPolicyPeopleTypeToNotificationPolicySenders(
+                    zenPolicy.getPriorityMessageSenders(), messageSenders);
         }
 
         if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CONVERSATIONS,
                 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CONVERSATIONS, defaultPolicy))) {
             priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS;
-            conversationSenders = getConversationSendersWithDefault(
+            conversationSenders = zenPolicyConversationSendersToNotificationPolicy(
                     zenPolicy.getPriorityConversationSenders(), conversationSenders);
         } else {
             conversationSenders = CONVERSATION_SENDERS_NONE;
@@ -1349,8 +1352,8 @@
         if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CALLS,
                 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CALLS, defaultPolicy))) {
             priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS;
-            callSenders = getNotificationPolicySenders(zenPolicy.getPriorityCallSenders(),
-                    callSenders);
+            callSenders = zenPolicyPeopleTypeToNotificationPolicySenders(
+                    zenPolicy.getPriorityCallSenders(), callSenders);
         }
 
         if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_REPEAT_CALLERS,
@@ -1449,47 +1452,6 @@
         return (policy.suppressedVisualEffects & visualEffect) == 0;
     }
 
-    private static int getNotificationPolicySenders(@ZenPolicy.PeopleType int senders,
-            int defaultPolicySender) {
-        switch (senders) {
-            case ZenPolicy.PEOPLE_TYPE_ANYONE:
-                return Policy.PRIORITY_SENDERS_ANY;
-            case ZenPolicy.PEOPLE_TYPE_CONTACTS:
-                return Policy.PRIORITY_SENDERS_CONTACTS;
-            case ZenPolicy.PEOPLE_TYPE_STARRED:
-                return Policy.PRIORITY_SENDERS_STARRED;
-            default:
-                return defaultPolicySender;
-        }
-    }
-
-    private static int getConversationSendersWithDefault(@ZenPolicy.ConversationSenders int senders,
-            int defaultPolicySender) {
-        switch (senders) {
-            case ZenPolicy.CONVERSATION_SENDERS_ANYONE:
-            case ZenPolicy.CONVERSATION_SENDERS_IMPORTANT:
-            case ZenPolicy.CONVERSATION_SENDERS_NONE:
-                return senders;
-            default:
-                return defaultPolicySender;
-        }
-    }
-
-    /**
-     * Maps NotificationManager.Policy senders type to ZenPolicy.PeopleType
-     */
-    public static @ZenPolicy.PeopleType int getZenPolicySenders(int senders) {
-        switch (senders) {
-            case Policy.PRIORITY_SENDERS_ANY:
-                return ZenPolicy.PEOPLE_TYPE_ANYONE;
-            case Policy.PRIORITY_SENDERS_CONTACTS:
-                return ZenPolicy.PEOPLE_TYPE_CONTACTS;
-            case Policy.PRIORITY_SENDERS_STARRED:
-            default:
-                return ZenPolicy.PEOPLE_TYPE_STARRED;
-        }
-    }
-
     public Policy toNotificationPolicy() {
         int priorityCategories = 0;
         int priorityCallSenders = Policy.PRIORITY_SENDERS_CONTACTS;
@@ -1524,7 +1486,7 @@
         }
         priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders);
         priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders);
-        priorityConversationSenders = getConversationSendersWithDefault(
+        priorityConversationSenders = zenPolicyConversationSendersToNotificationPolicy(
                 allowConversationsFrom, priorityConversationSenders);
 
         int state = areChannelsBypassingDnd ? Policy.STATE_CHANNELS_BYPASSING_DND : 0;
@@ -1559,15 +1521,6 @@
         }
     }
 
-    private static int prioritySendersToSource(int prioritySenders, int def) {
-        switch (prioritySenders) {
-            case Policy.PRIORITY_SENDERS_CONTACTS: return SOURCE_CONTACT;
-            case Policy.PRIORITY_SENDERS_STARRED: return SOURCE_STAR;
-            case Policy.PRIORITY_SENDERS_ANY: return SOURCE_ANYONE;
-            default: return def;
-        }
-    }
-
     private static int normalizePrioritySenders(int prioritySenders, int def) {
         if (!(prioritySenders == Policy.PRIORITY_SENDERS_CONTACTS
                 || prioritySenders == Policy.PRIORITY_SENDERS_STARRED
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
index bbb4bc6..6dbff71 100644
--- a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
+++ b/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
@@ -26,6 +26,7 @@
 import android.app.ondeviceintelligence.IListFeaturesCallback;
 import android.app.ondeviceintelligence.IFeatureDetailsCallback;
 import com.android.internal.infra.AndroidFuture;
+import android.service.ondeviceintelligence.IRemoteProcessingService;
 
 
 /**
@@ -35,10 +36,13 @@
  */
 oneway interface IOnDeviceIntelligenceService {
     void getVersion(in RemoteCallback remoteCallback);
-    void getFeature(in int featureId, in IFeatureCallback featureCallback);
-    void listFeatures(in IListFeaturesCallback listFeaturesCallback);
-    void getFeatureDetails(in Feature feature, in IFeatureDetailsCallback featureDetailsCallback);
+    void getFeature(int callerUid, int featureId, in IFeatureCallback featureCallback);
+    void listFeatures(int callerUid, in IListFeaturesCallback listFeaturesCallback);
+    void getFeatureDetails(int callerUid, in Feature feature, in IFeatureDetailsCallback featureDetailsCallback);
     void getReadOnlyFileDescriptor(in String fileName, in AndroidFuture<ParcelFileDescriptor> future);
     void getReadOnlyFeatureFileDescriptorMap(in Feature feature, in RemoteCallback remoteCallback);
-    void requestFeatureDownload(in Feature feature, in ICancellationSignal cancellationSignal, in IDownloadCallback downloadCallback);
+    void requestFeatureDownload(int callerUid, in Feature feature, in ICancellationSignal cancellationSignal, in IDownloadCallback downloadCallback);
+    void registerRemoteServices(in IRemoteProcessingService remoteProcessingService);
+    void notifyInferenceServiceConnected();
+    void notifyInferenceServiceDisconnected();
 }
\ No newline at end of file
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
similarity index 63%
rename from core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl
rename to core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
index 08eb927..73257ed 100644
--- a/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl
+++ b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
@@ -18,27 +18,31 @@
 
 import android.app.ondeviceintelligence.IStreamingResponseCallback;
 import android.app.ondeviceintelligence.IResponseCallback;
-import android.app.ondeviceintelligence.ITokenCountCallback;
+import android.app.ondeviceintelligence.ITokenInfoCallback;
 import android.app.ondeviceintelligence.IProcessingSignal;
 import android.app.ondeviceintelligence.Content;
 import android.app.ondeviceintelligence.Feature;
 import android.os.ICancellationSignal;
+import android.os.PersistableBundle;
+import android.os.Bundle;
 import android.service.ondeviceintelligence.IRemoteStorageService;
-
+import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
 
 /**
- * Interface for a concrete implementation to provide on device trusted inference.
+ * Interface for a concrete implementation to provide on-device sandboxed inference.
  *
  * @hide
  */
-oneway interface IOnDeviceTrustedInferenceService {
+oneway interface IOnDeviceSandboxedInferenceService {
     void registerRemoteStorageService(in IRemoteStorageService storageService);
-    void requestTokenCount(in Feature feature, in Content request, in ICancellationSignal cancellationSignal,
-                            in ITokenCountCallback tokenCountCallback);
-    void processRequest(in Feature feature, in Content request, in int requestType,
+    void requestTokenInfo(int callerUid, in Feature feature, in Content request, in ICancellationSignal cancellationSignal,
+                            in ITokenInfoCallback tokenInfoCallback);
+    void processRequest(int callerUid, in Feature feature, in Content request, in int requestType,
                         in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
                         in IResponseCallback callback);
-    void processRequestStreaming(in Feature feature, in Content request, in int requestType,
+    void processRequestStreaming(int callerUid, in Feature feature, in Content request, in int requestType,
                                 in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
                                 in IStreamingResponseCallback callback);
+    void updateProcessingState(in Bundle processingState,
+                                     in IProcessingUpdateStatusCallback callback);
 }
\ No newline at end of file
diff --git a/core/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl b/core/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl
new file mode 100644
index 0000000..7ead869
--- /dev/null
+++ b/core/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl
@@ -0,0 +1,14 @@
+package android.service.ondeviceintelligence;
+
+import android.os.PersistableBundle;
+
+/**
+  * Interface for receiving status from a updateProcessingState call from on-device intelligence
+  * service.
+  *
+  * @hide
+  */
+interface IProcessingUpdateStatusCallback {
+    void onSuccess(in PersistableBundle statusParams) = 1;
+    void onFailure(int errorCode, in String errorMessage) = 2;
+}
diff --git a/core/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl b/core/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl
new file mode 100644
index 0000000..32a8a6a
--- /dev/null
+++ b/core/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.ondeviceintelligence;
+
+import android.os.Bundle;
+import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
+
+
+/**
+ * Interface for a concrete implementation to provide methods to update state of a remote service.
+ *
+ * @hide
+ */
+oneway interface IRemoteProcessingService {
+    void updateProcessingState(in Bundle processingState,
+                                 in IProcessingUpdateStatusCallback callback);
+}
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
index 0cba1d3..fce3689 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
@@ -18,6 +18,7 @@
 
 import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -47,23 +48,34 @@
 
 import com.android.internal.infra.AndroidFuture;
 
+import androidx.annotation.IntDef;
+
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 import java.util.function.LongConsumer;
 
 /**
  * Abstract base class for performing setup for on-device inference and providing file access to
- * the isolated counter part {@link OnDeviceTrustedInferenceService}.
+ * the isolated counter part {@link OnDeviceSandboxedInferenceService}.
  *
  * <p> A service that provides configuration and model files relevant to performing inference on
  * device. The system's default OnDeviceIntelligenceService implementation is configured in
  * {@code config_defaultOnDeviceIntelligenceService}. If this config has no value, a stub is
  * returned.
  *
+ * <p> Similar to {@link OnDeviceIntelligenceManager} class, the contracts in this service are
+ * defined to be open-ended in general, to allow interoperability. Therefore, it is recommended
+ * that implementations of this system-service expose this API to the clients via a library which
+ * has more defined contract.</p>
  * <pre>
  * {@literal
  * <service android:name=".SampleOnDeviceIntelligenceService"
@@ -78,6 +90,8 @@
 public abstract class OnDeviceIntelligenceService extends Service {
     private static final String TAG = OnDeviceIntelligenceService.class.getSimpleName();
 
+    private volatile IRemoteProcessingService mRemoteProcessingService;
+
     /**
      * The {@link Intent} that must be declared as handled by the service. To be supported, the
      * service must also require the
@@ -88,6 +102,7 @@
     public static final String SERVICE_INTERFACE =
             "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
 
+
     /**
      * @hide
      */
@@ -95,6 +110,8 @@
     @Override
     public final IBinder onBind(@NonNull Intent intent) {
         if (SERVICE_INTERFACE.equals(intent.getAction())) {
+            // TODO(326052028) : Move the remote method calls to an app handler from the binder
+            //  thread.
             return new IOnDeviceIntelligenceService.Stub() {
                 /** {@inheritDoc} */
                 @Override
@@ -108,38 +125,40 @@
                 }
 
                 @Override
-                public void listFeatures(IListFeaturesCallback listFeaturesCallback) {
+                public void listFeatures(int callerUid,
+                        IListFeaturesCallback listFeaturesCallback) {
                     Objects.requireNonNull(listFeaturesCallback);
-                    OnDeviceIntelligenceService.this.onListFeatures(
+                    OnDeviceIntelligenceService.this.onListFeatures(callerUid,
                             wrapListFeaturesCallback(listFeaturesCallback));
                 }
 
                 @Override
-                public void getFeature(int id, IFeatureCallback featureCallback) {
+                public void getFeature(int callerUid, int id, IFeatureCallback featureCallback) {
                     Objects.requireNonNull(featureCallback);
-                    OnDeviceIntelligenceService.this.onGetFeature(id,
-                            wrapFeatureCallback(featureCallback));
+                    OnDeviceIntelligenceService.this.onGetFeature(callerUid,
+                            id, wrapFeatureCallback(featureCallback));
                 }
 
 
                 @Override
-                public void getFeatureDetails(Feature feature,
+                public void getFeatureDetails(int callerUid, Feature feature,
                         IFeatureDetailsCallback featureDetailsCallback) {
                     Objects.requireNonNull(feature);
                     Objects.requireNonNull(featureDetailsCallback);
 
-                    OnDeviceIntelligenceService.this.onGetFeatureDetails(feature,
-                            wrapFeatureDetailsCallback(featureDetailsCallback));
+                    OnDeviceIntelligenceService.this.onGetFeatureDetails(callerUid,
+                            feature, wrapFeatureDetailsCallback(featureDetailsCallback));
                 }
 
                 @Override
-                public void requestFeatureDownload(Feature feature,
+                public void requestFeatureDownload(int callerUid, Feature feature,
                         ICancellationSignal cancellationSignal,
                         IDownloadCallback downloadCallback) {
                     Objects.requireNonNull(feature);
                     Objects.requireNonNull(downloadCallback);
 
-                    OnDeviceIntelligenceService.this.onDownloadFeature(feature,
+                    OnDeviceIntelligenceService.this.onDownloadFeature(callerUid,
+                            feature,
                             CancellationSignal.fromTransport(cancellationSignal),
                             wrapDownloadCallback(downloadCallback));
                 }
@@ -167,12 +186,84 @@
                                 remoteCallback.sendResult(bundle);
                             });
                 }
+
+                @Override
+                public void registerRemoteServices(
+                        IRemoteProcessingService remoteProcessingService) {
+                    mRemoteProcessingService = remoteProcessingService;
+                }
+
+                @Override
+                public void notifyInferenceServiceConnected() {
+                    OnDeviceIntelligenceService.this.onInferenceServiceConnected();
+                }
+
+                @Override
+                public void notifyInferenceServiceDisconnected() {
+                    OnDeviceIntelligenceService.this.onInferenceServiceDisconnected();
+                }
             };
         }
         Slog.w(TAG, "Incorrect service interface, returning null.");
         return null;
     }
 
+
+    /**
+     * Invoked when a new instance of the remote inference service is created.
+     * This method should be used as a signal to perform any initialization operations, for e.g. by
+     * invoking the {@link #updateProcessingState} method to initialize the remote processing
+     * service.
+     */
+    public abstract void onInferenceServiceConnected();
+
+
+    /**
+     * Invoked when an instance of the remote inference service is disconnected.
+     */
+    public abstract void onInferenceServiceDisconnected();
+
+
+    /**
+     * Invoked by the {@link OnDeviceIntelligenceService} inorder to send updates to the inference
+     * service if there is a state change to be performed.
+     *
+     * @param processingState  the updated state to be applied.
+     * @param callbackExecutor executor to the run status callback on.
+     * @param statusReceiver   receiver to get status of the update state operation.
+     */
+    public final void updateProcessingState(@NonNull Bundle processingState,
+            @NonNull @CallbackExecutor Executor callbackExecutor,
+            @NonNull OutcomeReceiver<PersistableBundle, OnDeviceUpdateProcessingException> statusReceiver) {
+        Objects.requireNonNull(callbackExecutor);
+        if (mRemoteProcessingService == null) {
+            throw new IllegalStateException("Remote processing service is unavailable.");
+        }
+        try {
+            mRemoteProcessingService.updateProcessingState(processingState,
+                    new IProcessingUpdateStatusCallback.Stub() {
+                        @Override
+                        public void onSuccess(PersistableBundle result) {
+                            Binder.withCleanCallingIdentity(() -> {
+                                callbackExecutor.execute(
+                                        () -> statusReceiver.onResult(result));
+                            });
+                        }
+
+                        @Override
+                        public void onFailure(int errorCode, String errorMessage) {
+                            Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+                                    () -> statusReceiver.onError(
+                                            new OnDeviceUpdateProcessingException(
+                                                    errorCode, errorMessage))));
+                        }
+                    });
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Error in updateProcessingState: " + e);
+            throw new RuntimeException(e);
+        }
+    }
+
     private OutcomeReceiver<Feature,
             OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapFeatureCallback(
             IFeatureCallback featureCallback) {
@@ -197,7 +288,6 @@
                 }
             }
         };
-
     }
 
     private OutcomeReceiver<List<Feature>,
@@ -331,6 +421,7 @@
      * Request download for feature that is requested and listen to download progress updates. If
      * the download completes successfully, success callback should be populated.
      *
+     * @param callerUid          UID of the caller that initiated this call chain.
      * @param feature            the feature for which files need to be downlaoded.
      *                           process.
      * @param cancellationSignal signal to attach a listener to, and receive cancellation signals
@@ -338,7 +429,7 @@
      * @param downloadCallback   callback to populate download updates for clients to listen on..
      */
     public abstract void onDownloadFeature(
-            @NonNull Feature feature,
+            int callerUid, @NonNull Feature feature,
             @Nullable CancellationSignal cancellationSignal,
             @NonNull DownloadCallback downloadCallback);
 
@@ -347,20 +438,22 @@
      * implementation use the {@link Feature#getFeatureParams()} as a hint to communicate what
      * details the client is looking for.
      *
-     * @param feature               the feature for which status needs to be known.
-     * @param featureStatusCallback callback to populate the resulting feature status.
+     * @param callerUid              UID of the caller that initiated this call chain.
+     * @param feature                the feature for which status needs to be known.
+     * @param featureDetailsCallback callback to populate the resulting feature status.
      */
-    public abstract void onGetFeatureDetails(@NonNull Feature feature,
+    public abstract void onGetFeatureDetails(int callerUid, @NonNull Feature feature,
             @NonNull OutcomeReceiver<FeatureDetails,
-                    OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureStatusCallback);
+                    OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureDetailsCallback);
 
 
     /**
      * Get feature using the provided identifier to the remote implementation.
      *
+     * @param callerUid       UID of the caller that initiated this call chain.
      * @param featureCallback callback to populate the features list.
      */
-    public abstract void onGetFeature(int featureId,
+    public abstract void onGetFeature(int callerUid, int featureId,
             @NonNull OutcomeReceiver<Feature,
                     OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureCallback);
 
@@ -368,9 +461,10 @@
      * List all features which are available in the remote implementation. The implementation might
      * choose to provide only a certain list of features based on the caller.
      *
+     * @param callerUid            UID of the caller that initiated this call chain.
      * @param listFeaturesCallback callback to populate the features list.
      */
-    public abstract void onListFeatures(@NonNull OutcomeReceiver<List<Feature>,
+    public abstract void onListFeatures(int callerUid, @NonNull OutcomeReceiver<List<Feature>,
             OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> listFeaturesCallback);
 
     /**
@@ -380,4 +474,60 @@
      * @param versionConsumer consumer to populate the version.
      */
     public abstract void onGetVersion(@NonNull LongConsumer versionConsumer);
+
+
+    /**
+     * Exception type to be populated when calls to {@link #updateProcessingState} fail.
+     */
+    public static class OnDeviceUpdateProcessingException extends
+            OnDeviceIntelligenceServiceException {
+        /**
+         * The connection to remote service failed and the processing state could not be updated.
+         */
+        public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 1;
+
+
+        /**
+         * @hide
+         */
+        @IntDef(value = {
+                PROCESSING_UPDATE_STATUS_CONNECTION_FAILED
+        }, open = true)
+        @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER,
+                ElementType.FIELD})
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface ErrorCode {
+        }
+
+        public OnDeviceUpdateProcessingException(@ErrorCode int errorCode) {
+            super(errorCode);
+        }
+
+        public OnDeviceUpdateProcessingException(@ErrorCode int errorCode,
+                @NonNull String errorMessage) {
+            super(errorCode, errorMessage);
+        }
+    }
+
+    /**
+     * Exception type to be used for surfacing errors to service implementation.
+     */
+    public abstract static class OnDeviceIntelligenceServiceException extends Exception {
+        private final int mErrorCode;
+
+        public OnDeviceIntelligenceServiceException(int errorCode) {
+            this.mErrorCode = errorCode;
+        }
+
+        public OnDeviceIntelligenceServiceException(int errorCode,
+                @NonNull String errorMessage) {
+            super(errorMessage);
+            this.mErrorCode = errorCode;
+        }
+
+        public int getErrorCode() {
+            return mErrorCode;
+        }
+
+    }
 }
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
similarity index 63%
rename from core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java
rename to core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
index 96982e3..7f7f9c2 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
@@ -16,6 +16,7 @@
 
 package android.service.ondeviceintelligence;
 
+import static android.app.ondeviceintelligence.OnDeviceIntelligenceManager.AUGMENT_REQUEST_CONTENT_BUNDLE_KEY;
 import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
 
 import android.annotation.CallbackExecutor;
@@ -23,6 +24,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.app.Service;
 import android.app.ondeviceintelligence.Content;
@@ -30,19 +32,26 @@
 import android.app.ondeviceintelligence.IProcessingSignal;
 import android.app.ondeviceintelligence.IResponseCallback;
 import android.app.ondeviceintelligence.IStreamingResponseCallback;
-import android.app.ondeviceintelligence.ITokenCountCallback;
+import android.app.ondeviceintelligence.ITokenInfoCallback;
 import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
 import android.app.ondeviceintelligence.ProcessingSignal;
-import android.app.ondeviceintelligence.StreamingResponseReceiver;
+import android.app.ondeviceintelligence.ProcessingOutcomeReceiver;
+import android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver;
+import android.app.ondeviceintelligence.TokenInfo;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Bundle;
 import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.ICancellationSignal;
 import android.os.OutcomeReceiver;
 import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
+import android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException;
 import android.util.Log;
 import android.util.Slog;
 
@@ -65,10 +74,15 @@
  * non-streaming fashion. Also, provides a way to register a storage service that will be used to
  * read-only access files from the {@link OnDeviceIntelligenceService} counterpart. </p>
  *
+ * <p> Similar to {@link OnDeviceIntelligenceManager} class, the contracts in this service are
+ * defined to be open-ended in general, to allow interoperability. Therefore, it is recommended
+ * that implementations of this system-service expose this API to the clients via a library which
+ * has more defined contract.</p>
+ *
  * <pre>
  * {@literal
- * <service android:name=".SampleTrustedInferenceService"
- *          android:permission="android.permission.BIND_ONDEVICE_TRUSTED_INFERENCE_SERVICE"
+ * <service android:name=".SampleSandboxedInferenceService"
+ *          android:permission="android.permission.BIND_ONDEVICE_SANDBOXED_INFERENCE_SERVICE"
  *          android:isolatedProcess="true">
  * </service>}
  * </pre>
@@ -77,18 +91,18 @@
  */
 @SystemApi
 @FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public abstract class OnDeviceTrustedInferenceService extends Service {
-    private static final String TAG = OnDeviceTrustedInferenceService.class.getSimpleName();
+public abstract class OnDeviceSandboxedInferenceService extends Service {
+    private static final String TAG = OnDeviceSandboxedInferenceService.class.getSimpleName();
 
     /**
      * The {@link Intent} that must be declared as handled by the service. To be supported, the
      * service must also require the
-     * {@link android.Manifest.permission#BIND_ON_DEVICE_TRUSTED_INFERENCE_SERVICE}
+     * {@link android.Manifest.permission#BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE}
      * permission so that other applications can not abuse it.
      */
     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
     public static final String SERVICE_INTERFACE =
-            "android.service.ondeviceintelligence.OnDeviceTrustedInferenceService";
+            "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService";
 
     private IRemoteStorageService mRemoteStorageService;
 
@@ -99,7 +113,7 @@
     @Override
     public final IBinder onBind(@NonNull Intent intent) {
         if (SERVICE_INTERFACE.equals(intent.getAction())) {
-            return new IOnDeviceTrustedInferenceService.Stub() {
+            return new IOnDeviceSandboxedInferenceService.Stub() {
                 @Override
                 public void registerRemoteStorageService(IRemoteStorageService storageService) {
                     Objects.requireNonNull(storageService);
@@ -107,49 +121,58 @@
                 }
 
                 @Override
-                public void requestTokenCount(Feature feature, Content request,
+                public void requestTokenInfo(int callerUid, Feature feature, Content request,
                         ICancellationSignal cancellationSignal,
-                        ITokenCountCallback tokenCountCallback) {
+                        ITokenInfoCallback tokenInfoCallback) {
                     Objects.requireNonNull(feature);
-                    Objects.requireNonNull(tokenCountCallback);
-                    OnDeviceTrustedInferenceService.this.onCountTokens(feature,
+                    Objects.requireNonNull(tokenInfoCallback);
+                    OnDeviceSandboxedInferenceService.this.onTokenInfoRequest(callerUid,
+                            feature,
                             request,
                             CancellationSignal.fromTransport(cancellationSignal),
-                            wrapTokenCountCallback(tokenCountCallback));
+                            wrapTokenInfoCallback(tokenInfoCallback));
                 }
 
                 @Override
-                public void processRequestStreaming(Feature feature, Content request,
+                public void processRequestStreaming(int callerUid, Feature feature, Content request,
                         int requestType, ICancellationSignal cancellationSignal,
                         IProcessingSignal processingSignal,
                         IStreamingResponseCallback callback) {
                     Objects.requireNonNull(feature);
-                    Objects.requireNonNull(request);
                     Objects.requireNonNull(callback);
 
-                    OnDeviceTrustedInferenceService.this.onProcessRequestStreaming(feature,
+                    OnDeviceSandboxedInferenceService.this.onProcessRequestStreaming(callerUid,
+                            feature,
                             request,
                             requestType,
                             CancellationSignal.fromTransport(cancellationSignal),
                             ProcessingSignal.fromTransport(processingSignal),
-                            wrapStreamingResponseCallback(callback)
-                    );
+                            wrapStreamingResponseCallback(callback));
                 }
 
                 @Override
-                public void processRequest(Feature feature, Content request,
+                public void processRequest(int callerUid, Feature feature, Content request,
                         int requestType, ICancellationSignal cancellationSignal,
                         IProcessingSignal processingSignal,
                         IResponseCallback callback) {
                     Objects.requireNonNull(feature);
-                    Objects.requireNonNull(request);
                     Objects.requireNonNull(callback);
 
-
-                    OnDeviceTrustedInferenceService.this.onProcessRequest(feature, request,
-                            requestType, CancellationSignal.fromTransport(cancellationSignal),
+                    OnDeviceSandboxedInferenceService.this.onProcessRequest(callerUid, feature,
+                            request, requestType,
+                            CancellationSignal.fromTransport(cancellationSignal),
                             ProcessingSignal.fromTransport(processingSignal),
-                            wrapResponseCallback(callback)
+                            wrapResponseCallback(callback));
+                }
+
+                @Override
+                public void updateProcessingState(Bundle processingState,
+                        IProcessingUpdateStatusCallback callback) {
+                    Objects.requireNonNull(processingState);
+                    Objects.requireNonNull(callback);
+
+                    OnDeviceSandboxedInferenceService.this.onUpdateProcessingState(processingState,
+                            wrapOutcomeReceiver(callback)
                     );
                 }
             };
@@ -159,35 +182,37 @@
     }
 
     /**
-     * Invoked when caller  wants to obtain a count of number of tokens present in the passed in
-     * Request associated with the provided feature.
+     * Invoked when caller  wants to obtain token info related to the payload in the passed
+     * content, associated with the provided feature.
      * The expectation from the implementation is that when processing is complete, it
-     * should provide the token count in the {@link OutcomeReceiver#onResult}.
+     * should provide the token info in the {@link OutcomeReceiver#onResult}.
      *
+     * @param callerUid          UID of the caller that initiated this call chain.
      * @param feature            feature which is associated with the request.
      * @param request            request that requires processing.
      * @param cancellationSignal Cancellation Signal to receive cancellation events from client and
      *                           configure a listener to.
-     * @param callback           callback to populate failure and full response for the provided
+     * @param callback           callback to populate failure or the token info for the provided
      *                           request.
      */
     @NonNull
-    public abstract void onCountTokens(
-            @NonNull Feature feature,
+    public abstract void onTokenInfoRequest(
+            int callerUid, @NonNull Feature feature,
             @NonNull Content request,
             @Nullable CancellationSignal cancellationSignal,
-            @NonNull OutcomeReceiver<Long,
+            @NonNull OutcomeReceiver<TokenInfo,
                     OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
 
     /**
      * Invoked when caller provides a request for a particular feature to be processed in a
      * streaming manner. The expectation from the implementation is that when processing the
      * request,
-     * it periodically populates the {@link StreamingResponseReceiver#onNewContent} to continuously
+     * it periodically populates the {@link StreamedProcessingOutcomeReceiver#onNewContent} to continuously
      * provide partial Content results for the caller to utilize. Optionally the implementation can
-     * provide the complete response in the {@link StreamingResponseReceiver#onResult} upon
+     * provide the complete response in the {@link StreamedProcessingOutcomeReceiver#onResult} upon
      * processing completion.
      *
+     * @param callerUid          UID of the caller that initiated this call chain.
      * @param feature            feature which is associated with the request.
      * @param request            request that requires processing.
      * @param requestType        identifier representing the type of request.
@@ -199,13 +224,12 @@
      */
     @NonNull
     public abstract void onProcessRequestStreaming(
-            @NonNull Feature feature,
-            @NonNull Content request,
+            int callerUid, @NonNull Feature feature,
+            @Nullable Content request,
             @OnDeviceIntelligenceManager.RequestType int requestType,
             @Nullable CancellationSignal cancellationSignal,
             @Nullable ProcessingSignal processingSignal,
-            @NonNull StreamingResponseReceiver<Content, Content,
-                    OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
+            @NonNull StreamedProcessingOutcomeReceiver callback);
 
     /**
      * Invoked when caller provides a request for a particular feature to be processed in one shot
@@ -214,6 +238,7 @@
      * should
      * provide the complete response in the {@link OutcomeReceiver#onResult}.
      *
+     * @param callerUid          UID of the caller that initiated this call chain.
      * @param feature            feature which is associated with the request.
      * @param request            request that requires processing.
      * @param requestType        identifier representing the type of request.
@@ -225,13 +250,27 @@
      */
     @NonNull
     public abstract void onProcessRequest(
-            @NonNull Feature feature,
-            @NonNull Content request,
+            int callerUid, @NonNull Feature feature,
+            @Nullable Content request,
             @OnDeviceIntelligenceManager.RequestType int requestType,
             @Nullable CancellationSignal cancellationSignal,
             @Nullable ProcessingSignal processingSignal,
-            @NonNull OutcomeReceiver<Content,
-                    OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
+            @NonNull ProcessingOutcomeReceiver callback);
+
+
+    /**
+     * Invoked when processing environment needs to be updated or refreshed with fresh
+     * configuration, files or state.
+     *
+     * @param processingState contains updated state and params that are to be applied to the
+     *                        processing environmment,
+     * @param callback        callback to populate the update status and if there are params
+     *                        associated with the status.
+     */
+    public abstract void onUpdateProcessingState(@NonNull Bundle processingState,
+            @NonNull OutcomeReceiver<PersistableBundle,
+                    OnDeviceUpdateProcessingException> callback);
+
 
     /**
      * Overrides {@link Context#openFileInput} to read files with the given file names under the
@@ -301,6 +340,26 @@
         }
     }
 
+
+    /**
+     * Returns the {@link Executor} to use for incoming IPC from request sender into your service
+     * implementation. For e.g. see
+     * {@link ProcessingOutcomeReceiver#onDataAugmentRequest(Content,
+     * Consumer)} where we use the executor to populate the consumer.
+     * <p>
+     * Override this method in your {@link OnDeviceSandboxedInferenceService} implementation to
+     * provide the executor you want to use for incoming IPC.
+     *
+     * @return the {@link Executor} to use for incoming IPC from {@link OnDeviceIntelligenceManager}
+     * to {@link OnDeviceSandboxedInferenceService}.
+     */
+    @SuppressLint("OnNameExpected")
+    @NonNull
+    public Executor getCallbackExecutor() {
+        return new HandlerExecutor(Handler.createAsync(getMainLooper()));
+    }
+
+
     private RemoteCallback wrapResultReceiverAsReadOnly(
             @NonNull Consumer<Map<String, FileInputStream>> resultConsumer,
             @NonNull Executor executor) {
@@ -321,10 +380,9 @@
         });
     }
 
-    private OutcomeReceiver<Content,
-            OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapResponseCallback(
+    private ProcessingOutcomeReceiver wrapResponseCallback(
             IResponseCallback callback) {
-        return new OutcomeReceiver<>() {
+        return new ProcessingOutcomeReceiver() {
             @Override
             public void onResult(@androidx.annotation.NonNull Content response) {
                 try {
@@ -344,13 +402,23 @@
                     Slog.e(TAG, "Error sending result: " + e);
                 }
             }
+
+            @Override
+            public void onDataAugmentRequest(@NonNull Content content,
+                    @NonNull Consumer<Content> contentCallback) {
+                try {
+                    callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback));
+
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error sending augment request: " + e);
+                }
+            }
         };
     }
 
-    private StreamingResponseReceiver<Content, Content,
-            OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapStreamingResponseCallback(
+    private StreamedProcessingOutcomeReceiver wrapStreamingResponseCallback(
             IStreamingResponseCallback callback) {
-        return new StreamingResponseReceiver<>() {
+        return new StreamedProcessingOutcomeReceiver() {
             @Override
             public void onNewContent(@androidx.annotation.NonNull Content content) {
                 try {
@@ -379,17 +447,43 @@
                     Slog.e(TAG, "Error sending result: " + e);
                 }
             }
+
+            @Override
+            public void onDataAugmentRequest(@NonNull Content content,
+                    @NonNull Consumer<Content> contentCallback) {
+                try {
+                    callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback));
+
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error sending augment request: " + e);
+                }
+            }
         };
     }
 
-    private OutcomeReceiver<Long,
-            OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapTokenCountCallback(
-            ITokenCountCallback tokenCountCallback) {
+    private RemoteCallback wrapRemoteCallback(
+            @androidx.annotation.NonNull Consumer<Content> contentCallback) {
+        return new RemoteCallback(
+                result -> {
+                    if (result != null) {
+                        getCallbackExecutor().execute(() -> contentCallback.accept(
+                                result.getParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY,
+                                        Content.class)));
+                    } else {
+                        getCallbackExecutor().execute(
+                                () -> contentCallback.accept(null));
+                    }
+                });
+    }
+
+    private OutcomeReceiver<TokenInfo,
+            OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapTokenInfoCallback(
+            ITokenInfoCallback tokenInfoCallback) {
         return new OutcomeReceiver<>() {
             @Override
-            public void onResult(Long tokenCount) {
+            public void onResult(TokenInfo tokenInfo) {
                 try {
-                    tokenCountCallback.onSuccess(tokenCount);
+                    tokenInfoCallback.onSuccess(tokenInfo);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Error sending result: " + e);
                 }
@@ -399,7 +493,7 @@
             public void onError(
                     OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
                 try {
-                    tokenCountCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
+                    tokenInfoCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
                             exception.getErrorParams());
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Error sending failure: " + e);
@@ -407,4 +501,31 @@
             }
         };
     }
+
+    @NonNull
+    private static OutcomeReceiver<PersistableBundle, OnDeviceUpdateProcessingException> wrapOutcomeReceiver(
+            IProcessingUpdateStatusCallback callback) {
+        return new OutcomeReceiver<>() {
+            @Override
+            public void onResult(@NonNull PersistableBundle result) {
+                try {
+                    callback.onSuccess(result);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error sending result: " + e);
+
+                }
+            }
+
+            @Override
+            public void onError(
+                    @androidx.annotation.NonNull OnDeviceUpdateProcessingException error) {
+                try {
+                    callback.onFailure(error.getErrorCode(), error.getMessage());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error sending exception details: " + e);
+                }
+            }
+        };
+    }
+
 }
diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java
index 6140812..df4a2bb 100644
--- a/core/java/android/service/voice/HotwordDetectedResult.java
+++ b/core/java/android/service/voice/HotwordDetectedResult.java
@@ -662,6 +662,8 @@
 
     /**
      * Id of the current speaker
+     *
+     * <p>Only values between 0 and {@link #getMaxSpeakerId} (inclusive) are accepted.
      */
     @DataClass.Generated.Member
     @FlaggedApi(Flags.FLAG_ALLOW_SPEAKER_ID_EGRESS)
@@ -982,6 +984,8 @@
 
         /**
          * Id of the current speaker
+         *
+         * <p>Only values between 0 and {@link #getMaxSpeakerId} (inclusive) are accepted.
          */
         @DataClass.Generated.Member
         @FlaggedApi(Flags.FLAG_ALLOW_SPEAKER_ID_EGRESS)
@@ -1228,7 +1232,7 @@
     }
 
     @DataClass.Generated(
-            time = 1704944087827L,
+            time = 1709773165191L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java",
             inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\npublic static final  int BACKGROUND_AUDIO_POWER_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate static final  java.lang.String EXTRA_PROXIMITY\npublic static final  int PROXIMITY_UNKNOWN\npublic static final  int PROXIMITY_NEAR\npublic static final  int PROXIMITY_FAR\nprivate final  int mSpeakerId\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final  int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate final  int mBackgroundAudioPower\nprivate static  int defaultSpeakerId()\npublic static @android.annotation.FlaggedApi int getMaxSpeakerId()\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\nprivate static  int defaultHotwordPhraseId()\npublic static  int getMaxHotwordPhraseId()\nprivate static  java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\nprivate static  int defaultBackgroundAudioPower()\npublic static  int getMaxBackgroundAudioPower()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static  int bitCount(long)\nprivate  void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic  void setProximity(double)\npublic @android.service.voice.HotwordDetectedResult.ProximityValue int getProximity()\nprivate @android.service.voice.HotwordDetectedResult.ProximityValue int convertToProximityLevel(double)\npublic  android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\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.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 20adc54..306410c 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -519,7 +519,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);
@@ -545,10 +545,6 @@
             @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);
@@ -615,11 +611,6 @@
             @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);
@@ -671,11 +662,7 @@
             @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AlwaysOnHotwordDetector.Callback callback) {
-        // 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.
+        // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
 
         Objects.requireNonNull(keyphrase);
         Objects.requireNonNull(locale);
@@ -702,10 +689,6 @@
             @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/tracing/flags.aconfig b/core/java/android/tracing/flags.aconfig
index b1bca96..cedba85 100644
--- a/core/java/android/tracing/flags.aconfig
+++ b/core/java/android/tracing/flags.aconfig
@@ -8,7 +8,7 @@
 }
 
 flag {
-    name: "perfetto_protolog"
+    name: "perfetto_protolog_tracing"
     namespace: "windowing_tools"
     description: "Migrate protolog to Perfetto"
     bug: "276432490"
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index b91a878..4d4cb6c 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -194,13 +194,6 @@
     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;
 
     static {
@@ -246,7 +239,6 @@
         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,7 +256,6 @@
         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/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 00657ea..66655fc 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -188,8 +188,7 @@
                 // check whether the stylus we are tracking goes up.
                 if (mState != null) {
                     mState.mShouldInitHandwriting = false;
-                    if (!mState.mHasInitiatedHandwriting
-                            && !mState.mHasPreparedHandwritingDelegation) {
+                    if (!mState.mHandled) {
                         // The user just did a click, long click or another stylus gesture,
                         // show hover icon again for the connected view.
                         mShowHoverIconForConnectedView = true;
@@ -204,16 +203,14 @@
                 // Either we've already tried to initiate handwriting, or the ongoing MotionEvent
                 // sequence is considered to be tap, long-click or other gestures.
                 if (!mState.mShouldInitHandwriting || mState.mExceedHandwritingSlop) {
-                    return mState.mHasInitiatedHandwriting
-                            || mState.mHasPreparedHandwritingDelegation;
+                    return mState.mHandled;
                 }
 
                 final long timeElapsed =
                         motionEvent.getEventTime() - mState.mStylusDownTimeInMillis;
                 if (timeElapsed > mHandwritingTimeoutInMillis) {
                     mState.mShouldInitHandwriting = false;
-                    return mState.mHasInitiatedHandwriting
-                            || mState.mHasPreparedHandwritingDelegation;
+                    return mState.mHandled;
                 }
 
                 final int pointerIndex = motionEvent.findPointerIndex(mState.mStylusPointerId);
@@ -223,7 +220,7 @@
                     mState.mExceedHandwritingSlop = true;
                     View candidateView = findBestCandidateView(mState.mStylusDownX,
                             mState.mStylusDownY, /* isHover */ false);
-                    if (candidateView != null) {
+                    if (candidateView != null && candidateView.isEnabled()) {
                         if (candidateView == getConnectedOrFocusedView()) {
                             if (!mInitiateWithoutConnection && !candidateView.hasFocus()) {
                                 requestFocusWithoutReveal(candidateView);
@@ -246,7 +243,7 @@
                         }
                     }
                 }
-                return mState.mHasInitiatedHandwriting || mState.mHasPreparedHandwritingDelegation;
+                return mState.mHandled;
         }
         return false;
     }
@@ -382,7 +379,7 @@
     @VisibleForTesting
     public void startHandwriting(@NonNull View view) {
         mImm.startStylusHandwriting(view);
-        mState.mHasInitiatedHandwriting = true;
+        mState.mHandled = true;
         mState.mShouldInitHandwriting = false;
         mShowHoverIconForConnectedView = false;
         if (view instanceof TextView) {
@@ -402,13 +399,12 @@
             mImm.startConnectionlessStylusHandwritingForDelegation(
                     view, getCursorAnchorInfoForConnectionless(view), delegatePackageName,
                     view::post, new DelegationCallback(view, delegatePackageName));
-            mState.mHasInitiatedHandwriting = true;
             mState.mShouldInitHandwriting = false;
         } else {
             mImm.prepareStylusHandwritingDelegation(view, delegatePackageName);
             view.getHandwritingDelegatorCallback().run();
-            mState.mHasPreparedHandwritingDelegation = true;
         }
+        mState.mHandled = true;
     }
 
     /**
@@ -455,7 +451,7 @@
 
     private void onDelegationAccepted(View view) {
         if (mState != null) {
-            mState.mHasInitiatedHandwriting = true;
+            mState.mHandled = true;
             mState.mShouldInitHandwriting = false;
         }
         if (view instanceof TextView) {
@@ -795,12 +791,12 @@
          * This boolean will be set to false, and it won't request to start handwriting.
          */
         private boolean mShouldInitHandwriting;
-        /**
-         * Whether handwriting mode has already been initiated for the current MotionEvent sequence.
-         */
-        private boolean mHasInitiatedHandwriting;
 
-        private boolean mHasPreparedHandwritingDelegation;
+        /**
+         * Whether the current MotionEvent sequence has been handled by the handwriting initiator,
+         * either by initiating handwriting mode, or by preparing handwriting delegation.
+         */
+        private boolean mHandled;
 
         /**
          * Whether the current ongoing stylus MotionEvent sequence already exceeds the
@@ -838,8 +834,7 @@
             mStylusDownY = motionEvent.getY(actionIndex);
 
             mShouldInitHandwriting = true;
-            mHasInitiatedHandwriting = false;
-            mHasPreparedHandwritingDelegation = false;
+            mHandled = false;
             mExceedHandwritingSlop = false;
         }
     }
@@ -1052,8 +1047,6 @@
                     // Fall back to the old delegation flow
                     mImm.prepareStylusHandwritingDelegation(mView, mDelegatePackageName);
                     mView.getHandwritingDelegatorCallback().run();
-                    mState.mHasInitiatedHandwriting = false;
-                    mState.mHasPreparedHandwritingDelegation = true;
                     break;
             }
         }
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 11180ae..5ee526e 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -73,7 +73,7 @@
      *
      * @param types internal insets types (WindowInsets.Type.InsetsType) to show
      * @param fromIme true if this request originated from IME (InputMethodService).
-     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
+     * @param statsToken the token tracking the current IME request or {@code null} otherwise.
      */
     void showInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken);
 
@@ -82,7 +82,7 @@
      *
      * @param types internal insets types (WindowInsets.Type.InsetsType) to hide
      * @param fromIme true if this request originated from IME (InputMethodService).
-     * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
+     * @param statsToken the token tracking the current IME request or {@code null} otherwise.
      */
     void hideInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken);
 
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index d68a47c..e126836 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -148,13 +148,13 @@
             int seqId);
 
     @UnsupportedAppUsage
-    boolean performHapticFeedback(int effectId, boolean always);
+    boolean performHapticFeedback(int effectId, boolean always, boolean fromIme);
 
     /**
      * Called by attached views to perform predefined haptic feedback without requiring VIBRATE
      * permission.
      */
-    oneway void performHapticFeedbackAsync(int effectId, boolean always);
+    oneway void performHapticFeedbackAsync(int effectId, boolean always, boolean fromIme);
 
     /**
      * Initiate the drag operation itself
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index de809c8..821e13d 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -21,9 +21,9 @@
 import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER;
 import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.IBinder;
-import android.os.Process;
 import android.os.Trace;
 import android.util.proto.ProtoOutputStream;
 import android.view.SurfaceControl.Transaction;
@@ -70,7 +70,11 @@
         if (!isShowRequested()) {
             mIsRequestedVisibleAwaitingControl = false;
             if (!running && !mHasPendingRequest) {
-                notifyHidden(null /* statsToken */);
+                final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
+                        ImeTracker.ORIGIN_CLIENT,
+                        SoftInputShowHideReason.HIDE_SOFT_INPUT_ON_ANIMATION_STATE_CHANGED,
+                        mController.getHost().isHandlingPointerEvent() /* fromUser */);
+                notifyHidden(statsToken);
                 removeSurface();
             }
         }
@@ -144,9 +148,17 @@
 
     void requestHide(boolean fromIme, @Nullable ImeTracker.Token statsToken) {
         if (!fromIme) {
+            // Create a new token to track the hide request when we have control,
+            // as we use the passed in token for the insets animation already.
+            final var notifyStatsToken = getControl() != null
+                    ? ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
+                        ImeTracker.ORIGIN_CLIENT,
+                        SoftInputShowHideReason.HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL,
+                        mController.getHost().isHandlingPointerEvent() /* fromUser */)
+                    : statsToken;
             // The insets might be controlled by a remote target. Let the server know we are
             // requested to hide.
-            notifyHidden(statsToken);
+            notifyHidden(notifyStatsToken);
         }
         if (mAnimationState == ANIMATION_STATE_SHOW) {
             mHasPendingRequest = true;
@@ -157,21 +169,9 @@
      * Notify {@link com.android.server.inputmethod.InputMethodManagerService} that
      * IME insets are hidden.
      *
-     * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
+     * @param statsToken the token tracking the current IME request or {@code null} otherwise.
      */
-    private void notifyHidden(@Nullable ImeTracker.Token statsToken) {
-        // Create a new stats token to track the hide request when:
-        //  - we do not already have one, or
-        //  - we do already have one, but we have control and use the passed in token
-        //      for the insets animation already.
-        if (statsToken == null || getControl() != null) {
-            statsToken = ImeTracker.forLogging().onRequestHide(null /* component */,
-                    Process.myUid(),
-                    ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
-                    SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API,
-                    mController.getHost().isHandlingPointerEvent() /* fromUser */);
-        }
-
+    private void notifyHidden(@NonNull ImeTracker.Token statsToken) {
         ImeTracker.forLogging().onProgress(statsToken,
                 ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN);
 
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 7f1e037..85c779b 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -43,7 +43,6 @@
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
-import static android.view.inputmethod.ImeTracker.TOKEN_NONE;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 
@@ -165,9 +164,9 @@
         mStatsToken = statsToken;
         if (DEBUG_IME_VISIBILITY && (types & ime()) != 0) {
             EventLog.writeEvent(IMF_IME_ANIM_START,
-                    mStatsToken != null ? mStatsToken.getTag() : TOKEN_NONE, mAnimationType,
-                    mCurrentAlpha, "Current:" + mCurrentInsets, "Shown:" + mShownInsets,
-                    "Hidden:" + mHiddenInsets);
+                    mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE,
+                    mAnimationType, mCurrentAlpha, "Current:" + mCurrentInsets,
+                    "Shown:" + mShownInsets, "Hidden:" + mHiddenInsets);
         }
         mController.startAnimation(this, listener, types, mAnimation,
                 new Bounds(mHiddenInsets, mShownInsets));
@@ -245,6 +244,7 @@
     }
 
     @Override
+    @Nullable
     public ImeTracker.Token getStatsToken() {
         return mStatsToken;
     }
@@ -330,8 +330,8 @@
         mListener.onFinished(this);
         if (DEBUG_IME_VISIBILITY && (mTypes & ime()) != 0) {
             EventLog.writeEvent(IMF_IME_ANIM_FINISH,
-                    mStatsToken != null ? mStatsToken.getTag() : TOKEN_NONE, mAnimationType,
-                    mCurrentAlpha, shown ? 1 : 0, Objects.toString(insets));
+                    mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE,
+                    mAnimationType, mCurrentAlpha, shown ? 1 : 0, Objects.toString(insets));
         }
     }
 
@@ -355,8 +355,8 @@
         if (DEBUG) Log.d(TAG, "notify Control request cancelled for types: " + mTypes);
         if (DEBUG_IME_VISIBILITY && (mTypes & ime()) != 0) {
             EventLog.writeEvent(IMF_IME_ANIM_CANCEL,
-                    mStatsToken != null ? mStatsToken.getTag() : TOKEN_NONE, mAnimationType,
-                    Objects.toString(mPendingInsets));
+                    mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE,
+                    mAnimationType, Objects.toString(mPendingInsets));
         }
         releaseLeashes();
     }
diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java
index 079991a..92e20e0 100644
--- a/core/java/android/view/InsetsAnimationThreadControlRunner.java
+++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java
@@ -137,6 +137,7 @@
     }
 
     @Override
+    @Nullable
     public ImeTracker.Token getStatsToken() {
         return mControl.getStatsToken();
     }
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 1803a6e..1920fa3 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -28,7 +28,6 @@
 import static android.view.WindowInsets.Type.all;
 import static android.view.WindowInsets.Type.captionBar;
 import static android.view.WindowInsets.Type.ime;
-import static android.view.inputmethod.ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL;
 
 import android.animation.AnimationHandler;
 import android.animation.Animator;
@@ -47,7 +46,6 @@
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Process;
 import android.os.Trace;
 import android.text.TextUtils;
 import android.util.IntArray;
@@ -659,6 +657,7 @@
     private final Runnable mAnimCallback;
 
     /** Pending control request that is waiting on IME to be ready to be shown */
+    @Nullable
     private PendingControlRequest mPendingImeControlRequest;
 
     private int mWindowType;
@@ -1043,12 +1042,18 @@
         hideTypes[0] &= ~animatingTypes;
 
         if (showTypes[0] != 0) {
-            applyAnimation(showTypes[0], true /* show */, false /* fromIme */,
-                    null /* statsToken */);
+            final var statsToken = (showTypes[0] & ime()) == 0 ? null
+                    : ImeTracker.forLogging().onStart(ImeTracker.TYPE_SHOW,
+                            ImeTracker.ORIGIN_CLIENT, SoftInputShowHideReason.CONTROLS_CHANGED,
+                            mHost.isHandlingPointerEvent() /* fromUser */);
+            applyAnimation(showTypes[0], true /* show */, false /* fromIme */, statsToken);
         }
         if (hideTypes[0] != 0) {
-            applyAnimation(hideTypes[0], false /* show */, false /* fromIme */,
-                    null /* statsToken */);
+            final var statsToken = (hideTypes[0] & ime()) == 0 ? null
+                    : ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
+                            ImeTracker.ORIGIN_CLIENT, SoftInputShowHideReason.CONTROLS_CHANGED,
+                            mHost.isHandlingPointerEvent() /* fromUser */);
+            applyAnimation(hideTypes[0], false /* show */, false /* fromIme */, statsToken);
         }
 
         if (mControllableTypes != controllableTypes) {
@@ -1064,15 +1069,7 @@
 
     @Override
     public void show(@InsetsType int types) {
-        ImeTracker.Token statsToken = null;
-        if ((types & ime()) != 0) {
-            statsToken = ImeTracker.forLogging().onRequestShow(null /* component */,
-                    Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
-                    SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API,
-                    mHost.isHandlingPointerEvent() /* fromUser */);
-        }
-
-        show(types, false /* fromIme */, statsToken);
+        show(types, false /* fromIme */, null /* statsToken */);
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@@ -1080,6 +1077,13 @@
             @Nullable ImeTracker.Token statsToken) {
         if ((types & ime()) != 0) {
             Log.d(TAG, "show(ime(), fromIme=" + fromIme + ")");
+
+            if (statsToken == null) {
+                statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_SHOW,
+                        ImeTracker.ORIGIN_CLIENT,
+                        SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API,
+                        mHost.isHandlingPointerEvent() /* fromUser */);
+            }
         }
         if (fromIme) {
             ImeTracing.getInstance().triggerClientDump("InsetsController#show",
@@ -1148,9 +1152,11 @@
     }
 
     /**
-     * Handle the {@link #mPendingImeControlRequest} when
-     * - The IME insets is ready to show.
-     * - The IME insets has being requested invisible.
+     * Handle the {@link #mPendingImeControlRequest} when:
+     * <ul>
+     *     <li> The IME insets is ready to show.
+     *     <li> The IME insets has being requested invisible.
+     * </ul>
      */
     private void handlePendingControlRequest(@Nullable ImeTracker.Token statsToken) {
         PendingControlRequest pendingRequest = mPendingImeControlRequest;
@@ -1170,20 +1176,22 @@
 
     @Override
     public void hide(@InsetsType int types) {
-        ImeTracker.Token statsToken = null;
-        if ((types & ime()) != 0) {
-            statsToken = ImeTracker.forLogging().onRequestHide(null /* component */,
-                    Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
-                    SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API,
-                    mHost.isHandlingPointerEvent() /* fromUser */);
-        }
-
-        hide(types, false /* fromIme */, statsToken);
+        hide(types, false /* fromIme */, null /* statsToken */);
     }
 
     @VisibleForTesting
     public void hide(@InsetsType int types, boolean fromIme,
             @Nullable ImeTracker.Token statsToken) {
+        if ((types & ime()) != 0) {
+            Log.d(TAG, "hide(ime(), fromIme=" + fromIme + ")");
+
+            if (statsToken == null) {
+                statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
+                        ImeTracker.ORIGIN_CLIENT,
+                        SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API,
+                        mHost.isHandlingPointerEvent() /* fromUser */);
+            }
+        }
         if (fromIme) {
             ImeTracing.getInstance().triggerClientDump("InsetsController#hide",
                     mHost.getInputMethodManager(), null /* icProto */);
@@ -1307,10 +1315,12 @@
             if (monitoredAnimation && (types & Type.ime()) != 0) {
                 if (animationType == ANIMATION_TYPE_SHOW) {
                     ImeTracker.forLatency().onShowCancelled(statsToken,
-                            PHASE_CLIENT_ANIMATION_CANCEL, ActivityThread::currentApplication);
+                            ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL,
+                            ActivityThread::currentApplication);
                 } else {
                     ImeTracker.forLatency().onHideCancelled(statsToken,
-                            PHASE_CLIENT_ANIMATION_CANCEL, ActivityThread::currentApplication);
+                            ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL,
+                            ActivityThread::currentApplication);
                 }
                 ImeTracker.forLogging().onCancelled(statsToken,
                         ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
@@ -1602,12 +1612,12 @@
     private void cancelAnimation(InsetsAnimationControlRunner control, boolean invokeCallback) {
         if (invokeCallback) {
             ImeTracker.forLogging().onCancelled(control.getStatsToken(),
-                    PHASE_CLIENT_ANIMATION_CANCEL);
+                    ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL);
             control.cancel();
         } else {
             // Succeeds if invokeCallback is false (i.e. when called from notifyFinished).
             ImeTracker.forLogging().onProgress(control.getStatsToken(),
-                    PHASE_CLIENT_ANIMATION_CANCEL);
+                    ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL);
         }
         if (DEBUG) {
             Log.d(TAG, TextUtils.formatSimple(
@@ -1866,11 +1876,15 @@
 
     @Override
     public @Appearance int getSystemBarsAppearance() {
+        @Appearance int appearance = mHost.getSystemBarsAppearance();
+
+        // We only return the requested appearance, not the implied one.
+        appearance &= ~APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS;
         if (!mHost.isSystemBarsAppearanceControlled()) {
-            // We only return the requested appearance, not the implied one.
-            return 0;
+            appearance &= ~COMPATIBLE_APPEARANCE_FLAGS;
         }
-        return mHost.getSystemBarsAppearance();
+
+        return appearance;
     }
 
     @Override
diff --git a/core/java/android/view/InsetsFlags.java b/core/java/android/view/InsetsFlags.java
index 3355252..ca8a7a8 100644
--- a/core/java/android/view/InsetsFlags.java
+++ b/core/java/android/view/InsetsFlags.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.view.WindowInsetsController.APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
@@ -64,7 +65,11 @@
             @ViewDebug.FlagToString(
                     mask = APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS,
                     equals = APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS,
-                    name = "SEMI_TRANSPARENT_NAVIGATION_BARS")
+                    name = "SEMI_TRANSPARENT_NAVIGATION_BARS"),
+            @ViewDebug.FlagToString(
+                    mask = APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS,
+                    equals = APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS,
+                    name = "FORCE_LIGHT_NAVIGATION_BARS")
     })
     public @Appearance int appearance;
 
diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java
index bffaeea..ebdddd5 100644
--- a/core/java/android/view/InsetsResizeAnimationRunner.java
+++ b/core/java/android/view/InsetsResizeAnimationRunner.java
@@ -29,6 +29,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.annotation.Nullable;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.util.SparseArray;
@@ -92,6 +93,7 @@
     }
 
     @Override
+    @Nullable
     public ImeTracker.Token getStatsToken() {
         // Return null as resizing the IME view is not explicitly tracked.
         return null;
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index f9eba29..4ac78f5 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -355,11 +355,17 @@
         final Rect frame = getFrame();
         if (mBoundingRects == null) {
             // No bounding rects set, make a single bounding rect that covers the intersection of
-            // the |frame| and the |relativeFrame|.
+            // the |frame| and the |relativeFrame|. Also make it relative to the window origin.
             return mTmpBoundingRect.setIntersect(frame, relativeFrame)
-                    ? new Rect[]{ new Rect(mTmpBoundingRect) }
+                    ? new Rect[]{
+                            new Rect(
+                                    mTmpBoundingRect.left - relativeFrame.left,
+                                    mTmpBoundingRect.top - relativeFrame.top,
+                                    mTmpBoundingRect.right - relativeFrame.left,
+                                    mTmpBoundingRect.bottom - relativeFrame.top
+                            )
+                    }
                     : NO_BOUNDING_RECTS;
-
         }
 
         // Special treatment for captionBar inset type. During drag-resizing, the |frame| and
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 0ce61bb..fdb2a6e 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -314,7 +314,7 @@
      * @param fromController {@code true} if request is coming from controller.
      *                       (e.g. in IME case, controller is
      *                       {@link android.inputmethodservice.InputMethodService}).
-     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
+     * @param statsToken the token tracking the current IME request or {@code null} otherwise.
      *
      * @implNote The {@code statsToken} is ignored here, and only handled in
      * {@link ImeInsetsSourceConsumer} for IME animations only.
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 68e8c72..0ae3e59 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1733,6 +1733,8 @@
     private static native long nativeCopy(long destNativePtr, long sourceNativePtr,
             boolean keepHistory);
     @CriticalNative
+    private static native long nativeSplit(long destNativePtr, long sourceNativePtr, int idBits);
+    @CriticalNative
     private static native int nativeGetId(long nativePtr);
     @CriticalNative
     private static native int nativeGetDeviceId(long nativePtr);
@@ -1773,9 +1775,9 @@
     @CriticalNative
     private static native void nativeOffsetLocation(long nativePtr, float deltaX, float deltaY);
     @CriticalNative
-    private static native float nativeGetXOffset(long nativePtr);
+    private static native float nativeGetRawXOffset(long nativePtr);
     @CriticalNative
-    private static native float nativeGetYOffset(long nativePtr);
+    private static native float nativeGetRawYOffset(long nativePtr);
     @CriticalNative
     private static native float nativeGetXPrecision(long nativePtr);
     @CriticalNative
@@ -3743,7 +3745,7 @@
                     nativeGetAction(mNativePtr), nativeGetFlags(mNativePtr),
                     nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
                     nativeGetButtonState(mNativePtr), nativeGetClassification(mNativePtr),
-                    nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
+                    nativeGetRawXOffset(mNativePtr), nativeGetRawYOffset(mNativePtr),
                     nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
                     nativeGetDownTimeNanos(mNativePtr),
                     nativeGetEventTimeNanos(mNativePtr, HISTORY_CURRENT),
@@ -3767,86 +3769,23 @@
     }
 
     /**
-     * Splits a motion event such that it includes only a subset of pointer ids.
+     * Splits a motion event such that it includes only a subset of pointer IDs.
+     * @param idBits the bitset indicating all of the pointer IDs from this motion event that should
+     *               be in the new split event. idBits must be a non-empty subset of the pointer IDs
+     *               contained in this event.
      * @hide
      */
+    // TODO(b/327503168): Pass downTime as a parameter to split.
     @UnsupportedAppUsage
+    @NonNull
     public final MotionEvent split(int idBits) {
-        MotionEvent ev = obtain();
-        synchronized (gSharedTempLock) {
-            final int oldPointerCount = nativeGetPointerCount(mNativePtr);
-            ensureSharedTempPointerCapacity(oldPointerCount);
-            final PointerProperties[] pp = gSharedTempPointerProperties;
-            final PointerCoords[] pc = gSharedTempPointerCoords;
-            final int[] map = gSharedTempPointerIndexMap;
-
-            final int oldAction = nativeGetAction(mNativePtr);
-            final int oldActionMasked = oldAction & ACTION_MASK;
-            final int oldActionPointerIndex = (oldAction & ACTION_POINTER_INDEX_MASK)
-                    >> ACTION_POINTER_INDEX_SHIFT;
-            int newActionPointerIndex = -1;
-            int newPointerCount = 0;
-            for (int i = 0; i < oldPointerCount; i++) {
-                nativeGetPointerProperties(mNativePtr, i, pp[newPointerCount]);
-                final int idBit = 1 << pp[newPointerCount].id;
-                if ((idBit & idBits) != 0) {
-                    if (i == oldActionPointerIndex) {
-                        newActionPointerIndex = newPointerCount;
-                    }
-                    map[newPointerCount] = i;
-                    newPointerCount += 1;
-                }
-            }
-
-            if (newPointerCount == 0) {
-                throw new IllegalArgumentException("idBits did not match any ids in the event");
-            }
-
-            final int newAction;
-            if (oldActionMasked == ACTION_POINTER_DOWN || oldActionMasked == ACTION_POINTER_UP) {
-                if (newActionPointerIndex < 0) {
-                    // An unrelated pointer changed.
-                    newAction = ACTION_MOVE;
-                } else if (newPointerCount == 1) {
-                    // The first/last pointer went down/up.
-                    newAction = oldActionMasked == ACTION_POINTER_DOWN
-                            ? ACTION_DOWN
-                            : (getFlags() & FLAG_CANCELED) == 0 ? ACTION_UP : ACTION_CANCEL;
-                } else {
-                    // A secondary pointer went down/up.
-                    newAction = oldActionMasked
-                            | (newActionPointerIndex << ACTION_POINTER_INDEX_SHIFT);
-                }
-            } else {
-                // Simple up/down/cancel/move or other motion action.
-                newAction = oldAction;
-            }
-
-            final int historySize = nativeGetHistorySize(mNativePtr);
-            for (int h = 0; h <= historySize; h++) {
-                final int historyPos = h == historySize ? HISTORY_CURRENT : h;
-
-                for (int i = 0; i < newPointerCount; i++) {
-                    nativeGetPointerCoords(mNativePtr, map[i], historyPos, pc[i]);
-                }
-
-                final long eventTimeNanos = nativeGetEventTimeNanos(mNativePtr, historyPos);
-                if (h == 0) {
-                    ev.initialize(nativeGetDeviceId(mNativePtr), nativeGetSource(mNativePtr),
-                            nativeGetDisplayId(mNativePtr),
-                            newAction, nativeGetFlags(mNativePtr),
-                            nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
-                            nativeGetButtonState(mNativePtr), nativeGetClassification(mNativePtr),
-                            nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
-                            nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
-                            nativeGetDownTimeNanos(mNativePtr), eventTimeNanos,
-                            newPointerCount, pp, pc);
-                } else {
-                    nativeAddBatch(ev.mNativePtr, eventTimeNanos, pc, 0);
-                }
-            }
-            return ev;
+        if (idBits == 0) {
+            throw new IllegalArgumentException(
+                    "idBits must contain at least one pointer from this motion event");
         }
+        MotionEvent event = obtain();
+        event.mNativePtr = nativeSplit(event.mNativePtr, this.mNativePtr, idBits);
+        return event;
     }
 
     /**
diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS
index ad326e4..a2f767d 100644
--- a/core/java/android/view/OWNERS
+++ b/core/java/android/view/OWNERS
@@ -102,6 +102,7 @@
 per-file IWindow*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
 per-file RemoteAnimation*.java = file:/services/core/java/com/android/server/wm/OWNERS
 per-file RemoteAnimation*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
+per-file ScreenRecordingCallbacks.java = file:/services/core/java/com/android/server/wm/OWNERS
 per-file *SurfaceControl*.java = file:/services/core/java/com/android/server/wm/OWNERS
 per-file SurfaceControl*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
 per-file SurfaceSession.java = file:/services/core/java/com/android/server/wm/OWNERS
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index f5f4fd9..9099f98 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -428,13 +428,11 @@
 
     private BitmapDrawable getBitmapDrawableFromVectorDrawable(Resources resources,
             VectorDrawable vectorDrawable) {
-        Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(),
-                vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
-        // BitmapDrawables and Bitmap have a default density of DisplayMetrics.DENSITY_DEVICE,
-        // (which is deprecated in favor of DENSITY_DEVICE_STABLE/resources.densityDpi). In
-        // rare cases when device density differs from the resource density, the bitmap will
-        // scale as the BitmapDrawable is created. Avoid by explicitly setting density here.
-        bitmap.setDensity(resources.getDisplayMetrics().densityDpi);
+        // Ensure we pass the display metrics into the Bitmap constructor so that it is initialized
+        // with the correct density.
+        Bitmap bitmap = Bitmap.createBitmap(resources.getDisplayMetrics(),
+                vectorDrawable.getIntrinsicWidth(),
+                vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888, true /* hasAlpha */);
         Canvas canvas = new Canvas(bitmap);
         vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
         vectorDrawable.draw(canvas);
diff --git a/core/java/android/view/RoundedCorners.java b/core/java/android/view/RoundedCorners.java
index f68cc69..1f841bf 100644
--- a/core/java/android/view/RoundedCorners.java
+++ b/core/java/android/view/RoundedCorners.java
@@ -198,6 +198,12 @@
             radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius);
         }
         array.recycle();
+        // For devices with round displays (e.g. watches) that don't otherwise provide the rounded
+        // corner radius via resource overlays, we can infer the corner radius directly from the
+        // display size.
+        if (radius == 0 && res.getConfiguration().isScreenRound()) {
+            radius = res.getDisplayMetrics().widthPixels / 2;
+        }
         return radius;
     }
 
diff --git a/core/java/android/view/ScrollFeedbackProvider.java b/core/java/android/view/ScrollFeedbackProvider.java
index 8a44d4f..798203f 100644
--- a/core/java/android/view/ScrollFeedbackProvider.java
+++ b/core/java/android/view/ScrollFeedbackProvider.java
@@ -21,8 +21,11 @@
 import android.view.flags.Flags;
 
 /**
- * Interface to represent an entity giving consistent feedback for different events surrounding view
- * scroll.
+ * Provides feedback to the user for scroll events on a {@link View}. The type of feedback provided
+ * to the user may depend on the {@link InputDevice} that generated the scroll events.
+ *
+ * <p>An example of the type of feedback that this interface may provide is haptic feedback (that
+ * is, tactile feedback that provide the user physical feedback for their scroll).
  *
  * <p>The interface provides methods for the client to report different scroll events. The client
  * should report all scroll events that they want to be considered for scroll feedback using the
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index eff35c0c0..a098e4d 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -4099,7 +4099,7 @@
          * whose dataspace has RANGE_EXTENDED.
          *
          * @param sc The layer whose extended range brightness is being specified
-         * @param currentBufferRatio The current hdr/sdr ratio of the current buffer. For example
+         * @param currentBufferRatio The current HDR/SDR ratio of the current buffer. For example
          *                           if the buffer was rendered with a target SDR whitepoint of
          *                           100 nits and a max display brightness of 200 nits, this should
          *                           be set to 2.0f.
@@ -4113,7 +4113,7 @@
          *
          *                           <p>Must be finite && >= 1.0f
          *
-         * @param desiredRatio The desired hdr/sdr ratio. This can be used to communicate the max
+         * @param desiredRatio The desired HDR/SDR ratio. This can be used to communicate the max
          *                     desired brightness range. This is similar to the "max luminance"
          *                     value in other HDR metadata formats, but represented as a ratio of
          *                     the target SDR whitepoint to the max display brightness. The system
@@ -4150,15 +4150,15 @@
         }
 
         /**
-         * Sets the desired hdr headroom for the layer.
+         * Sets the desired HDR headroom for the layer.
          *
          * <p>Prefer using this API over {@link #setExtendedRangeBrightness} for formats that
          *. conform to HDR video standards like HLG or HDR10 which do not communicate a HDR/SDR
          * ratio as part of generating the buffer.
          *
-         * @param sc The layer whose desired hdr headroom is being specified
+         * @param sc The layer whose desired HDR headroom is being specified
          *
-         * @param desiredRatio The desired hdr/sdr ratio. This can be used to communicate the max
+         * @param desiredRatio The desired HDR/SDR ratio. This can be used to communicate the max
          *                     desired brightness range. This is similar to the "max luminance"
          *                     value in other HDR metadata formats, but represented as a ratio of
          *                     the target SDR whitepoint to the max display brightness. The system
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 124aece..c66abe8 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -889,11 +889,11 @@
      * @hide
      */
     @Override
-    protected int calculateFrameRateCategory(float sizePercentage) {
+    protected int calculateFrameRateCategory(int width, int height) {
         if (mMinusTwoFrameIntervalMillis > 15 && mMinusOneFrameIntervalMillis > 15) {
             return FRAME_RATE_CATEGORY_NORMAL;
         }
-        return super.calculateFrameRateCategory(sizePercentage);
+        return super.calculateFrameRateCategory(width, height);
     }
 
     @UnsupportedAppUsage
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index cbb3be7..a5ff48f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -25,6 +25,8 @@
 import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
 import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
 import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
+import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW;
@@ -2369,6 +2371,39 @@
      */
     protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
 
+    /**
+     * This indicates that the frame rate category was chosen because it was a small area update.
+     * @hide
+     */
+    public static final int FRAME_RATE_CATEGORY_REASON_SMALL = 0x0100_0000;
+
+    /**
+     * This indicates that the frame rate category was chosen because it was an intermittent update.
+     * @hide
+     */
+    public static final int FRAME_RATE_CATEGORY_REASON_INTERMITTENT = 0x0200_0000;
+
+    /**
+     * This indicates that the frame rate category was chosen because it was a large View.
+     * @hide
+     */
+    public static final int FRAME_RATE_CATEGORY_REASON_LARGE = 0x03000000;
+
+    /**
+     * This indicates that the frame rate category was chosen because it was requested.
+     * @hide
+     */
+    public static final int FRAME_RATE_CATEGORY_REASON_REQUESTED = 0x0400_0000;
+
+    /**
+     * This indicates that the frame rate category was chosen because an invalid frame rate was
+     * requested.
+     * @hide
+     */
+    public static final int FRAME_RATE_CATEGORY_REASON_INVALID = 0x0500_0000;
+
+    private static final int FRAME_RATE_CATEGORY_REASON_MASK = 0xFFFF_0000;
+
     private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
     private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
     // Used to set frame rate compatibility.
@@ -5628,17 +5663,24 @@
     @Nullable
     private ViewTranslationCallback mViewTranslationCallback;
 
-    private float mFrameContentVelocity = 0;
+    private float mFrameContentVelocity = -1;
 
     @Nullable
 
     private ViewTranslationResponse mViewTranslationResponse;
 
     /**
-     * A threshold value to determine the frame rate category of the View based on the size.
+     * Threshold size for something to be considered a small area update (in DP).
+     * This is the dimension for both width and height.
      */
-    private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f;
+    private static final float FRAME_RATE_SMALL_SIZE_THRESHOLD = 40f;
 
+    /**
+     * Threshold size for something to be considered a small area update (in DP) if
+     * it is narrow. This is for either width OR height. For example, a narrow progress
+     * bar could be considered a small area.
+     */
+    private static final float FRAME_RATE_NARROW_THRESHOLD = 10f;
 
     private static final long INFREQUENT_UPDATE_INTERVAL_MILLIS = 100;
     private static final int INFREQUENT_UPDATE_COUNTS = 2;
@@ -5659,6 +5701,9 @@
     protected long mMinusTwoFrameIntervalMillis = 0;
     private int mLastFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
 
+    private float mLastFrameX = Float.NaN;
+    private float mLastFrameY = Float.NaN;
+
     @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
     public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = Float.NaN;
     @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
@@ -7012,16 +7057,16 @@
 
     /**
      * Clears the request and callback previously set
-     * through {@link View#setCredentialManagerRequest}.
+     * through {@link View#setPendingCredentialRequest}.
      * Once this API is invoked, there will be no request fired to {@link CredentialManager}
      * on future view focus events.
      *
-     * @see #setCredentialManagerRequest
+     * @see #setPendingCredentialRequest
      */
     @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
-    public void clearCredentialManagerRequest() {
+    public void clearPendingCredentialRequest() {
         if (Log.isLoggable(AUTOFILL_LOG_TAG, Log.VERBOSE)) {
-            Log.v(AUTOFILL_LOG_TAG, "clearCredentialManagerRequest called");
+            Log.v(AUTOFILL_LOG_TAG, "clearPendingCredentialRequest called");
         }
         mViewCredentialHandler = null;
     }
@@ -7051,7 +7096,7 @@
      *                 propagated for the given view
      */
     @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
-    public void setCredentialManagerRequest(@NonNull GetCredentialRequest request,
+    public void setPendingCredentialRequest(@NonNull GetCredentialRequest request,
             @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
         Preconditions.checkNotNull(request, "request must not be null");
         Preconditions.checkNotNull(callback, "request must not be null");
@@ -8577,6 +8622,10 @@
     @CallSuper
     protected void onFocusChanged(boolean gainFocus, @FocusDirection int direction,
             @Nullable Rect previouslyFocusedRect) {
+        if (DBG) {
+            Log.d(VIEW_LOG_TAG, "onFocusChanged() entered. gainFocus: "
+                    + gainFocus);
+        }
         if (gainFocus) {
             sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
         } else {
@@ -8641,6 +8690,9 @@
         if (canNotifyAutofillEnterExitEvent()) {
             AutofillManager afm = getAutofillManager();
             if (afm != null) {
+                if (DBG) {
+                    Log.d(VIEW_LOG_TAG, this + " afm is not null");
+                }
                 if (enter) {
                     // We have not been laid out yet, hence cannot evaluate
                     // whether this view is visible to the user, we will do
@@ -8652,6 +8704,13 @@
                     // animation beginning. On the time, the view is not visible
                     // to the user. And then as the animation progresses, the view
                     // becomes visible to the user.
+                    if (DBG) {
+                        Log.d(VIEW_LOG_TAG,
+                                "notifyEnterOrExitForAutoFillIfNeeded:"
+                                + " isLaidOut(): " + isLaidOut()
+                                + " isVisibleToUser(): " + isVisibleToUser()
+                                + " isFocused(): " + isFocused());
+                    }
                     if (!isLaidOut() || !isVisibleToUser()) {
                         mPrivateFlags3 |= PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
                     } else if (isVisibleToUser()) {
@@ -9545,7 +9604,7 @@
                 structure.setIsCredential(isCredential());
             }
             if (getViewCredentialHandler() != null) {
-                structure.setCredentialManagerRequest(
+                structure.setPendingCredentialRequest(
                         getViewCredentialHandler().getRequest(),
                         getViewCredentialHandler().getCallback());
             }
@@ -9950,22 +10009,22 @@
      * @hide
      */
     public void onGetCredentialResponse(GetCredentialResponse response) {
-        if (getCredentialManagerCallback() == null) {
+        if (getPendingCredentialCallback() == null) {
             Log.w(AUTOFILL_LOG_TAG, "onGetCredentialResponse called but no callback found");
             return;
         }
-        getCredentialManagerCallback().onResult(response);
+        getPendingCredentialCallback().onResult(response);
     }
 
     /**
      * @hide
      */
     public void onGetCredentialException(String errorType, String errorMsg) {
-        if (getCredentialManagerCallback() == null) {
+        if (getPendingCredentialCallback() == null) {
             Log.w(AUTOFILL_LOG_TAG, "onGetCredentialException called but no callback found");
             return;
         }
-        getCredentialManagerCallback().onError(new GetCredentialException(errorType, errorMsg));
+        getPendingCredentialCallback().onError(new GetCredentialException(errorType, errorMsg));
     }
 
     /**
@@ -9996,13 +10055,13 @@
      * the active {@link android.service.autofill.AutofillService} on
      * the device.
      *
-     * <p>See {@link #setCredentialManagerRequest} for more info.
+     * <p>See {@link #setPendingCredentialRequest} for more info.
      *
      * @return The credential request associated with this View.
      */
     @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
     @Nullable
-    public final GetCredentialRequest getCredentialManagerRequest() {
+    public final GetCredentialRequest getPendingCredentialRequest() {
         if (mViewCredentialHandler == null) {
             return null;
         }
@@ -10012,14 +10071,14 @@
 
     /**
      * Returns the callback that has previously been set up on this view through
-     * the {@link #setCredentialManagerRequest} API.
+     * the {@link #setPendingCredentialRequest} API.
      * If the return value is null, that means no callback, or request, has been set
      * on the view and no {@link CredentialManager} flow will be invoked
      * when this view is focused. Traditioanl autofill flows will still
      * work, and autofillable content will still be returned through the
      * {@link #autofill(AutofillValue)} )} API.
      *
-     * <p>See {@link #setCredentialManagerRequest} for more info.
+     * <p>See {@link #setPendingCredentialRequest} for more info.
      *
      * @return The callback associated with this view that will be invoked on a response from
      * {@link CredentialManager} .
@@ -10027,7 +10086,7 @@
     @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
     @Nullable
     public final OutcomeReceiver<GetCredentialResponse,
-            GetCredentialException> getCredentialManagerCallback() {
+            GetCredentialException> getPendingCredentialCallback() {
         if (mViewCredentialHandler == null) {
             return null;
         }
@@ -10404,7 +10463,9 @@
     }
 
     /**
-     * Sets content sensitivity mode to determine whether this view displays sensitive content.
+     * Sets content sensitivity mode to determine whether this view displays sensitive content
+     * (e.g. username, password etc.). The system may improve user privacy i.e. hide content
+     * drawn by a sensitive view from screen sharing and recording.
      *
      * @param mode {@link #CONTENT_SENSITIVITY_AUTO}, {@link #CONTENT_SENSITIVITY_NOT_SENSITIVE}
      *                                            or {@link #CONTENT_SENSITIVITY_SENSITIVE}
@@ -10873,15 +10934,29 @@
     }
 
     private boolean isAutofillable() {
-        if (getAutofillType() == AUTOFILL_TYPE_NONE) return false;
+        if (DBG) {
+            Log.d(VIEW_LOG_TAG, "isAutofillable() entered.");
+        }
+        if (getAutofillType() == AUTOFILL_TYPE_NONE) {
+            if (DBG) {
+                Log.d(VIEW_LOG_TAG, "getAutofillType() returns AUTOFILL_TYPE_NONE");
+            }
+            return false;
+        }
 
         final AutofillManager afm = getAutofillManager();
         if (afm == null) {
+            if (DBG) {
+                Log.d(VIEW_LOG_TAG, "AutofillManager is null");
+            }
             return false;
         }
 
         // Check whether view is not part of an activity. If it's not, return false.
         if (getAutofillViewId() <= LAST_APP_AUTOFILL_ID) {
+            if (DBG) {
+                Log.d(VIEW_LOG_TAG, "getAutofillViewId()<=LAST_APP_AUTOFILL_ID");
+            }
             return false;
         }
 
@@ -10891,11 +10966,18 @@
         if ((isImportantForAutofill() && afm.isTriggerFillRequestOnFilteredImportantViewsEnabled())
                 || (!isImportantForAutofill()
                     && afm.isTriggerFillRequestOnUnimportantViewEnabled())) {
+            if (DBG) {
+                Log.d(VIEW_LOG_TAG, "isImportantForAutofill(): " + isImportantForAutofill()
+                        + "afm.isAutofillable(): " + afm.isAutofillable(this));
+            }
             return afm.isAutofillable(this) ? true : notifyAugmentedAutofillIfNeeded(afm);
         }
 
         // If the previous condition is not met, fall back to the previous way to trigger fill
         // request based on autofill importance instead.
+        if (DBG) {
+            Log.d(VIEW_LOG_TAG, "isImportantForAutofill(): " + isImportantForAutofill());
+        }
         return isImportantForAutofill() ? true : notifyAugmentedAutofillIfNeeded(afm);
     }
 
@@ -10910,6 +10992,11 @@
 
     /** @hide */
     public boolean canNotifyAutofillEnterExitEvent() {
+        if (DBG) {
+            Log.d(VIEW_LOG_TAG, "canNotifyAutofillEnterExitEvent() entered. "
+                    + " isAutofillable(): " + isAutofillable()
+                    + " isAttachedToWindow(): " + isAttachedToWindow());
+        }
         return isAutofillable() && isAttachedToWindow();
     }
 
@@ -10955,7 +11042,7 @@
                     AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId())));
         }
         if (getViewCredentialHandler() != null) {
-            structure.setCredentialManagerRequest(
+            structure.setPendingCredentialRequest(
                     getViewCredentialHandler().getRequest(),
                     getViewCredentialHandler().getCallback());
         }
@@ -15314,6 +15401,7 @@
      * {@link #isLongClickable()}, {@link #isContextClickable()},
      * {@link #isScreenReaderFocusable()}, or {@link #isFocusable()}
      * <li>Has an {@link AccessibilityDelegate}
+     * <li>Has an {@link AccessibilityNodeProvider}
      * <li>Has an interaction listener, e.g. {@link OnTouchListener},
      * {@link OnKeyListener}, etc.
      * <li>Is an accessibility live region, e.g.
@@ -15346,7 +15434,8 @@
         }
 
         return mode == IMPORTANT_FOR_ACCESSIBILITY_YES || isActionableForAccessibility()
-                || hasListenersForAccessibility() || mAccessibilityDelegate != null
+                || hasListenersForAccessibility() || getAccessibilityNodeProvider() != null
+                || getAccessibilityDelegate() != null
                 || getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE
                 || isAccessibilityPane() || isAccessibilityHeading();
     }
@@ -16813,10 +16902,15 @@
             mAttachInfo.mViewRootImpl.getWindowVisibleDisplayFrame(outRect);
             return;
         }
-        // The view is not attached to a display so we don't have a context.
-        // Make a best guess about the display size.
-        Display d = DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
-        d.getRectSize(outRect);
+        // TODO (b/327559224): Refine the behavior to better reflect the window environment with API
+        //  doc updates.
+        final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
+        final WindowMetrics metrics = windowManager.getMaximumWindowMetrics();
+        final Insets insets = metrics.getWindowInsets().getInsets(
+                WindowInsets.Type.navigationBars() | WindowInsets.Type.displayCutout());
+        outRect.set(metrics.getBounds());
+        outRect.inset(insets);
+        outRect.offsetTo(0, 0);
     }
 
     /**
@@ -24587,7 +24681,10 @@
     public void draw(@NonNull Canvas canvas) {
         final int privateFlags = mPrivateFlags;
         mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
-        mFrameContentVelocity = 0;
+
+        mFrameContentVelocity = -1;
+        mLastFrameX = mLeft + mRenderNode.getTranslationX();
+        mLastFrameY = mTop + mRenderNode.getTranslationY();
 
         /*
          * Draw traversal performs several drawing steps which must be executed
@@ -28369,15 +28466,19 @@
         }
 
         final boolean always = (flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0;
+        boolean fromIme = false;
+        if (mAttachInfo.mViewRootImpl != null) {
+            fromIme = mAttachInfo.mViewRootImpl.mWindowAttributes.type == TYPE_INPUT_METHOD;
+        }
         if (Flags.useVibratorHapticFeedback()) {
             if (!mAttachInfo.canPerformHapticFeedback()) {
                 return false;
             }
             getSystemVibrator().performHapticFeedback(
-                    feedbackConstant, always, "View#performHapticFeedback");
+                    feedbackConstant, always, "View#performHapticFeedback", fromIme);
             return true;
         }
-        return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant, always);
+        return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant, always, fromIme);
     }
 
     private Vibrator getSystemVibrator() {
@@ -31420,7 +31521,7 @@
 
         interface Callbacks {
             void playSoundEffect(int effectId);
-            boolean performHapticFeedback(int effectId, boolean always);
+            boolean performHapticFeedback(int effectId, boolean always, boolean fromIme);
         }
 
         /**
@@ -33634,18 +33735,28 @@
      *
      * @hide
      */
-    protected int calculateFrameRateCategory(float sizePercentage) {
+    protected int calculateFrameRateCategory(int width, int height) {
         if (mMinusTwoFrameIntervalMillis + mMinusOneFrameIntervalMillis
                 < INFREQUENT_UPDATE_INTERVAL_MILLIS) {
-            if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) {
-                return FRAME_RATE_CATEGORY_NORMAL;
+            DisplayMetrics displayMetrics = mResources.getDisplayMetrics();
+            float density = displayMetrics.density;
+            if (density == 0f) {
+                density = 1f;
+            }
+            float widthDp = width / density;
+            float heightDp = height / density;
+            if (widthDp <= FRAME_RATE_NARROW_THRESHOLD
+                    || heightDp <= FRAME_RATE_NARROW_THRESHOLD
+                    || (widthDp <= FRAME_RATE_SMALL_SIZE_THRESHOLD
+                    && heightDp <= FRAME_RATE_SMALL_SIZE_THRESHOLD)) {
+                return FRAME_RATE_CATEGORY_NORMAL | FRAME_RATE_CATEGORY_REASON_SMALL;
             } else {
-                return FRAME_RATE_CATEGORY_HIGH;
+                return FRAME_RATE_CATEGORY_HIGH | FRAME_RATE_CATEGORY_REASON_LARGE;
             }
         }
 
         if (mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS) {
-            return FRAME_RATE_CATEGORY_NORMAL;
+            return FRAME_RATE_CATEGORY_NORMAL | FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
         }
         return mLastFrameRateCategory;
     }
@@ -33653,34 +33764,74 @@
     private void votePreferredFrameRate() {
         // use toolkitSetFrameRate flag to gate the change
         ViewRootImpl viewRootImpl = getViewRootImpl();
-        float sizePercentage = getSizePercentage();
-        int frameRateCateogry = calculateFrameRateCategory(sizePercentage);
-        if (viewRootImpl != null && sizePercentage > 0) {
-            if (sToolkitMetricsForFrameRateDecisionFlagValue) {
-                viewRootImpl.recordViewPercentage(sizePercentage);
-            }
-            if (!Float.isNaN(mPreferredFrameRate)) {
-                if (mPreferredFrameRate < 0) {
-                    if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
-                        frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
-                    } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
-                        frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
-                    } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
-                        frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
-                    } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
-                        frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
-                    }
-                } else {
-                    viewRootImpl.votePreferredFrameRate(mPreferredFrameRate,
-                            mFrameRateCompatibility);
+        int width = mRight - mLeft;
+        int height = mBottom - mTop;
+        if (viewRootImpl != null && (width != 0 && height != 0)) {
+            if (viewVelocityApi()) {
+                float velocity = mFrameContentVelocity;
+                if (velocity < 0f) {
+                    velocity = calculateVelocity();
+                }
+                if (velocity > 0f) {
+                    float frameRate = convertVelocityToFrameRate(velocity);
+                    viewRootImpl.votePreferredFrameRate(frameRate, FRAME_RATE_COMPATIBILITY_GTE);
                     return;
                 }
             }
-            viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
-            mLastFrameRateCategory = frameRateCateogry;
+            int frameRateCategory;
+            if (Float.isNaN(mPreferredFrameRate)) {
+                frameRateCategory = calculateFrameRateCategory(width, height);
+            } else if (mPreferredFrameRate < 0) {
+                if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
+                    frameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE
+                            | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
+                    frameRateCategory = FRAME_RATE_CATEGORY_LOW
+                            | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
+                    frameRateCategory = FRAME_RATE_CATEGORY_NORMAL
+                            | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
+                    frameRateCategory = FRAME_RATE_CATEGORY_HIGH
+                            | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+                } else {
+                    // invalid frame rate, default to HIGH
+                    frameRateCategory = FRAME_RATE_CATEGORY_HIGH
+                            | FRAME_RATE_CATEGORY_REASON_INVALID;
+                }
+            } else {
+                viewRootImpl.votePreferredFrameRate(mPreferredFrameRate,
+                        mFrameRateCompatibility);
+                return;
+            }
+
+            int category = frameRateCategory & ~FRAME_RATE_CATEGORY_REASON_MASK;
+            if (sToolkitMetricsForFrameRateDecisionFlagValue) {
+                int reason = frameRateCategory & FRAME_RATE_CATEGORY_REASON_MASK;
+                viewRootImpl.recordCategory(category, reason, this);
+            }
+            viewRootImpl.votePreferredFrameRateCategory(category);
+            mLastFrameRateCategory = frameRateCategory;
         }
     }
 
+    private float convertVelocityToFrameRate(float velocityPps) {
+        float density = getResources().getDisplayMetrics().density;
+        float velocityDps = velocityPps / density;
+        // Choose a frame rate in increments of 10fps
+        return Math.min(140f, 60f + (10f * (float) Math.floor(velocityDps / 300f)));
+    }
+
+    private float calculateVelocity() {
+        // This current calculation is very simple. If something on the screen moved, then
+        // it votes for the highest velocity. If it doesn't move, then return 0.
+        float x = mLeft + mRenderNode.getTranslationX();
+        float y = mTop + mRenderNode.getTranslationY();
+
+        return (!Float.isNaN(mLastFrameX) && (x != mLastFrameX || y != mLastFrameY))
+                ? 100_000f : 0f;
+    }
+
     /**
      * Set the current velocity of the View, we only track positive value.
      * We will use the velocity information to adjust the frame rate when applicable.
@@ -33711,7 +33862,7 @@
     @FlaggedApi(FLAG_VIEW_VELOCITY_API)
     public float getFrameContentVelocity() {
         if (viewVelocityApi()) {
-            return mFrameContentVelocity;
+            return (mFrameContentVelocity < 0f) ? 0f : mFrameContentVelocity;
         }
         return 0;
     }
@@ -33763,13 +33914,13 @@
      * - otherwise, use the previous category value.
      */
     private void updateInfrequentCount() {
-        long currentTimeMillis = AnimationUtils.currentAnimationTimeMillis();
+        long currentTimeMillis = getDrawingTime();
         long timeIntervalMillis = currentTimeMillis - mLastUpdateTimeMillis;
         mMinusTwoFrameIntervalMillis = mMinusOneFrameIntervalMillis;
         mMinusOneFrameIntervalMillis = timeIntervalMillis;
 
-        if (mMinusOneFrameIntervalMillis - mMinusTwoFrameIntervalMillis >= 30
-                && timeIntervalMillis < 2) {
+        mLastUpdateTimeMillis = currentTimeMillis;
+        if (mMinusTwoFrameIntervalMillis >= 30 && timeIntervalMillis < 2) {
             return;
         }
         if (timeIntervalMillis >= INFREQUENT_UPDATE_INTERVAL_MILLIS) {
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index fbefbf3..52ff142 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3732,6 +3732,11 @@
         return afm.shouldAlwaysIncludeWebviewInAssistStructure();
     }
 
+    private boolean shouldIncludeInvisibleView(AutofillManager afm) {
+        if (afm == null) return false;
+        return afm.shouldIncludeInvisibleViewInAssistStructure();
+    }
+
     /** @hide */
     private void populateChildrenForAutofill(ArrayList<View> list, @AutofillFlags int flags) {
         final int childrenCount = mChildrenCount;
@@ -3754,7 +3759,7 @@
                     || (shouldIncludeAllChildrenViewWithAutofillTypeNotNone(afm)
                         && child.getAutofillType() != AUTOFILL_TYPE_NONE)
                     || shouldIncludeAllChildrenViews(afm)
-                    || (Flags.includeInvisibleViewGroupInAssistStructure()
+                    || (shouldIncludeInvisibleView(afm)
                     && child instanceof ViewGroup && child.getVisibility() != View.VISIBLE)) {
                 // If the child is a ViewGroup object and its visibility is not visible, include
                 // it as part of the assist structure. The children of these invisible ViewGroup
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b5f3b9a..6f178bb 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -31,6 +31,12 @@
 import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
 import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
 import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
+import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_INVALID;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_LARGE;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_REQUESTED;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_SMALL;
 import static android.view.View.PFLAG_DRAW_ANIMATION;
 import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
 import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -58,6 +64,7 @@
 import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES;
 import static android.view.ViewRootImplProto.WIN_FRAME;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
+import static android.view.WindowInsetsController.COMPATIBLE_APPEARANCE_FLAGS;
 import static android.view.flags.Flags.sensitiveContentAppProtection;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
@@ -95,6 +102,7 @@
 import static android.view.accessibility.Flags.fixMergedContentChangeEvent;
 import static android.view.accessibility.Flags.forceInvertColor;
 import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle;
+import static android.view.flags.Flags.toolkitFrameRateTypingReadOnly;
 import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision;
 import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
@@ -839,8 +847,9 @@
     private boolean mInsetsAnimationRunning;
 
     private long mPreviousFrameDrawnTime = -1;
-    // The largest view size percentage to the display size. Used on trace to collect metric.
-    private float mLargestChildPercentage = 0.0f;
+    // The reason the category was changed.
+    private int mFrameRateCategoryChangeReason = 0;
+    private String mFrameRateCategoryView;
 
     /**
      * The resolved pointer icon type requested by this window.
@@ -1061,9 +1070,6 @@
      * the variables below are used to determine whther a dVRR feature should be enabled
      */
 
-    // Used to determine whether to suppress boost on typing
-    private boolean mShouldSuppressBoostOnTyping = false;
-
     /**
      * A temporary object used so relayoutWindow can return the latest SyncSeqId
      * system. The SyncSeqId system was designed to work without synchronous relayout
@@ -1117,10 +1123,12 @@
 
     private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
     private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
+    private static boolean sToolkitFrameRateTypingReadOnlyFlagValue;
 
     static {
         sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly();
         sToolkitMetricsForFrameRateDecisionFlagValue = toolkitMetricsForFrameRateDecision();
+        sToolkitFrameRateTypingReadOnlyFlagValue = toolkitFrameRateTypingReadOnly();
     }
 
     // The latest input event from the gesture that was used to resolve the pointer icon.
@@ -1210,6 +1218,9 @@
             mSensitiveContentProtectionService =
                     ISensitiveContentProtectionManager.Stub.asInterface(
                         ServiceManager.getService(Context.SENSITIVE_CONTENT_PROTECTION_SERVICE));
+            if (mSensitiveContentProtectionService == null) {
+                Log.e(TAG, "SensitiveContentProtectionService shouldn't be null");
+            }
         } else {
             mSensitiveContentProtectionService = null;
         }
@@ -2857,7 +2868,7 @@
         final int adjust = inOutParams.softInputMode & SOFT_INPUT_MASK_ADJUST;
 
         if ((inOutParams.privateFlags & PRIVATE_FLAG_APPEARANCE_CONTROLLED) == 0) {
-            inOutParams.insetsFlags.appearance = 0;
+            inOutParams.insetsFlags.appearance &= ~COMPATIBLE_APPEARANCE_FLAGS;
             if ((sysUiVis & SYSTEM_UI_FLAG_LOW_PROFILE) != 0) {
                 inOutParams.insetsFlags.appearance |= APPEARANCE_LOW_PROFILE_BARS;
             }
@@ -3444,7 +3455,10 @@
             // other windows to resize/move based on the raw frame of the window, waiting until we
             // can finish laying out this window and get back to the window manager with the
             // ultimately computed insets.
-            insetsPending = computesInternalInsets;
+            insetsPending = computesInternalInsets
+                    // If this window provides insets via params, its insets source frame can be
+                    // updated directly without waiting for WindowSession#setInsets.
+                    && mWindowAttributes.providedInsets == null;
 
             if (mSurfaceHolder != null) {
                 mSurfaceHolder.mSurfaceLock.lock();
@@ -4179,6 +4193,9 @@
      */
     void notifySensitiveContentAppProtection(boolean showSensitiveContent) {
         try {
+            if (mSensitiveContentProtectionService == null) {
+                return;
+            }
             // The window would be blocked during screen share if it shows sensitive content.
             mSensitiveContentProtectionService.setSensitiveContentProtection(
                     getWindowToken(), mContext.getPackageName(), showSensitiveContent);
@@ -4837,10 +4854,6 @@
         long fps = NANOS_PER_SEC / timeDiff;
         Trace.setCounter(mFpsTraceName, fps);
         mPreviousFrameDrawnTime = expectedDrawnTime;
-
-        long percentage = (long) (mLargestChildPercentage * 100);
-        Trace.setCounter(mLargestViewTraceName, percentage);
-        mLargestChildPercentage = 0.0f;
     }
 
     private void reportDrawFinished(@Nullable Transaction t, int seqId) {
@@ -7562,7 +7575,8 @@
             }
 
             // For the variable refresh rate project
-            if (handled && shouldTouchBoost(action, mWindowAttributes.type)) {
+            if (handled && shouldTouchBoost(action & MotionEvent.ACTION_MASK,
+                    mWindowAttributes.type)) {
                 // set the frame rate to the maximum value.
                 mIsTouchBoosting = true;
                 setPreferredFrameRateCategory(mPreferredFrameRateCategory);
@@ -9201,18 +9215,18 @@
      * {@inheritDoc}
      */
     @Override
-    public boolean performHapticFeedback(int effectId, boolean always) {
+    public boolean performHapticFeedback(int effectId, boolean always, boolean fromIme) {
         if ((mDisplay.getFlags() & Display.FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
             return false;
         }
 
         try {
             if (USE_ASYNC_PERFORM_HAPTIC_FEEDBACK) {
-                mWindowSession.performHapticFeedbackAsync(effectId, always);
+                mWindowSession.performHapticFeedbackAsync(effectId, always, fromIme);
                 return true;
             } else {
                 // Original blocking binder call path.
-                return mWindowSession.performHapticFeedback(effectId, always);
+                return mWindowSession.performHapticFeedback(effectId, always, fromIme);
             }
         } catch (RemoteException e) {
             return false;
@@ -12349,16 +12363,29 @@
         // For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction.
         // FRAME_RATE_CATEGORY_HIGH is for boosting without user interaction
         // (e.g., Window Initialization).
-        if (mIsFrameRateBoosting || mInsetsAnimationRunning) {
+        if (mIsFrameRateBoosting || mInsetsAnimationRunning
+                || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
+                        && mPreferredFrameRate > 0)) {
             frameRateCategory = FRAME_RATE_CATEGORY_HIGH;
         }
 
         try {
             if (mLastPreferredFrameRateCategory != frameRateCategory) {
                 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                    String reason = "none";
+                    switch (mFrameRateCategoryChangeReason) {
+                        case FRAME_RATE_CATEGORY_REASON_INTERMITTENT -> reason = "intermittent";
+                        case FRAME_RATE_CATEGORY_REASON_SMALL -> reason = "small";
+                        case FRAME_RATE_CATEGORY_REASON_LARGE -> reason = "large";
+                        case FRAME_RATE_CATEGORY_REASON_REQUESTED -> reason = "requested";
+                        case FRAME_RATE_CATEGORY_REASON_INVALID -> reason = "invalid frame rate";
+                    }
+                    String sourceView = mFrameRateCategoryView == null ? "No View Given"
+                            : mFrameRateCategoryView;
                     Trace.traceBegin(
                             Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRateCategory "
-                                + frameRateCategory);
+                                    + frameRateCategory + ", reason " + reason + ", "
+                                    + sourceView);
                 }
                 mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
                         frameRateCategory, false).applyAsyncUnsafe();
@@ -12372,7 +12399,8 @@
     }
 
     private void setPreferredFrameRate(float preferredFrameRate) {
-        if (!shouldSetFrameRate()) {
+        if (!shouldSetFrameRate() || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
+                && preferredFrameRate > 0)) {
             return;
         }
 
@@ -12389,6 +12417,17 @@
                     mFrameRateCompatibility).applyAsyncUnsafe();
                 mLastPreferredFrameRate = preferredFrameRate;
             }
+            if (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE && mIsTouchBoosting) {
+                // We've received a velocity, so we'll let the velocity control the
+                // frame rate unless we receive additional motion events.
+                mIsTouchBoosting = false;
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                    Trace.instant(
+                            Trace.TRACE_TAG_VIEW,
+                            "ViewRootImpl#setFrameRate velocity used, no touch boost on next frame"
+                    );
+                }
+            }
         } catch (Exception e) {
             Log.e(mTag, "Unable to set frame rate", e);
         } finally {
@@ -12414,10 +12453,10 @@
     }
 
     private boolean shouldTouchBoost(int motionEventAction, int windowType) {
-        boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN
-                || motionEventAction == MotionEvent.ACTION_MOVE
-                || motionEventAction == MotionEvent.ACTION_UP;
-        boolean undesiredType = windowType == TYPE_INPUT_METHOD && mShouldSuppressBoostOnTyping;
+        // boost for almost all input
+        boolean desiredAction = motionEventAction != MotionEvent.ACTION_OUTSIDE;
+        boolean undesiredType = windowType == TYPE_INPUT_METHOD
+                && sToolkitFrameRateTypingReadOnlyFlagValue;
         // use toolkitSetFrameRate flag to gate the change
         return desiredAction && !undesiredType && shouldEnableDvrr()
                 && getFrameRateBoostOnTouchEnabled();
@@ -12521,6 +12560,22 @@
     }
 
     /**
+     * Get the value of mLastPreferredFrameRate
+     */
+    @VisibleForTesting
+    public float getLastPreferredFrameRate() {
+        return mLastPreferredFrameRate;
+    }
+
+    /**
+     * Returns whether touch boost is currently enabled.
+     */
+    @VisibleForTesting
+    public boolean getIsTouchBoosting() {
+        return mIsTouchBoosting;
+    }
+
+    /**
      * Get the value of mFrameRateCompatibility
      */
     @VisibleForTesting
@@ -12562,10 +12617,12 @@
         mWindowlessBackKeyCallback = callback;
     }
 
-    void recordViewPercentage(float percentage) {
+    void recordCategory(int category, int reason, View view) {
         if (!Trace.isEnabled()) return;
-        // Record the largest view of percentage to the display size.
-        mLargestChildPercentage = Math.max(percentage, mLargestChildPercentage);
+        if (category > mPreferredFrameRateCategory) {
+            mFrameRateCategoryChangeReason = reason;
+            mFrameRateCategoryView = view.getClass().getSimpleName();
+        }
     }
 
     /**
diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java
index f2a3b4c..4214141 100644
--- a/core/java/android/view/ViewRootInsetsControllerHost.java
+++ b/core/java/android/view/ViewRootInsetsControllerHost.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import static android.view.InsetsController.DEBUG;
+import static android.view.WindowInsetsController.COMPATIBLE_APPEARANCE_FLAGS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APPEARANCE_CONTROLLED;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
 
@@ -173,7 +174,9 @@
 
     @Override
     public void setSystemBarsAppearance(int appearance, int mask) {
-        mViewRoot.mWindowAttributes.privateFlags |= PRIVATE_FLAG_APPEARANCE_CONTROLLED;
+        if ((mask & COMPATIBLE_APPEARANCE_FLAGS) != 0) {
+            mViewRoot.mWindowAttributes.privateFlags |= PRIVATE_FLAG_APPEARANCE_CONTROLLED;
+        }
         final InsetsFlags insetsFlags = mViewRoot.mWindowAttributes.insetsFlags;
         final int newAppearance = (insetsFlags.appearance & ~mask) | (appearance & mask);
         if (insetsFlags.appearance != newAppearance) {
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 131fca7..6c852c3 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -315,7 +315,8 @@
     /**
      * Add to this view's child count.  This increases the current child count by
      * <var>num</var> children beyond what was last set by {@link #setChildCount}
-     * or {@link #addChildCount}.  The index at which the new child starts in the child
+     * or {@link #addChildCount}.  The index at which the new
+     * child starts in the child
      * array is returned.
      *
      * @param num The number of new children to add.
@@ -360,11 +361,11 @@
      * {@link android.credentials.CredentialManager} request will be fired when this
      * node is focused.
      * <p> For details on how a request and callback can be set, see
-     * {@link ViewStructure#setCredentialManagerRequest(GetCredentialRequest, OutcomeReceiver)}
+     * {@link ViewStructure#setPendingCredentialRequest(GetCredentialRequest, OutcomeReceiver)}
      */
     @Nullable
     @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
-    public GetCredentialRequest getCredentialManagerRequest() {
+    public GetCredentialRequest getPendingCredentialRequest() {
         return null;
     }
 
@@ -375,12 +376,12 @@
      * {@link android.credentials.CredentialManager} request will be fired when this
      * node is focused.
      * <p> For details on how a request and callback can be set, see
-     * {@link ViewStructure#setCredentialManagerRequest(GetCredentialRequest, OutcomeReceiver)}
+     * {@link ViewStructure#setPendingCredentialRequest(GetCredentialRequest, OutcomeReceiver)}
      */
     @Nullable
     @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
     public OutcomeReceiver<
-            GetCredentialResponse, GetCredentialException> getCredentialManagerCallback() {
+            GetCredentialResponse, GetCredentialException> getPendingCredentialCallback() {
         return null;
     }
 
@@ -554,12 +555,12 @@
      * @param callback the callback where the response or exception, is returned
      */
     @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
-    public void setCredentialManagerRequest(@NonNull GetCredentialRequest request,
+    public void setPendingCredentialRequest(@NonNull GetCredentialRequest request,
             @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {}
 
     /**
      * Clears the credential request previously set through
-     * {@link ViewStructure#setCredentialManagerRequest(GetCredentialRequest, OutcomeReceiver)}
+     * {@link ViewStructure#setPendingCredentialRequest(GetCredentialRequest, OutcomeReceiver)}
      */
     @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
     public void clearCredentialManagerRequest() {}
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 6b427fc..51229a7 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -666,7 +666,7 @@
          * Update the status bar appearance.
          */
 
-        void updateStatusBarAppearance(int appearance);
+        void updateSystemBarsAppearance(int appearance);
 
         /**
          * Update the navigation bar color to a forced one.
@@ -1038,6 +1038,11 @@
     }
 
     /** @hide */
+    public final void setSystemBarAppearance(@WindowInsetsController.Appearance int appearance) {
+        mSystemBarAppearance = appearance;
+    }
+
+    /** @hide */
     @WindowInsetsController.Appearance
     public final int getSystemBarAppearance() {
         return mSystemBarAppearance;
@@ -1046,12 +1051,12 @@
     /** @hide */
     public final void dispatchOnSystemBarAppearanceChanged(
             @WindowInsetsController.Appearance int appearance) {
-        mSystemBarAppearance = appearance;
+        setSystemBarAppearance(appearance);
         if (mDecorCallback != null) {
             mDecorCallback.onSystemBarAppearanceChanged(appearance);
         }
         if (mWindowControllerCallback != null) {
-            mWindowControllerCallback.updateStatusBarAppearance(appearance);
+            mWindowControllerCallback.updateSystemBarsAppearance(appearance);
         }
     }
 
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index fbebe1e..561d979 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -528,28 +528,7 @@
     @FlaggedApi(Flags.FLAG_CUSTOMIZABLE_WINDOW_HEADERS)
     @NonNull
     public List<Rect> getBoundingRects(@InsetsType int typeMask) {
-        Rect[] allRects = null;
-        for (int i = FIRST; i <= LAST; i = i << 1) {
-            if ((typeMask & i) == 0) {
-                continue;
-            }
-            final Rect[] rects = mTypeBoundingRectsMap[indexOf(i)];
-            if (rects == null) {
-                continue;
-            }
-            if (allRects == null) {
-                allRects = rects;
-            } else {
-                final Rect[] concat = new Rect[allRects.length + rects.length];
-                System.arraycopy(allRects, 0, concat, 0, allRects.length);
-                System.arraycopy(rects, 0, concat, allRects.length, rects.length);
-                allRects = concat;
-            }
-        }
-        if (allRects == null) {
-            return Collections.emptyList();
-        }
-        return Arrays.asList(allRects);
+        return getBoundingRects(mTypeBoundingRectsMap, typeMask);
     }
 
     /**
@@ -577,16 +556,28 @@
      *
      * @param typeMask the insets type for which to obtain the bounding rectangles
      * @return the bounding rectangles
+     * @throws IllegalArgumentException If the caller tries to query {@link Type#ime()}. Bounding
+     *                                  rects are not available if the IME isn't visible as the
+     *                                  height of the IME is dynamic depending on the
+     *                                  {@link EditorInfo} of the currently focused view, as well
+     *                                  as the UI state of the IME.
      */
     @FlaggedApi(Flags.FLAG_CUSTOMIZABLE_WINDOW_HEADERS)
     @NonNull
     public List<Rect> getBoundingRectsIgnoringVisibility(@InsetsType int typeMask) {
+        if ((typeMask & IME) != 0) {
+            throw new IllegalArgumentException("Unable to query the bounding rects for IME");
+        }
+        return getBoundingRects(mTypeMaxBoundingRectsMap, typeMask);
+    }
+
+    private List<Rect> getBoundingRects(Rect[][] typeBoundingRectsMap, @InsetsType int typeMask) {
         Rect[] allRects = null;
         for (int i = FIRST; i <= LAST; i = i << 1) {
             if ((typeMask & i) == 0) {
                 continue;
             }
-            final Rect[] rects = mTypeMaxBoundingRectsMap[indexOf(i)];
+            final Rect[] rects = typeBoundingRectsMap[indexOf(i)];
             if (rects == null) {
                 continue;
             }
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index b7542dc..7601ffa 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -94,15 +94,36 @@
     int APPEARANCE_LIGHT_CAPTION_BARS = 1 << 8;
 
     /**
+     * Same as {@link #APPEARANCE_LIGHT_NAVIGATION_BARS} but set by the system. The system will
+     * respect {@link #APPEARANCE_LIGHT_NAVIGATION_BARS} when this is cleared.
+     * @hide
+     */
+    int APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS = 1 << 9;
+
+    /**
+     * Appearance flags that can be implied from system UI flags.
+     * @hide
+     */
+    int COMPATIBLE_APPEARANCE_FLAGS = APPEARANCE_LOW_PROFILE_BARS
+            | APPEARANCE_LIGHT_STATUS_BARS
+            | APPEARANCE_LIGHT_NAVIGATION_BARS;
+
+    /**
      * Determines the appearance of system bars.
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(flag = true, value = {APPEARANCE_OPAQUE_STATUS_BARS, APPEARANCE_OPAQUE_NAVIGATION_BARS,
-            APPEARANCE_LOW_PROFILE_BARS, APPEARANCE_LIGHT_STATUS_BARS,
-            APPEARANCE_LIGHT_NAVIGATION_BARS, APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
+    @IntDef(flag = true, value = {
+            APPEARANCE_OPAQUE_STATUS_BARS,
+            APPEARANCE_OPAQUE_NAVIGATION_BARS,
+            APPEARANCE_LOW_PROFILE_BARS,
+            APPEARANCE_LIGHT_STATUS_BARS,
+            APPEARANCE_LIGHT_NAVIGATION_BARS,
+            APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
             APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS,
-            APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND, APPEARANCE_LIGHT_CAPTION_BARS})
+            APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND,
+            APPEARANCE_LIGHT_CAPTION_BARS,
+            APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS})
     @interface Appearance {
     }
 
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 2b2c507..22d8ed9 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -479,13 +479,13 @@
     }
 
     @Override
-    public boolean performHapticFeedback(int effectId, boolean always) {
+    public boolean performHapticFeedback(int effectId, boolean always, boolean fromIme) {
         return false;
     }
 
     @Override
-    public void performHapticFeedbackAsync(int effectId, boolean always) {
-        performHapticFeedback(effectId, always);
+    public void performHapticFeedbackAsync(int effectId, boolean always, boolean fromIme) {
+        performHapticFeedback(effectId, always, fromIme);
     }
 
     @Override
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index ae4a7d3..9708591 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -48,6 +48,7 @@
 import android.content.res.Resources;
 import android.os.Binder;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.IBinder;
@@ -67,6 +68,8 @@
 import android.view.accessibility.AccessibilityEvent.EventType;
 
 import com.android.internal.R;
+import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IntPair;
 
@@ -78,6 +81,8 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.Executor;
 
 /**
@@ -189,11 +194,13 @@
      * <p>Note: Keep in sync with {@link #SHORTCUT_TYPES}.</p>
      * @hide
      */
+    // TODO(b/323686675): reuse the one defined in ShortcutConstants
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(value = {
             // LINT.IfChange(shortcut_type_intdef)
             ACCESSIBILITY_BUTTON,
-            ACCESSIBILITY_SHORTCUT_KEY
+            ACCESSIBILITY_SHORTCUT_KEY,
+            UserShortcutType.QUICK_SETTINGS,
             // LINT.ThenChange(:shortcut_type_array)
     })
     public @interface ShortcutType {}
@@ -207,6 +214,7 @@
             // LINT.IfChange(shortcut_type_array)
             ACCESSIBILITY_BUTTON,
             ACCESSIBILITY_SHORTCUT_KEY,
+            UserShortcutType.QUICK_SETTINGS,
             // LINT.ThenChange(:shortcut_type_intdef)
     };
 
@@ -1631,6 +1639,74 @@
     }
 
     /**
+     * Turns on or off a shortcut type of the accessibility features. The shortcut type is one
+     * of the shortcut defined in the {@link ShortcutConstants.USER_SHORTCUT_TYPES}.
+     *
+     * @throws SecurityException if the app does not hold the
+     *                           {@link Manifest.permission#MANAGE_ACCESSIBILITY} permission
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+    public void enableShortcutsForTargets(boolean enable,
+            @UserShortcutType int shortcutTypes, @NonNull Set<String> targets,
+            @UserIdInt int userId) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.enableShortcutsForTargets(
+                    enable, shortcutTypes, targets.stream().toList(), userId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns accessibility feature's component and the provided tile map. This includes the
+     * TileService provided by the AccessibilityService or Accessibility Activity and the tile
+     * component provided by the framework's feature.
+     *
+     * @return a map of a feature's component name, and its provided tile's component name. The
+     * returned map's keys and values are not null. If a feature doesn't provide a tile, it won't
+     * have an entry in this map.
+     * @hide
+     * @see ShortcutConstants.A11Y_FEATURE_TO_FRAMEWORK_TILE
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+    @NonNull
+    public Map<ComponentName, ComponentName> getA11yFeatureToTileMap(@UserIdInt int userId) {
+        final IAccessibilityManager service;
+        Map<ComponentName, ComponentName> a11yFeatureToTileMap = new ArrayMap<>();
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return a11yFeatureToTileMap;
+            }
+        }
+        try {
+            Bundle a11yFeatureToTile = service.getA11yFeatureToTileMap(userId);
+            for (String key : a11yFeatureToTile.keySet()) {
+                ComponentName feature = ComponentName.unflattenFromString(key);
+                if (feature == null) {
+                    continue;
+                }
+                ComponentName tileService = a11yFeatureToTile.getParcelable(key,
+                        ComponentName.class);
+                if (tileService != null) {
+                    a11yFeatureToTileMap.put(feature, tileService);
+                }
+            }
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+        return a11yFeatureToTileMap;
+    }
+
+    /**
      * Register the provided {@link RemoteAction} with the given actionId
      * <p>
      * To perform established system actions, an accessibility service uses the GLOBAL_ACTION
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 9617606..e215950 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -22,6 +22,7 @@
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.content.ComponentName;
 import android.content.pm.ParceledListSlice;
+import android.os.Bundle;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.IAccessibilityInteractionConnection;
@@ -143,4 +144,10 @@
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE)")
     oneway void notifyQuickSettingsTilesChanged(int userId, in List<ComponentName> tileComponentNames);
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
+    oneway void enableShortcutsForTargets(boolean enable, int shortcutTypes, in List<String> shortcutTargets, int userId);
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
+    Bundle getA11yFeatureToTileMap(int userId);
 }
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 334c2b77..1acfc1b 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -220,6 +220,19 @@
             DEVICE_CONFIG_ALWAYS_INCLUDE_WEBVIEW_IN_ASSIST_STRUCTURE =
             "always_include_webview_in_assist_structure";
 
+    /**
+     * Whether to include invisible views in the assist structure. Including invisible views can fix
+     * some cases in which Session is destroyed earlier than it is suppose to.
+     *
+     * <p>See
+     * frameworks/base/services/autofill/bugfixes.aconfig#include_invisible_view_group_in_assist_structure
+     * for more information.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_INCLUDE_INVISIBLE_VIEW_GROUP_IN_ASSIST_STRUCTURE =
+            "include_invisible_view_group_in_assist_structure";
+
     // END AUTOFILL FOR ALL APPS FLAGS //
 
 
@@ -473,6 +486,13 @@
                 DEVICE_CONFIG_ALWAYS_INCLUDE_WEBVIEW_IN_ASSIST_STRUCTURE, true);
     }
 
+    /** @hide */
+    public static boolean shouldIncludeInvisibleViewInAssistStructure() {
+        return DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_AUTOFILL,
+                DEVICE_CONFIG_INCLUDE_INVISIBLE_VIEW_GROUP_IN_ASSIST_STRUCTURE,
+                false);
+    }
 
     /**
      * Whether should enable multi-line filter
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 64e5a5b..1484bfb 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -739,6 +739,9 @@
     // Indicate whether WebView should always be included in the assist structure
     private boolean mShouldAlwaysIncludeWebviewInAssistStructure;
 
+    // Indicate whether invisibles views should be included in the assist structure
+    private boolean mShouldIncludeInvisibleViewInAssistStructure;
+
     // Controls logic around apps changing some properties of their views when activity loses
     // focus due to autofill showing biometric activity, password manager, or password breach check.
     private boolean mRelayoutFix;
@@ -968,6 +971,9 @@
         mShouldAlwaysIncludeWebviewInAssistStructure =
                 AutofillFeatureFlags.shouldAlwaysIncludeWebviewInAssistStructure();
 
+        mShouldIncludeInvisibleViewInAssistStructure =
+                AutofillFeatureFlags.shouldIncludeInvisibleViewInAssistStructure();
+
         mRelayoutFix = Flags.relayout();
         mIsCredmanIntegrationEnabled = Flags.autofillCredmanIntegration();
     }
@@ -1055,6 +1061,13 @@
     }
 
     /**
+     * @hide
+     */
+    public boolean shouldIncludeInvisibleViewInAssistStructure() {
+        return mShouldIncludeInvisibleViewInAssistStructure;
+    }
+
+    /**
      * Get the denied or allowed activitiy names under specified package from the list string and
      * set it in fields accordingly
      *
@@ -1160,12 +1173,10 @@
 
         // denylist only applies to not important views
         if (!view.isImportantForAutofill() && isActivityDeniedForAutofill()) {
-            Log.d(TAG, "view is not autofillable - activity denied for autofill");
             return false;
         }
 
         if (isActivityAllowedForAutofill()) {
-            Log.d(TAG, "view is autofillable - activity allowed for autofill");
             return true;
         }
 
@@ -1482,20 +1493,29 @@
         if (infos.size() == 0) {
             throw new IllegalArgumentException("No VirtualViewInfo found");
         }
+        boolean isCredmanRequested = false;
         if (shouldSuppressDialogsForCredman(view)
                 && mIsFillAndSaveDialogDisabledForCredentialManager) {
-            if (sDebug) {
-                Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:"
-                        + view.getAutofillId().toString());
-            }
             mScreenHasCredmanField = true;
-            return;
+            if (isCredmanRequested(view)) {
+                if (sDebug) {
+                    Log.d(TAG, "Prefetching fill response for credMan: "
+                            + view.getAutofillId().toString());
+                }
+                isCredmanRequested = true;
+            } else {
+                if (sDebug) {
+                    Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:"
+                            + view.getAutofillId().toString());
+                }
+                return;
+            }
         }
         for (int i = 0; i < infos.size(); i++) {
             final VirtualViewFillInfo info = infos.valueAt(i);
             final int virtualId = infos.keyAt(i);
             notifyViewReadyInner(getAutofillId(view, virtualId),
-                    (info == null) ? null : info.getAutofillHints());
+                    (info == null) ? null : info.getAutofillHints(), isCredmanRequested);
         }
     }
 
@@ -1507,19 +1527,29 @@
      * @hide
      */
     public void notifyViewEnteredForFillDialog(View v) {
+        boolean isCredmanRequested = false;
         if (shouldSuppressDialogsForCredman(v)
                 && mIsFillAndSaveDialogDisabledForCredentialManager) {
-            if (sDebug) {
-                Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:"
-                        + v.getAutofillId());
-            }
             mScreenHasCredmanField = true;
-            return;
+            if (isCredmanRequested(v)) {
+                if (sDebug) {
+                    Log.d(TAG, "Prefetching fill response for credMan: "
+                            + v.getAutofillId().toString());
+                }
+                isCredmanRequested = true;
+            } else {
+                if (sDebug) {
+                    Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:"
+                            + v.getAutofillId().toString());
+                }
+                return;
+            }
         }
-        notifyViewReadyInner(v.getAutofillId(), v.getAutofillHints());
+        notifyViewReadyInner(v.getAutofillId(), v.getAutofillHints(), isCredmanRequested);
     }
 
-    private void notifyViewReadyInner(AutofillId id, @Nullable String[] autofillHints) {
+    private void notifyViewReadyInner(AutofillId id, @Nullable String[] autofillHints,
+            boolean isCredmanRequested) {
         if (sDebug) {
             Log.d(TAG, "notifyViewReadyInner:" + id);
         }
@@ -1594,6 +1624,12 @@
             }
             int flags = FLAG_SUPPORTS_FILL_DIALOG;
             flags |= FLAG_VIEW_NOT_FOCUSED;
+            if (isCredmanRequested) {
+                if (sDebug) {
+                    Log.d(TAG, "Pre fill request is triggered for credMan");
+                }
+                flags |= FLAG_VIEW_REQUESTS_CREDMAN_SERVICE;
+            }
             synchronized (mLock) {
                 // To match the id of the IME served view, used AutofillId.NO_AUTOFILL_ID on prefill
                 // request, because IME will reset the id of IME served view to 0 when activity
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index 9d613bc..05cabd5 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -74,4 +74,12 @@
     description: "Feature flag for setting frame rate based on velocity"
     bug: "239979904"
     is_fixed_read_only: true
+}
+
+flag {
+    name: "toolkit_frame_rate_typing_read_only"
+    namespace: "toolkit"
+    description: "Feature flag for suppressing boost on typing"
+    bug: "239979904"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index 491b0e3..cedf8d0 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -25,7 +25,6 @@
 import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.content.Context;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -298,7 +297,7 @@
 
     @AnyThread
     static boolean showSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
-            @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+            @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
             int lastClickToolType, @Nullable ResultReceiver resultReceiver,
             @SoftInputShowHideReason int reason) {
         final IInputMethodManager service = getService();
@@ -315,7 +314,7 @@
 
     @AnyThread
     static boolean hideSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
-            @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
+            @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
             @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         final IInputMethodManager service = getService();
         if (service == null) {
@@ -331,6 +330,20 @@
 
     // TODO(b/293640003): Remove method once Flags.useZeroJankProxy() is enabled.
     @AnyThread
+    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
+    static void hideSoftInputFromServerForTest() {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.hideSoftInputFromServerForTest();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
     @NonNull
     @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     static InputBindResult startInputOrWindowGainedFocus(@StartInputReason int startInputReason,
@@ -654,35 +667,18 @@
         }
     }
 
-    /** @see com.android.server.inputmethod.ImeTrackerService#onRequestShow */
+    /** @see com.android.server.inputmethod.ImeTrackerService#onStart */
     @AnyThread
     @NonNull
-    static ImeTracker.Token onRequestShow(@NonNull String tag, int uid,
+    static ImeTracker.Token onStart(@NonNull String tag, int uid, @ImeTracker.Type int type,
             @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason, boolean fromUser) {
-        final IImeTracker service = getImeTrackerService();
+        final var service = getImeTrackerService();
         if (service == null) {
-            // Create token with "fake" binder if the service was not found.
-            return new ImeTracker.Token(new Binder(), tag);
+            // Create token with "empty" binder if the service was not found.
+            return ImeTracker.Token.empty(tag);
         }
         try {
-            return service.onRequestShow(tag, uid, origin, reason, fromUser);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /** @see com.android.server.inputmethod.ImeTrackerService#onRequestHide */
-    @AnyThread
-    @NonNull
-    static ImeTracker.Token onRequestHide(@NonNull String tag, int uid,
-            @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason, boolean fromUser) {
-        final IImeTracker service = getImeTrackerService();
-        if (service == null) {
-            // Create token with "fake" binder if the service was not found.
-            return new ImeTracker.Token(new Binder(), tag);
-        }
-        try {
-            return service.onRequestHide(tag, uid, origin, reason, fromUser);
+            return service.onStart(tag, uid, type, origin, reason, fromUser);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index 74e1d10..d992feb 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -28,17 +28,20 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityThread;
 import android.content.Context;
+import android.os.Binder;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.Process;
 import android.os.SystemProperties;
 import android.util.Log;
 import android.view.InsetsController.AnimationType;
 import android.view.SurfaceControl;
 import android.view.View;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
 import com.android.internal.inputmethod.InputMethodDebug;
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.internal.jank.InteractionJankMonitor;
@@ -108,34 +111,32 @@
     /**
      * The origin of the IME request
      *
-     * The name follows the format {@code PHASE_x_...} where {@code x} denotes
-     * where the origin is (i.e. {@code PHASE_SERVER_...} occurs in the server).
+     * <p> The name follows the format {@code ORIGIN_x_...} where {@code x} denotes
+     * where the origin is (i.e. {@code ORIGIN_SERVER} occurs in the server).
      */
     @IntDef(prefix = { "ORIGIN_" }, value = {
-            ORIGIN_CLIENT_SHOW_SOFT_INPUT,
-            ORIGIN_CLIENT_HIDE_SOFT_INPUT,
-            ORIGIN_SERVER_START_INPUT,
-            ORIGIN_SERVER_HIDE_INPUT
+            ORIGIN_CLIENT,
+            ORIGIN_SERVER,
+            ORIGIN_IME
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface Origin {}
 
-    /** The IME show request originated in the client. */
-    int ORIGIN_CLIENT_SHOW_SOFT_INPUT = ImeProtoEnums.ORIGIN_CLIENT_SHOW_SOFT_INPUT;
+    /** The IME request originated in the client. */
+    int ORIGIN_CLIENT = ImeProtoEnums.ORIGIN_CLIENT;
 
-    /** The IME hide request originated in the client. */
-    int ORIGIN_CLIENT_HIDE_SOFT_INPUT = ImeProtoEnums.ORIGIN_CLIENT_HIDE_SOFT_INPUT;
+    /** The IME request originated in the server. */
+    int ORIGIN_SERVER = ImeProtoEnums.ORIGIN_SERVER;
 
-    /** The IME show request originated in the server. */
-    int ORIGIN_SERVER_START_INPUT = ImeProtoEnums.ORIGIN_SERVER_START_INPUT;
-
-    /** The IME hide request originated in the server. */
-    int ORIGIN_SERVER_HIDE_INPUT = ImeProtoEnums.ORIGIN_SERVER_HIDE_INPUT;
+    /** The IME request originated in the IME. */
+    int ORIGIN_IME = ImeProtoEnums.ORIGIN_IME;
+    /** The IME request originated in the WindowManager Shell. */
+    int ORIGIN_WM_SHELL = ImeProtoEnums.ORIGIN_WM_SHELL;
 
     /**
      * The current phase of the IME request.
      *
-     * The name follows the format {@code PHASE_x_...} where {@code x} denotes
+     * <p> The name follows the format {@code PHASE_x_...} where {@code x} denotes
      * where the phase is (i.e. {@code PHASE_SERVER_...} occurs in the server).
      */
     @IntDef(prefix = { "PHASE_" }, value = {
@@ -155,7 +156,6 @@
             PHASE_IME_SHOW_SOFT_INPUT,
             PHASE_IME_HIDE_SOFT_INPUT,
             PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE,
-            PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER,
             PHASE_SERVER_APPLY_IME_VISIBILITY,
             PHASE_WM_SHOW_IME_RUNNER,
             PHASE_WM_SHOW_IME_READY,
@@ -182,6 +182,10 @@
             PHASE_CLIENT_ANIMATION_FINISHED_SHOW,
             PHASE_CLIENT_ANIMATION_FINISHED_HIDE,
             PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT,
+            PHASE_IME_SHOW_WINDOW,
+            PHASE_IME_HIDE_WINDOW,
+            PHASE_IME_PRIVILEGED_OPERATIONS,
+            PHASE_SERVER_CURRENT_ACTIVE_IME,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface Phase {}
@@ -224,19 +228,15 @@
     /** Dispatched from the IME wrapper to the IME. */
     int PHASE_IME_WRAPPER_DISPATCH = ImeProtoEnums.PHASE_IME_WRAPPER_DISPATCH;
 
-    /** Reached the IME' showSoftInput method. */
+    /** Reached the IME's showSoftInput method. */
     int PHASE_IME_SHOW_SOFT_INPUT = ImeProtoEnums.PHASE_IME_SHOW_SOFT_INPUT;
 
-    /** Reached the IME' hideSoftInput method. */
+    /** Reached the IME's hideSoftInput method. */
     int PHASE_IME_HIDE_SOFT_INPUT = ImeProtoEnums.PHASE_IME_HIDE_SOFT_INPUT;
 
     /** The server decided the IME should be shown. */
     int PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE = ImeProtoEnums.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE;
 
-    /** Requested applying the IME visibility in the insets source consumer. */
-    int PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER =
-            ImeProtoEnums.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER;
-
     /** Applied the IME visibility. */
     int PHASE_SERVER_APPLY_IME_VISIBILITY = ImeProtoEnums.PHASE_SERVER_APPLY_IME_VISIBILITY;
 
@@ -323,54 +323,48 @@
     int PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT =
             ImeProtoEnums.PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT;
 
+    /** Reached the IME's showWindow method. */
+    int PHASE_IME_SHOW_WINDOW = ImeProtoEnums.PHASE_IME_SHOW_WINDOW;
+
+    /** Reached the IME's hideWindow method. */
+    int PHASE_IME_HIDE_WINDOW = ImeProtoEnums.PHASE_IME_HIDE_WINDOW;
+
+    /** Reached the InputMethodPrivilegedOperations handler. */
+    int PHASE_IME_PRIVILEGED_OPERATIONS = ImeProtoEnums.PHASE_IME_PRIVILEGED_OPERATIONS;
+
+    /** Checked that the calling IME is the currently active IME. */
+    int PHASE_SERVER_CURRENT_ACTIVE_IME = ImeProtoEnums.PHASE_SERVER_CURRENT_ACTIVE_IME;
+
     /**
-     * Creates an IME show request tracking token.
+     * Called when an IME request is started.
      *
-     * @param component the name of the component that created the IME request, or {@code null}
-     *                  otherwise (defaulting to {@link ActivityThread#currentProcessName()}).
-     * @param uid the uid of the client that requested the IME.
-     * @param origin the origin of the IME show request.
-     * @param reason the reason why the IME show request was created.
+     * @param component the name of the component that started the request.
+     * @param uid the uid of the client that started the request.
+     * @param type the type of the request.
+     * @param origin the origin of the request.
+     * @param reason the reason for starting the request.
      * @param fromUser whether this request was created directly from user interaction.
      *
-     * @return An IME tracking token.
+     * @return An IME request tracking token.
      */
     @NonNull
-    Token onRequestShow(@Nullable String component, int uid, @Origin int origin,
+    Token onStart(@NonNull String component, int uid, @Type int type, @Origin int origin,
             @SoftInputShowHideReason int reason, boolean fromUser);
 
     /**
-     * Alias for {@link #onRequestShow(String, int, int, int, boolean)} with
-     * {@code fromUser} set to {@code false}.
-     */
-    default Token onRequestShow(@Nullable String component, int uid, @Origin int origin,
-            @SoftInputShowHideReason int reason) {
-        return onRequestShow(component, uid, origin, reason, false /* fromUser */);
-    }
-
-    /**
-     * Creates an IME hide request tracking token.
+     * Called when an IME request is started for the current process.
      *
-     * @param component the name of the component that created the IME request, or {@code null}
-     *                  otherwise (defaulting to {@link ActivityThread#currentProcessName()}).
-     * @param uid the uid of the client that requested the IME.
-     * @param origin the origin of the IME hide request.
-     * @param reason the reason why the IME hide request was created.
+     * @param type the type of the request.
+     * @param origin the origin of the request.
+     * @param reason the reason for starting the request.
      * @param fromUser whether this request was created directly from user interaction.
      *
-     * @return An IME tracking token.
+     * @return An IME request tracking token.
      */
     @NonNull
-    Token onRequestHide(@Nullable String component, int uid, @Origin int origin,
-            @SoftInputShowHideReason int reason, boolean fromUser);
-
-    /**
-     * Alias for {@link #onRequestHide(String, int, int, int, boolean)} with
-     * {@code fromUser} set to {@code false}.
-     */
-    default Token onRequestHide(@Nullable String component, int uid, @Origin int origin,
-            @SoftInputShowHideReason int reason) {
-        return onRequestHide(component, uid, origin, reason, false /* fromUser */);
+    default Token onStart(@Type int type, @Origin int origin, @SoftInputShowHideReason int reason,
+            boolean fromUser) {
+        return onStart(Process.myProcessName(), Process.myUid(), type, origin, reason, fromUser);
     }
 
     /**
@@ -408,14 +402,14 @@
     /**
      * Called when the IME show request is successful.
      *
-     * @param token the token tracking the current IME show request or {@code null} otherwise.
+     * @param token the token tracking the current IME request or {@code null} otherwise.
      */
     void onShown(@Nullable Token token);
 
     /**
      * Called when the IME hide request is successful.
      *
-     * @param token the token tracking the current IME hide request or {@code null} otherwise.
+     * @param token the token tracking the current IME request or {@code null} otherwise.
      */
     void onHidden(@Nullable Token token);
 
@@ -497,33 +491,17 @@
 
         @NonNull
         @Override
-        public Token onRequestShow(@Nullable String component, int uid, @Origin int origin,
+        public Token onStart(@NonNull String component, int uid, @Type int type, @Origin int origin,
                 @SoftInputShowHideReason int reason, boolean fromUser) {
-            final var tag = getTag(component);
-            final var token = IInputMethodManagerGlobalInvoker.onRequestShow(tag, uid, origin,
-                    reason, fromUser);
+            final var tag = Token.createTag(component);
+            final var token = IInputMethodManagerGlobalInvoker.onStart(tag, uid, type,
+                    origin, reason, fromUser);
 
-            Log.i(TAG, token.mTag + ": onRequestShow at " + Debug.originToString(origin)
+            Log.i(TAG, token.mTag + ": onRequest" + (type == TYPE_SHOW ? "Show" : "Hide")
+                    + " at " + Debug.originToString(origin)
                     + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason)
                     + " fromUser " + fromUser,
                     mLogStackTrace ? new Throwable() : null);
-
-            return token;
-        }
-
-        @NonNull
-        @Override
-        public Token onRequestHide(@Nullable String component, int uid, @Origin int origin,
-                @SoftInputShowHideReason int reason, boolean fromUser) {
-            final var tag = getTag(component);
-            final var token = IInputMethodManagerGlobalInvoker.onRequestHide(tag, uid, origin,
-                    reason, fromUser);
-
-            Log.i(TAG, token.mTag + ": onRequestHide at " + Debug.originToString(origin)
-                    + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason)
-                    + " fromUser " + fromUser,
-                    mLogStackTrace ? new Throwable() : null);
-
             return token;
         }
 
@@ -574,20 +552,6 @@
 
             Log.i(TAG, token.mTag + ": onHidden");
         }
-
-        /**
-         * Returns a logging tag using the given component name.
-         *
-         * @param component the name of the component that created the IME request, or {@code null}
-         *                  otherwise (defaulting to {@link ActivityThread#currentProcessName()}).
-         */
-        @NonNull
-        private String getTag(@Nullable String component) {
-            if (component == null) {
-                component = ActivityThread.currentProcessName();
-            }
-            return component + ":" + Integer.toHexString(ThreadLocalRandom.current().nextInt());
-        }
     };
 
     /** The singleton IME tracker instance for instrumenting jank metrics. */
@@ -599,6 +563,10 @@
     /** A token that tracks the progress of an IME request. */
     final class Token implements Parcelable {
 
+        /** Empty binder, lazily initialized, used for empty token instantiation. */
+        @Nullable
+        private static IBinder sEmptyBinder;
+
         /** The binder used to identify this token. */
         @NonNull
         private final IBinder mBinder;
@@ -617,16 +585,56 @@
             mTag = in.readString8();
         }
 
+        /** Returns the binder used to identify this token. */
         @NonNull
         public IBinder getBinder() {
             return mBinder;
         }
 
+        /** Returns the logging tag of this token. */
         @NonNull
         public String getTag() {
             return mTag;
         }
 
+        /**
+         * Creates a logging tag.
+         *
+         * @param component the name of the component that created the IME request.
+         */
+        @NonNull
+        private static String createTag(@NonNull String component) {
+            return component + ":" + Integer.toHexString(ThreadLocalRandom.current().nextInt());
+        }
+
+        /** Returns a new token with an empty binder. */
+        @NonNull
+        @VisibleForTesting(visibility = Visibility.PACKAGE)
+        public static Token empty() {
+            final var tag = createTag(Process.myProcessName());
+            return empty(tag);
+        }
+
+        /** Returns a new token with an empty binder and the given logging tag. */
+        @NonNull
+        static Token empty(@NonNull String tag) {
+            return new Token(getEmptyBinder(), tag);
+        }
+
+        /** Returns the empty binder instance for empty token creation, lazily initializing it. */
+        @NonNull
+        private static IBinder getEmptyBinder() {
+            if (sEmptyBinder == null) {
+                sEmptyBinder = new Binder();
+            }
+            return sEmptyBinder;
+        }
+
+        @Override
+        public String toString() {
+            return super.toString() + "(tag: " + mTag + ")";
+        }
+
         /** For Parcelable, no special marshalled objects. */
         @Override
         public int describeContents() {
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 33f34c5..88607fc 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -281,7 +281,7 @@
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface ShowFlags {}
-    
+
     /**
      * Flag for {@link #showSoftInput}: this show has been explicitly
      * requested by the user.  If not set, the system has decided it may be
@@ -314,18 +314,18 @@
      * @param showInputToken an opaque {@link android.os.Binder} token to identify which API call
      *        of {@link InputMethodManager#showSoftInput(View, int)} is associated with
      *        this callback.
-     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
+     * @param statsToken the token tracking the current IME request.
      * @hide
      */
     @MainThread
     public default void showSoftInputWithToken(@ShowFlags int flags, ResultReceiver resultReceiver,
-            IBinder showInputToken, @Nullable ImeTracker.Token statsToken) {
+            IBinder showInputToken, @NonNull ImeTracker.Token statsToken) {
         showSoftInput(flags, resultReceiver);
     }
 
     /**
      * Request that any soft input part of the input method be shown to the user.
-     * 
+     *
      * @param resultReceiver The client requesting the show may wish to
      * be told the impact of their request, which should be supplied here.
      * The result code should be
@@ -352,12 +352,12 @@
      * @param hideInputToken an opaque {@link android.os.Binder} token to identify which API call
      *         of {@link InputMethodManager#hideSoftInputFromWindow(IBinder, int)}} is associated
      *         with this callback.
-     * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
+     * @param statsToken the token tracking the current IME request.
      * @hide
      */
     @MainThread
     public default void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver,
-            IBinder hideInputToken, @Nullable ImeTracker.Token statsToken) {
+            IBinder hideInputToken, @NonNull ImeTracker.Token statsToken) {
         hideSoftInput(flags, resultReceiver);
     }
 
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 7c9678f..7885cd9 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -497,6 +497,44 @@
      * @hide
      */
     @TestApi
+    public InputMethodInfo(@NonNull String packageName, @NonNull String className,
+            @NonNull CharSequence label, @NonNull String settingsActivity,
+            boolean supportStylusHandwriting,
+            @NonNull String stylusHandwritingSettingsActivityAttr) {
+        this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */,
+                settingsActivity, null /* languageSettingsActivity */,
+                null /* subtypes */, 0 /* isDefaultResId */,
+                false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
+                false /* inlineSuggestionsEnabled */, false /* isVrOnly */,
+                false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */,
+                supportStylusHandwriting, false /* supportConnectionlessStylusHandwriting */,
+                stylusHandwritingSettingsActivityAttr, false /* inlineSuggestionsEnabled */);
+    }
+
+    /**
+     * Test API for creating a built-in input method to verify stylus handwriting.
+     * @hide
+     */
+    @TestApi
+    public InputMethodInfo(@NonNull String packageName, @NonNull String className,
+            @NonNull CharSequence label, @NonNull String settingsActivity,
+            @NonNull String languageSettingsActivity, boolean supportStylusHandwriting,
+            @NonNull String stylusHandwritingSettingsActivityAttr) {
+        this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */,
+                settingsActivity, languageSettingsActivity, null /* subtypes */,
+                0 /* isDefaultResId */, false /* forceDefault */,
+                true /* supportsSwitchingToNextInputMethod */,
+                false /* inlineSuggestionsEnabled */, false /* isVrOnly */,
+                false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */,
+                supportStylusHandwriting, false /* supportConnectionlessStylusHandwriting */,
+                stylusHandwritingSettingsActivityAttr, false /* inlineSuggestionsEnabled */);
+    }
+
+    /**
+     * Test API for creating a built-in input method to verify stylus handwriting.
+     * @hide
+     */
+    @TestApi
     @FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING)
     public InputMethodInfo(@NonNull String packageName, @NonNull String className,
             @NonNull CharSequence label, @NonNull String settingsActivity,
@@ -862,6 +900,8 @@
                 + Integer.toHexString(mIsDefaultResId));
         pw.println(prefix + "Service:");
         mService.dump(pw, prefix + "  ");
+        pw.println(prefix + "InputMethodSubtype array: count=" + mSubtypes.getCount());
+        mSubtypes.dump(pw, prefix + "  ");
     }
 
     @Override
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 3bce155..985f542 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2262,21 +2262,22 @@
      * {@link #RESULT_HIDDEN}.
      */
     public boolean showSoftInput(View view, @ShowFlags int flags, ResultReceiver resultReceiver) {
-        return showSoftInput(view, null /* statsToken */, flags, resultReceiver,
-                SoftInputShowHideReason.SHOW_SOFT_INPUT);
+        return showSoftInput(view, flags, resultReceiver, SoftInputShowHideReason.SHOW_SOFT_INPUT);
     }
 
-    private boolean showSoftInput(View view, @Nullable ImeTracker.Token statsToken,
-            @ShowFlags int flags, ResultReceiver resultReceiver,
+    private boolean showSoftInput(View view, @ShowFlags int flags,
+            @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+        // TODO(b/303041796): handle tracking physical keyboard and DPAD as user interactions
+        final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_SHOW,
+                ImeTracker.ORIGIN_CLIENT, reason, ImeTracker.isFromUser(view));
+        return showSoftInput(view, statsToken, flags, resultReceiver, reason);
+    }
+
+    private boolean showSoftInput(View view, @NonNull ImeTracker.Token statsToken,
+            @ShowFlags int flags, @Nullable ResultReceiver resultReceiver,
             @SoftInputShowHideReason int reason) {
-        if (statsToken == null) {
-            // TODO(b/303041796): handle tracking physical keyboard and DPAD as user interactions
-            statsToken = ImeTracker.forLogging().onRequestShow(null /* component */,
-                    Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, reason,
-                    ImeTracker.isFromUser(view));
-        }
-        ImeTracker.forLatency().onRequestShow(statsToken, ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
-                reason, ActivityThread::currentApplication);
+        ImeTracker.forLatency().onRequestShow(statsToken,
+                ImeTracker.ORIGIN_CLIENT, reason, ActivityThread::currentApplication);
         ImeTracing.getInstance().triggerClientDump("InputMethodManager#showSoftInput", this,
                 null /* icProto */);
         // Re-dispatch if there is a context mismatch.
@@ -2289,9 +2290,8 @@
         synchronized (mH) {
             if (!hasServedByInputMethodLocked(view)) {
                 ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
-                ImeTracker.forLatency().onShowFailed(
-                        statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED,
-                        ActivityThread::currentApplication);
+                ImeTracker.forLatency().onShowFailed(statsToken,
+                        ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication);
                 Log.w(TAG, "Ignoring showSoftInput() as view=" + view + " is not served.");
                 return false;
             }
@@ -2326,9 +2326,9 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499)
     public void showSoftInputUnchecked(@ShowFlags int flags, ResultReceiver resultReceiver) {
         synchronized (mH) {
-            final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestShow(
-                    null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
-                    SoftInputShowHideReason.SHOW_SOFT_INPUT);
+            final int reason = SoftInputShowHideReason.SHOW_SOFT_INPUT;
+            final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_SHOW,
+                    ImeTracker.ORIGIN_CLIENT, reason, false /* fromUser */);
 
             Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be"
                     + " removed soon. If you are using androidx.appcompat.widget.SearchView,"
@@ -2352,7 +2352,7 @@
                     flags,
                     mCurRootView.getLastClickToolType(),
                     resultReceiver,
-                    SoftInputShowHideReason.SHOW_SOFT_INPUT);
+                    reason);
         }
     }
 
@@ -2428,11 +2428,10 @@
             initialServedView = getServedViewLocked();
         }
 
-        final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide(
-                null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
-                reason, ImeTracker.isFromUser(initialServedView));
-        ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
-                reason, ActivityThread::currentApplication);
+        final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
+                ImeTracker.ORIGIN_CLIENT, reason, ImeTracker.isFromUser(initialServedView));
+        ImeTracker.forLatency().onRequestHide(statsToken,
+                ImeTracker.ORIGIN_CLIENT, reason, ActivityThread::currentApplication);
         ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromWindow",
                 this, null /* icProto */);
         checkFocus();
@@ -2471,20 +2470,18 @@
             }
         }
 
-        final var reason = SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW;
-        final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide(
-                null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
-                reason, ImeTracker.isFromUser(view));
-        ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
-                reason, ActivityThread::currentApplication);
+        final int reason = SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW;
+        final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
+                ImeTracker.ORIGIN_CLIENT, reason, ImeTracker.isFromUser(view));
+        ImeTracker.forLatency().onRequestHide(statsToken,
+                ImeTracker.ORIGIN_CLIENT, reason, ActivityThread::currentApplication);
         ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromView",
                 this, null /* icProto */);
         synchronized (mH) {
             if (!hasServedByInputMethodLocked(view)) {
                 ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
-                ImeTracker.forLatency().onShowFailed(
-                        statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED,
-                        ActivityThread::currentApplication);
+                ImeTracker.forLatency().onShowFailed(statsToken,
+                        ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication);
                 Log.w(TAG, "Ignoring hideSoftInputFromView() as view=" + view + " is not served.");
                 return false;
             }
@@ -2497,6 +2494,19 @@
     }
 
     /**
+     * A test API for CTS to request hiding the current soft input window, with the request origin
+     * on the server side.
+     *
+     * @hide
+     */
+    @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+    @TestApi
+    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
+    public void hideSoftInputFromServerForTest() {
+        IInputMethodManagerGlobalInvoker.hideSoftInputFromServerForTest();
+    }
+
+    /**
      * Start stylus handwriting session.
      *
      * If supported by the current input method, a stylus handwriting session is started on the
@@ -2898,8 +2908,6 @@
      * @param flags {@link #HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED} or {@code 0}
      * @param executor The executor to run the callback on.
      * @param callback {@code true>} would be received if delegation was accepted.
-     * @return {@code true} if view belongs to allowed delegate package declared in {@link
-     *     #prepareStylusHandwritingDelegation(View, String)} and delegation is accepted
      * @see #prepareStylusHandwritingDelegation(View, String)
      * @see #acceptStylusHandwritingDelegation(View)
      */
@@ -2972,10 +2980,11 @@
             if (view != null) {
                 final WindowInsets rootInsets = view.getRootWindowInsets();
                 if (rootInsets != null && rootInsets.isVisible(WindowInsets.Type.ime())) {
-                    hideSoftInputFromWindow(view.getWindowToken(), hideFlags, null,
+                    hideSoftInputFromWindow(view.getWindowToken(), hideFlags,
+                            null /* resultReceiver */,
                             SoftInputShowHideReason.HIDE_TOGGLE_SOFT_INPUT);
                 } else {
-                    showSoftInput(view, null /* statsToken */, showFlags, null /* resultReceiver */,
+                    showSoftInput(view, showFlags, null /* resultReceiver */,
                             SoftInputShowHideReason.SHOW_TOGGLE_SOFT_INPUT);
                 }
             }
@@ -3490,8 +3499,7 @@
             return false;
         }
         mServedView = mNextServedView;
-        if (initiationWithoutInputConnection() && mServedView.onCheckIsTextEditor()
-                && mServedView.isHandwritingDelegate()) {
+        if (initiationWithoutInputConnection() && mServedView.isHandwritingDelegate()) {
             mServedView.getViewRootImpl().getHandwritingInitiator().onDelegateViewFocused(
                     mServedView);
         }
@@ -3537,11 +3545,11 @@
 
     @UnsupportedAppUsage
     void closeCurrentInput() {
-        final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide(
-                null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
-                SoftInputShowHideReason.HIDE_CLOSE_CURRENT_SESSION);
-        ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
-                SoftInputShowHideReason.HIDE_CLOSE_CURRENT_SESSION,
+        final int reason = SoftInputShowHideReason.HIDE_CLOSE_CURRENT_SESSION;
+        final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
+                ImeTracker.ORIGIN_CLIENT, reason, false /* fromUser */);
+        ImeTracker.forLatency().onRequestHide(statsToken,
+                ImeTracker.ORIGIN_CLIENT, reason,
                 ActivityThread::currentApplication);
 
         synchronized (mH) {
@@ -3562,7 +3570,7 @@
                     statsToken,
                     HIDE_NOT_ALWAYS,
                     null,
-                    SoftInputShowHideReason.HIDE_CLOSE_CURRENT_SESSION);
+                    reason);
         }
     }
 
@@ -3603,12 +3611,12 @@
      *
      * @param windowToken the window from which this request originates. If this doesn't match the
      *                    currently served view, the request is ignored and returns {@code false}.
-     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
+     * @param statsToken the token tracking the current IME request.
      *
      * @return {@code true} if IME can (eventually) be shown, {@code false} otherwise.
      * @hide
      */
-    public boolean requestImeShow(IBinder windowToken, @Nullable ImeTracker.Token statsToken) {
+    public boolean requestImeShow(IBinder windowToken, @NonNull ImeTracker.Token statsToken) {
         checkFocus();
         synchronized (mH) {
             final View servedView = getServedViewLocked();
@@ -3632,16 +3640,11 @@
      *
      * @param windowToken the window from which this request originates. If this doesn't match the
      *                    currently served view, the request is ignored.
-     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
+     * @param statsToken the token tracking the current IME request.
      * @hide
      */
-    public void notifyImeHidden(IBinder windowToken, @Nullable ImeTracker.Token statsToken) {
-        if (statsToken == null) {
-            statsToken = ImeTracker.forLogging().onRequestHide(null /* component */,
-                    Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
-                    SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
-        }
-        ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+    public void notifyImeHidden(IBinder windowToken, @NonNull ImeTracker.Token statsToken) {
+        ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT,
                 SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API,
                 ActivityThread::currentApplication);
         ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this,
@@ -4025,8 +4028,11 @@
      */
     @Deprecated
     public void hideSoftInputFromInputMethod(IBinder token, @HideFlags int flags) {
-        InputMethodPrivilegedOperationsRegistry.get(token).hideMySoftInput(
-                flags, SoftInputShowHideReason.HIDE_SOFT_INPUT_IMM_DEPRECATION);
+        final int reason = SoftInputShowHideReason.HIDE_SOFT_INPUT_IMM_DEPRECATION;
+        final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
+                ImeTracker.ORIGIN_CLIENT, reason, false /* fromUser */);
+        InputMethodPrivilegedOperationsRegistry.get(token).hideMySoftInput(statsToken, flags,
+                reason);
     }
 
     /**
@@ -4044,7 +4050,11 @@
      */
     @Deprecated
     public void showSoftInputFromInputMethod(IBinder token, @ShowFlags int flags) {
-        InputMethodPrivilegedOperationsRegistry.get(token).showMySoftInput(flags);
+        final int reason = SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION;
+        final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_SHOW,
+                ImeTracker.ORIGIN_CLIENT, reason, false /* fromUser */);
+        InputMethodPrivilegedOperationsRegistry.get(token).showMySoftInput(statsToken, flags,
+                reason);
     }
 
     /**
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index b0b9460..be91cfb 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -28,6 +28,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
+import android.util.Printer;
 import android.util.Slog;
 
 import com.android.internal.inputmethod.SubtypeLocaleUtils;
@@ -791,6 +792,20 @@
         dest.writeInt(mIsAsciiCapable ? 1 : 0);
     }
 
+    void dump(@NonNull Printer pw, @NonNull String prefix) {
+        pw.println(prefix + "mSubtypeNameOverride=" + mSubtypeNameOverride
+                + " mPkLanguageTag=" + mPkLanguageTag
+                + " mPkLayoutType=" + mPkLayoutType
+                + " mSubtypeId=" + mSubtypeId
+                + " mSubtypeLocale=" + mSubtypeLocale
+                + " mSubtypeLanguageTag=" + mSubtypeLanguageTag
+                + " mSubtypeMode=" + mSubtypeMode
+                + " mIsAuxiliary=" + mIsAuxiliary
+                + " mOverridesImplicitlyEnabledSubtype=" + mOverridesImplicitlyEnabledSubtype
+                + " mIsAsciiCapable=" + mIsAsciiCapable
+                + " mSubtypeHashCode=" + mSubtypeHashCode);
+    }
+
     public static final @android.annotation.NonNull Parcelable.Creator<InputMethodSubtype> CREATOR
             = new Parcelable.Creator<InputMethodSubtype>() {
         @Override
diff --git a/core/java/android/view/inputmethod/InputMethodSubtypeArray.java b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java
index ee36dc7..c243a22 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtypeArray.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java
@@ -16,9 +16,11 @@
 
 package android.view.inputmethod;
 
+import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.BadParcelableException;
 import android.os.Parcel;
+import android.util.Printer;
 import android.util.Slog;
 
 import java.io.ByteArrayInputStream;
@@ -174,6 +176,19 @@
     private volatile byte[] mCompressedData;
     private volatile int mDecompressedSize;
 
+    void dump(@NonNull Printer pw, @NonNull String prefix) {
+        final var innerPrefix = prefix + "  ";
+        for (int i = 0; i < mCount; i++) {
+            pw.println(prefix + "InputMethodSubtype #" + i + ":");
+            final var subtype = get(i);
+            if (subtype != null) {
+                subtype.dump(pw, innerPrefix);
+            } else {
+                pw.println(innerPrefix + "missing subtype");
+            }
+        }
+    }
+
     private static byte[] marshall(final InputMethodSubtype[] array) {
         Parcel parcel = null;
         try {
diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java
index 3fc0a30..8501474 100644
--- a/core/java/android/webkit/WebViewDelegate.java
+++ b/core/java/android/webkit/WebViewDelegate.java
@@ -175,8 +175,16 @@
 
     /**
      * Adds the WebView asset path to {@link android.content.res.AssetManager}.
+     * If {@link android.content.res.Flags#FLAG_REGISTER_RESOURCE_PATHS} is enabled, this function
+     * will be a no-op because the asset paths appending work will only be handled by
+     * {@link android.content.res.Resources#registerResourcePaths(String, ApplicationInfo)},
+     * otherwise it behaves the old way.
      */
     public void addWebViewAssetPath(Context context) {
+        if (android.content.res.Flags.registerResourcePaths()) {
+            return;
+        }
+
         final String[] newAssetPaths =
                 WebViewFactory.getLoadedPackageInfo().applicationInfo.getAllApkPaths();
         final ApplicationInfo appInfo = context.getApplicationInfo();
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index c748a57..8f1b72e 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -30,6 +30,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.Signature;
+import android.content.res.Resources;
 import android.os.Build;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -93,6 +94,9 @@
     // error for namespace lookup
     public static final int LIBLOAD_FAILED_TO_FIND_NAMESPACE = 10;
 
+    // generic error for future use
+    static final int LIBLOAD_FAILED_OTHER = 11;
+
     /**
      * Stores the timestamps at which various WebView startup events occurred in this process.
      */
@@ -544,8 +548,14 @@
             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()");
             try {
                 sTimestamps.mAddAssetsStart = SystemClock.uptimeMillis();
-                for (String newAssetPath : webViewContext.getApplicationInfo().getAllApkPaths()) {
-                    initialApplication.getAssets().addAssetPathAsSharedLibrary(newAssetPath);
+                if (android.content.res.Flags.registerResourcePaths()) {
+                    Resources.registerResourcePaths(webViewContext.getPackageName(),
+                            webViewContext.getApplicationInfo());
+                } else {
+                    for (String newAssetPath : webViewContext.getApplicationInfo()
+                            .getAllApkPaths()) {
+                        initialApplication.getAssets().addAssetPathAsSharedLibrary(newAssetPath);
+                    }
                 }
                 sTimestamps.mAddAssetsEnd = sTimestamps.mGetClassLoaderStart =
                         SystemClock.uptimeMillis();
diff --git a/core/java/android/webkit/WebViewProviderResponse.java b/core/java/android/webkit/WebViewProviderResponse.java
index 84e34a3..926aaff 100644
--- a/core/java/android/webkit/WebViewProviderResponse.java
+++ b/core/java/android/webkit/WebViewProviderResponse.java
@@ -40,6 +40,7 @@
                 STATUS_SUCCESS,
                 STATUS_FAILED_WAITING_FOR_RELRO,
                 STATUS_FAILED_LISTING_WEBVIEW_PACKAGES,
+                STATUS_FAILED_OTHER,
             })
     @Retention(RetentionPolicy.SOURCE)
     private @interface WebViewProviderStatus {}
@@ -49,6 +50,7 @@
             WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO;
     public static final int STATUS_FAILED_LISTING_WEBVIEW_PACKAGES =
             WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
+    public static final int STATUS_FAILED_OTHER = WebViewFactory.LIBLOAD_FAILED_OTHER;
 
     public WebViewProviderResponse(
             @Nullable PackageInfo packageInfo, @WebViewProviderStatus int status) {
diff --git a/core/java/android/webkit/WebViewUpdateManager.java b/core/java/android/webkit/WebViewUpdateManager.java
index 8ada598..07576a2 100644
--- a/core/java/android/webkit/WebViewUpdateManager.java
+++ b/core/java/android/webkit/WebViewUpdateManager.java
@@ -127,7 +127,7 @@
      *
      * This choice will be stored persistently.
      *
-     * @param newProvider the package name to use, or null to reset to default.
+     * @param newProvider the package name to use.
      * @return the package name which is now in use, which may not be the
      *         requested one if it was not usable.
      */
@@ -155,7 +155,7 @@
     /**
      * Get the WebView provider which will be used if no explicit choice has been made.
      *
-     * The default provider is not guaranteed to be currently valid/usable.
+     * The default provider is not guaranteed to be a valid/usable WebView implementation.
      *
      * @return the default WebView provider.
      */
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 55b2251..0b99df3 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -16,6 +16,8 @@
 
 package android.widget;
 
+import static android.view.flags.Flags.viewVelocityApi;
+
 import android.annotation.ColorInt;
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -1488,6 +1490,11 @@
             if (!awakenScrollBars()) {
                 postInvalidateOnAnimation();
             }
+
+            // For variable refresh rate project to track the current velocity of this View
+            if (viewVelocityApi()) {
+                setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+            }
         }
     }
 
@@ -1810,6 +1817,11 @@
                 mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0,
                         maxScroll, 0, 0, width / 2, 0);
 
+                // For variable refresh rate project to track the current velocity of this View
+                if (viewVelocityApi()) {
+                    setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+                }
+
                 final boolean movingRight = velocityX > 0;
 
                 View currentFocused = findFocus();
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 13dc4ef..a2d8d80 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1105,6 +1105,7 @@
         SetRemoteCollectionItemListAdapterAction(Parcel parcel) {
             mViewId = parcel.readInt();
             mIntentId = parcel.readInt();
+            mIsReplacedIntoAction = parcel.readBoolean();
             mServiceIntent = parcel.readTypedObject(Intent.CREATOR);
             mItems = mServiceIntent != null
                     ? null
@@ -1128,6 +1129,7 @@
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeInt(mViewId);
             dest.writeInt(mIntentId);
+            dest.writeBoolean(mIsReplacedIntoAction);
             dest.writeTypedObject(mServiceIntent, flags);
             if (mItems != null) {
                 mItems.writeToParcel(dest, flags, /* attached= */ true);
@@ -1209,6 +1211,19 @@
     }
 
     /**
+     * The maximum size for RemoteViews with converted RemoteCollectionItemsAdapter.
+     * When converting RemoteViewsAdapter to RemoteCollectionItemsAdapter, we want to put size
+     * limits on each unique RemoteCollectionItems in order to not exceed the transaction size limit
+     * for each parcel (typically 1 MB). We leave a certain ratio of the maximum size as a buffer
+     * for missing calculations of certain parameters (e.g. writing a RemoteCollectionItems to the
+     * parcel will write its Id array as well, but that is missing when writing itschild RemoteViews
+     * directly to the parcel as we did in RemoteViewsService)
+     *
+     * @hide
+     */
+    private static final int MAX_SINGLE_PARCEL_SIZE = (int) (1_000_000 * 0.8);
+
+    /**
      * @hide
      */
     public CompletableFuture<Void> collectAllIntents() {
@@ -1260,17 +1275,47 @@
             return mUriToCollectionMapping.get(uri);
         }
 
-        CompletableFuture<Void> collectAllIntentsNoComplete(@NonNull RemoteViews inViews) {
-            CompletableFuture<Void> collectionFuture = CompletableFuture.completedFuture(null);
+        public @NonNull CompletableFuture<Void> collectAllIntentsNoComplete(
+                @NonNull RemoteViews inViews) {
+            SparseArray<Intent> idToIntentMapping = new SparseArray<>();
+            // Collect the number of uinque Intent (which is equal to the number of new connections
+            // to make) for size allocation and exclude certain collections from being written to
+            // the parcel to better estimate the space left for reallocation.
+            collectAllIntentsInternal(inViews, idToIntentMapping);
+
+            // Calculate the individual size here
+            int numOfIntents = idToIntentMapping.size();
+            if (numOfIntents == 0) {
+                Log.e(LOG_TAG, "Possibly notifying updates for nonexistent view Id");
+                return CompletableFuture.completedFuture(null);
+            }
+
+            Parcel sizeTestParcel = Parcel.obtain();
+            // Write self RemoteViews to the parcel, which includes the actions/bitmaps/collection
+            // cache to see how much space is left for the RemoteCollectionItems that are to be
+            // updated.
+            RemoteViews.this.writeToParcel(sizeTestParcel,
+                    /* flags= */ 0,
+                    /* intentsToIgnore= */ idToIntentMapping);
+            int remainingSize = MAX_SINGLE_PARCEL_SIZE - sizeTestParcel.dataSize();
+            sizeTestParcel.recycle();
+
+            int individualSize = remainingSize < 0
+                    ? 0
+                    : remainingSize / numOfIntents;
+
+            return connectAllUniqueIntents(individualSize, idToIntentMapping);
+        }
+
+        private void collectAllIntentsInternal(@NonNull RemoteViews inViews,
+                @NonNull SparseArray<Intent> idToIntentMapping) {
             if (inViews.hasSizedRemoteViews()) {
                 for (RemoteViews remoteViews : inViews.mSizedRemoteViews) {
-                    collectionFuture = CompletableFuture.allOf(collectionFuture,
-                            collectAllIntentsNoComplete(remoteViews));
+                    collectAllIntentsInternal(remoteViews, idToIntentMapping);
                 }
             } else if (inViews.hasLandscapeAndPortraitLayouts()) {
-                collectionFuture = CompletableFuture.allOf(
-                        collectAllIntentsNoComplete(inViews.mLandscape),
-                        collectAllIntentsNoComplete(inViews.mPortrait));
+                collectAllIntentsInternal(inViews.mLandscape, idToIntentMapping);
+                collectAllIntentsInternal(inViews.mPortrait, idToIntentMapping);
             } else if (inViews.mActions != null) {
                 for (Action action : inViews.mActions) {
                     if (action instanceof SetRemoteCollectionItemListAdapterAction rca) {
@@ -1280,13 +1325,16 @@
                         }
 
                         if (rca.mIntentId != -1 && rca.mIsReplacedIntoAction) {
-                            final String uri = mIdToUriMapping.get(rca.mIntentId);
-                            collectionFuture = CompletableFuture.allOf(collectionFuture,
-                                    getItemsFutureFromIntentWithTimeout(rca.mServiceIntent)
-                                            .thenAccept(rc -> {
-                                                rc.setHierarchyRootData(getHierarchyRootData());
-                                                mUriToCollectionMapping.put(uri, rc);
-                                            }));
+                            rca.mIsReplacedIntoAction = false;
+
+                            // Avoid redundant connections for the same intent. Also making sure
+                            // that the number of connections we are making is always equal to the
+                            // nmuber of unique intents that are being used for the updates.
+                            if (idToIntentMapping.contains(rca.mIntentId)) {
+                                continue;
+                            }
+
+                            idToIntentMapping.put(rca.mIntentId, rca.mServiceIntent);
                             rca.mItems = null;
                             continue;
                         }
@@ -1295,7 +1343,7 @@
                         // intents.
                         if (rca.mServiceIntent != null) {
                             final String uri = rca.mServiceIntent.toUri(0);
-                            int index = mIdToUriMapping.indexOfValue(uri);
+                            int index = mIdToUriMapping.indexOfValueByValue(uri);
                             if (index == -1) {
                                 int newIntentId = mIdToUriMapping.size();
                                 rca.mIntentId = newIntentId;
@@ -1305,41 +1353,50 @@
                                 rca.mItems = null;
                                 continue;
                             }
-                            collectionFuture = CompletableFuture.allOf(collectionFuture,
-                                    getItemsFutureFromIntentWithTimeout(rca.mServiceIntent)
-                                            .thenAccept(rc -> {
-                                                rc.setHierarchyRootData(getHierarchyRootData());
-                                                mUriToCollectionMapping.put(uri, rc);
-                                            }));
+
+                            idToIntentMapping.put(rca.mIntentId, rca.mServiceIntent);
                             rca.mItems = null;
                         } else {
                             for (RemoteViews views : rca.mItems.mViews) {
-                                collectionFuture = CompletableFuture.allOf(collectionFuture,
-                                        collectAllIntentsNoComplete(views));
+                                collectAllIntentsInternal(views, idToIntentMapping);
                             }
                         }
                     } else if (action instanceof ViewGroupActionAdd vgaa
                             && vgaa.mNestedViews != null) {
-                        collectionFuture = CompletableFuture.allOf(collectionFuture,
-                                collectAllIntentsNoComplete(vgaa.mNestedViews));
+                        collectAllIntentsInternal(vgaa.mNestedViews, idToIntentMapping);
                     }
                 }
             }
+        }
 
-            return collectionFuture;
+        private @NonNull CompletableFuture<Void> connectAllUniqueIntents(int individualSize,
+                @NonNull SparseArray<Intent> idToIntentMapping) {
+            List<CompletableFuture<Void>> intentFutureList = new ArrayList<>();
+            for (int i = 0; i < idToIntentMapping.size(); i++) {
+                String currentIntentUri = mIdToUriMapping.get(idToIntentMapping.keyAt(i));
+                Intent currentIntent = idToIntentMapping.valueAt(i);
+                intentFutureList.add(getItemsFutureFromIntentWithTimeout(currentIntent,
+                        individualSize)
+                        .thenAccept(items -> {
+                            items.setHierarchyRootData(getHierarchyRootData());
+                            mUriToCollectionMapping.put(currentIntentUri, items);
+                        }));
+            }
+
+            return CompletableFuture.allOf(intentFutureList.toArray(CompletableFuture[]::new));
         }
 
         private static CompletableFuture<RemoteCollectionItems> getItemsFutureFromIntentWithTimeout(
-                Intent intent) {
+                Intent intent, int individualSize) {
             if (intent == null) {
                 Log.e(LOG_TAG, "Null intent received when generating adapter future");
                 return CompletableFuture.completedFuture(new RemoteCollectionItems
-                    .Builder().build());
+                        .Builder().build());
             }
 
             final Context context = ActivityThread.currentApplication();
-            final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>();
 
+            final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>();
             context.bindService(intent, Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE),
                     result.defaultExecutor(), new ServiceConnection() {
                         @Override
@@ -1348,11 +1405,11 @@
                             RemoteCollectionItems items;
                             try {
                                 items = IRemoteViewsFactory.Stub.asInterface(iBinder)
-                                    .getRemoteCollectionItems();
+                                        .getRemoteCollectionItems(individualSize);
                             } catch (RemoteException re) {
                                 items = new RemoteCollectionItems.Builder().build();
-                                Log.e(LOG_TAG, "Error getting collection items from the factory",
-                                        re);
+                                Log.e(LOG_TAG, "Error getting collection items from the"
+                                        + " factory", re);
                             } finally {
                                 context.unbindService(this);
                             }
@@ -1371,10 +1428,17 @@
             return result;
         }
 
-        public void writeToParcel(Parcel out, int flags) {
+        public void writeToParcel(Parcel out, int flags,
+                @Nullable SparseArray<Intent> intentsToIgnore) {
             out.writeInt(mIdToUriMapping.size());
             for (int i = 0; i < mIdToUriMapping.size(); i++) {
-                out.writeInt(mIdToUriMapping.keyAt(i));
+                int currentIntentId = mIdToUriMapping.keyAt(i);
+                if (intentsToIgnore != null && intentsToIgnore.contains(currentIntentId)) {
+                    // Skip writing collections that are to be updated in the following steps to
+                    // better estimate the RemoteViews size.
+                    continue;
+                }
+                out.writeInt(currentIntentId);
                 String intentUri = mIdToUriMapping.valueAt(i);
                 out.writeString8(intentUri);
                 mUriToCollectionMapping.get(intentUri).writeToParcel(out, flags, true);
@@ -3874,7 +3938,7 @@
         }
     }
 
-    private static class SetDrawInstructionAction extends Action {
+    private class SetDrawInstructionAction extends Action {
 
         @Nullable
         private final DrawInstructions mInstructions;
@@ -3909,6 +3973,15 @@
                 }
                 try (ByteArrayInputStream is = new ByteArrayInputStream(bytes.get(0))) {
                     player.setDocument(new RemoteComposeDocument(is));
+                    player.addClickListener((viewId, metadata) -> {
+                        mActions.forEach(action -> {
+                            if (viewId == action.mViewId
+                                    && action instanceof SetOnClickResponse setOnClickResponse) {
+                                setOnClickResponse.mResponse.handleViewInteraction(
+                                        player, params.handler);
+                            }
+                        });
+                    });
                 } catch (IOException e) {
                     Log.e(LOG_TAG, "Failed to render draw instructions", e);
                 }
@@ -4226,7 +4299,7 @@
         }
 
         if (mode == MODE_NORMAL) {
-            mApplication = ApplicationInfo.CREATOR.createFromParcel(parcel);
+            mApplication = parcel.readTypedObject(ApplicationInfo.CREATOR);
             mIdealSize = parcel.readInt() == 0 ? null : SizeF.CREATOR.createFromParcel(parcel);
             mLayoutId = parcel.readInt();
             mViewId = parcel.readInt();
@@ -6051,16 +6124,6 @@
         RemoteViews rvToApply = getRemoteViewsToApply(context, size);
         View result = inflateView(context, rvToApply, directParent,
                 params.applyThemeResId, params.colorResources);
-        if (result instanceof RemoteComposePlayer player) {
-            player.addClickListener((viewId, metadata) -> {
-                mActions.forEach(action -> {
-                    if (viewId == action.mViewId
-                            && action instanceof SetOnClickResponse setOnClickResponse) {
-                        setOnClickResponse.mResponse.handleViewInteraction(player, params.handler);
-                    }
-                });
-            });
-        }
         rvToApply.performApply(result, rootParent, params);
         return result;
     }
@@ -6725,7 +6788,13 @@
         return 0;
     }
 
+    @Override
     public void writeToParcel(Parcel dest, int flags) {
+        writeToParcel(dest, flags, /* intentsToIgnore= */ null);
+    }
+
+    private void writeToParcel(Parcel dest, int flags,
+            @Nullable SparseArray<Intent> intentsToIgnore) {
         boolean prevSquashingAllowed = dest.allowSquashing();
 
         if (!hasMultipleLayouts()) {
@@ -6734,9 +6803,9 @@
             // is shared by all children.
             if (mIsRoot) {
                 mBitmapCache.writeBitmapsToParcel(dest, flags);
-                mCollectionCache.writeToParcel(dest, flags);
+                mCollectionCache.writeToParcel(dest, flags, intentsToIgnore);
             }
-            mApplication.writeToParcel(dest, flags);
+            dest.writeTypedObject(mApplication, flags);
             if (mIsRoot || mIdealSize == null) {
                 dest.writeInt(0);
             } else {
@@ -6751,7 +6820,7 @@
             dest.writeInt(MODE_HAS_SIZED_REMOTEVIEWS);
             if (mIsRoot) {
                 mBitmapCache.writeBitmapsToParcel(dest, flags);
-                mCollectionCache.writeToParcel(dest, flags);
+                mCollectionCache.writeToParcel(dest, flags, intentsToIgnore);
             }
             dest.writeInt(mSizedRemoteViews.size());
             for (RemoteViews view : mSizedRemoteViews) {
@@ -6763,7 +6832,7 @@
             // is shared by all children.
             if (mIsRoot) {
                 mBitmapCache.writeBitmapsToParcel(dest, flags);
-                mCollectionCache.writeToParcel(dest, flags);
+                mCollectionCache.writeToParcel(dest, flags, intentsToIgnore);
             }
             mLandscape.writeToParcel(dest, flags);
             // Both RemoteViews already share the same package and user
@@ -6824,7 +6893,8 @@
      * @hide
      */
     public boolean hasSameAppInfo(ApplicationInfo info) {
-        return mApplication.packageName.equals(info.packageName) && mApplication.uid == info.uid;
+        return mApplication == null || mApplication.packageName.equals(info.packageName)
+                && mApplication.uid == info.uid;
     }
 
     /**
@@ -7603,8 +7673,7 @@
             byte[] instruction;
             final List<byte[]> instructions = new ArrayList<>(size);
             for (int i = 0; i < size; i++) {
-                instruction = new byte[in.readInt()];
-                in.readByteArray(instruction);
+                instruction = in.readBlob();
                 instructions.add(instruction);
             }
             return new DrawInstructions(instructions);
@@ -7619,8 +7688,7 @@
             final List<byte[]> instructions = drawInstructions.mInstructions;
             dest.writeInt(instructions.size());
             for (byte[] instruction : instructions) {
-                dest.writeInt(instruction.length);
-                dest.writeByteArray(instruction);
+                dest.writeBlob(instruction);
             }
         }
 
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
index a250a86..d4a5bbd 100644
--- a/core/java/android/widget/RemoteViewsService.java
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -19,6 +19,7 @@
 import android.app.Service;
 import android.content.Intent;
 import android.os.IBinder;
+import android.os.Parcel;
 
 import com.android.internal.widget.IRemoteViewsFactory;
 
@@ -43,13 +44,6 @@
     private static final Object sLock = new Object();
 
     /**
-     * Used for determining the maximum number of entries to retrieve from RemoteViewsFactory
-     *
-     * @hide
-     */
-    private static final int MAX_NUM_ENTRY = 10;
-
-    /**
      * An interface for an adapter between a remote collection view (ListView, GridView, etc) and
      * the underlying data for that view.  The implementor is responsible for making a RemoteView
      * for each item in the data set. This interface is a thin wrapper around {@link Adapter}.
@@ -235,9 +229,10 @@
         }
 
         @Override
-        public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() {
+        public RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize) {
             RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems
                     .Builder().build();
+            Parcel capSizeTestParcel = Parcel.obtain();
 
             try {
                 RemoteViews.RemoteCollectionItems.Builder itemsBuilder =
@@ -245,15 +240,25 @@
                 mFactory.onDataSetChanged();
 
                 itemsBuilder.setHasStableIds(mFactory.hasStableIds());
-                final int numOfEntries = Math.min(mFactory.getCount(), MAX_NUM_ENTRY);
+                final int numOfEntries = mFactory.getCount();
+
                 for (int i = 0; i < numOfEntries; i++) {
-                    itemsBuilder.addItem(mFactory.getItemId(i), mFactory.getViewAt(i));
+                    final long currentItemId = mFactory.getItemId(i);
+                    final RemoteViews currentView = mFactory.getViewAt(i);
+                    currentView.writeToParcel(capSizeTestParcel, 0);
+                    if (capSizeTestParcel.dataSize() > capSize) {
+                        break;
+                    }
+                    itemsBuilder.addItem(currentItemId, currentView);
                 }
 
                 items = itemsBuilder.build();
             } catch (Exception ex) {
                 Thread t = Thread.currentThread();
                 Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+            } finally {
+                // Recycle the parcel
+                capSizeTestParcel.recycle();
             }
             return items;
         }
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index a1ebde7..42c2d80 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -16,6 +16,8 @@
 
 package android.widget;
 
+import static android.view.flags.Flags.viewVelocityApi;
+
 import android.annotation.ColorInt;
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -726,6 +728,12 @@
                  * isFinished() is correct.
                 */
                 mScroller.computeScrollOffset();
+
+                // For variable refresh rate project to track the current velocity of this View
+                if (viewVelocityApi()) {
+                    setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+                }
+
                 mIsBeingDragged = !mScroller.isFinished() || !mEdgeGlowBottom.isFinished()
                     || !mEdgeGlowTop.isFinished();
                 // Catch the edge effect if it is active.
@@ -1573,6 +1581,11 @@
                 // Keep on drawing until the animation has finished.
                 postInvalidateOnAnimation();
             }
+
+            // For variable refresh rate project to track the current velocity of this View
+            if (viewVelocityApi()) {
+                setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+            }
         } else {
             if (mFlingStrictSpan != null) {
                 mFlingStrictSpan.finish();
@@ -1884,6 +1897,10 @@
             mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
                     Math.max(0, bottom - height), 0, height/2);
 
+            // For variable refresh rate project to track the current velocity of this View
+            if (viewVelocityApi()) {
+                setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+            }
             if (mFlingStrictSpan == null) {
                 mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling");
             }
diff --git a/core/java/android/widget/flags/notification_widget_flags.aconfig b/core/java/android/widget/flags/notification_widget_flags.aconfig
index bfe3d05..e60fa15 100644
--- a/core/java/android/widget/flags/notification_widget_flags.aconfig
+++ b/core/java/android/widget/flags/notification_widget_flags.aconfig
@@ -15,4 +15,14 @@
   metadata {
     purpose: PURPOSE_BUGFIX
   }
+}
+
+flag {
+  name: "conversation_style_set_avatar_async"
+  namespace: "systemui"
+  description: "Offloads conversation avatar drawable loading to the background thread"
+  bug: "305540309"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
 }
\ No newline at end of file
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index c76c7a4..e5658e6 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -36,6 +36,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
 import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
 import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 
@@ -424,9 +425,12 @@
         layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES)
                 | FLAG_NOT_FOCUSABLE
                 | FLAG_NOT_TOUCHABLE;
-        // Setting as trusted overlay to let touches pass through. This is safe because this
-        // window is controlled by the system.
-        layoutParams.privateFlags = (windowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS)
+        layoutParams.privateFlags =
+                (windowPrivateFlags
+                        & (PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS
+                        | PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED))
+                // Setting as trusted overlay to let touches pass through. This is safe because this
+                // window is controlled by the system.
                 | PRIVATE_FLAG_TRUSTED_OVERLAY;
         layoutParams.token = token;
         layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
@@ -475,14 +479,16 @@
             mStatusBarColor = DecorView.calculateBarColor(windowFlags, FLAG_TRANSLUCENT_STATUS,
                     semiTransparent, taskDescription.getStatusBarColor(), appearance,
                     APPEARANCE_LIGHT_STATUS_BARS,
-                    taskDescription.getEnsureStatusBarContrastWhenTransparent());
+                    taskDescription.getEnsureStatusBarContrastWhenTransparent(),
+                    false /* movesBarColorToScrim */);
             mNavigationBarColor = DecorView.calculateBarColor(windowFlags,
                     FLAG_TRANSLUCENT_NAVIGATION, semiTransparent,
                     taskDescription.getNavigationBarColor(), appearance,
                     APPEARANCE_LIGHT_NAVIGATION_BARS,
                     taskDescription.getEnsureNavigationBarContrastWhenTransparent()
                             && context.getResources().getBoolean(
-                            R.bool.config_navBarNeedsScrim));
+                            R.bool.config_navBarNeedsScrim),
+                    (windowPrivateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0);
             mStatusBarPaint.setColor(mStatusBarColor);
             mNavigationBarPaint.setColor(mNavigationBarColor);
             mRequestedVisibleTypes = requestedVisibleTypes;
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 15b9b78..3685bba 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -1021,6 +1021,10 @@
                 sb.append(" component=");
                 sb.append(mActivityComponent.flattenToShortString());
             }
+            if (mTaskInfo != null) {
+                sb.append(" taskParent=");
+                sb.append(mTaskInfo.parentTaskId);
+            }
             sb.append('}');
             return sb.toString();
         }
diff --git a/core/java/android/window/flags/OWNERS b/core/java/android/window/flags/OWNERS
index 3fa3760..fd73d35 100644
--- a/core/java/android/window/flags/OWNERS
+++ b/core/java/android/window/flags/OWNERS
@@ -1,2 +1,3 @@
 per-file responsible_apis.aconfig = file:/BAL_OWNERS
 per-file large_screen_experiences_app_compat.aconfig = file:/LSE_APP_COMPAT_OWNERS
+per-file accessibility.aconfig = file:/core/java/android/view/accessibility/OWNERS
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index 82067de..254f4f7 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -77,3 +77,10 @@
   bug: "309593314"
   is_fixed_read_only: true
 }
+
+flag {
+  name: "letterbox_background_wallpaper"
+  namespace: "large_screen_experiences_app_compat"
+  description: "Whether the blurred letterbox wallpaper background is enabled by default"
+  bug: "297195682"
+}
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index 51890ec..94c72c6 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -46,5 +46,5 @@
     name: "bal_respect_app_switch_state_when_check_bound_by_foreground_uid"
     namespace: "responsible_apis"
     description: "Prevent BAL based on it is bound by foreground Uid but the app switch is stopped."
-    bug: "171459802"
+    bug: "283801068"
 }
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index ce74848..82e613e 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -77,4 +77,12 @@
     description: "Properties to allow apps and activities to opt-in to cover display rendering"
     bug: "312530526"
     is_fixed_read_only: true
+}
+
+flag {
+    namespace: "windowing_sdk"
+    name: "enable_wm_extensions_for_all_flag"
+    description: "Whether to enable WM Extensions for all devices"
+    bug: "306666082"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
index 7ec8838..c08968d 100644
--- a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
+++ b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
@@ -16,10 +16,23 @@
 
 package com.android.internal.accessibility.common;
 
+import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.FONT_SIZE_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.FONT_SIZE_TILE_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.ONE_HANDED_TILE_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_TILE_SERVICE_COMPONENT_NAME;
+
 import android.annotation.IntDef;
+import android.content.ComponentName;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
 
 /**
  * Collection of common constants for accessibility shortcut.
@@ -44,6 +57,10 @@
      * choose accessibility shortcut as preferred shortcut.
      * {@code TRIPLETAP} for displaying specifying magnification to be toggled via quickly
      * tapping screen 3 times as preferred shortcut.
+     * {@code TWOFINGER_DOUBLETAP} for displaying specifying magnification to be toggled via
+     * quickly tapping screen 2 times with two fingers as preferred shortcut.
+     * {@code QUICK_SETTINGS} for displaying specifying the accessibility services or features which
+     * choose Quick Settings as preferred shortcut.
      */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({
@@ -51,12 +68,18 @@
             UserShortcutType.SOFTWARE,
             UserShortcutType.HARDWARE,
             UserShortcutType.TRIPLETAP,
+            UserShortcutType.TWOFINGER_DOUBLETAP,
+            UserShortcutType.QUICK_SETTINGS,
     })
     public @interface UserShortcutType {
         int DEFAULT = 0;
-        int SOFTWARE = 1; // 1 << 0
-        int HARDWARE = 2; // 1 << 1
-        int TRIPLETAP = 4; // 1 << 2
+        // LINT.IfChange(shortcut_type_intdef)
+        int SOFTWARE = 1 << 0;
+        int HARDWARE = 1 << 1;
+        int TRIPLETAP = 1 << 2;
+        int TWOFINGER_DOUBLETAP = 1 << 3;
+        int QUICK_SETTINGS = 1 << 4;
+        // LINT.ThenChange(:shortcut_type_array)
     }
 
     /**
@@ -64,9 +87,13 @@
      * non-default IntDef types.
      */
     public static final int[] USER_SHORTCUT_TYPES = {
+            // LINT.IfChange(shortcut_type_array)
             UserShortcutType.SOFTWARE,
             UserShortcutType.HARDWARE,
-            UserShortcutType.TRIPLETAP
+            UserShortcutType.TRIPLETAP,
+            UserShortcutType.TWOFINGER_DOUBLETAP,
+            UserShortcutType.QUICK_SETTINGS,
+            // LINT.ThenChange(:shortcut_type_intdef)
     };
 
 
@@ -109,4 +136,30 @@
         int LAUNCH = 0;
         int EDIT = 1;
     }
+
+    /**
+     * Annotation for different FAB shortcut type's menu size
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            FloatingMenuSize.UNKNOWN,
+            FloatingMenuSize.SMALL,
+            FloatingMenuSize.LARGE,
+    })
+    public @interface FloatingMenuSize {
+        int UNKNOWN = -1;
+        int SMALL = 0;
+        int LARGE = 1;
+    }
+
+    /**
+     * A map of a11y feature to its qs tile component
+     */
+    public static final Map<ComponentName, ComponentName> A11Y_FEATURE_TO_FRAMEWORK_TILE = Map.of(
+            COLOR_INVERSION_COMPONENT_NAME, COLOR_INVERSION_TILE_COMPONENT_NAME,
+            DALTONIZER_COMPONENT_NAME, DALTONIZER_TILE_COMPONENT_NAME,
+            ONE_HANDED_COMPONENT_NAME, ONE_HANDED_TILE_COMPONENT_NAME,
+            REDUCE_BRIGHT_COLORS_COMPONENT_NAME, REDUCE_BRIGHT_COLORS_TILE_SERVICE_COMPONENT_NAME,
+            FONT_SIZE_COMPONENT_NAME, FONT_SIZE_TILE_COMPONENT_NAME
+    );
 }
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
index 4f9fc39..e8472d4 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
@@ -70,6 +70,13 @@
     public @interface A11yTextChangeType {
     }
 
+    /** Denotes the accessibility enabled status */
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface State {
+        int OFF = 0;
+        int ON = 1;
+    }
+
     /** Specifies no content has been changed for accessibility. */
     public static final int NONE = 0;
     /** Specifies some readable sequence has been changed. */
diff --git a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
index 3fd3030..f9c4d37 100644
--- a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
+++ b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
@@ -53,10 +53,13 @@
      * Opts in component id into colon-separated {@link UserShortcutType}
      * key's string from Settings.
      *
-     * @param context The current context.
+     * @param context      The current context.
      * @param shortcutType The preferred shortcut type user selected.
-     * @param componentId The component id that need to be opted in Settings.
+     * @param componentId  The component id that need to be opted in Settings.
+     * @deprecated Use
+     * {@link AccessibilityManager#enableShortcutsForTargets(boolean, int, Set, int)}
      */
+    @Deprecated
     public static void optInValueToSettings(Context context, @UserShortcutType int shortcutType,
             @NonNull String componentId) {
         final StringJoiner joiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR));
@@ -83,7 +86,11 @@
      * @param context The current context.
      * @param shortcutType The preferred shortcut type user selected.
      * @param componentId The component id that need to be opted out of Settings.
+     *
+     * @deprecated Use
+     * {@link AccessibilityManager#enableShortcutForTargets(boolean, int, Set, int)}
      */
+    @Deprecated
     public static void optOutValueFromSettings(
             Context context, @UserShortcutType int shortcutType, @NonNull String componentId) {
         final StringJoiner joiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR));
@@ -166,6 +173,10 @@
                 return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
             case UserShortcutType.TRIPLETAP:
                 return Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED;
+            case UserShortcutType.TWOFINGER_DOUBLETAP:
+                return Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED;
+            case UserShortcutType.QUICK_SETTINGS:
+                return Settings.Secure.ACCESSIBILITY_QS_TARGETS;
             default:
                 throw new IllegalArgumentException(
                         "Unsupported user shortcut type: " + type);
@@ -252,10 +263,13 @@
      * If you just want to know the current state, you can use
      * {@link AccessibilityManager#getAccessibilityShortcutTargets(int)}
      */
+    @NonNull
     public static Set<String> getShortcutTargetsFromSettings(
             Context context, @UserShortcutType int shortcutType, int userId) {
         final String targetKey = convertToKey(shortcutType);
-        if (Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED.equals(targetKey)) {
+        if (Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED.equals(targetKey)
+                || Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED
+                .equals(targetKey)) {
             boolean magnificationEnabled = Settings.Secure.getIntForUser(
                     context.getContentResolver(), targetKey, /* def= */ 0, userId) == 1;
             return magnificationEnabled ? Set.of(MAGNIFICATION_CONTROLLER_NAME)
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 86d3037..29669d3 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -96,6 +96,7 @@
 import android.provider.OpenableColumns;
 import android.provider.Settings;
 import android.service.chooser.ChooserTarget;
+import android.service.chooser.Flags;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.HashedStringCache;
@@ -2543,6 +2544,9 @@
 
         @Override
         public boolean isComponentPinned(ComponentName name) {
+            if (Flags.legacyChooserPinningRemoval()) {
+                return false;
+            }
             return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
         }
 
@@ -3147,6 +3151,10 @@
     }
 
     private boolean shouldShowTargetDetails(TargetInfo ti) {
+        if (Flags.legacyChooserPinningRemoval()) {
+            // Never show the long press menu if we've removed pinning.
+            return false;
+        }
         ComponentName nearbyShare = getNearbySharingComponent();
         //  Suppress target details for nearby share to hide pin/unpin action
         boolean isNearbyShare = nearbyShare != null && nearbyShare.equals(
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 65b5979..c769518 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -172,6 +172,20 @@
         newIntent.prepareToLeaveUser(callingUserId);
         final CompletableFuture<ResolveInfo> targetResolveInfoFuture =
                 mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId);
+
+        if (isPrivateProfile(callingUserId)) {
+            buildAndExecuteForPrivateProfile(intentReceived, className, newIntent, callingUserId,
+                    targetUserId);
+        } else {
+            buildAndExecute(targetResolveInfoFuture, intentReceived, className, newIntent,
+                    callingUserId,
+                    targetUserId, userMessage, managedProfile);
+        }
+    }
+
+    private void buildAndExecute(CompletableFuture<ResolveInfo> targetResolveInfoFuture,
+            Intent intentReceived, String className, Intent newIntent, int callingUserId,
+            int targetUserId, String userMessage, UserInfo managedProfile) {
         targetResolveInfoFuture
                 .thenApplyAsync(targetResolveInfo -> {
                     if (isResolverActivityResolveInfo(targetResolveInfo)) {
@@ -195,6 +209,23 @@
                 }, getApplicationContext().getMainExecutor());
     }
 
+    private void buildAndExecuteForPrivateProfile(
+            Intent intentReceived, String className, Intent newIntent, int callingUserId,
+            int targetUserId) {
+        final CompletableFuture<ResolveInfo> targetResolveInfoFuture =
+                mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId);
+        targetResolveInfoFuture
+                .thenAcceptAsync(targetResolveInfo -> {
+                    if (isResolverActivityResolveInfo(targetResolveInfo)) {
+                        launchResolverActivityWithCorrectTab(intentReceived, className, newIntent,
+                                callingUserId, targetUserId);
+                    } else {
+                        maybeShowUserConsentMiniResolverPrivate(targetResolveInfo, newIntent,
+                                targetUserId);
+                    }
+                }, getApplicationContext().getMainExecutor());
+    }
+
     private void maybeShowUserConsentMiniResolver(
             ResolveInfo target, Intent launchIntent, UserInfo managedProfile) {
         if (target == null || isIntentForwarderResolveInfo(target) || !isDeviceProvisioned()) {
@@ -233,24 +264,70 @@
                 "Showing user consent for redirection into the managed profile for intent [%s] and "
                         + " calling package [%s]",
                 launchIntent, callingPackage));
+        PackageManager packageManagerForTargetUser =
+                createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0)
+                        .getPackageManager();
+        buildMiniResolver(target, launchIntent, targetUserId,
+                getOpenInWorkMessage(launchIntent, target.loadLabel(packageManagerForTargetUser)),
+                packageManagerForTargetUser);
+
+
+        View telephonyInfo = findViewById(R.id.miniresolver_info_section);
+
+        // Additional information section is work telephony specific. Therefore, it is only shown
+        // for telephony related intents, when all sim subscriptions are in the work profile.
+        if ((isDialerIntent(launchIntent) || isTextMessageIntent(launchIntent))
+                && devicePolicyManager.getManagedSubscriptionsPolicy().getPolicyType()
+                == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
+            telephonyInfo.setVisibility(View.VISIBLE);
+            ((TextView) findViewById(R.id.miniresolver_info_section_text))
+                    .setText(getWorkTelephonyInfoSectionMessage(launchIntent));
+        } else {
+            telephonyInfo.setVisibility(View.GONE);
+        }
+    }
+
+    private void maybeShowUserConsentMiniResolverPrivate(
+            ResolveInfo target, Intent launchIntent, int targetUserId) {
+        if (target == null || isIntentForwarderResolveInfo(target)) {
+            finish();
+            return;
+        }
+
+        String callingPackage = getCallingPackage();
+
+        Log.i("IntentForwarderActivity", String.format(
+                "Showing user consent for redirection into the main profile for intent [%s] and "
+                        + " calling package [%s]",
+                launchIntent, callingPackage));
+        PackageManager packageManagerForTargetUser =
+                createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0)
+                        .getPackageManager();
+        buildMiniResolver(target, launchIntent, targetUserId,
+                getString(R.string.miniresolver_open_in_personal,
+                        target.loadLabel(packageManagerForTargetUser)),
+                packageManagerForTargetUser);
+
+        View telephonyInfo = findViewById(R.id.miniresolver_info_section);
+        telephonyInfo.setVisibility(View.GONE);
+    }
+
+    private void buildMiniResolver(ResolveInfo target, Intent launchIntent, int targetUserId,
+            String resolverTitle, PackageManager pmForTargetUser) {
         int layoutId = R.layout.miniresolver;
         setContentView(layoutId);
 
         findViewById(R.id.title_container).setElevation(0);
 
-        PackageManager packageManagerForTargetUser =
-                createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0)
-                        .getPackageManager();
-
         ImageView icon = findViewById(R.id.icon);
         icon.setImageDrawable(
-                getAppIcon(target, launchIntent, targetUserId, packageManagerForTargetUser));
+                getAppIcon(target, launchIntent, targetUserId, pmForTargetUser));
 
         View buttonContainer = findViewById(R.id.button_bar_container);
         buttonContainer.setPadding(0, 0, 0, buttonContainer.getPaddingBottom());
 
         ((TextView) findViewById(R.id.open_cross_profile)).setText(
-                getOpenInWorkMessage(launchIntent, target.loadLabel(packageManagerForTargetUser)));
+                resolverTitle);
 
         // The mini-resolver's negative button is reused in this flow to cancel the intent
         ((Button) findViewById(R.id.use_same_profile_browser)).setText(R.string.cancel);
@@ -269,21 +346,6 @@
                     targetUserId);
             finish();
         });
-
-
-        View telephonyInfo = findViewById(R.id.miniresolver_info_section);
-
-        // Additional information section is work telephony specific. Therefore, it is only shown
-        // for telephony related intents, when all sim subscriptions are in the work profile.
-        if ((isDialerIntent(launchIntent) || isTextMessageIntent(launchIntent))
-                && devicePolicyManager.getManagedSubscriptionsPolicy().getPolicyType()
-                    == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
-            telephonyInfo.setVisibility(View.VISIBLE);
-            ((TextView) findViewById(R.id.miniresolver_info_section_text))
-                    .setText(getWorkTelephonyInfoSectionMessage(launchIntent));
-        } else {
-            telephonyInfo.setVisibility(View.GONE);
-        }
     }
 
     private Drawable getAppIcon(
@@ -548,6 +610,18 @@
     }
 
     /**
+     * Returns the private profile for this device or null if there is no private profile.
+     */
+    @Nullable
+    private UserInfo getPrivateProfile() {
+        List<UserInfo> relatedUsers = mInjector.getUserManager().getProfiles(UserHandle.myUserId());
+        for (UserInfo userInfo : relatedUsers) {
+            if (userInfo.isPrivateProfile()) return userInfo;
+        }
+        return null;
+    }
+
+    /**
      * Returns the userId of the profile parent or UserHandle.USER_NULL if there is
      * no parent.
      */
@@ -577,6 +651,17 @@
         return mMetricsLogger;
     }
 
+    private boolean isPrivateProfile(int userId) {
+        UserInfo privateProfile = getPrivateProfile();
+        return privateSpaceFlagsEnabled() && privateProfile != null
+                && privateProfile.id == userId;
+    }
+
+    private boolean privateSpaceFlagsEnabled() {
+        return android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enablePrivateSpaceIntentRedirection();
+    }
+
     @VisibleForTesting
     protected Injector createInjector() {
         return new InjectorImpl();
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 7dcbbea..78f06b6 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -2655,7 +2655,8 @@
 
     private boolean privateSpaceEnabled() {
         return mIsIntentPicker && android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.allowResolverSheetForPrivateSpace();
+                && android.multiuser.Flags.allowResolverSheetForPrivateSpace()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures();
     }
 
     /**
diff --git a/core/java/com/android/internal/app/SetScreenLockDialogActivity.java b/core/java/com/android/internal/app/SetScreenLockDialogActivity.java
index 93fe37c..360fcaf 100644
--- a/core/java/com/android/internal/app/SetScreenLockDialogActivity.java
+++ b/core/java/com/android/internal/app/SetScreenLockDialogActivity.java
@@ -75,7 +75,8 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         if (!(android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.showSetScreenLockDialog())) {
+                && android.multiuser.Flags.showSetScreenLockDialog()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures())) {
             finish();
             return;
         }
diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
index 4ef0a1b..97f8084 100644
--- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java
+++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
@@ -77,6 +77,7 @@
         }
 
         if (android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()
                 && !userManager.isManagedProfile(mUserId)) {
             Log.e(TAG, "Unlaunchable activity for target package " + targetPackageName
                     + " called for a non-managed-profile " + mUserId);
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index 85bdbb9..e55cdef 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -80,11 +80,10 @@
             in Bundle extras, in IntentSender resultIntent);
     boolean isRequestPinAppWidgetSupported();
     oneway void noteAppWidgetTapped(in String callingPackage, in int appWidgetId);
-    void setWidgetPreview(in ComponentName providerComponent, in int widgetCategories,
+    boolean setWidgetPreview(in ComponentName providerComponent, in int widgetCategories,
             in RemoteViews preview);
     @nullable RemoteViews getWidgetPreview(in String callingPackage,
             in ComponentName providerComponent, in int profileId, in int widgetCategory);
     void removeWidgetPreview(in ComponentName providerComponent, in int widgetCategories);
-
 }
 
diff --git a/core/java/com/android/internal/compat/Android.bp b/core/java/com/android/internal/compat/Android.bp
new file mode 100644
index 0000000..9ff05a6
--- /dev/null
+++ b/core/java/com/android/internal/compat/Android.bp
@@ -0,0 +1,7 @@
+aconfig_declarations {
+    name: "compat_logging_flags",
+    package: "com.android.internal.compat.flags",
+    srcs: [
+        "compat_logging_flags.aconfig",
+    ],
+}
diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java
index b9d3df6..6ff546f 100644
--- a/core/java/com/android/internal/compat/ChangeReporter.java
+++ b/core/java/com/android/internal/compat/ChangeReporter.java
@@ -24,6 +24,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.flags.Flags;
 import com.android.internal.util.FrameworkStatsLog;
 
 import java.lang.annotation.Retention;
@@ -40,7 +41,7 @@
  * @hide
  */
 public final class ChangeReporter {
-    private static final String TAG = "CompatibilityChangeReporter";
+    private static final String TAG = "CompatChangeReporter";
     private int mSource;
 
     private static final class ChangeReport {
@@ -84,19 +85,34 @@
      * Report the change to stats log and to the debug log if the change was not previously
      * logged already.
      *
+     * @param uid             affected by the change
+     * @param changeId        the reported change id
+     * @param state           of the reported change - enabled/disabled/only logged
+     * @param isLoggableBySdk whether debug logging is allowed for this change based on target
+     *                        SDK version. This is combined with other logic to determine whether to
+     *                        actually log. If the sdk version does not matter, should be true.
+     */
+    public void reportChange(int uid, long changeId, int state, boolean isLoggableBySdk) {
+        if (shouldWriteToStatsLog(uid, changeId, state)) {
+            FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid,
+                    changeId, state, mSource);
+        }
+        if (shouldWriteToDebug(uid, changeId, state, isLoggableBySdk)) {
+            debugLog(uid, changeId, state);
+        }
+        markAsReported(uid, new ChangeReport(changeId, state));
+    }
+
+    /**
+     * Report the change to stats log and to the debug log if the change was not previously
+     * logged already.
+     *
      * @param uid      affected by the change
      * @param changeId the reported change id
      * @param state    of the reported change - enabled/disabled/only logged
      */
     public void reportChange(int uid, long changeId, int state) {
-        if (shouldWriteToStatsLog(uid, changeId, state)) {
-            FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid,
-                    changeId, state, mSource);
-        }
-        if (shouldWriteToDebug(uid, changeId, state)) {
-            debugLog(uid, changeId, state);
-        }
-        markAsReported(uid, new ChangeReport(changeId, state));
+        reportChange(uid, changeId, state, true);
     }
 
     /**
@@ -130,14 +146,43 @@
     /**
      * Returns whether the next report should be logged to logcat.
      *
-     * @param uid      affected by the change
-     * @param changeId the reported change id
-     * @param state    of the reported change - enabled/disabled/only logged
+     * @param uid             affected by the change
+     * @param changeId        the reported change id
+     * @param state           of the reported change - enabled/disabled/only logged
+     * @param isLoggableBySdk whether debug logging is allowed for this change based on target
+     *                        SDK version. This is combined with other logic to determine whether to
+     *                        actually log. If the sdk version does not matter, should be true.
+     * @return true if the report should be logged
+     */
+    @VisibleForTesting
+    public boolean shouldWriteToDebug(
+            int uid, long changeId, int state, boolean isLoggableBySdk) {
+        // If log all bit is on, always return true.
+        if (mDebugLogAll) return true;
+        // If the change has already been reported, do not write.
+        if (isAlreadyReported(uid, new ChangeReport(changeId, state))) return false;
+
+        // If the flag is turned off or the TAG's logging is forced to debug level with
+        // `adb setprop log.tag.CompatChangeReporter=DEBUG`, write to debug since the above checks
+        // have already passed.
+        boolean skipLoggingFlag = Flags.skipOldAndDisabledCompatLogging();
+        if (!skipLoggingFlag || Log.isLoggable(TAG, Log.DEBUG)) return true;
+
+        // Log if the change is enabled and targets the latest sdk version.
+        return isLoggableBySdk && state != STATE_DISABLED;
+    }
+
+    /**
+     * Returns whether the next report should be logged to logcat.
+     *
+     * @param uid         affected by the change
+     * @param changeId    the reported change id
+     * @param state       of the reported change - enabled/disabled/only logged
      * @return true if the report should be logged
      */
     @VisibleForTesting
     public boolean shouldWriteToDebug(int uid, long changeId, int state) {
-        return mDebugLogAll || !isAlreadyReported(uid, new ChangeReport(changeId, state));
+        return shouldWriteToDebug(uid, changeId, state, true);
     }
 
     private boolean isAlreadyReported(int uid, ChangeReport report) {
diff --git a/core/java/com/android/internal/compat/compat_logging_flags.aconfig b/core/java/com/android/internal/compat/compat_logging_flags.aconfig
new file mode 100644
index 0000000..fab3856
--- /dev/null
+++ b/core/java/com/android/internal/compat/compat_logging_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.internal.compat.flags"
+
+flag {
+    name: "skip_old_and_disabled_compat_logging"
+    namespace: "platform_compat"
+    description: "Feature flag for skipping debug logging for changes that do not target the latest sdk or are disabled"
+    bug: "323949942"
+    is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index bd806bf..91678c7 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -560,6 +560,19 @@
      */
     public static final String CURSOR_HOVER_STATES_ENABLED = "cursor_hover_states_enabled";
 
+
+    /*
+     * (long) The reset interval for generated preview API calls.
+     */
+    public static final String GENERATED_PREVIEW_API_RESET_INTERVAL_MS =
+            "generated_preview_api_reset_interval_ms";
+
+    /*
+     * (int) The max number of generated preview API calls per reset interval.
+     */
+    public static final String GENERATED_PREVIEW_API_MAX_CALLS_PER_INTERVAL =
+            "generated_preview_api_max_calls_per_interval";
+
     private SystemUiDeviceConfigFlags() {
     }
 }
diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
index 3020d77..0e4f04f 100644
--- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java
+++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
@@ -17,8 +17,6 @@
 package com.android.internal.inputmethod;
 
 import static android.view.inputmethod.InputConnectionProto.CURSOR_CAPS_MODE;
-import static android.view.inputmethod.InputConnectionProto.EDITABLE_TEXT;
-import static android.view.inputmethod.InputConnectionProto.SELECTED_TEXT;
 import static android.view.inputmethod.InputConnectionProto.SELECTED_TEXT_END;
 import static android.view.inputmethod.InputConnectionProto.SELECTED_TEXT_START;
 
@@ -335,16 +333,6 @@
     @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
-        CharSequence editableText = mTextView.getText();
-        CharSequence selectedText = getSelectedText(0 /* flags */);
-        if (InputConnectionProtoDumper.DUMP_TEXT) {
-            if (editableText != null) {
-                proto.write(EDITABLE_TEXT, editableText.toString());
-            }
-            if (selectedText != null) {
-                proto.write(SELECTED_TEXT, selectedText.toString());
-            }
-        }
         final Editable content = getEditable();
         if (content != null) {
             int start = Selection.getSelectionStart(content);
diff --git a/core/java/com/android/internal/inputmethod/IImeTracker.aidl b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
index 2759043..b45bc1c 100644
--- a/core/java/com/android/internal/inputmethod/IImeTracker.aidl
+++ b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
@@ -25,28 +25,19 @@
 interface IImeTracker {
 
     /**
-     * Called when an IME show request is created.
+     * Called when an IME request is started.
      *
      * @param tag the logging tag.
-     * @param uid the uid of the client that requested the IME.
-     * @param origin the origin of the IME show request.
-     * @param reason the reason why the IME show request was created.
+     * @param uid the uid of the client that started the request.
+     * @param type the type of the request.
+     * @param origin the origin of the request.
      * @param fromUser whether this request was created directly from user interaction.
-     * @return A new IME tracking token.
-     */
-    ImeTracker.Token onRequestShow(String tag, int uid, int origin, int reason, boolean fromUser);
-
-    /**
-     * Called when an IME hide request is created.
+     * @param reason the reason for starting the request.
      *
-     * @param tag the logging tag.
-     * @param uid the uid of the client that requested the IME.
-     * @param origin the origin of the IME hide request.
-     * @param reason the reason why the IME hide request was created.
-     * @param fromUser whether this request was created directly from user interaction.
-     * @return A new IME tracking token.
+     * @return An IME request tracking token.
      */
-    ImeTracker.Token onRequestHide(String tag, int uid, int origin, int reason, boolean fromUser);
+    ImeTracker.Token onStart(String tag, int uid, int type, int origin, int reason,
+        boolean fromUser);
 
     /**
      * Called when the IME request progresses to a further phase.
diff --git a/core/java/com/android/internal/inputmethod/IInputMethod.aidl b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
index 6abd9e8..2593b78 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethod.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
@@ -71,11 +71,11 @@
 
     void setSessionEnabled(IInputMethodSession session, boolean enabled);
 
-    void showSoftInput(in IBinder showInputToken, in @nullable ImeTracker.Token statsToken,
-            int flags, in ResultReceiver resultReceiver);
+    void showSoftInput(in IBinder showInputToken, in ImeTracker.Token statsToken, int flags,
+            in ResultReceiver resultReceiver);
 
-    void hideSoftInput(in IBinder hideInputToken, in @nullable ImeTracker.Token statsToken,
-            int flags, in ResultReceiver resultReceiver);
+    void hideSoftInput(in IBinder hideInputToken, in ImeTracker.Token statsToken, int flags,
+            in ResultReceiver resultReceiver);
 
     void updateEditorToolType(int toolType);
 
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
index 65a2f4b..457b9dd 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
@@ -35,15 +35,17 @@
     void setInputMethod(String id, in AndroidFuture future /* T=Void */);
     void setInputMethodAndSubtype(String id, in InputMethodSubtype subtype,
             in AndroidFuture future /* T=Void */);
-    void hideMySoftInput(int flags, int reason, in AndroidFuture future /* T=Void */);
-    void showMySoftInput(int flags, in AndroidFuture future /* T=Void */);
+    void hideMySoftInput(in ImeTracker.Token statsToken, int flags, int reason,
+            in AndroidFuture future /* T=Void */);
+    void showMySoftInput(in ImeTracker.Token statsToken, int flags, int reason,
+            in AndroidFuture future /* T=Void */);
     void updateStatusIconAsync(String packageName, int iconId);
     void switchToPreviousInputMethod(in AndroidFuture future /* T=Boolean */);
     void switchToNextInputMethod(boolean onlyCurrentIme, in AndroidFuture future /* T=Boolean */);
     void shouldOfferSwitchingToNextInputMethod(in AndroidFuture future /* T=Boolean */);
     void notifyUserActionAsync();
     void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible,
-            in @nullable ImeTracker.Token statsToken);
+            in 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/InputConnectionProtoDumper.java b/core/java/com/android/internal/inputmethod/InputConnectionProtoDumper.java
index 7172d0a..31cf758 100644
--- a/core/java/com/android/internal/inputmethod/InputConnectionProtoDumper.java
+++ b/core/java/com/android/internal/inputmethod/InputConnectionProtoDumper.java
@@ -44,7 +44,6 @@
  */
 public final class InputConnectionProtoDumper {
     static final String TAG = "InputConnectionProtoDumper";
-    public static final boolean DUMP_TEXT = false;
 
     private InputConnectionProtoDumper() {}
 
@@ -67,11 +66,6 @@
         final long token = proto.start(GET_TEXT_AFTER_CURSOR);
         proto.write(GetTextAfterCursor.LENGTH, length);
         proto.write(GetTextAfterCursor.FLAGS, flags);
-        if (result == null) {
-            proto.write(GetTextAfterCursor.RESULT, "null result");
-        } else if (DUMP_TEXT) {
-            proto.write(GetTextAfterCursor.RESULT, result.toString());
-        }
         proto.end(token);
         return proto.getBytes();
     }
@@ -95,11 +89,6 @@
         final long token = proto.start(GET_TEXT_BEFORE_CURSOR);
         proto.write(GetTextBeforeCursor.LENGTH, length);
         proto.write(GetTextBeforeCursor.FLAGS, flags);
-        if (result == null) {
-            proto.write(GetTextBeforeCursor.RESULT, "null result");
-        } else if (DUMP_TEXT) {
-            proto.write(GetTextBeforeCursor.RESULT, result.toString());
-        }
         proto.end(token);
         return proto.getBytes();
     }
@@ -122,11 +111,6 @@
         ProtoOutputStream proto = new ProtoOutputStream();
         final long token = proto.start(GET_SELECTED_TEXT);
         proto.write(GetSelectedText.FLAGS, flags);
-        if (result == null) {
-            proto.write(GetSelectedText.RESULT, "null result");
-        } else if (DUMP_TEXT) {
-            proto.write(GetSelectedText.RESULT, result.toString());
-        }
         proto.end(token);
         return proto.getBytes();
     }
@@ -155,13 +139,8 @@
         proto.write(GetSurroundingText.BEFORE_LENGTH, beforeLength);
         proto.write(GetSurroundingText.AFTER_LENGTH, afterLength);
         proto.write(GetSurroundingText.FLAGS, flags);
-        if (result == null) {
+        if (result != null) {
             final long token_result = proto.start(GetSurroundingText.RESULT);
-            proto.write(GetSurroundingText.SurroundingText.TEXT, "null result");
-            proto.end(token_result);
-        } else if (DUMP_TEXT) {
-            final long token_result = proto.start(GetSurroundingText.RESULT);
-            proto.write(GetSurroundingText.SurroundingText.TEXT, result.getText().toString());
             proto.write(GetSurroundingText.SurroundingText.SELECTION_START,
                     result.getSelectionStart());
             proto.write(GetSurroundingText.SurroundingText.SELECTION_END,
@@ -188,9 +167,7 @@
         ProtoOutputStream proto = new ProtoOutputStream();
         final long token = proto.start(GET_CURSOR_CAPS_MODE);
         proto.write(GetCursorCapsMode.REQ_MODES, reqModes);
-        if (DUMP_TEXT) {
-            proto.write(GetCursorCapsMode.RESULT, result);
-        }
+        proto.write(GetCursorCapsMode.RESULT, result);
         proto.end(token);
         return proto.getBytes();
     }
@@ -223,11 +200,6 @@
         proto.write(GetExtractedText.ExtractedTextRequest.HINT_MAX_CHARS, request.hintMaxChars);
         proto.end(token_request);
         proto.write(GetExtractedText.FLAGS, flags);
-        if (result == null) {
-            proto.write(GetExtractedText.RESULT, "null result");
-        } else if (DUMP_TEXT) {
-            proto.write(GetExtractedText.RESULT, result.text.toString());
-        }
         proto.end(token);
         return proto.getBytes();
     }
diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
index 9b7fa2f..a0aad31 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
@@ -189,6 +189,8 @@
      */
     public static String softInputDisplayReasonToString(@SoftInputShowHideReason int reason) {
         switch (reason) {
+            case SoftInputShowHideReason.NOT_SET:
+                return "NOT_SET";
             case SoftInputShowHideReason.SHOW_SOFT_INPUT:
                 return "SHOW_SOFT_INPUT";
             case SoftInputShowHideReason.ATTACH_NEW_INPUT:
@@ -265,6 +267,36 @@
                 return "HIDE_SOFT_INPUT_CLOSE_CURRENT_SESSION";
             case SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW:
                 return "HIDE_SOFT_INPUT_FROM_VIEW";
+            case SoftInputShowHideReason.SHOW_SOFT_INPUT_LEGACY_DIRECT:
+                return "SHOW_SOFT_INPUT_LEGACY_DIRECT";
+            case SoftInputShowHideReason.HIDE_SOFT_INPUT_LEGACY_DIRECT:
+                return "HIDE_SOFT_INPUT_LEGACY_DIRECT";
+            case SoftInputShowHideReason.SHOW_WINDOW_LEGACY_DIRECT:
+                return "SHOW_WINDOW_LEGACY_DIRECT";
+            case SoftInputShowHideReason.HIDE_WINDOW_LEGACY_DIRECT:
+                return "HIDE_WINDOW_LEGACY_DIRECT";
+            case SoftInputShowHideReason.RESET_NEW_CONFIGURATION:
+                return "RESET_NEW_CONFIGURATION";
+            case SoftInputShowHideReason.UPDATE_CANDIDATES_VIEW_VISIBILITY:
+                return "UPDATE_CANDIDATES_VIEW_VISIBILITY";
+            case SoftInputShowHideReason.CONTROLS_CHANGED:
+                return "CONTROLS_CHANGED";
+            case SoftInputShowHideReason.DISPLAY_CONFIGURATION_CHANGED:
+                return "DISPLAY_CONFIGURATION_CHANGED";
+            case SoftInputShowHideReason.DISPLAY_INSETS_CHANGED:
+                return "DISPLAY_INSETS_CHANGED";
+            case SoftInputShowHideReason.DISPLAY_CONTROLS_CHANGED:
+                return "DISPLAY_CONTROLS_CHANGED";
+            case SoftInputShowHideReason.UNBIND_CURRENT_METHOD:
+                return "UNBIND_CURRENT_METHOD";
+            case SoftInputShowHideReason.HIDE_SOFT_INPUT_ON_ANIMATION_STATE_CHANGED:
+                return "HIDE_SOFT_INPUT_ON_ANIMATION_STATE_CHANGED";
+            case SoftInputShowHideReason.HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL:
+                return "HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL";
+            case SoftInputShowHideReason.SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT:
+                return "SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT";
+            case SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION:
+                return "SHOW_SOFT_INPUT_IMM_DEPRECATION";
             default:
                 return "Unknown=" + reason;
         }
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 792388d..635a227 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -252,20 +252,21 @@
     }
 
     /**
-     * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int, int, AndroidFuture)}
-     *
-     * @param reason the reason to hide soft input
+     * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput}
      */
     @AnyThread
-    public void hideMySoftInput(@InputMethodManager.HideFlags int flags,
-            @SoftInputShowHideReason int reason) {
+    public void hideMySoftInput(@NonNull ImeTracker.Token statsToken,
+            @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason) {
         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
         if (ops == null) {
+            ImeTracker.forLogging().onFailed(statsToken,
+                    ImeTracker.PHASE_IME_PRIVILEGED_OPERATIONS);
             return;
         }
+        ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_PRIVILEGED_OPERATIONS);
         try {
             final AndroidFuture<Void> future = new AndroidFuture<>();
-            ops.hideMySoftInput(flags, reason, future);
+            ops.hideMySoftInput(statsToken, flags, reason, future);
             CompletableFutureUtil.getResult(future);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -273,17 +274,21 @@
     }
 
     /**
-     * Calls {@link IInputMethodPrivilegedOperations#showMySoftInput(int, AndroidFuture)}
+     * Calls {@link IInputMethodPrivilegedOperations#showMySoftInput}
      */
     @AnyThread
-    public void showMySoftInput(@InputMethodManager.ShowFlags int flags) {
+    public void showMySoftInput(@NonNull ImeTracker.Token statsToken,
+            @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason) {
         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
         if (ops == null) {
+            ImeTracker.forLogging().onFailed(statsToken,
+                    ImeTracker.PHASE_IME_PRIVILEGED_OPERATIONS);
             return;
         }
+        ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_PRIVILEGED_OPERATIONS);
         try {
             final AndroidFuture<Void> future = new AndroidFuture<>();
-            ops.showMySoftInput(flags, future);
+            ops.showMySoftInput(statsToken, flags, reason, future);
             CompletableFutureUtil.getResult(future);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -379,19 +384,19 @@
      *        {@link android.view.inputmethod.InputMethodManager#hideSoftInputFromWindow(IBinder,
      *        int)}
      * @param setVisible {@code true} to set IME visible, else hidden.
-     * @param statsToken the token tracking the current IME request or {@code null} otherwise.
+     * @param statsToken the token tracking the current IME request.
      */
     @AnyThread
     public void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible,
-            @Nullable ImeTracker.Token statsToken) {
+            @NonNull ImeTracker.Token statsToken) {
         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
         if (ops == null) {
             ImeTracker.forLogging().onFailed(statsToken,
-                    ImeTracker.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER);
+                    ImeTracker.PHASE_IME_PRIVILEGED_OPERATIONS);
             return;
         }
         ImeTracker.forLogging().onProgress(statsToken,
-                ImeTracker.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER);
+                ImeTracker.PHASE_IME_PRIVILEGED_OPERATIONS);
         try {
             ops.applyImeVisibilityAsync(showOrHideInputToken, setVisible, statsToken);
         } catch (RemoteException e) {
diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
index 861b8a7..da738a0 100644
--- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
+++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
@@ -34,6 +34,7 @@
  */
 @Retention(SOURCE)
 @IntDef(value = {
+        SoftInputShowHideReason.NOT_SET,
         SoftInputShowHideReason.SHOW_SOFT_INPUT,
         SoftInputShowHideReason.ATTACH_NEW_INPUT,
         SoftInputShowHideReason.SHOW_SOFT_INPUT_FROM_IME,
@@ -72,8 +73,26 @@
         SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE,
         SoftInputShowHideReason.HIDE_CLOSE_CURRENT_SESSION,
         SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW,
+        SoftInputShowHideReason.SHOW_SOFT_INPUT_LEGACY_DIRECT,
+        SoftInputShowHideReason.HIDE_SOFT_INPUT_LEGACY_DIRECT,
+        SoftInputShowHideReason.SHOW_WINDOW_LEGACY_DIRECT,
+        SoftInputShowHideReason.HIDE_WINDOW_LEGACY_DIRECT,
+        SoftInputShowHideReason.RESET_NEW_CONFIGURATION,
+        SoftInputShowHideReason.UPDATE_CANDIDATES_VIEW_VISIBILITY,
+        SoftInputShowHideReason.CONTROLS_CHANGED,
+        SoftInputShowHideReason.DISPLAY_CONFIGURATION_CHANGED,
+        SoftInputShowHideReason.DISPLAY_INSETS_CHANGED,
+        SoftInputShowHideReason.DISPLAY_CONTROLS_CHANGED,
+        SoftInputShowHideReason.UNBIND_CURRENT_METHOD,
+        SoftInputShowHideReason.HIDE_SOFT_INPUT_ON_ANIMATION_STATE_CHANGED,
+        SoftInputShowHideReason.HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL,
+        SoftInputShowHideReason.SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT,
+        SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION,
 })
 public @interface SoftInputShowHideReason {
+    /** Default, undefined reason. */
+    int NOT_SET = ImeProtoEnums.REASON_NOT_SET;
+
     /** Show soft input by {@link android.view.inputmethod.InputMethodManager#showSoftInput}. */
     int SHOW_SOFT_INPUT = ImeProtoEnums.REASON_SHOW_SOFT_INPUT;
 
@@ -291,4 +310,91 @@
      * Hide soft input when {@link InputMethodManager#hideSoftInputFromView(View, int)} gets called.
      */
     int HIDE_SOFT_INPUT_FROM_VIEW = ImeProtoEnums.REASON_HIDE_SOFT_INPUT_FROM_VIEW;
+
+    /**
+     * Show soft input by legacy (discouraged) call to
+     * {@link android.inputmethodservice.InputMethodService.InputMethodImpl#showSoftInput}.
+     */
+    int SHOW_SOFT_INPUT_LEGACY_DIRECT = ImeProtoEnums.REASON_SHOW_SOFT_INPUT_LEGACY_DIRECT;
+
+    /**
+     * Hide soft input by legacy (discouraged) call to
+     * {@link android.inputmethodservice.InputMethodService.InputMethodImpl#hideSoftInput}.
+     */
+    int HIDE_SOFT_INPUT_LEGACY_DIRECT = ImeProtoEnums.REASON_HIDE_SOFT_INPUT_LEGACY_DIRECT;
+
+    /**
+     * Show soft input by legacy (discouraged) call to
+     * {@link android.inputmethodservice.InputMethodService#showWindow}.
+     */
+    int SHOW_WINDOW_LEGACY_DIRECT = ImeProtoEnums.REASON_SHOW_WINDOW_LEGACY_DIRECT;
+
+    /**
+     * Hide soft input by legacy (discouraged) call to
+     * {@link android.inputmethodservice.InputMethodService#hideWindow}.
+     */
+    int HIDE_WINDOW_LEGACY_DIRECT = ImeProtoEnums.REASON_HIDE_WINDOW_LEGACY_DIRECT;
+
+    /**
+     * Show / Hide soft input by
+     * {@link android.inputmethodservice.InputMethodService#resetStateForNewConfiguration}.
+     */
+    int RESET_NEW_CONFIGURATION = ImeProtoEnums.REASON_RESET_NEW_CONFIGURATION;
+
+    /**
+     * Show / Hide soft input by
+     * {@link android.inputmethodservice.InputMethodService#updateCandidatesVisibility}.
+     */
+    int UPDATE_CANDIDATES_VIEW_VISIBILITY = ImeProtoEnums.REASON_UPDATE_CANDIDATES_VIEW_VISIBILITY;
+
+    /**
+     * Show / Hide soft input by {@link android.view.InsetsController#onControlsChanged}.
+     */
+    int CONTROLS_CHANGED = ImeProtoEnums.REASON_CONTROLS_CHANGED;
+
+    /**
+     * Show soft input by
+     * {@link com.android.wm.shell.common.DisplayImeController#onDisplayConfigurationChanged}.
+     */
+    int DISPLAY_CONFIGURATION_CHANGED = ImeProtoEnums.REASON_DISPLAY_CONFIGURATION_CHANGED;
+
+    /**
+     * Show soft input by
+     * {@link com.android.wm.shell.common.DisplayImeController.PerDisplay#insetsChanged}.
+     */
+    int DISPLAY_INSETS_CHANGED = ImeProtoEnums.REASON_DISPLAY_INSETS_CHANGED;
+
+    /**
+     * Show / Hide soft input by
+     * {@link com.android.wm.shell.common.DisplayImeController.PerDisplay#insetsControlChanged}.
+     */
+    int DISPLAY_CONTROLS_CHANGED = ImeProtoEnums.REASON_DISPLAY_CONTROLS_CHANGED;
+
+    /** Hide soft input by
+     * {@link com.android.server.inputmethod.InputMethodManagerService#onUnbindCurrentMethodByReset}.
+     */
+    int UNBIND_CURRENT_METHOD = ImeProtoEnums.REASON_UNBIND_CURRENT_METHOD;
+
+    /** Hide soft input by {@link android.view.ImeInsetsSourceConsumer#onAnimationStateChanged}. */
+    int HIDE_SOFT_INPUT_ON_ANIMATION_STATE_CHANGED =
+            ImeProtoEnums.REASON_HIDE_SOFT_INPUT_ON_ANIMATION_STATE_CHANGED;
+
+    /** Hide soft input when we already have a {@link android.view.InsetsSourceControl} by
+     * {@link android.view.ImeInsetsSourceConsumer#requestHide}.
+     */
+    int HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL =
+            ImeProtoEnums.REASON_HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL;
+
+    /**
+     * Show soft input by
+     * {@link android.inputmethodservice.InputMethodService#onToggleSoftInput(int, int)}.
+     */
+    int SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT =
+            ImeProtoEnums.REASON_SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT;
+
+    /**
+     * Show soft input by the deprecated
+     * {@link InputMethodManager#showSoftInputFromInputMethod(IBinder, int)}.
+     */
+    int SHOW_SOFT_INPUT_IMM_DEPRECATION = ImeProtoEnums.REASON_SHOW_SOFT_INPUT_IMM_DEPRECATION;
 }
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index cbe0700..d4dcec9 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -93,6 +93,9 @@
             throw ex;
         }
 
+        if (peer.getUid() != Process.SYSTEM_UID) {
+            throw new ZygoteSecurityException("Only system UID is allowed to connect to Zygote.");
+        }
         isEof = false;
     }
 
diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
index 73df5e8..5e89d06 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
@@ -421,6 +421,8 @@
     @NonNull
     private String[] mUsesStaticLibrariesSorted;
 
+    private boolean mAppMetadataFileInApk = false;
+
     @NonNull
     public static PackageImpl forParsing(@NonNull String packageName, @NonNull String baseCodePath,
             @NonNull String codePath, @NonNull TypedArray manifestArray, boolean isCoreApp,
@@ -1063,6 +1065,11 @@
         return memtagMode;
     }
 
+    @Override
+    public boolean isAppMetadataFileInApk() {
+        return mAppMetadataFileInApk;
+    }
+
     @Nullable
     @Override
     public Bundle getMetaData() {
@@ -2151,6 +2158,12 @@
     }
 
     @Override
+    public PackageImpl setAppMetadataFileInApk(boolean fileInApk) {
+        mAppMetadataFileInApk = fileInApk;
+        return this;
+    }
+
+    @Override
     public PackageImpl setMetaData(@Nullable Bundle value) {
         metaData = value;
         return this;
@@ -3264,6 +3277,7 @@
         dest.writeLong(this.mBooleans);
         dest.writeLong(this.mBooleans2);
         dest.writeBoolean(this.mAllowCrossUidActivitySwitchFromBelow);
+        dest.writeBoolean(this.mAppMetadataFileInApk);
     }
 
     public PackageImpl(Parcel in) {
@@ -3426,6 +3440,7 @@
         this.mBooleans = in.readLong();
         this.mBooleans2 = in.readLong();
         this.mAllowCrossUidActivitySwitchFromBelow = in.readBoolean();
+        this.mAppMetadataFileInApk = in.readBoolean();
 
         assignDerivedFields();
         assignDerivedFields2();
diff --git a/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java b/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java
index 66cfb69..3c26a7c 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java
@@ -127,4 +127,7 @@
     ParsedPackage setDirectBootAware(boolean directBootAware);
 
     ParsedPackage setPersistent(boolean persistent);
+
+    /** Retrieves whether the apk contains a app metadata file. */
+    boolean isAppMetadataFileInApk();
 }
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
index 5d185af..5ab17a6 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
@@ -133,6 +133,9 @@
             @Nullable SparseArray<int[]> splitDependencies
     );
 
+    /** Sets whether the apk contains a app metadata file. */
+    ParsingPackage setAppMetadataFileInApk(boolean fileInApk);
+
     ParsingPackage setMetaData(Bundle metaData);
 
     ParsingPackage setForceQueryable(boolean forceQueryable);
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index 2e6053d..95ecd47 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -46,6 +46,7 @@
 import android.content.pm.ConfigurationInfo;
 import android.content.pm.FeatureGroupInfo;
 import android.content.pm.FeatureInfo;
+import android.content.pm.Flags;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.Property;
@@ -133,6 +134,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.security.PublicKey;
@@ -163,6 +165,8 @@
      */
     public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
 
+    public static final String APP_METADATA_FILE_NAME = "app.metadata";
+
     /**
      * Path prefix for apps on expanded storage
      */
@@ -636,6 +640,12 @@
                 pkg.setSigningDetails(SigningDetails.UNKNOWN);
             }
 
+            if (Flags.aslInApkAppMetadataSource()) {
+                try (InputStream in = assets.open(APP_METADATA_FILE_NAME)) {
+                    pkg.setAppMetadataFileInApk(true);
+                } catch (Exception e) { }
+            }
+
             return input.success(pkg);
         } catch (Exception e) {
             return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
@@ -686,7 +696,8 @@
      */
     private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, String apkPath,
             String codePath, Resources res, XmlResourceParser parser, int flags,
-            boolean shouldSkipComponents) throws XmlPullParserException, IOException {
+            boolean shouldSkipComponents)
+            throws XmlPullParserException, IOException {
         final String splitName;
         final String pkgName;
 
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 8566263..0f1f7e9 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -29,6 +29,7 @@
 import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
 import static android.view.Window.DECOR_CAPTION_SHADE_DARK;
 import static android.view.Window.DECOR_CAPTION_SHADE_LIGHT;
+import static android.view.WindowInsetsController.APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
@@ -136,6 +137,8 @@
 
     private static final int SCRIM_LIGHT = 0xe6ffffff; // 90% white
 
+    private static final int SCRIM_ALPHA = 0xcc0000; // 80% alpha
+
     public static final ColorViewAttributes STATUS_BAR_COLOR_VIEW_ATTRIBUTES =
             new ColorViewAttributes(FLAG_TRANSLUCENT_STATUS,
                     Gravity.TOP, Gravity.LEFT, Gravity.RIGHT,
@@ -989,6 +992,16 @@
         if (mOriginalBackgroundDrawable != drawable) {
             mOriginalBackgroundDrawable = drawable;
             updateBackgroundDrawable();
+            if (mWindow.mEdgeToEdgeEnforced && !mWindow.mNavigationBarColorSpecified
+                    && drawable instanceof ColorDrawable) {
+                final int color = ((ColorDrawable) drawable).getColor();
+                final boolean lightBar = Color.valueOf(color).luminance() > 0.5f;
+                getWindowInsetsController().setSystemBarsAppearance(
+                        lightBar ? APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS : 0,
+                        APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS);
+                mWindow.mNavigationBarColor = color;
+                updateColorViews(null /* insets */, false /* animate */);
+            }
             if (drawable != null) {
                 mResizingBackgroundDrawable = enforceNonTranslucentBackground(drawable,
                         mWindow.isTranslucent() || mWindow.isShowingWallpaper());
@@ -1407,7 +1420,8 @@
                 mSemiTransparentBarColor, mWindow.mStatusBarColor,
                 appearance, APPEARANCE_LIGHT_STATUS_BARS,
                 mWindow.mEnsureStatusBarContrastWhenTransparent
-                        && (mLastSuppressScrimTypes & WindowInsets.Type.statusBars()) == 0);
+                        && (mLastSuppressScrimTypes & WindowInsets.Type.statusBars()) == 0,
+                false /* movesBarColorToScrim */);
     }
 
     private int calculateNavigationBarColor(@Appearance int appearance) {
@@ -1415,22 +1429,29 @@
                 mSemiTransparentBarColor, mWindow.mNavigationBarColor,
                 appearance, APPEARANCE_LIGHT_NAVIGATION_BARS,
                 mWindow.mEnsureNavigationBarContrastWhenTransparent
-                        && (mLastSuppressScrimTypes & WindowInsets.Type.navigationBars()) == 0);
+                        && (mLastSuppressScrimTypes & WindowInsets.Type.navigationBars()) == 0,
+                mWindow.mEdgeToEdgeEnforced);
     }
 
     public static int calculateBarColor(int flags, int translucentFlag, int semiTransparentBarColor,
             int barColor, @Appearance int appearance, @Appearance int lightAppearanceFlag,
-            boolean scrimTransparent) {
+            boolean ensuresContrast, boolean movesBarColorToScrim) {
         if ((flags & translucentFlag) != 0) {
             return semiTransparentBarColor;
         } else if ((flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0) {
             return Color.BLACK;
-        } else if (scrimTransparent && Color.alpha(barColor) == 0) {
-            boolean light = (appearance & lightAppearanceFlag) != 0;
-            return light ? SCRIM_LIGHT : semiTransparentBarColor;
-        } else {
-            return barColor;
+        } else if (ensuresContrast) {
+            final int alpha = Color.alpha(barColor);
+            if (alpha == 0) {
+                boolean light = (appearance & lightAppearanceFlag) != 0;
+                return light ? SCRIM_LIGHT : semiTransparentBarColor;
+            } else if (movesBarColorToScrim) {
+                return (barColor & 0xffffff) | SCRIM_ALPHA;
+            }
+        } else if (movesBarColorToScrim) {
+            return Color.TRANSPARENT;
         }
+        return barColor;
     }
 
     private int getCurrentColor(ColorViewState state) {
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 0dd01e4..9868ceb 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -21,6 +21,7 @@
 import static android.view.View.SYSTEM_UI_LAYOUT_FLAGS;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowInsetsController.APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
@@ -298,6 +299,7 @@
     int mStatusBarColor = Color.TRANSPARENT;
     int mNavigationBarColor = Color.TRANSPARENT;
     int mNavigationBarDividerColor = Color.TRANSPARENT;
+    boolean mNavigationBarColorSpecified = false;
     private boolean mForcedStatusBarColor = false;
     private boolean mForcedNavigationBarColor = false;
 
@@ -370,7 +372,7 @@
 
     boolean mDecorFitsSystemWindows = true;
 
-    private boolean mEdgeToEdgeEnforced;
+    boolean mEdgeToEdgeEnforced;
 
     private final ProxyOnBackInvokedDispatcher mProxyOnBackInvokedDispatcher;
 
@@ -406,6 +408,7 @@
             mElevation = preservedWindow.getElevation();
             mLoadElevation = false;
             mForceDecorInstall = true;
+            setSystemBarAppearance(preservedWindow.getSystemBarAppearance());
             // If we're preserving window, carry over the app token from the preserved
             // window, as we'll be skipping the addView in handleResumeActivity(), and
             // the token will not be updated as for a new window.
@@ -2577,21 +2580,27 @@
         if (!mForcedStatusBarColor && !mEdgeToEdgeEnforced) {
             mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, Color.BLACK);
         }
-        if (!mForcedNavigationBarColor && !mEdgeToEdgeEnforced) {
+        if (!mForcedNavigationBarColor) {
             final int navBarCompatibleColor = context.getColor(R.color.navigation_bar_compatible);
             final int navBarDefaultColor = context.getColor(R.color.navigation_bar_default);
             final int navBarColor = a.getColor(R.styleable.Window_navigationBarColor,
                     navBarDefaultColor);
+            final boolean navigationBarColorSpecified = navBarColor != navBarDefaultColor;
 
             mNavigationBarColor =
-                    navBarColor == navBarDefaultColor
+                    !navigationBarColorSpecified
+                            && !mEdgeToEdgeEnforced
                             && !context.getResources().getBoolean(
                                     R.bool.config_navBarDefaultTransparent)
                     ? navBarCompatibleColor
                     : navBarColor;
 
-            mNavigationBarDividerColor = a.getColor(R.styleable.Window_navigationBarDividerColor,
-                    Color.TRANSPARENT);
+            mNavigationBarColorSpecified |= navigationBarColorSpecified;
+
+            if (!mEdgeToEdgeEnforced) {
+                mNavigationBarDividerColor = a.getColor(
+                        R.styleable.Window_navigationBarDividerColor, Color.TRANSPARENT);
+            }
         }
         if (!targetPreQ) {
             mEnsureStatusBarContrastWhenTransparent = a.getBoolean(
@@ -3941,17 +3950,20 @@
 
     @Override
     public void setNavigationBarColor(int color) {
-        if (mEdgeToEdgeEnforced) {
-            return;
-        }
         if (mNavigationBarColor == color && mForcedNavigationBarColor) {
             return;
         }
         mNavigationBarColor = color;
         mForcedNavigationBarColor = true;
+        mNavigationBarColorSpecified = true;
         if (mDecor != null) {
+            mDecor.getWindowInsetsController().setSystemBarsAppearance(
+                    0, APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS);
             mDecor.updateColorViews(null, false /* animate */);
         }
+        if (mEdgeToEdgeEnforced) {
+            return;
+        }
         final WindowControllerCallback callback = getWindowControllerCallback();
         if (callback != null) {
             getWindowControllerCallback().updateNavigationBarColor(color);
diff --git a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
index d9ac5a9..2096ba4 100644
--- a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
@@ -55,37 +55,38 @@
  * A service for the ProtoLog logging system.
  */
 public class LegacyProtoLogImpl implements IProtoLog {
-    private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
-
     private static final int BUFFER_CAPACITY = 1024 * 1024;
     private static final int PER_CHUNK_SIZE = 1024;
     private static final String TAG = "ProtoLog";
     private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
-    static final String PROTOLOG_VERSION = "1.0.0";
-    private static final int DEFAULT_PER_CHUNK_SIZE = 0;
+    static final String PROTOLOG_VERSION = "2.0.0";
 
     private final File mLogFile;
     private final String mLegacyViewerConfigFilename;
     private final TraceBuffer mBuffer;
     private final LegacyProtoLogViewerConfigReader mViewerConfig;
+    private final TreeMap<String, IProtoLogGroup> mLogGroups;
     private final int mPerChunkSize;
 
     private boolean mProtoLogEnabled;
     private boolean mProtoLogEnabledLockFree;
     private final Object mProtoLogEnabledLock = new Object();
 
-    public LegacyProtoLogImpl(String outputFile, String viewerConfigFilename) {
+    public LegacyProtoLogImpl(String outputFile, String viewerConfigFilename,
+            TreeMap<String, IProtoLogGroup> logGroups) {
         this(new File(outputFile), viewerConfigFilename, BUFFER_CAPACITY,
-                new LegacyProtoLogViewerConfigReader(), PER_CHUNK_SIZE);
+                new LegacyProtoLogViewerConfigReader(), PER_CHUNK_SIZE, logGroups);
     }
 
     public LegacyProtoLogImpl(File file, String viewerConfigFilename, int bufferCapacity,
-            LegacyProtoLogViewerConfigReader viewerConfig, int perChunkSize) {
+            LegacyProtoLogViewerConfigReader viewerConfig, int perChunkSize,
+            TreeMap<String, IProtoLogGroup> logGroups) {
         mLogFile = file;
         mBuffer = new TraceBuffer(bufferCapacity);
         mLegacyViewerConfigFilename = viewerConfigFilename;
         mViewerConfig = viewerConfig;
         mPerChunkSize = perChunkSize;
+        this.mLogGroups = logGroups;
     }
 
     /**
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 53062d8..4ead82f 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -22,9 +22,9 @@
 import static perfetto.protos.PerfettoTrace.InternedString.STR;
 import static perfetto.protos.PerfettoTrace.ProtoLogMessage.BOOLEAN_PARAMS;
 import static perfetto.protos.PerfettoTrace.ProtoLogMessage.DOUBLE_PARAMS;
-import static perfetto.protos.PerfettoTrace.ProtoLogMessage.STACKTRACE_IID;
 import static perfetto.protos.PerfettoTrace.ProtoLogMessage.MESSAGE_ID;
 import static perfetto.protos.PerfettoTrace.ProtoLogMessage.SINT64_PARAMS;
+import static perfetto.protos.PerfettoTrace.ProtoLogMessage.STACKTRACE_IID;
 import static perfetto.protos.PerfettoTrace.ProtoLogMessage.STR_PARAM_IIDS;
 import static perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.GROUPS;
 import static perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.Group.ID;
@@ -70,6 +70,8 @@
 import java.util.ArrayList;
 import java.util.Map;
 import java.util.TreeMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.MessageData;
@@ -78,7 +80,6 @@
  * A service for the ProtoLog logging system.
  */
 public class PerfettoProtoLogImpl implements IProtoLog {
-    private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
     private static final String LOG_TAG = "ProtoLog";
     private final AtomicInteger mTracingInstances = new AtomicInteger();
 
@@ -89,8 +90,12 @@
     );
     private final ProtoLogViewerConfigReader mViewerConfigReader;
     private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
+    private final TreeMap<String, IProtoLogGroup> mLogGroups;
 
-    public PerfettoProtoLogImpl(String viewerConfigFilePath) {
+    private final ExecutorService mBackgroundLoggingService = Executors.newCachedThreadPool();
+
+    public PerfettoProtoLogImpl(String viewerConfigFilePath,
+            TreeMap<String, IProtoLogGroup> logGroups) {
         this(() -> {
             try {
                 return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
@@ -98,23 +103,28 @@
                 Slog.w(LOG_TAG, "Failed to load viewer config file " + viewerConfigFilePath, e);
                 return null;
             }
-        });
+        }, logGroups);
     }
 
-    public PerfettoProtoLogImpl(ViewerConfigInputStreamProvider viewerConfigInputStreamProvider) {
+    public PerfettoProtoLogImpl(
+            ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
+            TreeMap<String, IProtoLogGroup> logGroups
+    ) {
         this(viewerConfigInputStreamProvider,
-                new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
+                new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider), logGroups);
     }
 
     @VisibleForTesting
     public PerfettoProtoLogImpl(
             ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
-            ProtoLogViewerConfigReader viewerConfigReader
+            ProtoLogViewerConfigReader viewerConfigReader,
+            TreeMap<String, IProtoLogGroup> logGroups
     ) {
         Producer.init(InitArguments.DEFAULTS);
         mDataSource.register(DataSourceParams.DEFAULTS);
         this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
         this.mViewerConfigReader = viewerConfigReader;
+        this.mLogGroups = logGroups;
     }
 
     /**
@@ -128,7 +138,8 @@
 
         long tsNanos = SystemClock.elapsedRealtimeNanos();
         try {
-            logToProto(level, group.name(), messageHash, paramsMask, args, tsNanos);
+            mBackgroundLoggingService.submit(() ->
+                    logToProto(level, group.name(), messageHash, paramsMask, args, tsNanos));
             if (group.isLogToLogcat()) {
                 logToLogcat(group.getTag(), level, messageHash, messageString, args);
             }
@@ -456,40 +467,6 @@
     }
 
     /**
-     * Responds to a shell command.
-     */
-    public int onShellCommand(ShellCommand shell) {
-        PrintWriter pw = shell.getOutPrintWriter();
-        String cmd = shell.getNextArg();
-        if (cmd == null) {
-            return unknownCommand(pw);
-        }
-        ArrayList<String> args = new ArrayList<>();
-        String arg;
-        while ((arg = shell.getNextArg()) != null) {
-            args.add(arg);
-        }
-        final ILogger logger = (msg) -> logAndPrintln(pw, msg);
-        String[] groups = args.toArray(new String[args.size()]);
-        switch (cmd) {
-            case "enable-text":
-                return this.startLoggingToLogcat(groups, logger);
-            case "disable-text":
-                return this.stopLoggingToLogcat(groups, logger);
-            default:
-                return unknownCommand(pw);
-        }
-    }
-
-    private int unknownCommand(PrintWriter pw) {
-        pw.println("Unknown command");
-        pw.println("Window manager logging options:");
-        pw.println("  enable-text [group...]: Enable logcat logging for given groups");
-        pw.println("  disable-text [group...]: Disable logcat logging for given groups");
-        return -1;
-    }
-
-    /**
      * Returns {@code true} iff logging to proto is enabled.
      */
     public boolean isProtoEnabled() {
@@ -548,6 +525,49 @@
         return 0;
     }
 
+    /**
+     * Responds to a shell command.
+     */
+    public int onShellCommand(ShellCommand shell) {
+        PrintWriter pw = shell.getOutPrintWriter();
+        String cmd = shell.getNextArg();
+        if (cmd == null) {
+            return unknownCommand(pw);
+        }
+        ArrayList<String> args = new ArrayList<>();
+        String arg;
+        while ((arg = shell.getNextArg()) != null) {
+            args.add(arg);
+        }
+        final ILogger logger = (msg) -> logAndPrintln(pw, msg);
+        String[] groups = args.toArray(new String[0]);
+        switch (cmd) {
+            case "start", "stop" -> {
+                pw.println("Command not supported. "
+                        + "Please start and stop ProtoLog tracing with Perfetto.");
+                return -1;
+            }
+            case "enable-text" -> {
+                mViewerConfigReader.loadViewerConfig(logger);
+                return setTextLogging(true, logger, groups);
+            }
+            case "disable-text" -> {
+                return setTextLogging(false, logger, groups);
+            }
+            default -> {
+                return unknownCommand(pw);
+            }
+        }
+    }
+
+    private int unknownCommand(PrintWriter pw) {
+        pw.println("Unknown command");
+        pw.println("Window manager logging options:");
+        pw.println("  enable-text [group...]: Enable logcat logging for given groups");
+        pw.println("  disable-text [group...]: Disable logcat logging for given groups");
+        return -1;
+    }
+
     static void logAndPrintln(@Nullable PrintWriter pw, String msg) {
         Slog.i(LOG_TAG, msg);
         if (pw != null) {
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 78bed94..487ae814 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LEGACY_OUTPUT_FILE_PATH;
 import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LEGACY_VIEWER_CONFIG_PATH;
+import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LOG_GROUPS;
 import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.VIEWER_CONFIG_PATH;
 
 import android.annotation.Nullable;
@@ -28,6 +29,8 @@
 import com.android.internal.protolog.common.LogLevel;
 import com.android.internal.protolog.common.ProtoLogToolInjected;
 
+import java.util.TreeMap;
+
 /**
  * A service for the ProtoLog logging system.
  */
@@ -43,6 +46,9 @@
     @ProtoLogToolInjected(LEGACY_OUTPUT_FILE_PATH)
     private static String sLegacyOutputFilePath;
 
+    @ProtoLogToolInjected(LOG_GROUPS)
+    private static TreeMap<String, IProtoLogGroup> sLogGroups;
+
     /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
     public static void d(IProtoLogGroup group, long messageHash, int paramsMask,
             @Nullable String messageString,
@@ -98,12 +104,11 @@
      */
     public static synchronized IProtoLog getSingleInstance() {
         if (sServiceInstance == null) {
-            if (android.tracing.Flags.perfettoProtolog()) {
-                sServiceInstance =
-                        new PerfettoProtoLogImpl(sViewerConfigPath);
+            if (android.tracing.Flags.perfettoProtologTracing()) {
+                sServiceInstance = new PerfettoProtoLogImpl(sViewerConfigPath, sLogGroups);
             } else {
-                sServiceInstance =
-                        new LegacyProtoLogImpl(sLegacyOutputFilePath, sLegacyViewerConfigPath);
+                sServiceInstance = new LegacyProtoLogImpl(
+                        sLegacyOutputFilePath, sLegacyViewerConfigPath, sLogGroups);
             }
         }
         return sServiceInstance;
diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
index 3c206ac..ae3d448 100644
--- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
+++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
@@ -4,6 +4,7 @@
 import static perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.MessageData.MESSAGE;
 import static perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.MessageData.MESSAGE_ID;
 
+import android.util.ArrayMap;
 import android.util.proto.ProtoInputStream;
 
 import com.android.internal.protolog.common.ILogger;
@@ -57,6 +58,7 @@
     }
 
     private void doLoadViewerConfig() throws IOException {
+        mLogMessageMap = new ArrayMap<>();
         final ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream();
 
         while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
diff --git a/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java b/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java
index ffd0d76..17c82d7 100644
--- a/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java
+++ b/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java
@@ -22,7 +22,9 @@
 
 @Target({ElementType.FIELD, ElementType.PARAMETER})
 public @interface ProtoLogToolInjected {
-    enum Value { VIEWER_CONFIG_PATH, LEGACY_OUTPUT_FILE_PATH, LEGACY_VIEWER_CONFIG_PATH }
+    enum Value {
+        VIEWER_CONFIG_PATH, LEGACY_OUTPUT_FILE_PATH, LEGACY_VIEWER_CONFIG_PATH, LOG_GROUPS
+    }
 
     Value value();
 }
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index d463b62..a2efbd2 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -360,15 +360,20 @@
     /** Shows rear display educational dialog */
     void showRearDisplayDialog(int currentBaseState);
 
-    /** Called when requested to go to fullscreen from the active split app. */
-    void goToFullscreenFromSplit();
+    /**
+     *  Called when requested to go to fullscreen from the focused app.
+     *
+     *  @param displayId the id of the current display.
+     */
+    void moveFocusedTaskToFullscreen(int displayId);
 
     /**
      * Enters stage split from a current running app.
      *
+     * @param displayId the id of the current display.
      * @param leftOrTop indicates where the stage split is.
      */
-    void enterStageSplitFromRunningApp(boolean leftOrTop);
+    void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop);
 
     /**
      * Shows the media output switcher dialog.
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 1f4503a..0257033 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -33,8 +33,10 @@
 import com.android.internal.inputmethod.InputBindResult;
 
 /**
- * Public interface to the global input method manager, used by all client
- * applications.
+ * Public interface to the global input method manager, used by all client applications.
+ *
+ * When adding new methods, make sure the associated user can be inferred from the arguments.
+ * Consider passing the associated userId when not already passing a display id or a window token.
  */
 interface IInputMethodManager {
     void addClient(in IInputMethodClient client, in IRemoteInputConnection inputmethod,
@@ -65,12 +67,21 @@
     InputMethodSubtype getLastInputMethodSubtype(int userId);
 
     boolean showSoftInput(in IInputMethodClient client, @nullable IBinder windowToken,
-            in @nullable ImeTracker.Token statsToken, int flags, int lastClickToolType,
+            in ImeTracker.Token statsToken, int flags, int lastClickToolType,
             in @nullable ResultReceiver resultReceiver, int reason);
     boolean hideSoftInput(in IInputMethodClient client, @nullable IBinder windowToken,
-            in @nullable ImeTracker.Token statsToken, int flags,
+            in ImeTracker.Token statsToken, int flags,
             in @nullable ResultReceiver resultReceiver, int reason);
 
+    /**
+     * A test API for CTS to request hiding the current soft input window, with the request origin
+     * on the server side.
+     */
+    @EnforcePermission("TEST_INPUT_METHOD")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.TEST_INPUT_METHOD)")
+    void hideSoftInputFromServerForTest();
+
     // TODO(b/293640003): Remove method once Flags.useZeroJankProxy() is enabled.
     // If windowToken is null, this just does startInput().  Otherwise this reports that a window
     // has gained focus, and if 'editorInfo' is non-null then also does startInput.
diff --git a/core/java/com/android/internal/widget/ConversationAvatarData.java b/core/java/com/android/internal/widget/ConversationAvatarData.java
new file mode 100644
index 0000000..e04772f
--- /dev/null
+++ b/core/java/com/android/internal/widget/ConversationAvatarData.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.graphics.drawable.Drawable;
+
+/**
+ * @hide
+ */
+interface ConversationAvatarData {
+    final class OneToOneConversationAvatarData implements ConversationAvatarData {
+        final Drawable mDrawable;
+
+        OneToOneConversationAvatarData(Drawable drawable) {
+            mDrawable = drawable;
+        }
+    }
+
+    final class GroupConversationAvatarData implements ConversationAvatarData {
+        final Drawable mLastIcon;
+        final Drawable mSecondLastIcon;
+
+        GroupConversationAvatarData(Drawable lastIcon, Drawable secondLastIcon) {
+            mLastIcon = lastIcon;
+            mSecondLastIcon = secondLastIcon;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/ConversationHeaderData.java b/core/java/com/android/internal/widget/ConversationHeaderData.java
new file mode 100644
index 0000000..0953b39
--- /dev/null
+++ b/core/java/com/android/internal/widget/ConversationHeaderData.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.Nullable;
+
+/**
+ * @hide
+ */
+final class ConversationHeaderData {
+    private final CharSequence mConversationText;
+
+    private final ConversationAvatarData mConversationAvatarData;
+
+    ConversationHeaderData(CharSequence conversationText,
+            ConversationAvatarData conversationAvatarData) {
+        mConversationText = conversationText;
+        mConversationAvatarData = conversationAvatarData;
+    }
+
+    @Nullable
+    CharSequence getConversationText() {
+        return mConversationText;
+    }
+
+    @Nullable
+    ConversationAvatarData getConversationAvatar() {
+        return mConversationAvatarData;
+    }
+}
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 889434f..06835f0 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -34,8 +34,10 @@
 import android.content.res.ColorStateList;
 import android.graphics.Rect;
 import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.Icon;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.text.Spannable;
@@ -59,8 +61,11 @@
 import android.widget.LinearLayout;
 import android.widget.RemoteViews;
 import android.widget.TextView;
+import android.widget.flags.Flags;
 
 import com.android.internal.R;
+import com.android.internal.widget.ConversationAvatarData.GroupConversationAvatarData;
+import com.android.internal.widget.ConversationAvatarData.OneToOneConversationAvatarData;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -403,11 +408,14 @@
      */
     @RemotableViewMethod(asyncImpl = "setDataAsync")
     public void setData(Bundle extras) {
-        bind(parseMessagingData(extras, /* usePrecomputedText= */ false));
+        bind(parseMessagingData(extras,
+                /* usePrecomputedText= */ false,
+                /*includeConversationIcon= */false));
     }
 
     @NonNull
-    private MessagingData parseMessagingData(Bundle extras, boolean usePrecomputedText) {
+    private MessagingData parseMessagingData(Bundle extras, boolean usePrecomputedText,
+            boolean includeConversationIcon) {
         Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
         List<Notification.MessagingStyle.Message> newMessages =
                 Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages);
@@ -438,8 +446,20 @@
         // Lets first find the groups (populate `groups` and `senders`)
         findGroups(newHistoricMessagingMessages, newMessagingMessages, user, groups, senders);
 
+        // load conversation header data, avatar and title.
+        final ConversationHeaderData conversationHeaderData;
+        if (includeConversationIcon && Flags.conversationStyleSetAvatarAsync()) {
+            conversationHeaderData = loadConversationHeaderData(mIsOneToOne,
+                    mConversationTitle,
+                    mShortcutIcon,
+                    mLargeIcon, newMessagingMessages, user, groups, mLayoutColor);
+        } else {
+            conversationHeaderData = null;
+        }
+
         return new MessagingData(user, showSpinner, unreadCount,
-                newHistoricMessagingMessages, newMessagingMessages, groups, senders);
+                newHistoricMessagingMessages, newMessagingMessages, groups, senders,
+                conversationHeaderData);
     }
 
     /**
@@ -457,7 +477,9 @@
         }
 
         final MessagingData messagingData =
-                parseMessagingData(extras, /* usePrecomputedText= */ true);
+                parseMessagingData(extras,
+                        /* usePrecomputedText= */ true,
+                        /*includeConversationIcon=*/true);
 
         return () -> {
             finalizeInflate(messagingData.getHistoricMessagingMessages());
@@ -536,11 +558,10 @@
 
         mMessages = messagingData.getNewMessagingMessages();
         mHistoricMessages = messagingData.getHistoricMessagingMessages();
-
         updateHistoricMessageVisibility();
         updateTitleAndNamesDisplay();
 
-        updateConversationLayout();
+        updateConversationLayout(messagingData);
 
         // Recycle everything at the end of the update, now that we know it's no longer needed.
         for (MessagingLinearLayout.MessagingChild child : mToRecycle) {
@@ -552,7 +573,31 @@
     /**
      * Update the layout according to the data provided (i.e mIsOneToOne, expanded etc);
      */
-    private void updateConversationLayout() {
+    private void updateConversationLayout(MessagingData messagingData) {
+        if (!Flags.conversationStyleSetAvatarAsync()) {
+            computeAndSetConversationAvatarAndName();
+        } else {
+            ConversationHeaderData conversationHeaderData =
+                    messagingData.getConversationHeaderData();
+            if (conversationHeaderData == null) {
+                conversationHeaderData = loadConversationHeaderData(mIsOneToOne,
+                        mConversationTitle, mShortcutIcon, mLargeIcon, mMessages, mUser,
+                        messagingData.getGroups(),
+                        mLayoutColor);
+            }
+            setConversationAvatarAndNameFromData(conversationHeaderData);
+        }
+
+        updateAppName();
+        updateIconPositionAndSize();
+        updateImageMessages();
+        updatePaddingsBasedOnContentAvailability();
+        updateActionListPadding();
+        updateAppNameDividerVisibility();
+    }
+
+    @Deprecated
+    private void computeAndSetConversationAvatarAndName() {
         // Set avatar and name
         CharSequence conversationText = mConversationTitle;
         mConversationIcon = mShortcutIcon;
@@ -603,12 +648,43 @@
         // Update if the groups can hide the sender if they are first (applies to 1:1 conversations)
         // This needs to happen after all of the above o update all of the groups
         mPeopleHelper.maybeHideFirstSenderName(mGroups, mIsOneToOne, conversationText);
-        updateAppName();
-        updateIconPositionAndSize();
-        updateImageMessages();
-        updatePaddingsBasedOnContentAvailability();
-        updateActionListPadding();
-        updateAppNameDividerVisibility();
+    }
+
+    private void setConversationAvatarAndNameFromData(
+            ConversationHeaderData conversationHeaderData) {
+        final OneToOneConversationAvatarData oneToOneConversationDrawable;
+        final GroupConversationAvatarData groupConversationAvatarData;
+        final ConversationAvatarData conversationAvatar =
+                conversationHeaderData.getConversationAvatar();
+        if (conversationAvatar instanceof OneToOneConversationAvatarData) {
+            oneToOneConversationDrawable =
+                    ((OneToOneConversationAvatarData) conversationAvatar);
+            groupConversationAvatarData = null;
+        } else {
+            oneToOneConversationDrawable = null;
+            groupConversationAvatarData = ((GroupConversationAvatarData) conversationAvatar);
+        }
+
+        if (oneToOneConversationDrawable != null) {
+            mConversationIconView.setVisibility(VISIBLE);
+            mConversationFacePile.setVisibility(GONE);
+            mConversationIconView.setImageDrawable(oneToOneConversationDrawable.mDrawable);
+        } else {
+            mConversationIconView.setVisibility(GONE);
+            // This will also inflate it!
+            mConversationFacePile.setVisibility(VISIBLE);
+            // rebind the value to the inflated view instead of the stub
+            mConversationFacePile = findViewById(R.id.conversation_face_pile);
+            bindFacePile(groupConversationAvatarData);
+        }
+        CharSequence conversationText = conversationHeaderData.getConversationText();
+        if (TextUtils.isEmpty(conversationText)) {
+            conversationText = mIsOneToOne ? mFallbackChatName : mFallbackGroupChatName;
+        }
+        mConversationText.setText(conversationText);
+        // Update if the groups can hide the sender if they are first (applies to 1:1 conversations)
+        // This needs to happen after all of the above o update all of the groups
+        mPeopleHelper.maybeHideFirstSenderName(mGroups, mIsOneToOne, conversationText);
     }
 
     private void updateActionListPadding() {
@@ -675,7 +751,12 @@
         topView.setImageIcon(secondLastIcon);
     }
 
+    @Deprecated
     private void bindFacePile() {
+        bindFacePile(null);
+    }
+
+    private void bindFacePile(@Nullable GroupConversationAvatarData groupConversationAvatarData) {
         ImageView bottomBackground = mConversationFacePile.findViewById(
                 R.id.conversation_face_pile_bottom_background);
         ImageView bottomView = mConversationFacePile.findViewById(
@@ -683,7 +764,13 @@
         ImageView topView = mConversationFacePile.findViewById(
                 R.id.conversation_face_pile_top);
 
-        bindFacePile(bottomBackground, bottomView, topView);
+        if (groupConversationAvatarData == null) {
+            bindFacePile(bottomBackground, bottomView, topView);
+        } else {
+            bindFacePileWithDrawable(bottomBackground, bottomView, topView,
+                    groupConversationAvatarData);
+
+        }
 
         int conversationAvatarSize;
         int facepileAvatarSize;
@@ -718,6 +805,13 @@
         bottomBackground.setLayoutParams(layoutParams);
     }
 
+    private void bindFacePileWithDrawable(ImageView bottomBackground, ImageView bottomView,
+            ImageView topView, GroupConversationAvatarData groupConversationAvatarData) {
+        applyNotificationBackgroundColor(bottomBackground);
+        bottomView.setImageDrawable(groupConversationAvatarData.mLastIcon);
+        topView.setImageDrawable(groupConversationAvatarData.mSecondLastIcon);
+    }
+
     private void updateAppName() {
         mAppName.setVisibility(mIsCollapsed ? GONE : VISIBLE);
     }
@@ -789,22 +883,62 @@
                 mMessagingLinearLayout.getPaddingBottom());
     }
 
+    /**
+     * async version of {@link ConversationLayout#setLargeIcon}
+     */
     @RemotableViewMethod
+    public Runnable setLargeIconAsync(Icon largeIcon) {
+        if (!Flags.conversationStyleSetAvatarAsync()) {
+            return () -> setLargeIcon(largeIcon);
+        }
+
+        mLargeIcon = largeIcon;
+        return NotificationRunnables.NOOP;
+    }
+
+    @RemotableViewMethod(asyncImpl = "setLargeIconAsync")
     public void setLargeIcon(Icon largeIcon) {
         mLargeIcon = largeIcon;
     }
 
+    /**
+     * async version of {@link ConversationLayout#setShortcutIcon}
+     */
     @RemotableViewMethod
+    public Runnable setShortcutIconAsync(Icon shortcutIcon) {
+        if (!Flags.conversationStyleSetAvatarAsync()) {
+            return () -> setShortcutIcon(shortcutIcon);
+        }
+
+        mShortcutIcon = shortcutIcon;
+        return NotificationRunnables.NOOP;
+    }
+
+    @RemotableViewMethod(asyncImpl = "setShortcutIconAsync")
     public void setShortcutIcon(Icon shortcutIcon) {
         mShortcutIcon = shortcutIcon;
     }
 
     /**
+     * async version of {@link ConversationLayout#setConversationTitle}
+     */
+    @RemotableViewMethod
+    public Runnable setConversationTitleAsync(CharSequence conversationTitle) {
+        if (!Flags.conversationStyleSetAvatarAsync()) {
+            return () -> setConversationTitle(conversationTitle);
+        }
+
+        // Remove formatting from the title.
+        mConversationTitle = conversationTitle != null ? conversationTitle.toString() : null;
+        return NotificationRunnables.NOOP;
+    }
+
+    /**
      * Sets the conversation title of this conversation.
      *
      * @param conversationTitle the conversation title
      */
-    @RemotableViewMethod
+    @RemotableViewMethod(asyncImpl = "setConversationTitleAsync")
     public void setConversationTitle(CharSequence conversationTitle) {
         // Remove formatting from the title.
         mConversationTitle = conversationTitle != null ? conversationTitle.toString() : null;
@@ -888,12 +1022,37 @@
         }
     }
 
+    /**
+     * async version of {@link ConversationLayout#setLayoutColor}
+     */
     @RemotableViewMethod
+    public Runnable setLayoutColorAsync(int color) {
+        if (!Flags.conversationStyleSetAvatarAsync()) {
+            return () -> setLayoutColor(color);
+        }
+
+        mLayoutColor = color;
+        return NotificationRunnables.NOOP;
+    }
+
+    @RemotableViewMethod(asyncImpl = "setLayoutColorAsync")
     public void setLayoutColor(int color) {
         mLayoutColor = color;
     }
 
+    /**
+     * async version of {@link ConversationLayout#setIsOneToOne}
+     */
     @RemotableViewMethod
+    public Runnable setIsOneToOneAsync(boolean oneToOne) {
+        if (!Flags.conversationStyleSetAvatarAsync()) {
+            return () -> setIsOneToOne(oneToOne);
+        }
+        mIsOneToOne = oneToOne;
+        return NotificationRunnables.NOOP;
+    }
+
+    @RemotableViewMethod(asyncImpl = "setIsOneToOneAsync")
     public void setIsOneToOne(boolean oneToOne) {
         mIsOneToOne = oneToOne;
     }
@@ -1022,6 +1181,125 @@
         return person == null ? null : person.getKey() == null ? person.getName() : person.getKey();
     }
 
+    private ConversationHeaderData loadConversationHeaderData(boolean isOneToOne,
+            CharSequence conversationTitle, Icon shortcutIcon, Icon largeIcon,
+            List<MessagingMessage> messages,
+            Person user,
+            List<List<MessagingMessage>> groups, int layoutColor) {
+        Icon conversationIcon = shortcutIcon;
+        CharSequence conversationText = conversationTitle;
+        final CharSequence userKey = getKey(user);
+        if (isOneToOne) {
+            for (int i = messages.size() - 1; i >= 0; i--) {
+                final Notification.MessagingStyle.Message message = messages.get(i).getMessage();
+                final Person sender = message.getSenderPerson();
+                final CharSequence senderKey = getKey(sender);
+                if ((sender != null && senderKey != userKey) || i == 0) {
+                    if (conversationText == null || conversationText.length() == 0) {
+                        conversationText = sender != null ? sender.getName() : "";
+                    }
+                    if (conversationIcon == null) {
+                        conversationIcon = sender != null ? sender.getIcon()
+                                : mPeopleHelper.createAvatarSymbol(conversationText, "",
+                                        layoutColor);
+                    }
+                    break;
+                }
+            }
+        }
+
+        if (conversationIcon == null) {
+            conversationIcon = largeIcon;
+        }
+
+        if (isOneToOne || conversationIcon != null) {
+            return new ConversationHeaderData(
+                    conversationText,
+                    new OneToOneConversationAvatarData(
+                            resolveAvatarImage(conversationIcon)));
+        }
+
+        final List<List<Notification.MessagingStyle.Message>> groupMessages = new ArrayList<>();
+        for (int i = 0; i < groups.size(); i++) {
+            final List<Notification.MessagingStyle.Message> groupMessage = new ArrayList<>();
+            for (int j = 0; j < groups.get(i).size(); j++) {
+                groupMessage.add(groups.get(i).get(j).getMessage());
+            }
+            groupMessages.add(groupMessage);
+        }
+
+        final PeopleHelper.NameToPrefixMap nameToPrefixMap =
+                mPeopleHelper.mapUniqueNamesToPrefixWithGroupList(groupMessages);
+
+        Icon lastIcon = null;
+        Icon secondLastIcon = null;
+
+        CharSequence lastKey = null;
+
+        for (int i = groups.size() - 1; i >= 0; i--) {
+            final Notification.MessagingStyle.Message message = groups.get(i).get(0).getMessage();
+            final Person sender =
+                    message.getSenderPerson() != null ? message.getSenderPerson() : user;
+            final CharSequence senderKey = getKey(sender);
+            final boolean notUser = senderKey != userKey;
+            final boolean notIncluded = senderKey != lastKey;
+
+            if ((notUser && notIncluded) || (i == 0 && lastKey == null)) {
+                if (lastIcon == null) {
+                    if (sender.getIcon() != null) {
+                        lastIcon = sender.getIcon();
+                    } else {
+                        final CharSequence senderName =
+                                sender.getName() != null ? sender.getName() : "";
+                        lastIcon = mPeopleHelper.createAvatarSymbol(
+                                senderName, nameToPrefixMap.getPrefix(senderName),
+                                layoutColor);
+                    }
+                    lastKey = senderKey;
+                } else {
+                    if (sender.getIcon() != null) {
+                        secondLastIcon = sender.getIcon();
+                    } else {
+                        final CharSequence senderName =
+                                sender.getName() != null ? sender.getName() : "";
+                        secondLastIcon = mPeopleHelper.createAvatarSymbol(
+                                senderName, nameToPrefixMap.getPrefix(senderName),
+                                layoutColor);
+                    }
+                    break;
+                }
+            }
+        }
+
+        if (lastIcon == null) {
+            lastIcon = mPeopleHelper.createAvatarSymbol(
+                    "", "", layoutColor);
+        }
+
+        if (secondLastIcon == null) {
+            secondLastIcon = mPeopleHelper.createAvatarSymbol(
+                    "", "", layoutColor);
+        }
+
+        return new ConversationHeaderData(
+                conversationText,
+                new GroupConversationAvatarData(resolveAvatarImage(lastIcon),
+                        resolveAvatarImage(secondLastIcon)));
+    }
+
+    /**
+     * {@link ImageResolver#loadImage(Uri)} accepts Uri to load images. However Conversation Avatars
+     * are received as Icon. So, we can't make use of ImageResolver.
+     */
+    @Nullable
+    private Drawable resolveAvatarImage(Icon conversationIcon) {
+        try {
+            return LocalImageResolver.resolveImage(conversationIcon, getContext());
+        } catch (Exception ex) {
+            return null;
+        }
+    }
+
     /**
      * Creates new messages, reusing existing ones if they are available.
      *
diff --git a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
index 918d9c0..1d0e972 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
+++ b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
@@ -39,6 +39,6 @@
     boolean hasStableIds();
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     boolean isCreated();
-    RemoteViews.RemoteCollectionItems getRemoteCollectionItems();
+    RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize);
 }
 
diff --git a/core/java/com/android/internal/widget/MessagingData.java b/core/java/com/android/internal/widget/MessagingData.java
index 42de60e..fb1f28f 100644
--- a/core/java/com/android/internal/widget/MessagingData.java
+++ b/core/java/com/android/internal/widget/MessagingData.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.widget;
 
+import android.annotation.Nullable;
 import android.app.Person;
 
 import java.util.List;
@@ -32,6 +33,8 @@
     private final List<Person> mSenders;
     private final int mUnreadCount;
 
+    private ConversationHeaderData mConversationHeaderData;
+
     MessagingData(Person user, boolean showSpinner,
             List<MessagingMessage> historicMessagingMessages,
             List<MessagingMessage> newMessagingMessages, List<List<MessagingMessage>> groups,
@@ -39,7 +42,7 @@
         this(user, showSpinner, /* unreadCount= */0,
                 historicMessagingMessages, newMessagingMessages,
                 groups,
-                senders);
+                senders, null);
     }
 
     MessagingData(Person user, boolean showSpinner,
@@ -47,7 +50,8 @@
             List<MessagingMessage> historicMessagingMessages,
             List<MessagingMessage> newMessagingMessages,
             List<List<MessagingMessage>> groups,
-            List<Person> senders) {
+            List<Person> senders,
+            @Nullable ConversationHeaderData conversationHeaderData) {
         mUser = user;
         mShowSpinner = showSpinner;
         mUnreadCount = unreadCount;
@@ -55,6 +59,7 @@
         mNewMessagingMessages = newMessagingMessages;
         mGroups = groups;
         mSenders = senders;
+        mConversationHeaderData = conversationHeaderData;
     }
 
     public Person getUser() {
@@ -84,4 +89,9 @@
     public List<List<MessagingMessage>> getGroups() {
         return mGroups;
     }
+
+    @Nullable
+    public ConversationHeaderData getConversationHeaderData() {
+        return mConversationHeaderData;
+    }
 }
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt b/core/java/com/android/internal/widget/NotificationRunnables.java
similarity index 81%
copy from packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
copy to core/java/com/android/internal/widget/NotificationRunnables.java
index 8ad0a08..cb7ae61 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
+++ b/core/java/com/android/internal/widget/NotificationRunnables.java
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel.component.mediaoutput
+package com.android.internal.widget;
 
-import dagger.Module
-
-@Module interface MediaOutputModule
+public final class NotificationRunnables {
+    public static final Runnable NOOP = () -> {
+    };
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
index b7cb392..7c9fda5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
@@ -92,7 +92,7 @@
         mIndex = 0;
         mStartingIndex = 0;
         mSize = 0;
-        if (expectedSize > mMaxSize) {
+        if (expectedSize >= mMaxSize) {
             resize(expectedSize);
         }
     }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 240028c..a0dc94f 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -93,6 +93,7 @@
     shared_libs: [
         "libbase",
         "libcutils",
+        "libtracing_perfetto",
         "libharfbuzz_ng",
         "liblog",
         "libminikin",
@@ -358,6 +359,7 @@
                 "libimage_io",
                 "libultrahdr",
                 "libperfetto_c",
+                "libtracing_perfetto",
             ],
             export_shared_lib_headers: [
                 // our headers include libnativewindow's public headers
@@ -400,6 +402,7 @@
                 "libbinary_parse",
                 "libdng_sdk",
                 "libft2",
+                "libhostgraphics",
                 "libhwui",
                 "libimage_type_recognition",
                 "libjpeg",
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp
index 01e9f43..83b6afa 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/LayoutlibLoader.cpp
@@ -196,14 +196,6 @@
     return result;
 }
 
-static vector<string> parseCsv(JNIEnv* env, jstring csvJString) {
-    const char* charArray = env->GetStringUTFChars(csvJString, 0);
-    string csvString(charArray);
-    vector<string> result = parseCsv(csvString);
-    env->ReleaseStringUTFChars(csvJString, charArray);
-    return result;
-}
-
 void LayoutlibLogger(base::LogId, base::LogSeverity severity, const char* tag, const char* file,
                      unsigned int line, const char* message) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -362,6 +354,22 @@
 
 using namespace android;
 
+// Called right before aborting by LOG_ALWAYS_FATAL. Print the pending exception.
+void abort_handler(const char* abort_message) {
+    ALOGE("About to abort the process...");
+
+    JNIEnv* env = NULL;
+    if (javaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        ALOGE("vm->GetEnv() failed");
+        return;
+    }
+    if (env->ExceptionOccurred() != NULL) {
+        ALOGE("Pending exception:");
+        env->ExceptionDescribe();
+    }
+    ALOGE("Aborting because: %s", abort_message);
+}
+
 JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
     javaVM = vm;
     JNIEnv* env = nullptr;
@@ -369,6 +377,8 @@
         return JNI_ERR;
     }
 
+    __android_log_set_aborter(abort_handler);
+
     init_android_graphics();
 
     // Configuration is stored as java System properties.
@@ -377,20 +387,28 @@
     jmethodID getPropertyMethod = GetStaticMethodIDOrDie(env, system, "getProperty",
                                                          "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
 
-    // Get the names of classes that need to register their native methods
-    auto nativesClassesJString =
-            (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
-                                                 env->NewStringUTF("core_native_classes"),
-                                                 env->NewStringUTF(""));
-    vector<string> classesToRegister = parseCsv(env, nativesClassesJString);
+    // Java system properties that contain LayoutLib config. The initial values in the map
+    // are the default values if the property is not specified.
+    std::unordered_map<std::string, std::string> systemProperties =
+            {{"core_native_classes", ""},
+             {"register_properties_during_load", ""},
+             {"icu.data.path", ""},
+             {"use_bridge_for_logging", ""},
+             {"keyboard_paths", ""}};
 
-    jstring registerProperty =
-            (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
-                                                 env->NewStringUTF(
-                                                         "register_properties_during_load"),
-                                                 env->NewStringUTF(""));
-    const char* registerPropertyString = env->GetStringUTFChars(registerProperty, 0);
-    if (strcmp(registerPropertyString, "true") == 0) {
+    for (auto& [name, defaultValue] : systemProperties) {
+        jstring propertyString =
+                (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
+                                                     env->NewStringUTF(name.c_str()),
+                                                     env->NewStringUTF(defaultValue.c_str()));
+        const char* propertyChars = env->GetStringUTFChars(propertyString, 0);
+        systemProperties[name] = string(propertyChars);
+        env->ReleaseStringUTFChars(propertyString, propertyChars);
+    }
+    // Get the names of classes that need to register their native methods
+    vector<string> classesToRegister = parseCsv(systemProperties["core_native_classes"]);
+
+    if (systemProperties["register_properties_during_load"] == "true") {
         // Set the system properties first as they could be used in the static initialization of
         // other classes
         if (register_android_os_SystemProperties(env) < 0) {
@@ -405,35 +423,20 @@
         env->CallStaticVoidMethod(bridge, setSystemPropertiesMethod);
         property_initialize_ro_cpu_abilist();
     }
-    env->ReleaseStringUTFChars(registerProperty, registerPropertyString);
 
     if (register_jni_procs(gRegJNIMap, classesToRegister, env) < 0) {
         return JNI_ERR;
     }
 
-    // Set the location of ICU data
-    auto stringPath = (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
-                                                           env->NewStringUTF("icu.data.path"),
-                                                           env->NewStringUTF(""));
-    const char* path = env->GetStringUTFChars(stringPath, 0);
-
-    if (strcmp(path, "**n/a**") != 0) {
-        bool icuInitialized = init_icu(path);
+    if (!systemProperties["icu.data.path"].empty()) {
+        // Set the location of ICU data
+        bool icuInitialized = init_icu(systemProperties["icu.data.path"].c_str());
         if (!icuInitialized) {
-            fprintf(stderr, "Failed to initialize ICU\n");
             return JNI_ERR;
         }
-    } else {
-        fprintf(stderr, "Skip initializing ICU\n");
     }
-    env->ReleaseStringUTFChars(stringPath, path);
 
-    jstring useJniProperty =
-            (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
-                                                 env->NewStringUTF("use_bridge_for_logging"),
-                                                 env->NewStringUTF(""));
-    const char* useJniString = env->GetStringUTFChars(useJniProperty, 0);
-    if (strcmp(useJniString, "true") == 0) {
+    if (systemProperties["use_bridge_for_logging"] == "true") {
         layoutLog = FindClassOrDie(env, "com/android/ide/common/rendering/api/ILayoutLog");
         layoutLog = MakeGlobalRefOrDie(env, layoutLog);
         logMethodId = GetMethodIDOrDie(env, layoutLog, "logAndroidFramework",
@@ -450,23 +453,16 @@
         // initialize logging, so ANDROD_LOG_TAGS env variable is respected
         android::base::InitLogging(nullptr, android::base::StderrLogger);
     }
-    env->ReleaseStringUTFChars(useJniProperty, useJniString);
 
     // Use English locale for number format to ensure correct parsing of floats when using strtof
     setlocale(LC_NUMERIC, "en_US.UTF-8");
 
-    auto keyboardPathsJString =
-            (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
-                                                 env->NewStringUTF("keyboard_paths"),
-                                                 env->NewStringUTF(""));
-    const char* keyboardPathsString = env->GetStringUTFChars(keyboardPathsJString, 0);
-    if (strcmp(keyboardPathsString, "**n/a**") != 0) {
-        vector<string> keyboardPaths = parseCsv(env, keyboardPathsJString);
+    if (!systemProperties["keyboard_paths"].empty()) {
+        vector<string> keyboardPaths = parseCsv(systemProperties["keyboard_paths"]);
         init_keyboard(env, keyboardPaths);
     } else {
         fprintf(stderr, "Skip initializing keyboard\n");
     }
-    env->ReleaseStringUTFChars(keyboardPathsJString, keyboardPathsString);
 
     return JNI_VERSION_1_6;
 }
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 070d07c..5223798 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -18,6 +18,8 @@
 //#define LOG_NDEBUG 0
 
 #define LOG_TAG "AudioSystem-JNI"
+#include <android/binder_ibinder_jni.h>
+#include <android/binder_libbinder.h>
 #include <android/media/AudioVibratorInfo.h>
 #include <android/media/INativeSpatializerCallback.h>
 #include <android/media/ISpatializer.h>
@@ -25,6 +27,7 @@
 #include <android_media_audiopolicy.h>
 #include <android_os_Parcel.h>
 #include <audiomanager/AudioManager.h>
+#include <binder/IBinder.h>
 #include <jni.h>
 #include <media/AidlConversion.h>
 #include <media/AudioContainers.h>
@@ -150,13 +153,14 @@
 static jclass gAudioMixClass;
 static jmethodID gAudioMixCstor;
 static struct {
-    jfieldID    mRule;
-    jfieldID    mFormat;
-    jfieldID    mRouteFlags;
-    jfieldID    mDeviceType;
-    jfieldID    mDeviceAddress;
-    jfieldID    mMixType;
-    jfieldID    mCallbackFlags;
+    jfieldID mRule;
+    jfieldID mFormat;
+    jfieldID mRouteFlags;
+    jfieldID mDeviceType;
+    jfieldID mDeviceAddress;
+    jfieldID mMixType;
+    jfieldID mCallbackFlags;
+    jfieldID mToken;
 } gAudioMixFields;
 
 static jclass gAudioFormatClass;
@@ -2300,11 +2304,15 @@
     if (status != AUDIO_JAVA_SUCCESS) {
         return status;
     }
+    std::unique_ptr<AIBinder, decltype(&AIBinder_decStrong)> aiBinder(AIBinder_fromPlatformBinder(
+                                                                              nAudioMix.mToken),
+                                                                      &AIBinder_decStrong);
+    jobject jBinderToken = AIBinder_toJavaBinder(env, aiBinder.get());
 
     jstring deviceAddress = env->NewStringUTF(nAudioMix.mDeviceAddress.c_str());
     *jAudioMix = env->NewObject(gAudioMixClass, gAudioMixCstor, jAudioMixingRule, jAudioFormat,
                                 nAudioMix.mRouteFlags, nAudioMix.mCbFlags, nAudioMix.mDeviceType,
-                                deviceAddress);
+                                deviceAddress, jBinderToken);
     return AUDIO_JAVA_SUCCESS;
 }
 
@@ -2333,6 +2341,12 @@
     nAudioMix->mVoiceCommunicationCaptureAllowed =
             env->GetBooleanField(jRule, gAudioMixingRuleFields.mVoiceCommunicationCaptureAllowed);
 
+    jobject jToken = env->GetObjectField(jAudioMix, gAudioMixFields.mToken);
+
+    std::unique_ptr<AIBinder, decltype(&AIBinder_decStrong)>
+            aiBinder(AIBinder_fromJavaBinder(env, jToken), &AIBinder_decStrong);
+    nAudioMix->mToken = AIBinder_toPlatformBinder(aiBinder.get());
+
     jint status = convertAudioMixingRuleToNative(env, jRule, &(nAudioMix->mCriteria));
 
     env->DeleteLocalRef(jRule);
@@ -3659,9 +3673,10 @@
     jclass audioMixClass = FindClassOrDie(env, "android/media/audiopolicy/AudioMix");
     gAudioMixClass = MakeGlobalRefOrDie(env, audioMixClass);
     if (audio_flags::audio_mix_test_api()) {
-        gAudioMixCstor = GetMethodIDOrDie(env, audioMixClass, "<init>",
-                                          "(Landroid/media/audiopolicy/AudioMixingRule;Landroid/"
-                                          "media/AudioFormat;IIILjava/lang/String;)V");
+        gAudioMixCstor =
+                GetMethodIDOrDie(env, audioMixClass, "<init>",
+                                 "(Landroid/media/audiopolicy/AudioMixingRule;Landroid/"
+                                 "media/AudioFormat;IIILjava/lang/String;Landroid/os/IBinder;)V");
     }
     gAudioMixFields.mRule = GetFieldIDOrDie(env, audioMixClass, "mRule",
                                                 "Landroid/media/audiopolicy/AudioMixingRule;");
@@ -3673,6 +3688,7 @@
                                                       "Ljava/lang/String;");
     gAudioMixFields.mMixType = GetFieldIDOrDie(env, audioMixClass, "mMixType", "I");
     gAudioMixFields.mCallbackFlags = GetFieldIDOrDie(env, audioMixClass, "mCallbackFlags", "I");
+    gAudioMixFields.mToken = GetFieldIDOrDie(env, audioMixClass, "mToken", "Landroid/os/IBinder;");
 
     jclass audioFormatClass = FindClassOrDie(env, "android/media/AudioFormat");
     gAudioFormatClass = MakeGlobalRefOrDie(env, audioFormatClass);
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 1504a00..a98f947 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -410,6 +410,7 @@
             stats[sub_heap].swappedOut += usage.swap;
             stats[sub_heap].swappedOutPss += usage.swap_pss;
         }
+        return true;
     };
 
     return meminfo::ForEachVmaFromFile(smaps_path, vma_scan);
diff --git a/core/jni/android_os_Trace.cpp b/core/jni/android_os_Trace.cpp
index ffacd9c..b579daf 100644
--- a/core/jni/android_os_Trace.cpp
+++ b/core/jni/android_os_Trace.cpp
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
+#include <cutils/compiler.h>
 #include <jni.h>
-
-#include <cutils/trace.h>
 #include <log/log.h>
 #include <nativehelper/JNIHelp.h>
+#include <tracing_perfetto.h>
 
 #include <array>
 
@@ -59,33 +59,30 @@
 
 static void android_os_Trace_nativeTraceCounter(JNIEnv* env, jclass,
         jlong tag, jstring nameStr, jlong value) {
-    withString(env, nameStr, [tag, value](const char* str) {
-        atrace_int64(tag, str, value);
-    });
+    withString(env, nameStr,
+               [tag, value](const char* str) { tracing_perfetto::traceCounter(tag, str, value); });
 }
 
 static void android_os_Trace_nativeTraceBegin(JNIEnv* env, jclass,
         jlong tag, jstring nameStr) {
-    withString(env, nameStr, [tag](const char* str) {
-        atrace_begin(tag, str);
-    });
+    withString(env, nameStr, [tag](const char* str) { tracing_perfetto::traceBegin(tag, str); });
 }
 
 static void android_os_Trace_nativeTraceEnd(JNIEnv*, jclass, jlong tag) {
-    atrace_end(tag);
+    tracing_perfetto::traceEnd(tag);
 }
 
 static void android_os_Trace_nativeAsyncTraceBegin(JNIEnv* env, jclass,
         jlong tag, jstring nameStr, jint cookie) {
     withString(env, nameStr, [tag, cookie](const char* str) {
-        atrace_async_begin(tag, str, cookie);
+        tracing_perfetto::traceAsyncBegin(tag, str, cookie);
     });
 }
 
 static void android_os_Trace_nativeAsyncTraceEnd(JNIEnv* env, jclass,
         jlong tag, jstring nameStr, jint cookie) {
     withString(env, nameStr, [tag, cookie](const char* str) {
-        atrace_async_end(tag, str, cookie);
+        tracing_perfetto::traceAsyncEnd(tag, str, cookie);
     });
 }
 
@@ -93,7 +90,7 @@
         jlong tag, jstring trackStr, jstring nameStr, jint cookie) {
     withString(env, trackStr, [env, tag, nameStr, cookie](const char* track) {
         withString(env, nameStr, [tag, track, cookie](const char* name) {
-            atrace_async_for_track_begin(tag, track, name, cookie);
+            tracing_perfetto::traceAsyncBeginForTrack(tag, name, track, cookie);
         });
     });
 }
@@ -101,77 +98,66 @@
 static void android_os_Trace_nativeAsyncTraceForTrackEnd(JNIEnv* env, jclass,
         jlong tag, jstring trackStr, jint cookie) {
     withString(env, trackStr, [tag, cookie](const char* track) {
-        atrace_async_for_track_end(tag, track, cookie);
+        tracing_perfetto::traceAsyncEndForTrack(tag, track, cookie);
     });
 }
 
 static void android_os_Trace_nativeSetAppTracingAllowed(JNIEnv*, jclass, jboolean allowed) {
-    atrace_update_tags();
+    // no-op
 }
 
 static void android_os_Trace_nativeSetTracingEnabled(JNIEnv*, jclass, jboolean enabled) {
-    atrace_set_tracing_enabled(enabled);
+    // no-op
 }
 
 static void android_os_Trace_nativeInstant(JNIEnv* env, jclass,
         jlong tag, jstring nameStr) {
-    withString(env, nameStr, [tag](const char* str) {
-        atrace_instant(tag, str);
-    });
+    withString(env, nameStr, [tag](const char* str) { tracing_perfetto::traceInstant(tag, str); });
 }
 
 static void android_os_Trace_nativeInstantForTrack(JNIEnv* env, jclass,
         jlong tag, jstring trackStr, jstring nameStr) {
     withString(env, trackStr, [env, tag, nameStr](const char* track) {
         withString(env, nameStr, [tag, track](const char* name) {
-            atrace_instant_for_track(tag, track, name);
+            tracing_perfetto::traceInstantForTrack(tag, track, name);
         });
     });
 }
 
+static jlong android_os_Trace_nativeGetEnabledTags(JNIEnv* env) {
+    return tracing_perfetto::getEnabledCategories();
+}
+
+static void android_os_Trace_nativeRegisterWithPerfetto(JNIEnv* env) {
+    tracing_perfetto::registerWithPerfetto();
+}
+
 static const JNINativeMethod gTraceMethods[] = {
-    /* name, signature, funcPtr */
-    { "nativeSetAppTracingAllowed",
-            "(Z)V",
-            (void*)android_os_Trace_nativeSetAppTracingAllowed },
-    { "nativeSetTracingEnabled",
-            "(Z)V",
-            (void*)android_os_Trace_nativeSetTracingEnabled },
+        /* name, signature, funcPtr */
+        {"nativeSetAppTracingAllowed", "(Z)V", (void*)android_os_Trace_nativeSetAppTracingAllowed},
+        {"nativeSetTracingEnabled", "(Z)V", (void*)android_os_Trace_nativeSetTracingEnabled},
 
-    // ----------- @FastNative  ----------------
+        // ----------- @FastNative  ----------------
 
-    { "nativeTraceCounter",
-            "(JLjava/lang/String;J)V",
-            (void*)android_os_Trace_nativeTraceCounter },
-    { "nativeTraceBegin",
-            "(JLjava/lang/String;)V",
-            (void*)android_os_Trace_nativeTraceBegin },
-    { "nativeTraceEnd",
-            "(J)V",
-            (void*)android_os_Trace_nativeTraceEnd },
-    { "nativeAsyncTraceBegin",
-            "(JLjava/lang/String;I)V",
-            (void*)android_os_Trace_nativeAsyncTraceBegin },
-    { "nativeAsyncTraceEnd",
-            "(JLjava/lang/String;I)V",
-            (void*)android_os_Trace_nativeAsyncTraceEnd },
-    { "nativeAsyncTraceForTrackBegin",
-            "(JLjava/lang/String;Ljava/lang/String;I)V",
-            (void*)android_os_Trace_nativeAsyncTraceForTrackBegin },
-    { "nativeAsyncTraceForTrackEnd",
-            "(JLjava/lang/String;I)V",
-            (void*)android_os_Trace_nativeAsyncTraceForTrackEnd },
-    { "nativeInstant",
-            "(JLjava/lang/String;)V",
-            (void*)android_os_Trace_nativeInstant },
-    { "nativeInstantForTrack",
-            "(JLjava/lang/String;Ljava/lang/String;)V",
-            (void*)android_os_Trace_nativeInstantForTrack },
+        {"nativeTraceCounter", "(JLjava/lang/String;J)V",
+         (void*)android_os_Trace_nativeTraceCounter},
+        {"nativeTraceBegin", "(JLjava/lang/String;)V", (void*)android_os_Trace_nativeTraceBegin},
+        {"nativeTraceEnd", "(J)V", (void*)android_os_Trace_nativeTraceEnd},
+        {"nativeAsyncTraceBegin", "(JLjava/lang/String;I)V",
+         (void*)android_os_Trace_nativeAsyncTraceBegin},
+        {"nativeAsyncTraceEnd", "(JLjava/lang/String;I)V",
+         (void*)android_os_Trace_nativeAsyncTraceEnd},
+        {"nativeAsyncTraceForTrackBegin", "(JLjava/lang/String;Ljava/lang/String;I)V",
+         (void*)android_os_Trace_nativeAsyncTraceForTrackBegin},
+        {"nativeAsyncTraceForTrackEnd", "(JLjava/lang/String;I)V",
+         (void*)android_os_Trace_nativeAsyncTraceForTrackEnd},
+        {"nativeInstant", "(JLjava/lang/String;)V", (void*)android_os_Trace_nativeInstant},
+        {"nativeInstantForTrack", "(JLjava/lang/String;Ljava/lang/String;)V",
+         (void*)android_os_Trace_nativeInstantForTrack},
+        {"nativeRegisterWithPerfetto", "()V", (void*)android_os_Trace_nativeRegisterWithPerfetto},
 
-    // ----------- @CriticalNative  ----------------
-    { "nativeGetEnabledTags",
-            "()J",
-            (void*)atrace_get_enabled_tags },
+        // ----------- @CriticalNative  ----------------
+        {"nativeGetEnabledTags", "()J", (void*)android_os_Trace_nativeGetEnabledTags},
 };
 
 int register_android_os_Trace(JNIEnv* env) {
diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp
index b6bf617..7c976b7 100644
--- a/core/jni/android_text_Hyphenator.cpp
+++ b/core/jni/android_text_Hyphenator.cpp
@@ -36,41 +36,43 @@
     return SYSTEM_HYPHENATOR_PREFIX + lowerLocale + SYSTEM_HYPHENATOR_SUFFIX;
 }
 
-static const uint8_t* mmapPatternFile(const std::string& locale) {
+static std::pair<const uint8_t*, uint32_t> mmapPatternFile(const std::string& locale) {
     const std::string hyFilePath = buildFileName(locale);
     const int fd = open(hyFilePath.c_str(), O_RDONLY | O_CLOEXEC);
     if (fd == -1) {
-        return nullptr;  // Open failed.
+        return std::make_pair(nullptr, 0); // Open failed.
     }
 
     struct stat st = {};
     if (fstat(fd, &st) == -1) {  // Unlikely to happen.
         close(fd);
-        return nullptr;
+        return std::make_pair(nullptr, 0);
     }
 
     void* ptr = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0 /* offset */);
     close(fd);
     if (ptr == MAP_FAILED) {
-        return nullptr;
+        return std::make_pair(nullptr, 0);
     }
-    return reinterpret_cast<const uint8_t*>(ptr);
+    return std::make_pair(reinterpret_cast<const uint8_t*>(ptr), st.st_size);
 }
 
 static void addHyphenatorWithoutPatternFile(const std::string& locale, int minPrefix,
         int minSuffix) {
-    minikin::addHyphenator(locale, minikin::Hyphenator::loadBinary(
-            nullptr, minPrefix, minSuffix, locale));
+    minikin::addHyphenator(locale,
+                           minikin::Hyphenator::loadBinary(nullptr, 0, minPrefix, minSuffix,
+                                                           locale));
 }
 
 static void addHyphenator(const std::string& locale, int minPrefix, int minSuffix) {
-    const uint8_t* ptr = mmapPatternFile(locale);
+    auto [ptr, size] = mmapPatternFile(locale);
     if (ptr == nullptr) {
         ALOGE("Unable to find pattern file or unable to map it for %s", locale.c_str());
         return;
     }
-    minikin::addHyphenator(locale, minikin::Hyphenator::loadBinary(
-            ptr, minPrefix, minSuffix, locale));
+    minikin::addHyphenator(locale,
+                           minikin::Hyphenator::loadBinary(ptr, size, minPrefix, minSuffix,
+                                                           locale));
 }
 
 static void addHyphenatorAlias(const std::string& from, const std::string& to) {
diff --git a/core/jni/android_tracing_PerfettoDataSource.cpp b/core/jni/android_tracing_PerfettoDataSource.cpp
index 16c3ca9..1eff5ce 100644
--- a/core/jni/android_tracing_PerfettoDataSource.cpp
+++ b/core/jni/android_tracing_PerfettoDataSource.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "Perfetto"
+#define LOG_TAG "NativeJavaPerfettoDs"
 
 #include "android_tracing_PerfettoDataSource.h"
 
@@ -166,16 +166,25 @@
 
 void PerfettoDataSource::trace(JNIEnv* env, jobject traceFunction) {
     PERFETTO_DS_TRACE(dataSource, ctx) {
+        ALOG(LOG_DEBUG, LOG_TAG, "\tin native trace callback function %p", this);
         TlsState* tls_state =
                 reinterpret_cast<TlsState*>(PerfettoDsGetCustomTls(&dataSource, &ctx));
         IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(
                 PerfettoDsGetIncrementalState(&dataSource, &ctx));
 
+        ALOG(LOG_DEBUG, LOG_TAG, "\t tls_state = %p", tls_state);
+        ALOG(LOG_DEBUG, LOG_TAG, "\t incr_state = %p", incr_state);
+
+        ALOG(LOG_DEBUG, LOG_TAG, "\t tls_state->jobj = %p", tls_state->jobj);
+        ALOG(LOG_DEBUG, LOG_TAG, "\t incr_state->jobj = %p", incr_state->jobj);
+
         ScopedLocalRef<jobject> jCtx(env,
                                      env->NewObject(gTracingContextClassInfo.clazz,
                                                     gTracingContextClassInfo.init, &ctx,
                                                     tls_state->jobj, incr_state->jobj));
 
+        ALOG(LOG_DEBUG, LOG_TAG, "\t jCtx = %p", jCtx.get());
+
         jclass objclass = env->GetObjectClass(traceFunction);
         jmethodID method =
                 env->GetMethodID(objclass, "trace", "(Landroid/tracing/perfetto/TracingContext;)V");
@@ -209,7 +218,9 @@
 
 jlong nativeCreate(JNIEnv* env, jclass clazz, jobject javaDataSource, jstring name) {
     const char* nativeString = env->GetStringUTFChars(name, 0);
+    ALOG(LOG_DEBUG, LOG_TAG, "nativeCreate(%p, %s)", javaDataSource, nativeString);
     PerfettoDataSource* dataSource = new PerfettoDataSource(env, javaDataSource, nativeString);
+    ALOG(LOG_DEBUG, LOG_TAG, "\tdatasource* = %p", dataSource);
     env->ReleaseStringUTFChars(name, nativeString);
 
     dataSource->incStrong((void*)nativeCreate);
@@ -218,33 +229,39 @@
 }
 
 void nativeDestroy(void* ptr) {
+    ALOG(LOG_DEBUG, LOG_TAG, "nativeCreate(%p)", ptr);
     PerfettoDataSource* dataSource = reinterpret_cast<PerfettoDataSource*>(ptr);
     dataSource->decStrong((void*)nativeCreate);
 }
 
 static jlong nativeGetFinalizer(JNIEnv* /* env */, jclass /* clazz */) {
+    ALOG(LOG_DEBUG, LOG_TAG, "nativeGetFinalizer()");
     return static_cast<jlong>(reinterpret_cast<uintptr_t>(&nativeDestroy));
 }
 
 void nativeTrace(JNIEnv* env, jclass clazz, jlong dataSourcePtr, jobject traceFunctionInterface) {
+    ALOG(LOG_DEBUG, LOG_TAG, "nativeTrace(%p)", (void*)dataSourcePtr);
     sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
 
     datasource->trace(env, traceFunctionInterface);
 }
 
 void nativeFlush(JNIEnv* env, jclass clazz, jobject jCtx, jlong ctxPtr) {
+    ALOG(LOG_DEBUG, LOG_TAG, "nativeFlush(%p, %p)", jCtx, (void*)ctxPtr);
     auto* ctx = reinterpret_cast<struct PerfettoDsTracerIterator*>(ctxPtr);
     traceAllPendingPackets(env, jCtx, ctx);
     PerfettoDsTracerFlush(ctx, nullptr, nullptr);
 }
 
 void nativeFlushAll(JNIEnv* env, jclass clazz, jlong ptr) {
+    ALOG(LOG_DEBUG, LOG_TAG, "nativeFlushAll(%p)", (void*)ptr);
     sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(ptr);
     datasource->flushAll();
 }
 
 void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr,
                               int buffer_exhausted_policy) {
+    ALOG(LOG_DEBUG, LOG_TAG, "nativeRegisterDataSource(%p)", (void*)datasource_ptr);
     sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(datasource_ptr);
 
     struct PerfettoDsParams params = PerfettoDsParamsDefault();
@@ -267,6 +284,9 @@
         auto* datasource_instance =
                 new PerfettoDataSourceInstance(env, java_data_source_instance.get(), inst_id);
 
+        ALOG(LOG_DEBUG, LOG_TAG, "on_setup_cb ds=%p, ds_instance=%p", datasource,
+             datasource_instance);
+
         return static_cast<void*>(datasource_instance);
     };
 
@@ -280,6 +300,8 @@
 
         auto* tls_state = new TlsState(java_tls_state);
 
+        ALOG(LOG_DEBUG, LOG_TAG, "on_create_tls_cb ds=%p, tsl_state=%p", datasource, tls_state);
+
         return static_cast<void*>(tls_state);
     };
 
@@ -287,6 +309,9 @@
         JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
 
         TlsState* tls_state = reinterpret_cast<TlsState*>(ptr);
+
+        ALOG(LOG_DEBUG, LOG_TAG, "on_delete_tls_cb %p", tls_state);
+
         env->DeleteGlobalRef(tls_state->jobj);
         delete tls_state;
     };
@@ -299,6 +324,9 @@
         jobject java_incr_state = datasource->createIncrementalStateGlobalRef(env, inst_id);
 
         auto* incr_state = new IncrementalState(java_incr_state);
+
+        ALOG(LOG_DEBUG, LOG_TAG, "on_create_incr_cb ds=%p, incr_state=%p", datasource, incr_state);
+
         return static_cast<void*>(incr_state);
     };
 
@@ -306,6 +334,9 @@
         JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
 
         IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(ptr);
+
+        ALOG(LOG_DEBUG, LOG_TAG, "on_delete_incr_cb incr_state=%p", incr_state);
+
         env->DeleteGlobalRef(incr_state->jobj);
         delete incr_state;
     };
@@ -315,6 +346,9 @@
         JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
 
         auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+
+        ALOG(LOG_DEBUG, LOG_TAG, "on_start_cb ds_instance=%p", datasource_instance);
+
         datasource_instance->onStart(env);
     };
 
@@ -323,6 +357,9 @@
         JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
 
         auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+
+        ALOG(LOG_DEBUG, LOG_TAG, "on_flush_cb ds_instance=%p", datasource_instance);
+
         datasource_instance->onFlush(env);
     };
 
@@ -331,12 +368,18 @@
         JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
 
         auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+
+        ALOG(LOG_DEBUG, LOG_TAG, "on_stop_cb ds_instance=%p", datasource_instance);
+
         datasource_instance->onStop(env);
     };
 
     params.on_destroy_cb = [](struct PerfettoDsImpl* ds_impl, void* user_arg,
                               void* inst_ctx) -> void {
         auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+
+        ALOG(LOG_DEBUG, LOG_TAG, "on_destroy_cb ds_instance=%p", datasource_instance);
+
         delete datasource_instance;
     };
 
@@ -345,20 +388,28 @@
 
 jobject nativeGetPerfettoInstanceLocked(JNIEnv* env, jclass clazz, jlong dataSourcePtr,
                                         PerfettoDsInstanceIndex instance_idx) {
+    ALOG(LOG_DEBUG, LOG_TAG, "nativeGetPerfettoInstanceLocked ds=%p, idx=%d", (void*)dataSourcePtr,
+         instance_idx);
     sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
     auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(
             PerfettoDsImplGetInstanceLocked(datasource->dataSource.impl, instance_idx));
 
     if (datasource_instance == nullptr) {
         // datasource instance doesn't exist
+        ALOG(LOG_WARN, LOG_TAG,
+             "DS instance invalid!! nativeGetPerfettoInstanceLocked returning NULL");
         return nullptr;
     }
 
+    ALOG(LOG_DEBUG, LOG_TAG, "\tnativeGetPerfettoInstanceLocked got lock ds=%p, idx=%d",
+         (void*)dataSourcePtr, instance_idx);
     return datasource_instance->GetJavaDataSourceInstance();
 }
 
 void nativeReleasePerfettoInstanceLocked(JNIEnv* env, jclass clazz, jlong dataSourcePtr,
                                          PerfettoDsInstanceIndex instance_idx) {
+    ALOG(LOG_DEBUG, LOG_TAG, "nativeReleasePerfettoInstanceLocked got lock ds=%p, idx=%d",
+         (void*)dataSourcePtr, instance_idx);
     sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
     PerfettoDsImplReleaseInstanceLocked(datasource->dataSource.impl, instance_idx);
 }
diff --git a/core/jni/android_tracing_PerfettoDataSource.h b/core/jni/android_tracing_PerfettoDataSource.h
index 61a7654..906d9f5 100644
--- a/core/jni/android_tracing_PerfettoDataSource.h
+++ b/core/jni/android_tracing_PerfettoDataSource.h
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "Perfetto"
-
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
 #include <nativehelper/JNIHelp.h>
diff --git a/core/jni/android_tracing_PerfettoDataSourceInstance.h b/core/jni/android_tracing_PerfettoDataSourceInstance.h
index ebb5259..be71cbb 100644
--- a/core/jni/android_tracing_PerfettoDataSourceInstance.h
+++ b/core/jni/android_tracing_PerfettoDataSourceInstance.h
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "Perfetto"
-
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
 #include <nativehelper/JNIHelp.h>
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index b39d5cf..1a86363 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -411,8 +411,8 @@
             jniThrowNullPointerException(env, "pointerCoords");
             return;
         }
-        pointerCoordsToNative(env, pointerCoordsObj,
-                event->getXOffset(), event->getYOffset(), &rawPointerCoords[i]);
+        pointerCoordsToNative(env, pointerCoordsObj, event->getRawXOffset(), event->getRawYOffset(),
+                              &rawPointerCoords[i]);
         env->DeleteLocalRef(pointerCoordsObj);
     }
 
@@ -622,6 +622,18 @@
     return reinterpret_cast<jlong>(destEvent);
 }
 
+static jlong android_view_MotionEvent_nativeSplit(jlong destNativePtr, jlong sourceNativePtr,
+                                                  jint idBits) {
+    MotionEvent* destEvent = reinterpret_cast<MotionEvent*>(destNativePtr);
+    if (!destEvent) {
+        destEvent = new MotionEvent();
+    }
+    MotionEvent* sourceEvent = reinterpret_cast<MotionEvent*>(sourceNativePtr);
+    destEvent->splitFrom(*sourceEvent, static_cast<std::bitset<MAX_POINTER_ID + 1>>(idBits),
+                         InputEvent::nextId());
+    return reinterpret_cast<jlong>(destEvent);
+}
+
 static jint android_view_MotionEvent_nativeGetId(jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getId();
@@ -723,14 +735,14 @@
     return event->offsetLocation(deltaX, deltaY);
 }
 
-static jfloat android_view_MotionEvent_nativeGetXOffset(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetRawXOffset(jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
-    return event->getXOffset();
+    return event->getRawXOffset();
 }
 
-static jfloat android_view_MotionEvent_nativeGetYOffset(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetRawYOffset(jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
-    return event->getYOffset();
+    return event->getRawYOffset();
 }
 
 static jfloat android_view_MotionEvent_nativeGetXPrecision(jlong nativePtr) {
@@ -837,6 +849,7 @@
         // --------------- @CriticalNative ------------------
 
         {"nativeCopy", "(JJZ)J", (void*)android_view_MotionEvent_nativeCopy},
+        {"nativeSplit", "(JJI)J", (void*)android_view_MotionEvent_nativeSplit},
         {"nativeGetId", "(J)I", (void*)android_view_MotionEvent_nativeGetId},
         {"nativeGetDeviceId", "(J)I", (void*)android_view_MotionEvent_nativeGetDeviceId},
         {"nativeGetSource", "(J)I", (void*)android_view_MotionEvent_nativeGetSource},
@@ -858,8 +871,8 @@
         {"nativeGetClassification", "(J)I",
          (void*)android_view_MotionEvent_nativeGetClassification},
         {"nativeOffsetLocation", "(JFF)V", (void*)android_view_MotionEvent_nativeOffsetLocation},
-        {"nativeGetXOffset", "(J)F", (void*)android_view_MotionEvent_nativeGetXOffset},
-        {"nativeGetYOffset", "(J)F", (void*)android_view_MotionEvent_nativeGetYOffset},
+        {"nativeGetRawXOffset", "(J)F", (void*)android_view_MotionEvent_nativeGetRawXOffset},
+        {"nativeGetRawYOffset", "(J)F", (void*)android_view_MotionEvent_nativeGetRawYOffset},
         {"nativeGetXPrecision", "(J)F", (void*)android_view_MotionEvent_nativeGetXPrecision},
         {"nativeGetYPrecision", "(J)F", (void*)android_view_MotionEvent_nativeGetYPrecision},
         {"nativeGetXCursorPosition", "(J)F",
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 0938ce17..3ed9f49 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1965,7 +1965,7 @@
 
     // If this zygote isn't root, it won't be able to create a process group,
     // since the directory is owned by root.
-    if (!is_system_server && getuid() == 0) {
+    if (getuid() == 0) {
         const int rc = createProcessGroup(uid, getpid());
         if (rc != 0) {
             fail_fn(rc == -EROFS ? CREATE_ERROR("createProcessGroup failed, kernel missing "
diff --git a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
index 54c4cd5..e0cc055 100644
--- a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
+++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
@@ -354,6 +354,18 @@
   return result;
 }
 
+static uid_t getSocketPeerUid(int socket, const std::function<void(const std::string&)>& fail_fn) {
+  struct ucred credentials;
+  socklen_t cred_size = sizeof credentials;
+  if (getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1
+      || cred_size != sizeof credentials) {
+    fail_fn(CREATE_ERROR("Failed to get socket credentials, %s",
+                         strerror(errno)));
+  }
+
+  return credentials.uid;
+}
+
 // Read all lines from the current command into the buffer, and then reset the buffer, so
 // we will start reading again at the beginning of the command, starting with the argument
 // count. And we don't need access to the fd to do so.
@@ -413,19 +425,12 @@
     fail_fn_z("Failed to retrieve session socket timeout");
   }
 
-  struct ucred credentials;
-  socklen_t cred_size = sizeof credentials;
-  if (getsockopt(n_buffer->getFd(), SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1
-      || cred_size != sizeof credentials) {
-    fail_fn_1(CREATE_ERROR("ForkRepeatedly failed to get initial credentials, %s",
-                           strerror(errno)));
+  uid_t peerUid = getSocketPeerUid(session_socket, fail_fn_1);
+  if (peerUid != static_cast<uid_t>(expected_uid)) {
+    return JNI_FALSE;
   }
-
   bool first_time = true;
   do {
-    if (credentials.uid != static_cast<uid_t>(expected_uid)) {
-      return JNI_FALSE;
-    }
     n_buffer->readAllLines(first_time ? fail_fn_1 : fail_fn_n);
     n_buffer->reset();
     int pid = zygote::forkApp(env, /* no pipe FDs */ -1, -1, session_socket_fds,
@@ -453,6 +458,7 @@
       }
     }
     for (;;) {
+      bool valid_session_socket = true;
       // Clear buffer and get count from next command.
       n_buffer->clear();
       // Poll isn't strictly necessary for now. But without it, disconnect is hard to detect.
@@ -463,25 +469,50 @@
       if ((fd_structs[SESSION_IDX].revents & POLLIN) != 0) {
         if (n_buffer->getCount(fail_fn_z) != 0) {
           break;
-        }  // else disconnected;
+        } else {
+          // Session socket was disconnected
+          valid_session_socket = false;
+          close(session_socket);
+        }
       } else if (poll_res == 0 || (fd_structs[ZYGOTE_IDX].revents & POLLIN) == 0) {
         fail_fn_z(
             CREATE_ERROR("Poll returned with no descriptors ready! Poll returned %d", poll_res));
       }
-      // We've now seen either a disconnect or connect request.
-      close(session_socket);
-      int new_fd = TEMP_FAILURE_RETRY(accept(zygote_socket_fd, nullptr, nullptr));
+      int new_fd = -1;
+      do {
+        // We've now seen either a disconnect or connect request.
+        new_fd = TEMP_FAILURE_RETRY(accept(zygote_socket_fd, nullptr, nullptr));
+        if (new_fd == -1) {
+          fail_fn_z(CREATE_ERROR("Accept(%d) failed: %s", zygote_socket_fd, strerror(errno)));
+        }
+        uid_t newPeerUid = getSocketPeerUid(new_fd, fail_fn_1);
+        if (newPeerUid != static_cast<uid_t>(expected_uid)) {
+          ALOGW("Dropping new connection with a mismatched uid %d\n", newPeerUid);
+          close(new_fd);
+          new_fd = -1;
+        } else {
+          // If we still have a valid session socket, close it now
+          if (valid_session_socket) {
+              close(session_socket);
+          }
+          valid_session_socket = true;
+        }
+      } while (!valid_session_socket);
+
+      // At this point we either have a valid new connection (new_fd > 0), or
+      // an existing session socket we can poll on
       if (new_fd == -1) {
-        fail_fn_z(CREATE_ERROR("Accept(%d) failed: %s", zygote_socket_fd, strerror(errno)));
+        // The new connection wasn't valid, and we still have an old one; retry polling
+        continue;
       }
       if (new_fd != session_socket) {
-          // Move new_fd back to the old value, so that we don't have to change Java-level data
-          // structures to reflect a change. This implicitly closes the old one.
-          if (TEMP_FAILURE_RETRY(dup2(new_fd, session_socket)) != session_socket) {
-            fail_fn_z(CREATE_ERROR("Failed to move fd %d to %d: %s",
-                                   new_fd, session_socket, strerror(errno)));
-          }
-          close(new_fd);  //  On Linux, fd is closed even if EINTR is returned.
+        // Move new_fd back to the old value, so that we don't have to change Java-level data
+        // structures to reflect a change. This implicitly closes the old one.
+        if (TEMP_FAILURE_RETRY(dup2(new_fd, session_socket)) != session_socket) {
+          fail_fn_z(CREATE_ERROR("Failed to move fd %d to %d: %s",
+                                 new_fd, session_socket, strerror(errno)));
+        }
+        close(new_fd);  //  On Linux, fd is closed even if EINTR is returned.
       }
       // If we ever return, we effectively reuse the old Java ZygoteConnection.
       // None of its state needs to change.
@@ -493,13 +524,6 @@
         fail_fn_z(CREATE_ERROR("Failed to set send timeout for socket %d: %s",
                                session_socket, strerror(errno)));
       }
-      if (getsockopt(session_socket, SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1) {
-        fail_fn_z(CREATE_ERROR("ForkMany failed to get credentials: %s", strerror(errno)));
-      }
-      if (cred_size != sizeof credentials) {
-        fail_fn_z(CREATE_ERROR("ForkMany credential size = %d, should be %d",
-                               cred_size, static_cast<int>(sizeof credentials)));
-      }
     }
     first_time = false;
   } while (n_buffer->isSimpleForkCommand(minUid, fail_fn_n));
diff --git a/core/proto/android/hardware/sensorprivacy.proto b/core/proto/android/hardware/sensorprivacy.proto
index e368c6a..53aa710 100644
--- a/core/proto/android/hardware/sensorprivacy.proto
+++ b/core/proto/android/hardware/sensorprivacy.proto
@@ -91,9 +91,7 @@
     enum StateType {
         ENABLED = 1;
         DISABLED = 2;
-        AUTO_DRIVER_ASSISTANCE_HELPFUL_APPS = 3;
-        AUTO_DRIVER_ASSISTANCE_REQUIRED_APPS = 4;
-        AUTO_DRIVER_ASSISTANCE_APPS = 5;
+        ENABLED_EXCEPT_ALLOWLISTED_APPS = 3;
     }
 
     // DEPRECATED
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 75cfba0..d31baf3 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -144,6 +144,7 @@
     }
     repeated BroadcastSummary historical_broadcasts_summary = 6;
     repeated BroadcastRecordProto pending_broadcasts = 7;
+    repeated BroadcastRecordProto frozen_broadcasts = 8;
 }
 
 message MemInfoDumpProto {
diff --git a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
index 5a18d9e..b75d545 100644
--- a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
+++ b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
@@ -39,7 +39,7 @@
     optional string cur_token = 14;
     optional int32 cur_token_display_id = 15;
     optional bool system_ready = 16;
-    optional int32 last_switch_user_id = 17;
+    reserved 17; // deprecated last_switch_user_id
     optional bool have_connection = 18;
     optional bool bound_to_method = 19;
     optional bool is_interactive = 20;
diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto
index db99e5b..9151958 100644
--- a/core/proto/android/server/vibrator/vibratormanagerservice.proto
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -79,11 +79,28 @@
     repeated int32 delays = 2;
 }
 
+// Next Tag: 5
 message VibrationAttributesProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
     optional int32 usage = 1;
     optional int32 audio_usage = 2;
     optional int32 flags = 3;
+    optional int32 category = 4;
+}
+
+// Next Tag: 4
+message VibrationParamProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    optional VibrationScaleParamProto scale = 1;
+    optional int64 create_time = 2;
+    optional bool is_from_request = 3;
+}
+
+// Next Tag: 3
+message VibrationScaleParamProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    optional int32 types_mask = 1;
+    optional float scale = 2;
 }
 
 // Next Tag: 9
@@ -132,16 +149,19 @@
     }
 }
 
-// Next Tag: 25
+// Next Tag: 29
 message VibratorManagerServiceDumpProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
     repeated int32 vibrator_ids = 1;
     optional VibrationProto current_vibration = 2;
     optional bool is_vibrating = 3;
+    optional int32 is_vibrator_controller_registered = 27;
     optional VibrationProto current_external_vibration = 4;
     optional bool vibrator_under_external_control = 5;
     optional bool low_power_mode = 6;
     optional bool vibrate_on = 24;
+    optional bool keyboard_vibration_on = 25;
+    optional int32 default_vibration_amplitude = 26;
     optional int32 alarm_intensity = 18;
     optional int32 alarm_default_intensity = 19;
     optional int32 haptic_feedback_intensity = 7;
@@ -158,5 +178,6 @@
     repeated VibrationProto previous_notification_vibrations = 14;
     repeated VibrationProto previous_alarm_vibrations = 15;
     repeated VibrationProto previous_vibrations = 16;
-    repeated VibrationProto previous_external_vibrations = 17;
+    repeated VibrationParamProto previous_vibration_params = 28;
+    reserved 17; // prev previous_external_vibrations
 }
\ No newline at end of file
diff --git a/core/proto/android/view/inputmethod/inputconnection.proto b/core/proto/android/view/inputmethod/inputconnection.proto
index d1f257f..ff6871d 100644
--- a/core/proto/android/view/inputmethod/inputconnection.proto
+++ b/core/proto/android/view/inputmethod/inputconnection.proto
@@ -16,8 +16,6 @@
 
 syntax = "proto2";
 
-import "frameworks/base/core/proto/android/privacy.proto";
-
 package android.view.inputmethod;
 
 option java_multiple_files = true;
@@ -26,8 +24,8 @@
  * Represents a {@link android.view.inputmethod.InputConnection} object.
  */
 message InputConnectionProto {
-  optional string editable_text = 1 [(.android.privacy).dest = DEST_LOCAL];
-  optional string selected_text = 2 [(.android.privacy).dest = DEST_LOCAL];
+  reserved 1; // string editable_text
+  reserved 2; // string selected_text
   optional int32 selected_text_start = 3;
   optional int32 selected_text_end = 4;
   optional int32 cursor_caps_mode = 5;
@@ -50,18 +48,18 @@
   message GetTextBeforeCursor {
     optional int32 length = 1;
     optional int32 flags = 2;
-    optional string result = 3 [(.android.privacy).dest = DEST_LOCAL];
+    reserved 3; // string result
   }
 
   message GetTextAfterCursor {
     optional int32 length = 1;
     optional int32 flags = 2;
-    optional string result = 3 [(.android.privacy).dest = DEST_LOCAL];
+    reserved 3; // string result = 3
   }
 
   message GetSelectedText {
     optional int32 flags = 1;
-    optional string result = 2 [(.android.privacy).dest = DEST_LOCAL];
+    reserved 2; // string result = 2
   }
 
   message GetSurroundingText {
@@ -71,7 +69,7 @@
     optional SurroundingText result = 4;
 
     message SurroundingText {
-      optional string text = 1 [(.android.privacy).dest = DEST_LOCAL];
+      reserved 1; // string text = 1
       optional int32 selection_start = 2;
       optional int32 selection_end = 3;
       optional int32 offset = 4;
@@ -86,7 +84,7 @@
   message GetExtractedText {
     optional ExtractedTextRequest request = 1;
     optional int32 flags = 2;
-    optional string result = 3 [(.android.privacy).dest = DEST_LOCAL];
+    reserved 3; // string result = 3
 
     message ExtractedTextRequest {
       optional int32 token = 1;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 900f9cb..1acdc75 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2281,7 +2281,7 @@
     <!-- @SystemApi @hide Allows changing Thread network state and access to Thread network
         credentials such as Network Key and PSKc.
         <p>Not for use by third-party applications.
-        @FlaggedApi("com.android.net.thread.flags.thread_enabled_platform") -->
+        @FlaggedApi("com.android.net.thread.platform.flags.thread_enabled_platform") -->
     <permission android:name="android.permission.THREAD_NETWORK_PRIVILEGED"
                 android:protectionLevel="signature|privileged" />
 
@@ -3629,7 +3629,7 @@
 
     <!-- Allows an application to set policy related to <a
     href="https://www.threadgroup.org">Thread</a> network.
-        @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled")
+        @FlaggedApi("com.android.net.thread.platform.flags.thread_user_restriction_enabled")
     -->
     <permission android:name="android.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK"
                 android:protectionLevel="internal|role" />
@@ -6772,13 +6772,6 @@
     <permission android:name="android.permission.USE_BIOMETRIC_INTERNAL"
         android:protectionLevel="signature" />
 
-    <!-- Allows privileged apps to access the background face authentication.
-        @SystemApi
-        @FlaggedApi("android.hardware.biometrics.face_background_authentication")
-        @hide -->
-    <permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION"
-        android:protectionLevel="signature|privileged" />
-
     <!-- Allows the system to control the BiometricDialog (SystemUI). Reserved for the system. @hide -->
     <permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG"
                 android:protectionLevel="signature" />
@@ -7955,12 +7948,12 @@
         android:protectionLevel="signature|privileged" />
 
 
-    <!-- @SystemApi Allows an app to bind the on-device trusted service.
+    <!-- @SystemApi Allows an app to bind the on-device sandboxed service.
              <p>Protection level: signature|privileged
              @hide
          @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence")
         -->
-    <permission android:name="android.permission.BIND_ON_DEVICE_TRUSTED_SERVICE"
+    <permission android:name="android.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE"
         android:protectionLevel="signature"/>
 
 
@@ -7986,7 +7979,7 @@
         @hide
       -->
     <permission android:name="android.permission.GET_APP_METADATA"
-                android:protectionLevel="signature|installer" />
+                android:protectionLevel="signature|installer|verifier" />
 
     <!-- @hide @SystemApi Allows an application to stage HealthConnect's remote data so that
          HealthConnect can later integrate it. -->
@@ -8621,6 +8614,10 @@
                  android:permission="android.permission.BIND_JOB_SERVICE">
         </service>
 
+        <service android:name="com.android.system.virtualmachine.SecretkeeperJobService"
+                 android:permission="android.permission.BIND_JOB_SERVICE">
+        </service>
+
         <service android:name="com.android.server.PruneInstantAppsJobService"
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
@@ -8675,7 +8672,7 @@
                  android:permission="android.permission.BIND_JOB_SERVICE">
         </service>
 
-        <service android:name="com.android.server.companion.InactiveAssociationsRemovalService"
+        <service android:name="com.android.server.companion.association.InactiveAssociationsRemovalService"
                  android:permission="android.permission.BIND_JOB_SERVICE">
         </service>
 
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 6cd2c4e..09d23fa 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Hervat werkapps?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Hervat"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Noodgeval"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Stel ’n skermslot"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Stel skermslot"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Stel ’n skermslot op dié toestel om jou privaat ruimte te gebruik"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Program is nie beskikbaar nie"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is nie op die oomblik beskikbaar nie."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> is nie beskikbaar nie"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Jy kan boodskappe stuur en ontvang sonder ’n selfoon- of wi-fi-netwerk"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Maak Boodskappe oop"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Hoe dit werk"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index b485b4b..6de4a20 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"የሥራ መተግበሪያዎች ከቆሙበት ይቀጥሉ?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"ከቆመበት ቀጥል"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"ድንገተኛ አደጋ"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"ማያ ገጽ መቆለፊያን ያቀናብሩ"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"ማያ ገጽ መቆለፊያውን ያቀናብሩ"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"የግል ቦታዎን ለመጠቀም፣ በዚህ መሣሪያ ላይ ማያ ገጽ መቆለፊያን ያቀናብሩ"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"መተግበሪያ አይገኝም"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> አሁን አይገኝም።"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> አይገኝም"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"ያለ ሞባይል ወይም የWi-Fi አውታረ መረብ መልዕክቶችን መላክ እና መቀበል ይችላሉ"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"መልዕክቶች ይክፈቱ"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"እንዴት እንደሚሠራ"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index b12c115..3e98391 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -1990,6 +1990,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"أتريد إعادة تفعيل تطبيقات العمل؟"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"إعادة التفعيل"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"الطوارئ"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"ضبط قفل شاشة"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"ضبط قفل الشاشة"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"لاستخدام مساحتك الخاصة، يجب ضبط قفل شاشة على هذا الجهاز."</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"التطبيق غير متاح"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g> غير متاح الآن."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"تطبيق <xliff:g id="ACTIVITY">%1$s</xliff:g> غير متاح"</string>
@@ -2394,4 +2397,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"‏يمكنك إرسال الرسائل واستلامها بدون شبكة الجوّال أو شبكة Wi-Fi."</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"فتح تطبيق \"الرسائل\""</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"طريقة العمل"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 01705ad..0e3773b 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"কাম সম্পৰ্কীয় এপ্ আনপজ কৰিবনে?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"আনপজ কৰক"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"জৰুৰীকালীন"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"এটা স্ক্ৰীন লক ছেট কৰক"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"স্ক্ৰীন লক ছেট কৰা"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"আপোনাৰ প্ৰাইভেট স্পেচ ব্যৱহাৰ কৰিবলৈ এই ডিভাইচটোত স্ক্ৰীন লক ছেট কৰক"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"এপ্‌টো উপলব্ধ নহয়"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"এই মুহূৰ্তত <xliff:g id="APP_NAME">%1$s</xliff:g> উপলব্ধ নহয়।"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> উপলব্ধ নহয়"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"আপুনি ম’বাইল বা ৱাই-ফাই নেটৱৰ্কৰ জৰিয়তে পাঠ বাৰ্তা পঠিয়াব বা লাভ কৰিব পাৰে"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages খোলক"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ই কেনেকৈ কাম কৰে"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 4ed5f5e..98ec798 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"İş tətbiqi üzrə pauza bitsin?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Pauzanı bitirin"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Fövqəladə hal"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Ekran kilidi ayarlayın"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Ekran kilidi ayarlayın"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Bu cihazda ekran kilidi ayarlamaqla şəxsi sahədən istifadə edin"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Tətbiq əlçatan deyil"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> hazırda əlçatan deyil."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> əlçatan deyil"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Mobil və ya Wi-Fi şəbəkəsi olmadan mesaj göndərə və qəbul edə bilərsiniz"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Mesajı açın"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Haqqında"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index b31dca4..04b5a38 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -1987,6 +1987,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Uključiti poslovne aplikacije?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Ponovo aktiviraj"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Hitan slučaj"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Podesite zaključavanje ekrana"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Podesi zaključavanje ekrana"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Da biste koristili privatni prostor, podesite zaključavanje ekrana na ovom uređaju"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija nije dostupna"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutno nije dostupna."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> – nije dostupno"</string>
@@ -2391,4 +2394,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Možete da šaljete i primate poruke bez mobilne ili WiFi mreže"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvori Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Princip rada"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 2bd78e9..bb1b2d3 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -1988,6 +1988,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Уключыць працоўныя праграмы?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Уключыць"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Экстранны выпадак"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Наладзьце блакіроўку экрана"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Наладзіць блакіроўку экрана"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Каб выкарыстоўваць прыватную прастору, на прыладзе неабходна наладзіць блакіроўку экрана"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Праграма недаступная"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" цяпер недаступная."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"Недаступна: <xliff:g id="ACTIVITY">%1$s</xliff:g>"</string>
@@ -2392,4 +2395,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Вы можаце адпраўляць і атрымліваць паведамленні без доступу да мабільнай сеткі або Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Адкрыць Паведамленні"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Як гэта працуе"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index dc1e92c..b03faf7 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Отмяна на паузата за служ. прил.?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Отмяна на паузата"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Спешен случай"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Настройте заключване на екрана"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Заключване на екрана"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"За да ползвате частното си пространство, настройте заключване на екрана"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Приложението не е достъпно"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"В момента няма достъп до <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> не е налице"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Можете да изпращате и получавате съобщения без мобилна или Wi-Fi мрежа"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Отваряне на Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Начин на работа"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index 5dbaad6..f637f5a 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"অফিসের অ্যাপ আনপজ করতে চান?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"আনপজ করুন"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"জরুরি"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"\'স্ক্রিন লক\' সেট-আপ করুন"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"\'স্ক্রিন লক\' ফিচার সেট করুন"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"নিজের প্রাইভেট স্পেস ব্যবহার করতে এই ডিভাইসে \'স্ক্রিন লক\' সেট করুন"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"অ্যাপ পাওয়া যাচ্ছে না"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"এই মুহূর্তে <xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপ পাওয়া যাচ্ছে না।"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> উপলভ্য নেই"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"আপনি কোনও মেবাইল বা ওয়াই-ফাই নেটওয়ার্ক ছাড়াই মেসেজ পাঠাতে ও পেতে পারবেন"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages খুলুন"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"এটি কীভাবে কাজ করে"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 838a2db..9700fe1 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -1987,6 +1987,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Pokrenuti poslovne aplikacije?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Ponovo pokreni"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Hitan slučaj"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Postavite zaključavanje ekrana"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Postavite zaključavanje ekrana"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Za upotrebu privatn. prostora postavite zaklj. ekr. na uređ."</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija nije dostupna"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutno nije dostupna."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"Nedostupno: <xliff:g id="ACTIVITY">%1$s</xliff:g>"</string>
@@ -2391,4 +2394,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Možete slati i primati poruke bez mobilne ili WiFi mreže"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvorite Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kako ovo funkcionira"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index feb67c4..e56b8bc 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -1987,6 +1987,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Reactivar les apps de treball?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Reactiva"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Emergència"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Defineix un bloqueig de pantalla"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Defineix un bloqueig de pantalla"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Per utilitzar l\'espai privat, defineix un bloq. de pantalla"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"L\'aplicació no està disponible"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Ara mateix, <xliff:g id="APP_NAME">%1$s</xliff:g> no està disponible."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> no està disponible"</string>
@@ -2391,4 +2394,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Pots enviar i rebre missatges sense una xarxa mòbil o Wi‑Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Obre Missatges"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Com funciona"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 1c0529a..0c168e5 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1988,6 +1988,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Zrušit pozastavení pracovních aplikací?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Zrušit pozastavení"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Stav nouze"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Nastavení zámku obrazovky"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Nastavit zámek obrazovky"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Pokud chcete používat soukromý prostor, nastavte na tomto zařízení zámek obrazovky"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Aplikace není k dispozici"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> v tuto chvíli není k dispozici."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> není k dispozici"</string>
@@ -2392,4 +2395,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Zprávy můžete odesílat a přijímat bez mobilní sítě nebo sítě Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otevřít Zprávy"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Jak to funguje"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 3010dc7..aa037c7 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -522,7 +522,7 @@
     <string name="permdesc_vibrate" msgid="8733343234582083721">"Tillader, at appen kan administrere vibratoren."</string>
     <string name="permdesc_vibrator_state" msgid="7050024956594170724">"Tillader, at appen bruger vibration."</string>
     <string name="permlab_callPhone" msgid="1798582257194643320">"ringe direkte op til telefonnumre"</string>
-    <string name="permdesc_callPhone" msgid="7892422187827695656">"Tillader, at appen kan ringe til telefonnumre uden din indgriben. Dette kan resultere i uventede debiteringer eller opkald. Vær opmærksom på, at dette ikke giver appen tilladelse til at ringe til alarmnumre. Skadelige apps kan koste dig penge ved at foretage opkald uden din bekræftelse eller ved at ringe op til operatørkoder, hvilket resulterer i, at indgående opkald automatisk viderestilles til et andet nummer."</string>
+    <string name="permdesc_callPhone" msgid="7892422187827695656">"Tillader, at appen kan ringe til telefonnumre uden din indgriben. Dette kan resultere i uventede opkrævninger eller opkald. Vær opmærksom på, at dette ikke giver appen tilladelse til at ringe til alarmnumre. Skadelige apps kan koste dig penge ved at foretage opkald uden din bekræftelse eller ved at ringe op til operatørkoder, hvilket resulterer i, at indgående opkald automatisk viderestilles til et andet nummer."</string>
     <string name="permlab_accessImsCallService" msgid="442192920714863782">"få adgang til chat-opkaldstjeneste"</string>
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Tillader, at appen kan bruge chat-tjenesten til at foretage opkald, uden du gør noget."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"læse telefonens status og identitet"</string>
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Vil du genoptage arbejdsapps?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Genoptag"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Nødopkald"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Konfigurer en skærmlås"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Konfigurer skærmlås"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Konfigurer en skærmlås på enheden for at bruge dit private område"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Appen er ikke tilgængelig"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> er ikke tilgængelig lige nu."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> er ikke understøttet"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Du kan sende og modtage beskeder uden et mobil- eller Wi-Fi-netværk"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Åbn Beskeder"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Sådan fungerer det"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 803b967..9f1af24 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -477,9 +477,9 @@
     <string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"Ermöglicht der App, die Anrufliste deines Android TV-Geräts zu ändern, einschließlich der Daten über ein- und ausgehende Anrufe. Du solltest wissen, dass dies von schädlichen Apps genutzt werden kann, um Einträge in der Anrufliste zu löschen oder zu ändern."</string>
     <string name="permdesc_writeCallLog" product="default" msgid="5903033505665134802">"Ermöglicht der App, die Anrufliste deines Telefons zu ändern, einschließlich der Daten über ein- und ausgehende Anrufe. Schädliche Apps können so die Einträge in der Anrufliste löschen oder sie ändern."</string>
     <string name="permlab_bodySensors" msgid="662918578601619569">"Zugriff auf Daten des Körpersensors, etwa Herzfrequenz, wenn in Benutzung"</string>
-    <string name="permdesc_bodySensors" product="default" msgid="7652650410295512140">"Ermöglicht der App den Zugriff auf Daten des Körpersensors, etwa solche zur Herzfrequenz, zur Temperatur und zum Blutsauerstoffanteil, während die App in Benutzung ist."</string>
+    <string name="permdesc_bodySensors" product="default" msgid="7652650410295512140">"Ermöglicht der App den Zugriff auf Daten des Körpersensors, etwa solche zur Herzfrequenz, zur Temperatur und zur Sauerstoffsättigung, während die App in Benutzung ist."</string>
     <string name="permlab_bodySensors_background" msgid="4912560779957760446">"Zugriff auf Daten des Körpersensors, etwa Herzfrequenz, wenn im Hintergrund"</string>
-    <string name="permdesc_bodySensors_background" product="default" msgid="8870726027557749417">"Ermöglicht der App den Zugriff auf Daten des Körpersensors, etwa solche zur Herzfrequenz, zur Temperatur und zum Blutsauerstoffanteil, während die App im Hintergrund ist."</string>
+    <string name="permdesc_bodySensors_background" product="default" msgid="8870726027557749417">"Ermöglicht der App den Zugriff auf Daten des Körpersensors, etwa solche zur Herzfrequenz, zur Temperatur und zur Sauerstoffsättigung, während die App im Hintergrund ist."</string>
     <string name="permlab_readCalendar" msgid="6408654259475396200">"Kalendertermine und Details lesen"</string>
     <string name="permdesc_readCalendar" product="tablet" msgid="515452384059803326">"Diese App kann alle auf deinem Tablet gespeicherten Kalendertermine lesen und deine Kalenderdaten teilen oder speichern."</string>
     <string name="permdesc_readCalendar" product="tv" msgid="5811726712981647628">"Diese App kann alle auf deinem Android TV-Gerät gespeicherten Kalendertermine lesen und die Kalenderdaten teilen oder speichern."</string>
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Geschäftliche Apps nicht mehr pausieren?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Nicht mehr pausieren"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Notruf"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Displaysperre einrichten"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Displaysperre einrichten"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Richte zur Nutzung des privaten Bereichs auf dem Gerät die Displaysperre ein"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"App ist nicht verfügbar"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ist derzeit nicht verfügbar."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> nicht verfügbar"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Du kannst Nachrichten ohne Mobilfunknetz oder WLAN senden und empfangen"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages öffnen"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"So funktionierts"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index c030d25..bc4d929 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -186,8 +186,8 @@
     <string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"Αδυναμία συγχρονισμού"</string>
     <string name="contentServiceTooManyDeletesNotificationDesc" msgid="4562226280528716090">"Επιχειρήθηκε η διαγραφή πάρα πολλών <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string>
     <string name="low_memory" product="tablet" msgid="5557552311566179924">"Ο αποθηκευτικός χώρος του tablet είναι πλήρης. Διαγράψτε μερικά αρχεία για να δημιουργήσετε ελεύθερο χώρο."</string>
-    <string name="low_memory" product="watch" msgid="3479447988234030194">"Ο αποθηκευτικός χώρος παρακολούθησης είναι πλήρης! Διαγράψτε μερικά αρχεία για να απελευθερώσετε χώρο."</string>
-    <string name="low_memory" product="tv" msgid="6663680413790323318">"Ο αποθηκευτικός χώρος της συσκευής Android TV είναι πλήρης. Διαγράψτε μερικά αρχεία για να απελευθερώσετε χώρο."</string>
+    <string name="low_memory" product="watch" msgid="3479447988234030194">"Ο αποθηκευτικός χώρος παρακολούθησης είναι πλήρης! Διαγράψτε μερικά αρχεία για να αποδεσμεύσετε χώρο."</string>
+    <string name="low_memory" product="tv" msgid="6663680413790323318">"Ο αποθηκευτικός χώρος της συσκευής Android TV είναι πλήρης. Διαγράψτε μερικά αρχεία για να αποδεσμεύσετε χώρο."</string>
     <string name="low_memory" product="default" msgid="2539532364144025569">"Ο αποθηκευτικός χώρος του τηλεφώνου είναι πλήρης. Διαγράψτε μερικά αρχεία για να δημιουργήσετε ελεύθερο χώρο."</string>
     <string name="ssl_ca_cert_warning" msgid="7233573909730048571">"{count,plural, =1{Η αρχή έκδοσης πιστοποιητικών εγκαταστάθηκε}other{Οι αρχές έκδοσης πιστοποιητικών εγκαταστάθηκαν}}"</string>
     <string name="ssl_ca_cert_noti_by_unknown" msgid="4961102218216815242">"Από ένα άγνωστο τρίτο μέρος"</string>
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Αναίρ. παύσης εφαρμ. εργασιών;"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Αναίρεση παύσης"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Έκτακτη ανάγκη"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Ρυθμίστε ένα κλείδωμα οθόνης"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Ρύθμιση κλειδώματος οθόνης"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Ορίστε κλείδωμα οθόνης"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Η εφαρμογή δεν είναι διαθέσιμη"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> δεν είναι διαθέσιμη αυτήν τη στιγμή."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> δεν διατίθεται"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Μπορείτε να στέλνετε και να λαμβάνετε μηνύματα χωρίς δίκτυο κινητής τηλεφωνίας ή Wi-Fi."</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Άνοιγμα Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Πώς λειτουργεί"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index cecdce6..969bbbf 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Unpause work apps?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Unpause"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Emergency"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Set a screen lock"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Set screen lock"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"To use your private space, set a screen lock on this device"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> unavailable"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"You can send and receive messages without a mobile or Wi-Fi network"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index c3cf404..4148ad7 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Unpause work apps?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Unpause"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Emergency"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Set a screen lock"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Set screen lock"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"To use your private space, set a screen lock on this device"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> unavailable"</string>
@@ -2390,4 +2393,5 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"You can send and receive messages without a mobile or Wi-Fi network"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string>
+    <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending..."</string>
 </resources>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index cda0168..0a890b2 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Unpause work apps?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Unpause"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Emergency"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Set a screen lock"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Set screen lock"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"To use your private space, set a screen lock on this device"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> unavailable"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"You can send and receive messages without a mobile or Wi-Fi network"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index f979e27..5ca5236 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Unpause work apps?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Unpause"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Emergency"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Set a screen lock"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Set screen lock"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"To use your private space, set a screen lock on this device"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> unavailable"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"You can send and receive messages without a mobile or Wi-Fi network"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index e620c35..edba901 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‏‏‎‏‏‏‎‎‎‏‏‎‏‎‏‏‏‏‎‎‎‏‎‎‏‎‎‏‏‎‏‏‏‏‏‎‏‎‏‏‎‏‎‏‏‏‎‏‎‏‎‏‎Unpause work apps?‎‏‎‎‏‎"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‎‎‏‎‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‎‏‏‎‏‏‏‎‏‎‏‎‎‎‏‎‎‏‎‎‏‎‎‏‏‎‎‎‎Unpause‎‏‎‎‏‎"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‎‎‎‏‎‏‏‏‎‎‎‏‎‏‎‏‏‏‏‎‏‏‎‏‎‎‏‎‎‎‎‏‏‏‎‎‏‏‎‏‏‎‏‎‎‎‏‎‎‎‏‎‎Emergency‎‏‎‎‏‎"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‎‏‏‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‏‏‏‎‎Set a screen lock‎‏‎‎‏‎"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‎‎‏‏‎‎‎‏‏‎‎‏‎‏‏‎‎‏‏‏‎‏‎‎‏‏‏‏‎‏‎‏‏‏‏‏‎‎‏‏‏‎‎‏‎‎‎‎‎‏‎‏‏‏‎Set screen lock‎‏‎‎‏‎"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‏‏‎‏‏‏‏‎‏‎‎‏‏‎‏‏‏‏‎‎‏‏‎‎‎‎‏‎‏‏‎‏‏‎‏‏‎‎‏‏‏‎‏‏‎‎To use your private space, set a screen lock on this device‎‏‎‎‏‎"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‎‏‏‎‎‎‎‎‎‏‎‎‎‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‎‎‎‎‎‎‎App is not available‎‏‎‎‏‎"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‏‎‎‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‎‎‎‏‏‎‎‏‎‏‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is not available right now.‎‏‎‎‏‎"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‏‎‏‏‏‎‏‏‏‎‎‏‎‎‎‎‎‎‎‎‏‏‎‎‏‏‏‎‏‏‎‎‏‎‎‏‏‎<xliff:g id="ACTIVITY">%1$s</xliff:g>‎‏‎‎‏‏‏‎ unavailable‎‏‎‎‏‎"</string>
@@ -2390,4 +2393,5 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‏‎‎‎‏‎‎‎‏‎‎‎‏‏‎‎‎‎‏‏‎‎‎‎‏‎‏‎‎‏‎‏‏‏‎‎‎‎‎‏‏‏‎‏‎‏‎‎‎‏‏‏‎‏‎‎You can send and receive messages without a mobile or Wi-Fi network‎‏‎‎‏‎"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‎‏‎‏‎‎‎‎‏‎‏‏‎‎‏‏‏‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‏‎‏‎‎‎‎‎‏‎Open Messages‎‏‎‎‏‎"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‏‎‏‏‏‎‏‎‏‏‎‎‎‎‏‏‏‎‏‎‎‎‎‏‏‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‎‎‎‎‏‏‎‎‎‎‏‏‏‎How it works‎‏‎‎‏‎"</string>
+    <string name="unarchival_session_app_label" msgid="6811856981546348205">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‏‎‎‎‏‎‎‏‎‎‏‏‏‏‎‏‎‏‎‏‎‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‏‎‏‎‏‏‏‎‏‎‏‎‏‏‎‏‎Pending...‎‏‎‎‏‎"</string>
 </resources>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index da500d7..65e53db 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -109,7 +109,7 @@
     <string name="serviceClassPAD" msgid="6850244583416306321">"PAD"</string>
     <string name="roamingText0" msgid="7793257871609854208">"Indicador de roaming activado"</string>
     <string name="roamingText1" msgid="5073028598334616445">"Indicador de roaming desactivado"</string>
-    <string name="roamingText2" msgid="2834048284153110598">"Indicador de roaming titilando"</string>
+    <string name="roamingText2" msgid="2834048284153110598">"Indicador de roaming parpadeando"</string>
     <string name="roamingText3" msgid="831690234035748988">"Fuera del vecindario"</string>
     <string name="roamingText4" msgid="2171252529065590728">"Fuera de construcción"</string>
     <string name="roamingText5" msgid="4294671587635796641">"Roaming: sistema preferido"</string>
@@ -1987,6 +1987,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"¿Reanudar apps de trabajo?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Reanudar"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Emergencia"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Configurar bloqueo de pantalla"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Configurar bloqueo de pantalla"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Para usar tu espacio privado, configura un bloqueo de pantalla"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"La app no está disponible"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> no está disponible en este momento."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> no disponible"</string>
@@ -2391,4 +2394,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Puedes enviar y recibir mensajes incluso si no tienes conexión a una red móvil o Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir Mensajes"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cómo funciona"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 35cd1eb..a0e4a51 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1987,6 +1987,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"¿Reactivar apps de trabajo?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Reactivar"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Emergencia"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Define un bloqueo de pantalla"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Establecer bloqueo de pantalla"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Para usar el espacio privado, define un bloqueo de pantalla"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"La aplicación no está disponible"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"En estos momentos, <xliff:g id="APP_NAME">%1$s</xliff:g> no está disponible."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> no disponible"</string>
@@ -2391,4 +2394,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Puedes enviar y recibir mensajes sin una red móvil o Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abre Mensajes"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cómo funciona"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 24ee884..d6553ab 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Kas lõpetada töörakenduste peatamine?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Lõpeta peatamine"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Hädaolukord"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Seadistage ekraanilukk"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Seadistage ekraanilukk"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Seadistage oma privaatse ruumi jaoks seadmele ekraanilukk"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Rakendus ei ole saadaval"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ei ole praegu saadaval."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ei ole saadaval"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Teil on võimalik sõnumeid saata ja vastu võtta ilma mobiilside- ja WiFi-võrguta"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Ava rakendus Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Tööpõhimõtted"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 583f8a9..bdf946e 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Laneko aplikazioak berraktibatu?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Berraktibatu"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Larrialdia"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Ezarri pantailaren blokeoa"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Ezarri pantailaren blokeoa"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Eremu pribatua erabiltzeko, ezarri pantailaren blokeoa gailuan"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Aplikazioa ez dago erabilgarri"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ez dago erabilgarri une honetan."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ez dago erabilgarri"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Mezuak bidal eta jaso ditzakezu sare mugikorrik edo wifi-sarerik gabe"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Ireki Mezuak"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Nola funtzionatzen du?"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 52de6da..827ddaa 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -633,8 +633,8 @@
     <string name="permdesc_imagesWrite" msgid="5195054463269193317">"به برنامه اجازه می‌دهد مجموعه عکستان را تغییر دهد."</string>
     <string name="permlab_mediaLocation" msgid="7368098373378598066">"خواندن مکان‌ها از مجموعه رسانه شما"</string>
     <string name="permdesc_mediaLocation" msgid="597912899423578138">"به برنامه اجازه می‌دهد مکان‌ها را از مجموعه رسانه‌تان بخواند."</string>
-    <string name="biometric_app_setting_name" msgid="3339209978734534457">"استفاده از زیست‌سنجشی"</string>
-    <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"استفاده از زیست‌سنجشی یا قفل صفحه"</string>
+    <string name="biometric_app_setting_name" msgid="3339209978734534457">"استفاده از داده‌های زیست‌سنجشی"</string>
+    <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"استفاده از داده‌های زیست‌سنجشی یا قفل صفحه"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"تأیید کنید این شمایید"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"برای ادامه، از زیست‌سنجشی استفاده کنید"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"برای ادامه، از زیست‌سنجشی یا قفل صفحه استفاده کنید"</string>
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"مکث برنامه‌های کاری لغو شود؟"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"لغو مکث"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"اضطراری"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"قفل صفحه تنظیم کنید"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"تنظیم قفل صفحه"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"برای استفاده از فضای خصوصی، قفل صفحه تنظیم کنید"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"برنامه در دسترس نیست"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> درحال‌حاضر در دسترس نیست."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> دردسترس نیست"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"‏می‌توانید بدون شبکه تلفن همراه یا Wi-Fi پیام ارسال و دریافت کنید"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"باز کردن «پیام‌ها»"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"روش کار"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 20d4c14..dca0e26 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Laita työsovellukset päälle?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Laita päälle"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Hätätilanne"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Näytön lukituksen asettaminen"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Aseta näytön lukitus"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Edellyttää näytön lukitusta"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Sovellus ei ole käytettävissä"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ei ole nyt käytettävissä."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ei käytettävissä"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Voit lähettää ja vastaanottaa viestejä ilman mobiili‑ tai Wi-Fi-verkkoa"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Avaa Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Näin se toimii"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-fr-feminine/strings.xml b/core/res/res/values-fr-feminine/strings.xml
new file mode 100644
index 0000000..2ad85d1
--- /dev/null
+++ b/core/res/res/values-fr-feminine/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT 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"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"lire les flux auxquels vous êtes abonnée"</string>
+</resources>
diff --git a/core/res/res/values-fr-masculine/strings.xml b/core/res/res/values-fr-masculine/strings.xml
new file mode 100644
index 0000000..744ef2b
--- /dev/null
+++ b/core/res/res/values-fr-masculine/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT 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"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"lire les flux auxquels vous êtes abonné"</string>
+</resources>
diff --git a/core/res/res/values-fr-neuter/strings.xml b/core/res/res/values-fr-neuter/strings.xml
new file mode 100644
index 0000000..b4f4cc7
--- /dev/null
+++ b/core/res/res/values-fr-neuter/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT 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"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"lire les flux auxquels vous êtes abonné·e"</string>
+</resources>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index b634942..dc030bb 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -1987,6 +1987,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Réactiver les applis pros?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Réactiver"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Urgence"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Config. Verrouillage d\'écran"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Config. Verrouillage d\'écran"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Config. VÉ pour util. Esp. pr."</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"L\'application n\'est pas accessible"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> n\'est pas accessible pour le moment."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> non accessible"</string>
@@ -2391,4 +2394,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Vous pouvez envoyer et recevoir des messages sans avoir recours à un appareil mobile ou à un réseau Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Ouvrir Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Fonctionnement"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index f0ed3d6..31754e4 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1987,6 +1987,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Réactiver les applis pro ?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Réactiver"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Urgence"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Définir verrouillage écran"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Activer verrouillage écran"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Pour utiliser votre espace privé, définissez un verrouillage de l\'écran sur cet appareil"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Application non disponible"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> n\'est pas disponible pour le moment."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> indisponible"</string>
@@ -2391,4 +2394,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Vous pouvez envoyer et recevoir des messages sans connexion au réseau mobile ou Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Ouvrir Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Fonctionnement"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index da791af..1b99b4b 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Reactivar apps do traballo?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Reactivar"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Emerxencia"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Define un bloqueo de pantalla"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Define un bloqueo de pantalla"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Para usar o espazo privado, define un bloqueo de pantalla"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"A aplicación non está dispoñible"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"A aplicación <xliff:g id="APP_NAME">%1$s</xliff:g> non está dispoñible neste momento."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> non está dispoñible"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Podes enviar e recibir mensaxes sen unha rede de telefonía móbil ou wifi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir Mensaxes"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona?"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 8f6affa..57c09da 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"ઑફિસની થોભાવેલી ઍપ ચાલુ કરીએ?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"ફરી ચાલુ કરો"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"ઇમર્જન્સી"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"સ્ક્રીન લૉક સેટ કરો"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"સ્ક્રીન લૉક સેટ કરો"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"તમારી ખાનગી સ્પેસનો ઉપયોગ કરવા, આ ડિવાઇસ પર સ્ક્રીન લૉક સેટ કરો"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"ઍપ ઉપલબ્ધ નથી"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> હાલમાં ઉપલબ્ધ નથી."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ઉપલબ્ધ નથી"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"તમે મોબાઇલ અથવા વાઇ-ફાઇ નેટવર્ક વિના મેસેજ મોકલી અને પ્રાપ્ત કરી શકો છો"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ખોલો"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"તેની કામ કરવાની રીત"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 53b39ff..6bba6b0 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"वर्क ऐप्लिकेशन चालू करने हैं?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"चालू करें"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"आपातकालीन कॉल"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"स्क्रीन लॉक सेट करें"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"स्क्रीन लॉक सेट करें"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"प्राइवेट स्पेस के लिए, इस डिवाइस पर स्क्रीन लॉक सेट करें"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"ऐप्लिकेशन उपलब्ध नहीं है"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> इस समय उपलब्ध नहीं है."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> उपलब्ध नहीं है"</string>
@@ -2359,9 +2362,9 @@
     <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_title" msgid="5265409360902073556">"स्क्रीन शेयर नहीं की जा सकती"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"कोई दूसरा केबल इस्तेमाल करके फिर से कोशिश करें"</string>
-    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"आपका डिवाइस बहुत गर्म हो गया है. जब तक यह ठंडा नहीं हो जाता, तब तक दूसरे डिवाइस पर इसकी स्क्रीन डिसप्ले नहीं की जा सकती"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"आपका डिवाइस बहुत गर्म हो गया है. जब तक यह ठंडा नहीं हो जाता, तब तक दूसरे डिवाइस पर इसकी स्क्रीन शेयर नहीं की जा सकती"</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>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"मोबाइल या वाई-फ़ाई नेटवर्क के बिना भी मैसेज भेजे और पाए जा सकते हैं"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ऐप्लिकेशन खोलें"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"यह सेटिंग कैसे काम करती है"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 9af2496..75ca762 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1987,6 +1987,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Pokrenuti poslovne aplikacije?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Ponovno pokreni"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Hitni slučaj"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Postavite zaključavanje zaslona"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Postavi zaključavanje zaslona"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Za upotrebu privatnog prostora postavite zaključavanje zaslona na ovom uređaju"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija nije dostupna"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutačno nije dostupna."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> – nije dostupno"</string>
@@ -2391,4 +2394,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Možete slati i primati poruke bez mobilne mreže ili Wi-Fi mreže"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvori Poruke"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kako to funkcionira"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 17f80d1..c825bd7 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Feloldja a munkahelyi appokat?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Szüneteltetés feloldása"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Vészhelyzet"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Állítson be képernyőzárat"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Képernyőzár beállítása"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"A magánterület használatához állítson be képernyőzárat"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Az alkalmazás nem hozzáférhető"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> jelenleg nem hozzáférhető."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"A(z) <xliff:g id="ACTIVITY">%1$s</xliff:g> nem áll rendelkezése"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Küldhet és fogadhat üzeneteket mobil- és Wi-Fi-hálózat nélkül is"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"A Messages megnyitása"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Hogyan működik?"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 51d0ded..1745f27 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Վերսկսե՞լ աշխ. հավելվածները"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Վերսկսել"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Արտակարգ իրավիճակ"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Կարգավորեք էկրանի կողպումը"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Կարգավորել էկրանի կողպումը"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Մասնավոր տարածքն օգտագործելու համար այս սարքում կարգավորեք էկրանի կողպումը"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Հավելվածը հասանելի չէ"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածն այս պահին հասանելի չէ։"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g>՝ անհասանելի է"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Դուք կարող եք ուղարկել և ստանալ հաղորդագրություններ՝ առանց բջջային կամ Wi-Fi կապի"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Բացել Messages-ը"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ինչպես է դա աշխատում"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 29dc273..af1ec29 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Batalkan jeda aplikasi kerja?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Batalkan jeda"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Darurat"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Setel kunci layar"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Setel kunci layar"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Untuk menggunakan ruang pribadi, setel kunci layar di perangkat ini"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Aplikasi tidak tersedia"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> tidak tersedia saat ini."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> tidak tersedia"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Anda dapat mengirim dan menerima pesan tanpa jaringan seluler atau Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Buka Message"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cara kerjanya"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index ca702fa..d921828 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Ljúka hléi vinnuforrita?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Ljúka hléi"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Neyðartilvik"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Stilltu skjálás"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Stilltu skjálás"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Stilltu skjálás í tækinu til að nota leynirými"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Forrit er ekki tiltækt"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> er ekki tiltækt núna."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ekki í boði"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Þú getur sent og móttekið skilaboð án tengingar við farsímakerfi eða Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Opna Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Svona virkar þetta"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 2cb3d3d..86640f8 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1987,6 +1987,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Riattivare le app di lavoro?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Riattiva"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Emergenza"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Imposta un blocco schermo"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Imposta il blocco schermo"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Per utilizzare il tuo spazio privato, imposta un blocco schermo sul dispositivo"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"L\'app non è disponibile"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"L\'app <xliff:g id="APP_NAME">%1$s</xliff:g> non è al momento disponibile."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> non disponibile"</string>
@@ -2391,4 +2394,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Puoi inviare e ricevere messaggi senza una rete mobile o Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Apri Messaggi"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Come funziona"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 3e7c79e..d0ad38c 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1987,6 +1987,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"להפעיל את האפליקציות לעבודה?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"ביטול ההשהיה"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"שיחת חירום"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"הגדרת נעילת מסך"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"הגדרה של נעילת מסך"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"כדי להשתמש במרחב הפרטי יש להגדיר נעילת מסך במכשיר"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"האפליקציה לא זמינה"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> לא זמינה בשלב זה."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> לא זמינה"</string>
@@ -2391,4 +2394,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"‏אפשר לשלוח ולקבל הודעות ללא רשת סלולרית או רשת Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"‏לפתיחת Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"איך זה עובד"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 576a495..f899084 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"仕事用アプリの停止解除"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"停止解除"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"緊急通報"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"画面ロックの設定"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"画面ロックを設定"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"プライベート スペースには画面ロックの設定が必要です"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"アプリの利用不可"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"現在 <xliff:g id="APP_NAME">%1$s</xliff:g> はご利用になれません。"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g>は利用できません"</string>
@@ -2390,4 +2393,5 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"モバイル ネットワークや Wi-Fi ネットワークを使わずにメッセージを送受信できます"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"メッセージ アプリを開く"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"仕組み"</string>
+    <string name="unarchival_session_app_label" msgid="6811856981546348205">"保留中..."</string>
 </resources>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 571ae42..e4e6042 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"კვლავ გააქტიურდეს სამსახურის აპები?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"გააქტიურება"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"საგანგებო სიტუაცია"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"ეკრანის დაბლოკვის დაყენება"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"ეკრანის დაბლოკვის დაყენება"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"კერძო სივრცის გამოსაყენებლად დააყენეთ ამ მოწყობილობაზე ეკრანის დაბლოკვა"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"აპი მიუწვდომელია"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ამჟამად მიუწვდომელია."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> მიუწვდომელია"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"შეგიძლიათ გაგზავნოთ და მიიღოთ შეტყობინებები მობილური ან Wi-Fi ქსელის გარეშე"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages-ის გახსნა"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"მუშაობის პრინციპი"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 4f50140..e9d00c7 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Жұмыс қолданбаларын қайта қосасыз ба?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Қайта қосу"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Құтқару қызметіне қоңырау шалу"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Экран құлпын орнатыңыз"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Экран құлпын орнату"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Құпия кеңістігіңізді қолдану үшін осы құрылғыда экран құлпын орнатыңыз."</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Қолданба қолжетімді емес"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> қазір қолжетімді емес."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> қолжетімсіз"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Мобильдік не Wi-Fi желісіне қосылмастан хабар жібере аласыз және ала аласыз."</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages қолданбасын ашу"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Бұл қалай орындалады?"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 8c33299..118e6e6 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"ឈប់ផ្អាកកម្មវិធីការងារឬ?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"ឈប់ផ្អាក"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"ពេលមានអាសន្ន"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"កំណត់​ការចាក់​សោអេក្រង់"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"កំណត់​ការចាក់​សោ​អេក្រង់"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"ដើម្បីប្រើលំហឯកជនរបស់អ្នក សូមកំណត់ការចាក់សោអេក្រង់នៅលើឧបករណ៍នេះ"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"មិនអាច​ប្រើ​កម្មវិធី​នេះបានទេ"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"មិនអាច​ប្រើ <xliff:g id="APP_NAME">%1$s</xliff:g> នៅពេល​នេះ​បានទេ​។"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"មិនអាចប្រើ <xliff:g id="ACTIVITY">%1$s</xliff:g> បានទេ"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"អ្នកអាចផ្ញើ និងទទួលសារដោយមិនប្រើបណ្តាញទូរសព្ទចល័ត ឬ Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"បើក​កម្មវិធី Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"របៀបដែលវាដំណើរការ"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index e106f16..4331819 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"ಕೆಲಸದ ಆ್ಯಪ್ ವಿರಾಮ ರದ್ದುಮಾಡಬೇಕೇ"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"ವಿರಾಮವನ್ನು ರದ್ದುಗೊಳಿಸಿ"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"ತುರ್ತು"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಸೆಟ್ ಮಾಡಿ"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಸೆಟ್ ಮಾಡಿ"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"ನಿಮ್ಮ ಪ್ರೈವೆಟ್ ಸ್ಪೇಸ್ ಅನ್ನು ಬಳಸಲು, ಈ ಸಾಧನದಲ್ಲಿ ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಸೆಟ್ ಮಾಡಿ"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"ಆ್ಯಪ್ ಲಭ್ಯವಿಲ್ಲ"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಇದೀಗ ಲಭ್ಯವಿಲ್ಲ."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ಲಭ್ಯವಿಲ್ಲ"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"ನೀವು ಮೊಬೈಲ್ ಅಥವಾ ವೈ-ಫೈ ನೆಟ್‌ವರ್ಕ್ ಇಲ್ಲದೆಯೇ ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಬಹುದು ಮತ್ತು ಸ್ವೀಕರಿಸಬಹುದು"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ಅನ್ನು ತೆರೆಯಿರಿ"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ಇದು ಹೇಗೆ ಕೆಲಸ ಮಾಡುತ್ತದೆ"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index ff480c5..8975b43 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"직장 앱 일시중지를 해제하시겠습니까?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"일시중지 해제"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"긴급 전화"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"화면 잠금 설정"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"화면 잠금 설정"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"비공개 스페이스를 사용하려면 이 기기에 화면 잠금을 설정하세요"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"앱을 사용할 수 없습니다"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"현재 <xliff:g id="APP_NAME">%1$s</xliff:g> 앱을 사용할 수 없습니다."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> 사용할 수 없음"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"모바일 또는 Wi-Fi 네트워크 없이 메시지를 주고 받을 수 있습니다"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"메시지 열기"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"작동 방식"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index e44689d..c8b5981 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Жумуш колдонмолорун иштетесизби?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Иштетүү"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Шашылыш чалуу"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Экран кулпусун коюп алыңыз"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Экран кулпусун коюу"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Жеке мейкиндикти колдонуу үчүн бул түзмөктүн экранын кулпулаңыз"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Колдонмо учурда жеткиликсиз"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> учурда жеткиликсиз"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> жеткиликсиз"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Сиз мобилдик же Wi-Fi тармагы жок эле билдирүүлөрдү жөнөтүп, ала аласыз"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Жазышуулар колдонмосун ачуу"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ал кантип иштейт"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 1f6c1a1..06a572d 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"ຍົກເລີກການຢຸດຊົ່ວຄາວແອັບບ່ອນເຮັດວຽກບໍ?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"ຍົກເລີກການຢຸດຊົ່ວຄາວ"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"ສຸກເສີນ"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"ຕັ້ງການລັອກໜ້າຈໍ"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"ຕັ້ງການລັອກໜ້າຈໍ"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"ເພື່ອໃຊ້ພື້ນທີ່ສ່ວນບຸກຄົນ, ໃຫ້ຕັ້ງລັອກໜ້າຈໍຢູ່ອຸປະກອນນີ້"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"ແອັບບໍ່ສາມາດໃຊ້ໄດ້"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ່ສາມາດໃຊ້ໄດ້ໃນຕອນນີ້."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"ບໍ່ສາມາດໃຊ້ <xliff:g id="ACTIVITY">%1$s</xliff:g> ໄດ້"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"ທ່ານສາມາດສົ່ງ ແລະ ຮັບຂໍ້ຄວາມໂດຍບໍ່ຕ້ອງໃຊ້ເຄືອຂ່າຍມືຖື ຫຼື Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"ເປີດ Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ມັນເຮັດວຽກແນວໃດ"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 0196351..98bcca9 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -1988,6 +1988,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Atš. darbo progr. pristabd.?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Atšaukti pristabdymą"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Pagalbos tarnyba"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Ekrano užrako nustatymas"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Nustatykite ekrano užraktą"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Jei norite naudoti privačią erdvę, nustatykite ekrano užraktą šiame įrenginyje"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Programa nepasiekiama."</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Programa „<xliff:g id="APP_NAME">%1$s</xliff:g>“ šiuo metu nepasiekiama."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"„<xliff:g id="ACTIVITY">%1$s</xliff:g>“ nepasiekiama"</string>
@@ -2392,4 +2395,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Galite siųsti ir gauti pranešimus be mobiliojo ryšio ar „Wi-Fi“ tinklo"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Atidaryti programą „Messages“"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kaip tai veikia"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 46aae3f..6ed95d3 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -1987,6 +1987,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Vai aktivizēt darba lietotnes?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Aktivizēt"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Ārkārtas zvans"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Iestatiet ekrāna bloķēšanu"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Iestatīt ekrāna bloķēšanu"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Lai izmantotu privāto telpu, iestatiet ekrāna bloķēšanu."</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Lietotne nav pieejama"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> pašlaik nav pieejama."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> nav pieejams"</string>
@@ -2391,4 +2394,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Varat sūtīt un saņemt ziņojumus bez mobilā vai Wi-Fi tīkla."</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Atvērt lietotni Ziņojumi"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Darbības principi"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-mcc334-mnc020-fr-feminine/strings.xml b/core/res/res/values-mcc334-mnc020-fr-feminine/strings.xml
new file mode 100644
index 0000000..ba278df
--- /dev/null
+++ b/core/res/res/values-mcc334-mnc020-fr-feminine/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+** 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" msgid="3838388706348367865">"Vous n\'êtes pas autorisée à avoir plusieurs connexions PDN pour un APN donné -55-"</string>
+</resources>
diff --git a/core/res/res/values-mcc334-mnc020-fr-masculine/strings.xml b/core/res/res/values-mcc334-mnc020-fr-masculine/strings.xml
new file mode 100644
index 0000000..8227cd6
--- /dev/null
+++ b/core/res/res/values-mcc334-mnc020-fr-masculine/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+** 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" msgid="3838388706348367865">"Vous n\'êtes pas autorisé à avoir plusieurs connexions PDN pour un APN donné -55-"</string>
+</resources>
diff --git a/core/res/res/values-mcc334-mnc020-fr-neuter/strings.xml b/core/res/res/values-mcc334-mnc020-fr-neuter/strings.xml
new file mode 100644
index 0000000..110d962
--- /dev/null
+++ b/core/res/res/values-mcc334-mnc020-fr-neuter/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+** 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" msgid="3838388706348367865">"Vous n\'êtes pas autorisé·e à avoir plusieurs connexions PDN pour un APN donné -55-"</string>
+</resources>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 1c85e47..3a60709 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Да се актив. работните аплик.?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Прекини ја паузата"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Итен случај"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Поставете заклучување екран"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Поставување заклучување екран"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"За да користите „Приватен простор“, поставете заклучување екран на уредов"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Апликацијата не е достапна"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> не е достапна во моментов."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> е недостапна"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Може да испраќате и примате пораки без мобилна или Wi-Fi мрежа"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Отворете ја Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Дознајте како функционира"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 9917448..55633dc 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -706,8 +706,8 @@
     <string name="face_acquired_too_far" msgid="2922278214231064859">"ഫോൺ അടുത്തേക്ക് നീക്കുക"</string>
     <string name="face_acquired_too_high" msgid="8278815780046368576">"ഫോൺ മുകളിലേക്ക് ഉയർത്തുക"</string>
     <string name="face_acquired_too_low" msgid="4075391872960840081">"ഫോൺ കൂടുതൽ താഴേക്ക് നീക്കുക"</string>
-    <string name="face_acquired_too_right" msgid="6245286514593540859">"ഫോൺ നിങ്ങളുടെ ഇടതുവശത്തേക്ക് നീക്കുക"</string>
-    <string name="face_acquired_too_left" msgid="9201762240918405486">"ഫോൺ നിങ്ങളുടെ വലതുവശത്തേക്ക് നീക്കുക"</string>
+    <string name="face_acquired_too_right" msgid="6245286514593540859">"ഫോൺ ഇടതുവശത്തേക്ക് നീക്കുക"</string>
+    <string name="face_acquired_too_left" msgid="9201762240918405486">"ഫോൺ വലതുവശത്തേക്ക് നീക്കുക"</string>
     <string name="face_acquired_poor_gaze" msgid="4427153558773628020">"നിങ്ങളുടെ ഉപകരണത്തിന് നേരെ കൂടുതൽ നന്നായി നോക്കുക."</string>
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"മുഖം കാണുന്നില്ല. ഫോൺ കണ്ണിന് നേരെ പിടിക്കുക."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"വളരെയധികം ചലനം. ഫോൺ അനക്കാതെ നേരെ പിടിക്കുക."</string>
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"വർക്ക് ആപ്പുകൾ പുനരാരംഭിക്കണോ?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"പുനരാരംഭിക്കുക"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"അടിയന്തരം"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"സ്‌ക്രീൻ ലോക്ക് സജ്ജീകരിക്കൂ"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"സ്‌ക്രീൻ ലോക്ക് സജ്ജീകരിക്കൂ"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"സ്വകാര്യ സ്പേസിന്, ഇതിൽ സ്ക്രീൻ ലോക്ക് സജ്ജീകരിക്കൂ"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"ആപ്പ് ലഭ്യമല്ല"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ഇപ്പോൾ ലഭ്യമല്ല."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ലഭ്യമല്ല"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"മൊബൈലോ വൈഫൈ നെറ്റ്‌വർക്കോ ഇല്ലാതെ തന്നെ സന്ദേശങ്ങൾ അയയ്‌ക്കാനും സ്വീകരിക്കാനും നിങ്ങൾക്ക് കഴിയും"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages തുറക്കുക"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ഇത് പ്രവർത്തിക്കുന്നത് എങ്ങനെയാണ്"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 48d926c..a9e1c149 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Ажлын аппыг үргэлжлүүлэх үү?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Үргэлжлүүлэх"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Яаралтай тусламж"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Дэлгэцийн түгжээ тохируулах"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Дэлгэцийн түгжээ тохируулах"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Хаалттай орон зайгаа ашиглах бол уг төхөөрөмжид дэлгэцийн түгжээ тохируулна уу"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Апп боломжгүй байна"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> яг одоо боломжгүй байна."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> боломжгүй байна"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Та мобайл эсвэл Wi-Fi сүлжээгүйгээр мессеж илгээх болон хүлээн авах боломжтой"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Мессежийг нээх"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Энэ хэрхэн ажилладаг вэ?"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 8fb6b0f..c20bdb0 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"वर्क ॲप्स पुन्हा सुरू करायची?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"पुन्हा सुरू करा"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"आणीबाणी"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"स्क्रीन लॉक सेट करा"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"स्क्रीन लॉक सेट करा"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"तुमची खाजगी स्पेस वापरण्यास, या डिव्हाइसवर स्क्रीन लॉक सेट करा"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"ॲप उपलब्ध नाही"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> आता उपलब्ध नाही."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> उपलब्ध नाही"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"तुम्ही मोबाइल किंवा वाय-फाय नेटवर्कशिवाय मेसेज पाठवू आणि मिळवू शकता"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages उघडा"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ते कसे काम करते"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 7112ed3..9048ffc 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Nyahjeda apl kerja?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Nyahjeda"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Kecemasan"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Tetapkan kunci skrin"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Tetapkan kunci skrin"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Tetapkan kunci skrin pada peranti untuk guna ruang privasi"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Apl tidak tersedia"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> tidak tersedia sekarang."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> tidak tersedia"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Anda boleh menghantar dan menerima mesej tanpa rangkaian mudah alih atau Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Buka Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cara ciri ini berfungsi"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 2c3e7b74..32aed95 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"အလုပ်သုံးအက်ပ် ပြန်ဖွင့်မလား။"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"ပြန်ဖွင့်ရန်"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"အရေးပေါ်"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"ဖန်သားပြင်လော့ခ် သတ်မှတ်ပါ"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"ဖန်သားပြင်လော့ခ် သတ်မှတ်ရန်"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"သင့်သီးသန့်နေရာသုံးရန် ဤစက်၌ ဖန်သားပြင်လော့ခ် သတ်မှတ်ပါ"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"အက်ပ်ကို မရနိုင်ပါ"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ကို ယခု မရနိုင်ပါ။"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> မရနိုင်ပါ"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"မိုဘိုင်း (သို့) Wi-Fi ကွန်ရက်မရှိဘဲ မက်ဆေ့ဂျ်များကို ပို့နိုင်၊ လက်ခံနိုင်သည်"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ဖွင့်ရန်"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"အလုပ်လုပ်ပုံ"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 541e1b5..41e217d 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Vil du slå på jobbapper igjen?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Slå på"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Nød"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Angi en skjermlås"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Angi skjermlås"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"For å bruke det private området, angi en skjermlås på enheten"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Appen er ikke tilgjengelig"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> er ikke tilgjengelig for øyeblikket."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> er utilgjengelig"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Du kan sende og motta meldinger uten mobil- eller wifi-nettverk"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Åpne Meldinger"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Slik fungerer det"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 8c951e0..6f139cd 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"कामसम्बन्धी एपहरू अनपज गर्ने हो?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"अनपज गर्नुहोस्"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"आपत्‌कालीन"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"स्क्रिन लक सेटअप गर्नुहोस्"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"स्क्रिन लक सेटअप गर्नुहोस्"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"निजी स्पेस प्रयोग गर्न यो डिभाइसमा स्क्रिन लक सेटअप गर्नुहोस्"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"एप उपलब्ध छैन"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> अहिले उपलब्ध छैन।"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> उपलब्ध छैन"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"तपाईं मोबाइल वा Wi-Fi नेटवर्कविनै म्यासेज पठाउन र प्राप्त गर्न सक्नुहुन्छ"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages खोल्नुहोस्"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"यसले काम गर्ने तरिका"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index bbcc2aa..b17d7a8 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Werk-apps hervatten?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Hervatten"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Noodgeval"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Schermvergrendeling instellen"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Schermvergrendeling instellen"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Als je je privéruimte wilt gebruiken, stel je een schermvergrendeling op dit apparaat in"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"App is niet beschikbaar"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is momenteel niet beschikbaar."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> niet beschikbaar"</string>
@@ -2359,7 +2362,7 @@
     <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_title" msgid="5265409360902073556">"Kan niet mirroren 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_thermally_unavailable_notification_content" msgid="9205758199439955949">"Je apparaat is te warm en kan pas naar het scherm mirroren als het is afgekoeld"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Je kunt berichten sturen en krijgen zonder een mobiel of wifi-netwerk"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Berichten openen"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Hoe het werkt"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index d8bcbd1..ade7c82 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"ୱାର୍କ ଆପ୍ସକୁ ପୁଣି ଚାଲୁ କରିବେ?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"ପୁଣି ଚାଲୁ କରନ୍ତୁ"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"ଜରୁରୀକାଳୀନ"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"ଏକ ସ୍କ୍ରିନ୍ ଲକ୍ ସେଟ୍ କରନ୍ତୁ"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"ସ୍କ୍ରିନ ଲକ ସେଟ କରନ୍ତୁ"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"ଆପଣଙ୍କ ପ୍ରାଇଭେଟ ସ୍ପେସ ବ୍ୟବହାର କରିବାକୁ ଏହି ଡିଭାଇସରେ ଏକ ସ୍କ୍ରିନ ଲକ ସେଟ କରନ୍ତୁ"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"ଆପ୍ ଉପଲବ୍ଧ ନାହିଁ"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ବର୍ତ୍ତମାନ ଉପଲବ୍ଧ ନାହିଁ।"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ଉପଲବ୍ଧ ନାହିଁ"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"ଏକ ମୋବାଇଲ କିମ୍ବା ୱାଇ-ଫାଇ ନେଟୱାର୍କ ବିନା ଆପଣ ମେସେଜ ପଠାଇପାରିବେ ଏବଂ ପାଇପାରିବେ"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ଖୋଲନ୍ତୁ"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ଏହା କିପରି କାମ କରେ"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index b762d66..439190b 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"ਕੰਮ ਸੰਬੰਧੀ ਐਪਾਂ ਤੋਂ ਰੋਕ ਹਟਾਈਏ?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"ਰੋਕ ਹਟਾਓ"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"ਐਮਰਜੈਂਸੀ"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"ਸਕ੍ਰੀਨ ਲਾਕ ਸੈੱਟ ਕਰੋ"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"ਸਕ੍ਰੀਨ ਲਾਕ ਸੈੱਟ ਕਰੋ"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"ਆਪਣੀ ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ ਵਰਤਣ ਲਈ, ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਸਕ੍ਰੀਨ ਲਾਕ ਸੈੱਟ ਕਰੋ"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"ਐਪ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਐਪ ਇਸ ਵੇਲੇ ਉਪਲਬਧ ਨਹੀਂ ਹੈ।"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
@@ -2390,4 +2393,5 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"ਤੁਸੀਂ ਮੋਬਾਈਲ ਜਾਂ ਵਾਈ-ਫਾਈ ਨੈੱਟਵਰਕ ਤੋਂ ਬਿਨਾਂ ਸੁਨੇਹੇ ਭੇਜ ਅਤੇ ਪ੍ਰਾਪਤ ਕਰ ਸਕਦੇ ਹੋ"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ਐਪ ਖੋਲ੍ਹੋ"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ਇਹ ਕਿਵੇਂ ਕੰਮ ਕਰਦਾ ਹੈ"</string>
+    <string name="unarchival_session_app_label" msgid="6811856981546348205">"ਵਿਚਾਰ-ਅਧੀਨ..."</string>
 </resources>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 5f3e683..61814b7 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1988,6 +1988,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Cofnąć wstrzymanie aplikacji służbowych?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Cofnij wstrzymanie"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Połączenie alarmowe"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Ustaw blokadę ekranu"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Ustaw blokadę ekranu"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Aby korzystać z przestrzeni prywatnej, ustaw na tym urządzeniu blokadę ekranu"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacja jest niedostępna"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> jest obecnie niedostępna."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> – brak dostępu"</string>
@@ -2392,4 +2395,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Możesz wymieniać wiadomości bez dostępu do sieci komórkowej lub Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otwórz Wiadomości"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Jak to działa"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 9887bf6..cf1a200 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -1987,6 +1987,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Reativar apps de trabalho?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Reativar"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Emergência"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Defina um bloqueio de tela"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Definir bloqueio de tela"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Para usar o espaço privado, defina um bloqueio de tela neste dispositivo"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"O app não está disponível"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> não está disponível no momento."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> indisponível"</string>
@@ -2391,4 +2394,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Você pode enviar e receber mensagens sem um dispositivo móvel ou uma rede Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir o app Mensagens"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 9903acb..9f6b733 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1987,6 +1987,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Retomar apps de trabalho?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Retomar"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Emergência"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Defina um bloqueio de ecrã"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Definir bloqueio de ecrã"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Para usar espaço privado, defina bloqueio de ecrã no disp."</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"A app não está disponível"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"De momento, a app <xliff:g id="APP_NAME">%1$s</xliff:g> não está disponível."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> indisponível"</string>
@@ -2119,11 +2122,11 @@
     <string name="notification_appops_camera_active" msgid="8177643089272352083">"Câmara"</string>
     <string name="notification_appops_microphone_active" msgid="581333393214739332">"Microfone"</string>
     <string name="notification_appops_overlay_active" msgid="5571732753262836481">"sobrepõe-se a outras aplicações no ecrã"</string>
-    <string name="notification_feedback_indicator" msgid="663476517711323016">"Fornecer feedback"</string>
-    <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"Esta notificação foi promovida para Predefinida. Toque para fornecer feedback."</string>
-    <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"Esta notificação foi despromovida para Silenciosa. Toque para fornecer feedback."</string>
-    <string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"Esta notificação passou para uma classificação superior. Toque para fornecer feedback."</string>
-    <string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"Esta notificação passou para uma classificação inferior. Toque para fornecer feedback."</string>
+    <string name="notification_feedback_indicator" msgid="663476517711323016">"Dar feedback"</string>
+    <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"Esta notificação foi promovida para Predefinida. Toque para dar feedback."</string>
+    <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"Esta notificação foi despromovida para Silenciosa. Toque para dar feedback."</string>
+    <string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"Esta notificação passou para uma classificação superior. Toque para dar feedback."</string>
+    <string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"Esta notificação passou para uma classificação inferior. Toque para dar feedback."</string>
     <string name="nas_upgrade_notification_title" msgid="8436359459300146555">"Notificações melhoradas"</string>
     <string name="nas_upgrade_notification_content" msgid="5157550369837103337">"As ações e as respostas sugeridas são agora fornecidas por notificações melhoradas. As notificações adaptáveis do Android já não são suportadas."</string>
     <string name="nas_upgrade_notification_enable_action" msgid="3046406808378726874">"OK"</string>
@@ -2391,4 +2394,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Pode enviar e receber mensagens sem uma rede móvel ou Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abre a app Mensagens"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 9887bf6..cf1a200 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -1987,6 +1987,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Reativar apps de trabalho?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Reativar"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Emergência"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Defina um bloqueio de tela"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Definir bloqueio de tela"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Para usar o espaço privado, defina um bloqueio de tela neste dispositivo"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"O app não está disponível"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> não está disponível no momento."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> indisponível"</string>
@@ -2391,4 +2394,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Você pode enviar e receber mensagens sem um dispositivo móvel ou uma rede Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir o app Mensagens"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 73d92fb..38b6e53 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1987,6 +1987,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Reactivezi aplicații de lucru?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Reactivează"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Urgență"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Setează o blocare a ecranului"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Setează blocarea ecranului"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Ca să folosești spațiul privat, setează blocarea ecranului pe acest dispozitiv"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Aplicația nu este disponibilă"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> nu este disponibilă momentan."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> nu este disponibilă"</string>
@@ -2391,4 +2394,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Poți să trimiți și să primești mesaje fără o rețea mobilă sau Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Deschide Mesaje"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cum funcționează"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 5b434a5..866f316 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1988,6 +1988,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Включить рабочие приложения?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Включить"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Экстренный вызов"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Настройте блокировку экрана"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Настроить блокировку экрана"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Чтобы использовать личное пространство, настройте блокировку экрана на этом устройстве."</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Приложение недоступно"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" сейчас недоступно."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"Недоступно: <xliff:g id="ACTIVITY">%1$s</xliff:g>"</string>
@@ -2392,4 +2395,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Вы можете отправлять и получать сообщения без доступа к мобильной сети или Wi-Fi."</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Открыть Сообщения"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Узнать принцип работы"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 5a27118..3668de1e 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"කාර්ය යෙදුම් විරාම නොකරන්න ද?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"විරාම නොකරන්න"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"හදිසි අවස්ථාව"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"තිර අගුලක් සකසන්න"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"තිර අගුල සකසන්න"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"ඔබේ රහසිගත අවකාශය භාවිතා කිරීමට, මෙම උපාංගයේ තිර අගුලක් සකසන්න"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"යෙදුම ලබා ගත නොහැකිය"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> මේ දැන් ලබා ගත නොහැකිය."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> නොතිබේ"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"ඔබට ජංගම හෝ Wi-Fi ජාලයක් නොමැතිව පණිවිඩ යැවීමට සහ ලැබීමට හැක"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages විවෘත කරන්න"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"එය ක්‍රියා කරන ආකාරය"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 16a56d3..1c642f6 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -1988,6 +1988,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Zrušiť pozast. prac. aplikácií?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Zrušiť pozastavenie"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Tiesňová linka"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Nastavte zámku obrazovky"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Nastavte zámku obrazovky"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Ak chcete používať súkromný priestor, nastavte v tomto zariadení zámku obrazovky"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Aplikácia nie je dostupná"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g> nie je teraz dostupná."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> nie je k dispozícii"</string>
@@ -2392,4 +2395,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Správy môžete odosielať a prijímať bez mobilnej siete či siete Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvoriť Správy"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ako to funguje"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 79a275c..0e72e0c 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -1988,6 +1988,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Želite znova aktivirati delovne aplikacije?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Znova aktiviraj"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Nujni primer"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Nastavitev zaklepanja zaslona"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Nastavite zaklepanje zaslona"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Če želite uporabljati zasebni prostor, v tej napravi nastavite zaklepanje zaslona"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija ni na voljo"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutno ni na voljo."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"»<xliff:g id="ACTIVITY">%1$s</xliff:g>« ni na voljo"</string>
@@ -2392,4 +2395,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Sporočila SMS lahko pošiljate in prejemate brez mobilnega omrežja ali omrežja Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Odpri Sporočila"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kako deluje"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index fc2e715..caa3409 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Hiq nga pauza apl. e punës?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Hiq nga pauza"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Urgjenca"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Cakto një kyçje ekrani"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Cakto kyçjen e ekranit"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Për të përdorur hapësirën private, cakto një kyçje ekrani në këtë pajisje"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacioni nuk ofrohet"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> nuk ofrohet për momentin."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> nuk ofrohet"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Mund të dërgosh dhe të marrësh mesazhe pa një rrjet celular apo rrjet Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Hap \"Mesazhet\""</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Si funksionon"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 4f9ce47..75439d7 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1987,6 +1987,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Укључити пословне апликације?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Поново активирај"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Хитан случај"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Подесите закључавање екрана"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Подеси закључавање екрана"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Да бисте користили приватни простор, подесите закључавање екрана на овом уређају"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Апликација није доступна"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Апликација <xliff:g id="APP_NAME">%1$s</xliff:g> тренутно није доступна."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> – није доступно"</string>
@@ -2391,4 +2394,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Можете да шаљете и примате поруке без мобилне или WiFi мреже"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Отвори Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Принцип рада"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 7f0a201..be777f0 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -1489,7 +1489,7 @@
     <string name="permdesc_requestDeletePackages" msgid="6133633516423860381">"Tillåter att en app begär paketborttagning."</string>
     <string name="permlab_requestIgnoreBatteryOptimizations" msgid="7646611326036631439">"får be om tillstånd att ignorera batterioptimering"</string>
     <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Appen får be om tillstånd att ignorera batterioptimering."</string>
-    <string name="permlab_queryAllPackages" msgid="2928450604653281650">"fråga alla paket"</string>
+    <string name="permlab_queryAllPackages" msgid="2928450604653281650">"sök efter alla paket"</string>
     <string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Tillåter att en app ser alla installerade paket."</string>
     <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Peka två gånger för zoomkontroll"</string>
     <string name="gadget_host_error_inflating" msgid="2449961590495198720">"Det gick inte att lägga till widgeten."</string>
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Vill du återuppta jobbappar?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Återuppta"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Nödsituation"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Ställ in ett skärmlås"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Ställ in skärmlås"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Ställ in ett skärmlås för enheten om du vill använda ditt privata område."</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Appen är inte tillgänglig"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> är inte tillgängligt just nu."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> är inte tillgänglig"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Du kan skicka och ta emot meddelanden utan mobil- eller wifi-nätverk"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Öppna Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Så fungerar det"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 4d00440..65c6321 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Je, ungependa kuacha kusitisha programu za kazini?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Acha kusitisha"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Simu za dharura"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Weka mbinu ya kufunga skrini"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Weka mbinu ya kufunga skrini"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Ili utumie sehemu ya faragha, weka mbinu ya kufunga skrini kwenye kifaa hiki"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Programu haipatikani"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> haipatikani hivi sasa."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> haipatikani"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Unaweza kutuma na kupokea ujumbe bila mtandao wa simu au Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Fungua Programu ya Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Utaratibu wake"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index f962728..b3d16d7 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"பணி ஆப்ஸை மீண்டும் இயக்கவா?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"மீண்டும் இயக்கு"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"அவசர அழைப்பு"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"திரைப் பூட்டை அமையுங்கள்"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"திரைப் பூட்டை அமை"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"ரகசிய இடத்தைப் பயன்படுத்த, சாதனத்தில் திரைப் பூட்டை அமையுங்கள்"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"இந்த ஆப்ஸ் இப்போது கிடைப்பதில்லை"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் இப்போது கிடைப்பதில்லை."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> இல்லை"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"மொபைல்/வைஃபை நெட்வொர்க் இல்லாமல் நீங்கள் மெசேஜ்களை அனுப்பலாம் பெறலாம்"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ஆப்ஸைத் திறக்கவும்"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"இது செயல்படும் விதம்"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 30c2812..c8778a4 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"వర్క్ యాప్స్ అన్‌పాజ్ చేయాలా?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"అన్‌పాజ్ చేయండి"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"ఎమర్జెన్సీ"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"స్క్రీన్ లాక్‌ను సెట్ చేయండి"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"స్క్రీన్ లాక్‌ను సెట్ చేయండి"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"మీ ప్రైవేట్ స్పేస్‌ను ఉపయోగించడానికి, ఈ పరికరంలో స్క్రీన్ లాక్ సెట్ చేయండి"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"యాప్ అందుబాటులో లేదు"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ప్రస్తుతం అందుబాటులో లేదు."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> అందుబాటులో లేదు"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"మీరు మొబైల్ లేదా Wi-Fi నెట్‌వర్క్ లేకుండా మెసేజ్‌లను పంపవచ్చు, స్వీకరించవచ్చు"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messagesను తెరవండి"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ఇది ఎలా పని చేస్తుంది"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index c92d613..8a05689 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"ยกเลิกการหยุดแอปงานชั่วคราวไหม"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"ยกเลิกการหยุดชั่วคราว"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"ฉุกเฉิน"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"ตั้งล็อกหน้าจอ"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"ตั้งล็อกหน้าจอ"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"หากต้องการใช้พื้นที่ส่วนตัว ให้ตั้งการล็อกหน้าจอในอุปกรณ์นี้"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"แอปไม่พร้อมใช้งาน"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ไม่พร้อมใช้งานในขณะนี้"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ไม่พร้อมใช้งาน"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"คุณรับส่งข้อความผ่านดาวเทียมได้โดยไม่ต้องใช้เครือข่ายมือถือหรือ Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"เปิด Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"วิธีการทำงาน"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index d944334..3b3797a 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"I-unpause ang mga work app?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"I-unpause"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Emergency"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Magtakda ng lock ng screen"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Itakda ang lock ng screen"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Para gamitin ang iyong pribadong space, magtakda ng lock ng screen sa device na ito."</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Hindi available ang app"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Hindi available sa ngayon ang <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"Hindi available ang <xliff:g id="ACTIVITY">%1$s</xliff:g>"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Puwede kang magpadala at tumanggap ng mga mensahe nang walang mobile o Wi-Fi network"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Buksan ang Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Paano ito gumagana"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index dfae301..70ea414 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"İş uygulamaları devam ettirilsin mi?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Devam ettir"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Acil durum"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Ekran kilidi ayarlayın"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Ekran kilidi ayarla"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Özel alanı kullanmak için cihazda ekran kilidi ayarlayın"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Uygulama kullanılamıyor"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulaması şu anda kullanılamıyor."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> kullanılamıyor"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Mobil veya kablosuz ağa bağlı olmadan mesaj alıp gönderebilirsiniz"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Mesajlar\'ı aç"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"İşleyiş şekli"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index c8fa210..9c7c8ef 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1988,6 +1988,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Увімкнути робочі додатки?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Увімкнути"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Екстрений виклик"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Налаштуйте блокування екрана"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Налаштувати блокування екрана"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Для доступу до приватного простору налаштуйте блокування екрана"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Додаток недоступний"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> зараз недоступний."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"Недоступно: <xliff:g id="ACTIVITY">%1$s</xliff:g>"</string>
@@ -2392,4 +2395,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Ви можете надсилати й отримувати повідомлення, не використовуючи Wi-Fi або мобільну мережу"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Відкрийте Повідомлення"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Як це працює"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 5ad1f18..e60299f 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"ورک ایپس کو غیر موقوف کریں؟"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"غیر موقوف کریں"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"ایمرجنسی"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"اسکرین لاک سیٹ کریں"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"اسکرین لاک سیٹ کریں"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"اپنی نجی اسپیس استعمال کرنے کیلئے، اس آلہ پر اسکرین لاک سیٹ کریں"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"ایپ دستیاب نہیں ہے"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ابھی دستیاب نہیں ہے۔"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> دستیاب نہیں ہے"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"‏آپ موبائل یا Wi-Fi نیٹ ورک کے بغیر پیغامات بھیج اور موصول کر سکتے ہیں"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"پیغامات ایپ کو کھولیں"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"اس کے کام کرنے کا طریقہ"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index a3ffb9c..58e576b 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Ishga oid ilovalar qaytarilsinmi?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Davom ettirish"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Favqulodda holat"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Ekran qulfini sozlash"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Ekran qulfini sozlash"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Maxfiy makon ishlatish uchun bu qurilma ekran qulfini sozlang"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Ilova ishlamayapti"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Ayni vaqtda <xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi ishlamayapti."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> kanali ish faoliyatida emas"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Mobil yoki Wi-Fi tarmoqsiz xabarlarni yuborishingiz va qabul qilishingiz mumkin"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Xabarlar ilovasini ochish"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ishlash tartibi"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 8f52be0..a8d8188 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Tiếp tục dùng ứng dụng công việc?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Tiếp tục dùng"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Khẩn cấp"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Đặt phương thức khoá màn hình"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Đặt phương thức khoá màn hình"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Để dùng không gian riêng tư, hãy thiết lập một phương thức khoá màn hình trên thiết bị này"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Ứng dụng này không dùng được"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> hiện không dùng được."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"Không hỗ trợ <xliff:g id="ACTIVITY">%1$s</xliff:g>"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Bạn có thể gửi và nhận tin nhắn mà không cần có mạng di động hoặc mạng Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Mở ứng dụng Tin nhắn"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cách hoạt động"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-watch/colors.xml b/core/res/res/values-watch/colors.xml
index 0b00bd8..e2b7505 100644
--- a/core/res/res/values-watch/colors.xml
+++ b/core/res/res/values-watch/colors.xml
@@ -18,11 +18,26 @@
 <resources>
   <color name="system_error_light">#B3261E</color>
   <color name="system_on_error_light">#FFFFFF</color>
-  <color name="system_error_container_light">#F9DEDC</color>
+  <color name="system_error_container_light">#F7DCDA</color>
   <color name="system_on_error_container_light">#410E0B</color>
 
-  <color name="system_error_dark">#EC928E</color>
-  <color name="system_on_error_dark">#410E0B</color>
-  <color name="system_error_container_dark">#F2B8B5</color>
-  <color name="system_on_error_container_dark">#601410</color>
+  <color name="system_error_dark">#F2B8B5</color>
+  <color name="system_on_error_dark">#601410</color>
+  <color name="system_error_container_dark">#FF8986</color>
+  <color name="system_on_error_container_dark">#410E0B</color>
+
+  <!-- With material deprecation of 'background' in favor of 'surface' we flatten these
+       on watches to match the black background requirements -->
+  <color name="system_surface_dark">#000000</color>
+  <color name="system_surface_dim_dark">#000000</color>
+  <color name="system_surface_bright_dark">#000000</color>
+
+  <!-- Wear flattens the typical 5 container layers to 3; container + high & low -->
+  <color name="system_surface_container_dark">#303030</color>
+  <color name="system_surface_variant_dark">#303030</color>
+  <color name="system_surface_container_high_dark">#474747</color>
+  <color name="system_surface_container_highest_dark">#474747</color>
+  <color name="system_surface_container_low_dark">#252626</color>
+  <color name="system_surface_container_lowest_dark">#252626</color>
+
 </resources>
diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml
index df8158d..6e804c0 100644
--- a/core/res/res/values-watch/themes_device_defaults.xml
+++ b/core/res/res/values-watch/themes_device_defaults.xml
@@ -146,7 +146,7 @@
         <item name="windowAnimationStyle">@style/Animation.InputMethod</item>
         <item name="imeFullscreenBackground">?colorBackground</item>
         <item name="imeExtractEnterAnimation">@anim/input_method_extract_enter</item>
-        <item name="windowSwipeToDismiss">false</item>
+        <item name="windowSwipeToDismiss">true</item>
     </style>
 
     <!-- DeviceDefault theme for dialog windows and activities. In contrast to Material, the
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 28f563d..b0f052e 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"是否为工作应用解除暂停状态?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"解除暂停"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"紧急呼叫"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"设置一种屏锁方式"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"设置屏锁方式"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"若要使用私密空间,请在此设备上设置屏锁方式"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"应用无法使用"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g>目前无法使用。"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g>不可用"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"您无需使用移动网络或 WLAN 网络便能收发消息"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"打开“信息”应用"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"运作方式"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 59a2abd..0bf9079 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"要取消暫停工作應用程式嗎?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"取消暫停"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"撥打緊急電話"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"設定螢幕鎖定"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"設定螢幕鎖定"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"如要使用私人空間,請在此裝置上設定螢幕鎖定功能"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"無法使用應用程式"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"目前無法使用「<xliff:g id="APP_NAME">%1$s</xliff:g>」。"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"無法使用「<xliff:g id="ACTIVITY">%1$s</xliff:g>」"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"你可在沒有流動/Wi-Fi 網絡的情況下收發訊息"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"開啟「訊息」"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"運作方式"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 2233668..c15262d 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -492,8 +492,8 @@
     <string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"允許應用程式存取額外位置資訊提供者指令。這項設定可能會造成應用程式干擾 GPS 或其他位置資訊來源的運作。"</string>
     <string name="permlab_accessFineLocation" msgid="6426318438195622966">"僅可在前景中取得精確位置"</string>
     <string name="permdesc_accessFineLocation" msgid="6732174080240016335">"只有在你使用時,這個應用程式才能透過定位服務取得你的精確位置。你必須在裝置上開啟定位服務,這個應用程式才能取得位置資訊。這麼做可能會增加電池用量。"</string>
-    <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"僅可在前景中取得概略位置"</string>
-    <string name="permdesc_accessCoarseLocation" msgid="778521847873199160">"只有在你使用時,這個應用程式才能透過定位服務取得你的概略位置。你必須在裝置上開啟定位服務,這個應用程式才能取得位置資訊。"</string>
+    <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"僅可在前景中取得大概位置"</string>
+    <string name="permdesc_accessCoarseLocation" msgid="778521847873199160">"只有在你使用時,這個應用程式才能透過定位服務取得你的大概位置。你必須在裝置上開啟定位服務,這個應用程式才能取得位置資訊。"</string>
     <string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"在背景存取位置資訊"</string>
     <string name="permdesc_accessBackgroundLocation" msgid="8264885066095638105">"這個應用程式隨時都能取得位置資訊 (包括未使用應用程式時)。"</string>
     <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"變更音訊設定"</string>
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"要解除工作應用程式的暫停狀態嗎?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"取消暫停"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"撥打緊急電話"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"設定螢幕鎖定功能"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"設定螢幕鎖定功能"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"如要使用私人空間,請在這部裝置設定螢幕鎖定功能"</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"應用程式無法使用"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」目前無法使用。"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"無法存取「<xliff:g id="ACTIVITY">%1$s</xliff:g>」"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"你可以收發訊息,沒有行動/Wi-Fi 網路也無妨"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"開啟「訊息」應用程式"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"運作方式"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 176c3743..91050bf 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -1986,6 +1986,9 @@
     <string name="work_mode_off_title" msgid="6367463960165135829">"Susa ukumisa ama-app omsebenzi?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Qhubekisa"</string>
     <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Isimo esiphuthumayo"</string>
+    <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Setha ukukhiya isikrini"</string>
+    <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Setha ukukhiya isikrini"</string>
+    <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Ukuze usebenzise isikhala esigodliwe, setha ukukhiya kwesikrini kule divayisi."</string>
     <string name="app_blocked_title" msgid="7353262160455028160">"Uhlelo lokusebenza alutholakali"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> ayitholakali khona manje."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"okungatholakali <xliff:g id="ACTIVITY">%1$s</xliff:g>"</string>
@@ -2390,4 +2393,6 @@
     <string name="satellite_notification_summary" msgid="5207364139430767162">"Ungathumela futhi wamukele imilayezo ngaphandle kwenethiwekhi yeselula noma ye-Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Vula Imilayezo"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Indlela esebenza ngayo"</string>
+    <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index c1fd619..48cf09a 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4409,7 +4409,7 @@
         <attr name="requireDeviceScreenOn" format="boolean"/>
         <!-- Whether the device should default to observe mode when this service is
              default or in the foreground. -->
-        <attr name="defaultToObserveMode" format="boolean"/>
+        <attr name="shouldDefaultToObserveMode" format="boolean"/>
     </declare-styleable>
 
     <!-- Use <code>offhost-apdu-service</code> as the root tag of the XML resource that
@@ -4436,7 +4436,7 @@
         <attr name="requireDeviceScreenOn"/>
         <!-- Whether the device should default to observe mode when this service is
              default or in the foreground. -->
-        <attr name="defaultToObserveMode"/>
+        <attr name="shouldDefaultToObserveMode"/>
     </declare-styleable>
 
     <!-- Specify one or more <code>aid-group</code> elements inside a
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index b2e0be7c..d89f236 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -301,7 +301,9 @@
             granted to the system companion device manager service -->
         <flag name="companion" value="0x800000" />
         <!-- Additional flag from base permission type: this permission will be granted to the
-             retail demo app, as defined by the OEM. -->
+             retail demo app, as defined by the OEM.
+             This flag has been replaced by the retail demo role and is a no-op since Android V.
+          -->
         <flag name="retailDemo" value="0x1000000" />
         <!-- Additional flag from base permission type: this permission will be granted to the
              recents app. -->
@@ -1618,15 +1620,13 @@
         <!-- Data (photo, file, account) upload/download, backup/restore, import/export, fetch,
             transfer over network between device and cloud.
 
-            <p><b>THIS TYPE IS DEPRECATED.</b>
-            <p><em>Note: For apps with <code>targetSdkVersion</code>
-            {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} and above, this type should
-            <b>NOT</b> be used: calling
-            {@link android.app.Service#startForeground(int, android.app.Notification, int)}
-            with this type on devices running
-            {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} is still allowed, but it may
-            throw an {@link android.app.InvalidForegroundServiceTypeException} in future platform
-            releases.</em>
+            <p>For apps with <code>targetSdkVersion</code>
+            {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, this type should NOT
+            be used: calling
+            {@link android.app.Service#startForeground(int, android.app.Notification, int)} with
+            this type on devices running {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
+            is still allowed, but calling it with this type on devices running future platform
+            releases may get a {@link android.app.InvalidForegroundServiceTypeException}.
         -->
         <flag name="dataSync" value="0x01" />
         <!-- Music, video, news or other media play.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1f06b0b..f59c099 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1608,7 +1608,10 @@
     <fraction name="config_autoBrightnessAdjustmentMaxGamma">300%</fraction>
 
     <!-- If we allow automatic adjustment of screen brightness while dozing, how many times we want
-         to reduce it to preserve the battery. Value of 100% means no scaling. -->
+         to reduce it to preserve the battery. Value of 100% means no scaling. Not used if there is
+         a designated auto-brightness doze mapping defined in Display Device Config.
+         Also used to scale the brightness for the doze mode when auto-brightness is disabled if
+         there is an offload session present. -->
     <fraction name="config_screenAutoBrightnessDozeScaleFactor">100%</fraction>
 
     <!-- When the screen is turned on, the previous estimate of the ambient light level at the time
@@ -3129,6 +3132,10 @@
     -->
     <bool name="config_enableWifiDisplay">false</bool>
 
+    <!-- Whether the default HDR conversion mode should be passthrough instead of system.
+    -->
+    <bool name="config_enableDefaultHdrConversionPassthrough">false</bool>
+
     <!-- When true, local displays that do not contain any of their own content will automatically
          mirror the content of the default display. -->
     <bool name="config_localDisplaysMirrorContent">true</bool>
@@ -3348,26 +3355,23 @@
     <string name="config_carrierAppInstallDialogComponent" translatable="false"
             >com.android.simappdialog/com.android.simappdialog.InstallCarrierAppActivity</string>
 
-    <!-- Name of the default framework dialog that is used to get or save an app credential.
+    <!-- Name of the fallback CredentialManager dialog that is used to get or save an app
+     credential.
 
-    This UI should be always launch-able and is used as a fallback when an oem replacement activity
-    (defined at config_oemCredentialManagerDialogComponent) is undefined / not found. -->
-    <string name="config_credentialManagerDialogComponent" translatable="false"
+     If empty, no fallback will be used. IMPORTANT: In that case the OEM dialog value specified in
+     config_oemCredentialManagerDialogComponent must always launch-able. Otherwise, the
+     CredentialManager API contract is broken.
+
+     If specified, this UI should be always launch-able. It will be used as a fallback when the OEM
+     dialog value specified in config_oemCredentialManagerDialogComponent) is undefined / not
+     found. -->
+    <string name="config_fallbackCredentialManagerDialogComponent" translatable="false"
             >com.android.credentialmanager/com.android.credentialmanager.CredentialSelectorActivity</string>
-    <!-- Whether to allow the credential selector activity to be replaced by an activity at
-     run-time (restricted to the privileged activity specified by
-     config_credentialSelectorActivityName).
-
-     When disabled, the fallback activity defined at
-     config_credentialManagerDialogComponent will be used instead. -->
-    <bool name="config_enableOemCredentialManagerDialogComponent" translatable="false">true</bool>
     <!-- Fully qualified activity name providing the credential selector UI, that serves the
-     CredentialManager APIs.
+     CredentialManager APIs. Must be a system app component.
 
-     Used only when config_enableOemCredentialManagerDialogComponent is true.
-
-     If the activity specified cannot be found or launched, then the fallback activity defined at
-     config_credentialManagerDialogComponent will be used instead. -->
+     If empty, or if this activity specified cannot be found or launched, then the fallback activity
+     defined at config_fallbackCredentialManagerDialogComponent will be used instead. -->
     <string name="config_oemCredentialManagerDialogComponent" translatable="false"></string>
 
     <!-- Name of the broadcast receiver that is used to receive provider change events -->
@@ -3554,10 +3558,6 @@
     <string name="config_keyguardComponent" translatable="false"
             >com.android.systemui/com.android.systemui.keyguard.KeyguardService</string>
 
-    <!-- Screen record dialog component -->
-    <string name="config_screenRecorderComponent" translatable="false"
-            >com.android.systemui/com.android.systemui.screenrecord.ScreenRecordDialog</string>
-
     <!-- The component name of a special dock app that merely launches a dream.
          We don't want to launch this app when docked because it causes an unnecessary
          activity transition.  We just want to start the dream. -->
@@ -3614,8 +3614,7 @@
     <!-- Whether this device prefers to show snapshot or splash screen on back predict target.
          When set true, there will create windowless starting surface for the preview target, so it
          won't affect activity's lifecycle. This should only be disabled on low-ram device. -->
-    <!-- TODO(b/268563842) enable once activity snapshot is ready -->
-    <bool name="config_predictShowStartingSurface">false</bool>
+    <bool name="config_predictShowStartingSurface">true</bool>
 
     <!-- default window ShowCircularMask property -->
     <bool name="config_windowShowCircularMask">false</bool>
@@ -4101,6 +4100,13 @@
     <!-- How close vibration request should be when they're aggregated for dumpsys, in ms. -->
     <integer name="config_previousVibrationsDumpAggregationTimeMillisLimit">1000</integer>
 
+    <!-- How long history of vibration control service should be kept for the dumpsys. -->
+    <integer name="config_vibratorControlServiceDumpSizeLimit">50</integer>
+
+    <!-- How close requests to vibration control service should be when they're aggregated for
+         dumpsys, in ms. -->
+    <integer name="config_vibratorControlServiceDumpAggregationTimeMillisLimit">60000</integer>
+
     <!-- The default vibration strength, must be between 1 and 255 inclusive. -->
     <integer name="config_defaultVibrationAmplitude">255</integer>
 
@@ -4674,8 +4680,8 @@
     <!-- The component name for the default system on-device intelligence service, -->
     <string name="config_defaultOnDeviceIntelligenceService" translatable="false"></string>
 
-    <!-- The component name for the default system on-device trusted inference service. -->
-    <string name="config_defaultOnDeviceTrustedInferenceService" translatable="false"></string>
+    <!-- The component name for the default system on-device sandboxed inference service. -->
+    <string name="config_defaultOnDeviceSandboxedInferenceService" translatable="false"></string>
 
     <!-- Component name that accepts ACTION_SEND intents for requesting ambient context consent for
          wearable sensing. -->
@@ -6967,4 +6973,16 @@
 
     <!-- Whether to use file hashes cache in watchlist-->
     <bool name="config_watchlistUseFileHashesCache">false</bool>
+
+    <!-- Name of the package responsible to handle Contextual Search. -->
+    <string name="config_defaultContextualSearchPackageName" translatable="false" />
+
+    <!-- The key containing the entrypoint for Contextual Search. -->
+    <string name="config_defaultContextualSearchKey" translatable="false" />
+
+    <!-- The key containing the branching boolean for Contextual Search. -->
+    <string name="config_defaultContextualSearchEnabled" translatable="false" />
+
+    <!-- The key containing the branching boolean for legacy Search. -->
+    <string name="config_defaultContextualSearchLegacyEnabled" translatable="false" />
 </resources>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index c87b7cd..7c9f2ef 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -84,7 +84,7 @@
          CarrierConfigManager#KEY_AUTO_DATA_SWITCH_RAT_SIGNAL_SCORE_STRING_ARRAY.
          If 0, the device always switch to the higher score SIM.
          If < 0, the network type and signal strength based auto switch is disabled. -->
-    <integer name="auto_data_switch_score_tolerance">4000</integer>
+    <integer name="auto_data_switch_score_tolerance">-1</integer>
     <java-symbol type="integer" name="auto_data_switch_score_tolerance" />
 
     <!-- Boolean indicating whether the Iwlan data service supports persistence of iwlan ipsec
@@ -334,4 +334,22 @@
     <bool name="config_enable_cellular_on_boot_default">true</bool>
     <java-symbol type="bool" name="config_enable_cellular_on_boot_default" />
 
+    <!-- Defines metrics pull cooldown period. The default cooldown period is 23 hours,
+         some Telephony metrics need to be pulled more frequently  -->
+    <integer name="config_metrics_pull_cooldown_millis">82800000</integer>
+    <java-symbol type="integer" name="config_metrics_pull_cooldown_millis" />
+
+    <!-- The network capabilities that would be forced marked as cellular transport regardless it's
+         on cellular or satellite-->
+    <string-array name="config_force_cellular_transport_capabilities">
+        <!-- Added the following three capabilities for now. For the long term solution, the client
+             requests satellite network should really include TRANSPORT_SATELLITE in the network
+             request. With the following workaround, the clients can continue request network with
+             the following capabilities with TRANSPORT_CELLULAR. The network with one of the
+             following capabilities would also be marked as cellular. -->
+        <item>ims</item>
+        <item>eims</item>
+        <item>xcap</item>
+    </string-array>
+    <java-symbol type="array" name="config_force_cellular_transport_capabilities" />
 </resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 5987f6e..3303c07 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -160,7 +160,7 @@
     <!-- @FlaggedApi("android.view.inputmethod.connectionless_handwriting") -->
     <public name="supportsConnectionlessStylusHandwriting" />
     <!-- @FlaggedApi("android.nfc.Flags.FLAG_OBSERVE_MODE") -->
-    <public name="defaultToObserveMode"/>
+    <public name="shouldDefaultToObserveMode"/>
     <!-- @FlaggedApi("android.security.asm_restrictions_enabled") -->
     <public name="allowCrossUidActivitySwitchFromBelow"/>
     <!-- @FlaggedApi("com.android.text.flags.use_bounds_for_width") -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 59066eb..c7e08e2 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5452,7 +5452,7 @@
     <string name="set_up_screen_lock_title">Set a screen lock</string>
     <!-- Action label for the dialog prompting the user to set up a screen lock [CHAR LIMIT=30] -->
     <string name="set_up_screen_lock_action_label">Set screen lock</string>
-    <!-- Message shown in the dialog prompting the user to set up a screen lock to access private space [CHAR LIMIT=30] -->
+    <!-- Message shown in the dialog prompting the user to set up a screen lock to access private space [CHAR LIMIT=120] -->
     <string name="private_space_set_up_screen_lock_message">To use your private space, set a screen lock on this device</string>
 
     <!-- Title of the dialog that is shown when the user tries to launch a blocked application [CHAR LIMIT=50] -->
@@ -6434,4 +6434,6 @@
     <string name="satellite_notification_open_message">Open Messages</string>
     <!-- Invoke Satellite setting activity of Settings -->
     <string name="satellite_notification_how_it_works">How it works</string>
+    <!-- Initial/System provided label shown for an app which gets unarchived. [CHAR LIMIT=64]. -->
+    <string name="unarchival_session_app_label">Pending...</string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b36b1d6..c603fa7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -348,6 +348,7 @@
   <java-symbol type="bool" name="config_enableScreenshotChord" />
   <java-symbol type="bool" name="config_fold_lock_behavior" />
   <java-symbol type="bool" name="config_enableWifiDisplay" />
+  <java-symbol type="bool" name="config_enableDefaultHdrConversionPassthrough" />
   <java-symbol type="bool" name="config_allowAnimationsInLowPowerMode" />
   <java-symbol type="bool" name="config_useDevInputEventForAudioJack" />
   <java-symbol type="bool" name="config_safe_media_volume_enabled" />
@@ -375,7 +376,6 @@
   <java-symbol type="string" name="config_recentsComponentName" />
   <java-symbol type="string" name="config_systemUIServiceComponent" />
   <java-symbol type="string" name="config_controlsPackage" />
-  <java-symbol type="string" name="config_screenRecorderComponent" />
   <java-symbol type="string" name="config_somnambulatorComponent" />
   <java-symbol type="string" name="config_screenshotAppClipsServiceComponent" />
   <java-symbol type="string" name="config_screenshotServiceComponent" />
@@ -2083,6 +2083,8 @@
   <java-symbol type="integer" name="config_recentVibrationsDumpSizeLimit" />
   <java-symbol type="integer" name="config_previousVibrationsDumpSizeLimit" />
   <java-symbol type="integer" name="config_previousVibrationsDumpAggregationTimeMillisLimit" />
+  <java-symbol type="integer" name="config_vibratorControlServiceDumpSizeLimit" />
+  <java-symbol type="integer" name="config_vibratorControlServiceDumpAggregationTimeMillisLimit" />
   <java-symbol type="integer" name="config_defaultVibrationAmplitude" />
   <java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" />
   <java-symbol type="dimen" name="config_keyboardHapticFeedbackFixedAmplitude" />
@@ -2295,8 +2297,7 @@
   <java-symbol type="string" name="config_customVpnAlwaysOnDisconnectedDialogComponent" />
   <java-symbol type="string" name="config_platformVpnConfirmDialogComponent" />
   <java-symbol type="string" name="config_carrierAppInstallDialogComponent" />
-  <java-symbol type="string" name="config_credentialManagerDialogComponent" />
-  <java-symbol type="bool" name="config_enableOemCredentialManagerDialogComponent" />
+  <java-symbol type="string" name="config_fallbackCredentialManagerDialogComponent" />
   <java-symbol type="string" name="config_oemCredentialManagerDialogComponent" />
   <java-symbol type="string" name="config_credentialManagerReceiverComponent" />
   <java-symbol type="string" name="config_defaultNetworkScorerPackageName" />
@@ -3916,7 +3917,7 @@
   <java-symbol type="string" name="config_ambientContextEventArrayExtraKey" />
   <java-symbol type="string" name="config_defaultWearableSensingService" />
   <java-symbol type="string" name="config_defaultOnDeviceIntelligenceService" />
-  <java-symbol type="string" name="config_defaultOnDeviceTrustedInferenceService" />
+  <java-symbol type="string" name="config_defaultOnDeviceSandboxedInferenceService" />
   <java-symbol type="string" name="config_retailDemoPackage" />
   <java-symbol type="string" name="config_retailDemoPackageSignature" />
 
@@ -4364,6 +4365,7 @@
   <java-symbol type="dimen" name="seekbar_thumb_exclusion_max_size" />
   <java-symbol type="layout" name="chooser_az_label_row" />
   <java-symbol type="string" name="chooser_all_apps_button_label" />
+  <java-symbol type="anim" name="resolver_close_anim" />
   <java-symbol type="anim" name="resolver_launch_anim" />
   <java-symbol type="style" name="Animation.DeviceDefault.Activity.Resolver" />
 
@@ -5368,4 +5370,10 @@
   <java-symbol type="bool" name="config_evenDimmerEnabled" />
 
   <java-symbol type="bool" name="config_watchlistUseFileHashesCache" />
+  <java-symbol type="string" name="config_defaultContextualSearchPackageName" />
+  <java-symbol type="string" name="config_defaultContextualSearchKey" />
+  <java-symbol type="string" name="config_defaultContextualSearchEnabled" />
+  <java-symbol type="string" name="config_defaultContextualSearchLegacyEnabled" />
+
+  <java-symbol type="string" name="unarchival_session_app_label" />
 </resources>
diff --git a/core/res/res/xml/bookmarks.xml b/core/res/res/xml/bookmarks.xml
index 3087aaa..22d0226 100644
--- a/core/res/res/xml/bookmarks.xml
+++ b/core/res/res/xml/bookmarks.xml
@@ -33,7 +33,7 @@
 -->
 <bookmarks>
     <bookmark
-        category="android.intent.category.APP_BROWSER"
+        role="android.app.role.BROWSER"
         shortcut="b" />
     <bookmark
         category="android.intent.category.APP_CONTACTS"
@@ -51,7 +51,7 @@
         category="android.intent.category.APP_MUSIC"
         shortcut="p" />
     <bookmark
-        category="android.intent.category.APP_MESSAGING"
+        role="android.app.role.SMS"
         shortcut="s" />
     <bookmark
         category="android.intent.category.APP_CALCULATOR"
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 61e6a36..7d740ef 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -88,6 +88,9 @@
     <!-- Colombia: 1-6 digits (not confirmed) -->
     <shortcode country="co" pattern="\\d{1,6}" free="890350|908160|892255|898002|898880|899960|899948|87739|85517|491289" />
 
+    <!-- Costa Rica  -->
+    <shortcode country="cr" pattern="\\d{1,6}" free="466453" />
+
     <!-- Cyprus: 4-6 digits (not confirmed), known premium codes listed, plus EU -->
     <shortcode country="cy" pattern="\\d{4,6}" premium="7510" free="116\\d{3}" />
 
@@ -143,6 +146,9 @@
     <!-- Greece: 5 digits (54xxx, 19yxx, x=0-9, y=0-5): http://www.cmtelecom.com/premium-sms/greece -->
     <shortcode country="gr" pattern="\\d{5}" premium="54\\d{3}|19[0-5]\\d{2}" free="116\\d{3}|12115" />
 
+    <!--  Guatemala  -->
+    <shortcode country="gt" pattern="\\d{1,6}" free="466453" />
+
     <!-- Croatia -->
     <shortcode country="hr" pattern="\\d{1,5}" free="13062" />
 
@@ -241,10 +247,10 @@
     <shortcode country="ph" pattern="\\d{1,5}" free="2147|5495|5496" />
 
     <!-- Pakistan -->
-    <shortcode country="pk" pattern="\\d{1,5}" free="2057" />
+    <shortcode country="pk" pattern="\\d{1,5}" free="2057|9092" />
 
     <!-- Palestine: 5 digits, known premium codes listed -->
-    <shortcode country="ps" pattern="\\d{1,5}" free="37477" />
+    <shortcode country="ps" pattern="\\d{1,5}" free="37477|6681" />
 
     <!-- Poland: 4-5 digits (not confirmed), known premium codes listed, plus EU -->
     <shortcode country="pl" pattern="\\d{4,5}" premium="74240|79(?:10|866)|92525" free="116\\d{3}|8012|80921" />
@@ -324,4 +330,7 @@
     <!-- South Africa -->
     <shortcode country="za" pattern="\\d{1,5}" free="44136|30791|36056" />
 
+    <!-- Zimbabwe -->
+    <shortcode country="zw" pattern="\\d{1,5}" free="33679" />
+
 </shortcodes>
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
index 15bb66b..8d9fad9 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
@@ -90,7 +90,7 @@
     private static final long TEST_HD_FREQUENCY_VALUE = 95_300;
     private static final long TEST_HD_STATION_ID_EXT_VALUE = 0x100000001L
             | (TEST_HD_FREQUENCY_VALUE << 36);
-    private static final long TEST_HD_LOCATION_VALUE = 0x89CC8E06CCB9ECL;
+    private static final long TEST_HD_LOCATION_VALUE =  0x4E647007665CF6L;
     private static final long TEST_VENDOR_ID_VALUE = 9_901;
 
     private static final ProgramSelector.Identifier TEST_DAB_SID_EXT_ID =
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
index a034653..10ac05d 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
@@ -88,13 +88,6 @@
     }
 
     @Test
-    public void setInternalHalCallback_callbackSetInHal() throws Exception {
-        mRadioModule.setInternalHalCallback();
-
-        verify(mBroadcastRadioMock).setTunerCallback(any());
-    }
-
-    @Test
     public void getImage_withValidIdFromRadioModule() {
         int imageId = 1;
 
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 262f167..755bcdb 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
@@ -192,66 +192,6 @@
             mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0];
             return null;
         }).when(mBroadcastRadioMock).setTunerCallback(any());
-        mRadioModule.setInternalHalCallback();
-
-        doAnswer(invocation -> {
-            android.hardware.broadcastradio.ProgramSelector halSel =
-                    (android.hardware.broadcastradio.ProgramSelector) invocation.getArguments()[0];
-            mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(halSel, SIGNAL_QUALITY);
-            if (halSel.primaryId.type != IdentifierType.AMFM_FREQUENCY_KHZ) {
-                throw new ServiceSpecificException(Result.NOT_SUPPORTED);
-            }
-            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
-            return Result.OK;
-        }).when(mBroadcastRadioMock).tune(any());
-
-        doAnswer(invocation -> {
-            if ((boolean) invocation.getArguments()[0]) {
-                mHalCurrentInfo.selector.primaryId.value += AM_FM_FREQUENCY_SPACING;
-            } else {
-                mHalCurrentInfo.selector.primaryId.value -= AM_FM_FREQUENCY_SPACING;
-            }
-            mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
-            mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
-            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
-            return Result.OK;
-        }).when(mBroadcastRadioMock).step(anyBoolean());
-
-        doAnswer(invocation -> {
-            if (mHalCurrentInfo == null) {
-                android.hardware.broadcastradio.ProgramSelector placeHolderSelector =
-                        AidlTestUtils.makeHalFmSelector(/* freq= */ 97300);
-
-                mHalTunerCallback.onTuneFailed(Result.TIMEOUT, placeHolderSelector);
-                return Result.OK;
-            }
-            mHalCurrentInfo.selector.primaryId.value = getSeekFrequency(
-                    mHalCurrentInfo.selector.primaryId.value,
-                    !(boolean) invocation.getArguments()[0]);
-            mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
-            mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
-            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
-            return Result.OK;
-        }).when(mBroadcastRadioMock).seek(anyBoolean(), anyBoolean());
-
-        doReturn(null).when(mBroadcastRadioMock).getImage(anyInt());
-
-        doAnswer(invocation -> {
-            int configFlag = (int) invocation.getArguments()[0];
-            if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
-                throw new ServiceSpecificException(Result.NOT_SUPPORTED);
-            }
-            return mHalConfigMap.getOrDefault(configFlag, false);
-        }).when(mBroadcastRadioMock).isConfigFlagSet(anyInt());
-
-        doAnswer(invocation -> {
-            int configFlag = (int) invocation.getArguments()[0];
-            if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
-                throw new ServiceSpecificException(Result.NOT_SUPPORTED);
-            }
-            mHalConfigMap.put(configFlag, (boolean) invocation.getArguments()[1]);
-            return null;
-        }).when(mBroadcastRadioMock).setConfigFlag(anyInt(), anyBoolean());
     }
 
     @After
@@ -330,6 +270,7 @@
 
         expect.withMessage("Close state of broadcast radio service session")
                 .that(mTunerSessions[0].isClosed()).isTrue();
+        verify(mBroadcastRadioMock).unsetTunerCallback();
     }
 
     @Test
@@ -351,6 +292,7 @@
                         .that(mTunerSessions[index].isClosed()).isFalse();
             }
         }
+        verify(mBroadcastRadioMock, never()).unsetTunerCallback();
     }
 
     @Test
@@ -378,6 +320,7 @@
             expect.withMessage("Close state of broadcast radio service session of index %s", index)
                     .that(mTunerSessions[index].isClosed()).isTrue();
         }
+        verify(mBroadcastRadioMock).unsetTunerCallback();
     }
 
     @Test
@@ -1295,6 +1238,71 @@
             mAidlTunerCallbackMocks[index] = mock(android.hardware.radio.ITunerCallback.class);
             mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index]);
         }
+        setupMockedHalTunerSession();
+    }
+
+    private void setupMockedHalTunerSession() throws Exception {
+        expect.withMessage("Registered HAL tuner callback").that(mHalTunerCallback)
+                .isNotNull();
+
+        doAnswer(invocation -> {
+            android.hardware.broadcastradio.ProgramSelector halSel =
+                    (android.hardware.broadcastradio.ProgramSelector) invocation.getArguments()[0];
+            mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(halSel, SIGNAL_QUALITY);
+            if (halSel.primaryId.type != IdentifierType.AMFM_FREQUENCY_KHZ) {
+                throw new ServiceSpecificException(Result.NOT_SUPPORTED);
+            }
+            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+            return Result.OK;
+        }).when(mBroadcastRadioMock).tune(any());
+
+        doAnswer(invocation -> {
+            if ((boolean) invocation.getArguments()[0]) {
+                mHalCurrentInfo.selector.primaryId.value += AM_FM_FREQUENCY_SPACING;
+            } else {
+                mHalCurrentInfo.selector.primaryId.value -= AM_FM_FREQUENCY_SPACING;
+            }
+            mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+            mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+            return Result.OK;
+        }).when(mBroadcastRadioMock).step(anyBoolean());
+
+        doAnswer(invocation -> {
+            if (mHalCurrentInfo == null) {
+                android.hardware.broadcastradio.ProgramSelector placeHolderSelector =
+                        AidlTestUtils.makeHalFmSelector(/* freq= */ 97300);
+
+                mHalTunerCallback.onTuneFailed(Result.TIMEOUT, placeHolderSelector);
+                return Result.OK;
+            }
+            mHalCurrentInfo.selector.primaryId.value = getSeekFrequency(
+                    mHalCurrentInfo.selector.primaryId.value,
+                    !(boolean) invocation.getArguments()[0]);
+            mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+            mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+            return Result.OK;
+        }).when(mBroadcastRadioMock).seek(anyBoolean(), anyBoolean());
+
+        doReturn(null).when(mBroadcastRadioMock).getImage(anyInt());
+
+        doAnswer(invocation -> {
+            int configFlag = (int) invocation.getArguments()[0];
+            if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
+                throw new ServiceSpecificException(Result.NOT_SUPPORTED);
+            }
+            return mHalConfigMap.getOrDefault(configFlag, false);
+        }).when(mBroadcastRadioMock).isConfigFlagSet(anyInt());
+
+        doAnswer(invocation -> {
+            int configFlag = (int) invocation.getArguments()[0];
+            if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
+                throw new ServiceSpecificException(Result.NOT_SUPPORTED);
+            }
+            mHalConfigMap.put(configFlag, (boolean) invocation.getArguments()[1]);
+            return null;
+        }).when(mBroadcastRadioMock).setConfigFlag(anyInt(), anyBoolean());
     }
 
     private long getSeekFrequency(long currentFrequency, boolean seekDown) {
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ProgramInfoCacheTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ProgramInfoCacheTest.java
index fbb446b..36a6430 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ProgramInfoCacheTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ProgramInfoCacheTest.java
@@ -22,8 +22,8 @@
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
 import android.hardware.radio.UniqueProgramIdentifier;
-import android.test.suitebuilder.annotation.MediumTest;
 
+import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
diff --git a/core/tests/ConnectivityManagerTest/Android.bp b/core/tests/ConnectivityManagerTest/Android.bp
index beaf176..f17a28d 100644
--- a/core/tests/ConnectivityManagerTest/Android.bp
+++ b/core/tests/ConnectivityManagerTest/Android.bp
@@ -27,7 +27,10 @@
         "android.test.runner",
         "android.test.base",
     ],
-    static_libs: ["junit"],
+    static_libs: [
+        "junit",
+        "androidx.test.rules",
+    ],
     // Include all test java files.
     srcs: ["src/**/*.java"],
     platform_apis: true,
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java
index 2d291ff..32da1d8 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java
@@ -22,7 +22,8 @@
 import android.net.wifi.WifiManager;
 import android.os.SystemClock;
 import android.provider.Settings;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import com.android.connectivitymanagertest.ConnectivityManagerTestBase;
 import com.android.connectivitymanagertest.ConnectivityManagerTestRunner;
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/WifiAssociationTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/WifiAssociationTest.java
index 23135dd..9443766 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/WifiAssociationTest.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/WifiAssociationTest.java
@@ -22,9 +22,9 @@
 import android.net.wifi.WifiConfiguration.PairwiseCipher;
 import android.net.wifi.WifiConfiguration.Protocol;
 import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
 import android.os.Bundle;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import com.android.connectivitymanagertest.ConnectivityManagerTestBase;
 import com.android.connectivitymanagertest.WifiAssociationTestRunner;
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/WifiConnectionTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/WifiConnectionTest.java
index b37daa3..9c61647 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/WifiConnectionTest.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/WifiConnectionTest.java
@@ -18,7 +18,8 @@
 
 import android.net.wifi.WifiConfiguration;
 import android.os.SystemClock;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 import com.android.connectivitymanagertest.ConnectivityManagerTestBase;
 import com.android.connectivitymanagertest.WifiConfigurationHelper;
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java
index 4b82c3d..993b9ef 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java
@@ -29,9 +29,10 @@
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.provider.Settings;
-import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 
+import androidx.test.filters.LargeTest;
+
 import com.android.connectivitymanagertest.ConnectivityManagerStressTestRunner;
 import com.android.connectivitymanagertest.ConnectivityManagerTestBase;
 import com.android.connectivitymanagertest.WifiConfigurationHelper;
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/unit/WifiClientTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/unit/WifiClientTest.java
index 5c2f388..9dc3fce 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/unit/WifiClientTest.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/unit/WifiClientTest.java
@@ -17,19 +17,19 @@
 package com.android.connectivitymanagertest.unit;
 
 import android.content.BroadcastReceiver;
+import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.Context;
 import android.net.NetworkInfo;
-import android.net.wifi.WifiManager;
+import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.net.wifi.WifiConfiguration.Status;
-import android.net.wifi.SupplicantState;
-
-import android.test.suitebuilder.annotation.LargeTest;
+import android.net.wifi.WifiManager;
 import android.test.AndroidTestCase;
 
+import androidx.test.filters.LargeTest;
+
 import java.util.List;
 
 /**
diff --git a/core/tests/PlatformCompatFramework/Android.bp b/core/tests/PlatformCompatFramework/Android.bp
index 95e23ad..2621d28 100644
--- a/core/tests/PlatformCompatFramework/Android.bp
+++ b/core/tests/PlatformCompatFramework/Android.bp
@@ -18,6 +18,7 @@
     static_libs: [
         "junit",
         "androidx.test.rules",
+        "flag-junit",
     ],
     platform_apis: true,
 }
diff --git a/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
index a052543..12a42f9 100644
--- a/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
+++ b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
@@ -19,9 +19,17 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import com.android.internal.compat.flags.Flags;
+
+import org.junit.Rule;
 import org.junit.Test;
 
 public class ChangeReporterTest {
+
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Test
     public void testStatsLogOnce() {
         ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
@@ -63,7 +71,7 @@
         ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
         int myUid = 1022, otherUid = 1023;
         long myChangeId = 500L, otherChangeId = 600L;
-        int myState = ChangeReporter.STATE_ENABLED, otherState = ChangeReporter.STATE_DISABLED;
+        int myState = ChangeReporter.STATE_ENABLED, otherState = ChangeReporter.STATE_LOGGED;
 
         assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myState));
         reporter.reportChange(myUid, myChangeId, myState);
@@ -112,4 +120,80 @@
         reporter.stopDebugLogAll();
         assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myState));
     }
+
+    @Test
+    public void testDebugLogWithFlagOnAndOldSdk() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_SKIP_OLD_AND_DISABLED_COMPAT_LOGGING);
+        ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
+        int myUid = 1022;
+        long myChangeId = 500L;
+        int myEnabledState = ChangeReporter.STATE_ENABLED;
+        int myDisabledState = ChangeReporter.STATE_DISABLED;
+
+        // Report will not log if target sdk is before the previous version.
+        assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, false));
+
+        reporter.resetReportedChanges(myUid);
+
+        // Report will be logged if target sdk is the latest version.
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, true));
+
+        reporter.resetReportedChanges(myUid);
+
+        // If the report is disabled, the sdk version shouldn't matter.
+        assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, true));
+    }
+
+    @Test
+    public void testDebugLogWithFlagOnAndDisabledChange() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_SKIP_OLD_AND_DISABLED_COMPAT_LOGGING);
+        ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
+        int myUid = 1022;
+        long myChangeId = 500L;
+        int myEnabledState = ChangeReporter.STATE_ENABLED;
+        int myDisabledState = ChangeReporter.STATE_DISABLED;
+
+        // Report will not log if the change is disabled.
+        assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, true));
+
+        reporter.resetReportedChanges(myUid);
+
+        // Report will be logged if the change is enabled.
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, true));
+
+        reporter.resetReportedChanges(myUid);
+
+        // If the report is not the latest version, the disabled state doesn't matter.
+        assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, false));
+    }
+
+    @Test
+    public void testDebugLogWithFlagOff() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_SKIP_OLD_AND_DISABLED_COMPAT_LOGGING);
+        ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
+        int myUid = 1022;
+        long myChangeId = 500L;
+        int myEnabledState = ChangeReporter.STATE_ENABLED;
+        int myDisabledState = ChangeReporter.STATE_DISABLED;
+
+        // Report will be logged even if the change is not the latest sdk but the flag is off.
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, false));
+
+        reporter.resetReportedChanges(myUid);
+
+        // Report will be logged if the change is enabled and the latest sdk but the flag is off.
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, true));
+
+        reporter.resetReportedChanges(myUid);
+
+        // Report will be logged if the change is disabled and the latest sdk but the flag is
+        // off.
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, true));
+
+        reporter.resetReportedChanges(myUid);
+
+        // Report will be logged if the change is disabled and not the latest sdk but the flag is
+        // off.
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, false));
+    }
 }
diff --git a/core/tests/bandwidthtests/Android.bp b/core/tests/bandwidthtests/Android.bp
index d0b42f7..8645b39 100644
--- a/core/tests/bandwidthtests/Android.bp
+++ b/core/tests/bandwidthtests/Android.bp
@@ -31,6 +31,9 @@
         "org.apache.http.legacy",
         "android.test.base",
     ],
-    static_libs: ["junit"],
+    static_libs: [
+        "junit",
+        "androidx.test.rules",
+    ],
     platform_apis: true,
 }
diff --git a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
index 4b42f4ae..b2c85a2 100644
--- a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
+++ b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
@@ -30,9 +30,10 @@
 import android.os.SystemClock;
 import android.telephony.TelephonyManager;
 import android.test.InstrumentationTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 
+import androidx.test.filters.LargeTest;
+
 import com.android.bandwidthtest.util.BandwidthTestUtil;
 import com.android.bandwidthtest.util.ConnectionUtil;
 
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 37a499a..6cc5485 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
@@ -110,8 +110,6 @@
             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/wm_transition_trace.winscope"),
-            Paths.get("/data/misc/wmtrace/shell_transition_trace.winscope"),
     };
 
     private Handler mHandler;
@@ -257,6 +255,38 @@
         assertThatAllFileContentsAreDifferent(preDumpedTraceFiles, actualTraceFiles);
     }
 
+    @LargeTest
+    @Test
+    public void noPreDumpData_then_fullWithUsePreDumpFlag_ignoresFlag() throws Exception {
+        startPreDumpedUiTraces();
+
+        mBrm.preDumpUiData();
+        waitTillDumpstateExitedOrTimeout();
+
+        // Simulate lost of pre-dumped data.
+        // For example it can happen in this scenario:
+        // 1. Pre-dump data
+        // 2. Start bugreport + "use pre-dump" flag (USE AND REMOVE THE PRE-DUMP FROM DISK)
+        // 3. Start bugreport + "use pre-dump" flag (NO PRE-DUMP AVAILABLE ON DISK)
+        removeFilesIfNeeded(UI_TRACES_PREDUMPED);
+
+        // Start bugreport with "use predump" flag. Because the pre-dumped data is not available
+        // the flag will be ignored and data will be dumped as in normal flow.
+        BugreportCallbackImpl callback = new BugreportCallbackImpl();
+        mBrm.startBugreport(mBugreportFd, null, fullWithUsePreDumpFlag(), mExecutor,
+                callback);
+        shareConsentDialog(ConsentReply.ALLOW);
+        waitTillDoneOrTimeout(callback);
+
+        stopPreDumpedUiTraces();
+
+        assertThat(callback.isDone()).isTrue();
+        assertThat(mBugreportFile.length()).isGreaterThan(0L);
+        assertFdsAreClosed(mBugreportFd);
+
+        assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED);
+    }
+
     @Test
     public void simultaneousBugreportsNotAllowed() throws Exception {
         // Start bugreport #1
@@ -506,9 +536,6 @@
         InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
                 "cmd window tracing start"
         );
-        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
-                "service call SurfaceFlinger 1025 i32 1"
-        );
     }
 
     private static void stopPreDumpedUiTraces() {
@@ -611,6 +638,14 @@
         return files;
     }
 
+    private static void removeFilesIfNeeded(Path[] paths) throws Exception {
+        for (Path path : paths) {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                    "rm -f " + path.toString()
+            );
+        }
+    }
+
     private static ParcelFileDescriptor parcelFd(File file) throws Exception {
         return ParcelFileDescriptor.open(file,
                 ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND);
diff --git a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
index 73d7fe9..544ca56 100644
--- a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
+++ b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
@@ -220,6 +220,36 @@
         assertEquals(0, out.toByteArray().length);
     }
 
+    public void testDisassociationCleanup() throws InterruptedException {
+        // Create a new association
+        final int associationId = createAssociation();
+
+        // Subscribe to transport updates for new association
+        final CountDownLatch attached = new CountDownLatch(1);
+        final CountDownLatch detached = new CountDownLatch(1);
+        mCdm.addOnTransportsChangedListener(Runnable::run, associations -> {
+            if (associations.stream()
+                    .anyMatch(association -> associationId == association.getId())) {
+                attached.countDown();
+            } else if (attached.getCount() == 0) {
+                detached.countDown();
+            }
+        });
+
+        final ByteArrayInputStream in = new ByteArrayInputStream(new byte[0]);
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        mCdm.attachSystemDataTransport(associationId, in, out);
+
+        // Assert that the transport is attached
+        assertTrue(attached.await(1, TimeUnit.SECONDS));
+
+        // When CDM disassociates, any transport attached to that associated device should detach
+        mCdm.disassociate(associationId);
+
+        // Assert that the transport is detached
+        assertTrue(detached.await(1, TimeUnit.SECONDS));
+    }
+
     public static byte[] concat(byte[]... blobs) {
         int length = 0;
         for (byte[] blob : blobs) {
@@ -250,12 +280,14 @@
     }
 
     private int createAssociation() {
+        List<AssociationInfo> before = mCdm.getMyAssociations();
         getInstrumentation().getUiAutomation().executeShellCommand("cmd companiondevice associate "
                 + mContext.getUserId() + " " + mContext.getPackageName() + " AA:BB:CC:DD:EE:FF");
         List<AssociationInfo> infos;
         for (int i = 0; i < 100; i++) {
             infos = mCdm.getMyAssociations();
-            if (!infos.isEmpty()) {
+            if (infos.size() != before.size()) {
+                infos.removeAll(before);
                 return infos.get(0).getId();
             } else {
                 SystemClock.sleep(100);
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 37e6780..24031cad 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -28,10 +28,19 @@
     visibility: ["//visibility:private"],
 }
 
+java_defaults {
+    name: "FrameworksCoreTests-resources",
+    aaptflags: [
+        "-0 .dat",
+        "-0 .gld",
+        "-c fa",
+    ],
+    resource_dirs: ["res"],
+}
+
 android_test {
     name: "FrameworksCoreTests",
-    // FrameworksCoreTestsRavenwood references the .aapt.srcjar
-    use_resource_processor: false,
+    defaults: ["FrameworksCoreTests-resources"],
 
     srcs: [
         "src/**/*.java",
@@ -92,6 +101,7 @@
         "flickerlib-trace_processor_shell",
         "mockito-target-extended-minus-junit4",
         "TestParameterInjector",
+        "android.content.res.flags-aconfig-java",
     ],
 
     libs: [
@@ -117,7 +127,6 @@
 
     certificate: "platform",
 
-    resource_dirs: ["res"],
     resource_zips: [":FrameworksCoreTests_apks_as_resources"],
     java_resources: [":FrameworksCoreTests_unit_test_cert_der"],
 
@@ -128,6 +137,24 @@
     ],
 }
 
+// FrameworksCoreTestsRavenwood pulls in the R.java class from this one.
+// Note, "FrameworksCoreTests" and "FrameworksCoreTests-resonly" _might_ not have indentical
+// R.java (not sure if there's a guarantee), but that doesn't matter as long as
+// FrameworksCoreTestsRavenwood consistently uses the R definition in this module.
+android_app {
+    name: "FrameworksCoreTests-resonly",
+    defaults: ["FrameworksCoreTests-resources"],
+
+    // FrameworksCoreTestsRavenwood references the .aapt.srcjar
+    use_resource_processor: false,
+    libs: [
+        "framework-res",
+        "android.test.runner",
+        "org.apache.http.legacy",
+    ],
+    sdk_version: "core_platform",
+}
+
 // Rules to copy all the test apks to the intermediate raw resource directory
 java_genrule {
     name: "FrameworksCoreTests_apks_as_resources",
@@ -225,7 +252,11 @@
         "src/com/android/internal/util/**/*.java",
         "src/com/android/internal/power/EnergyConsumerStatsTest.java",
 
-        ":FrameworksCoreTests{.aapt.srcjar}",
+        // Pull in R.java from FrameworksCoreTests-resonly, not from FrameworksCoreTests,
+        // to avoid having a dependency to FrameworksCoreTests.
+        // This way, when updating source files and running this test, we don't need to
+        // rebuild the entire FrameworksCoreTests, which would be slow.
+        ":FrameworksCoreTests-resonly{.aapt.srcjar}",
         ":FrameworksCoreTests-aidl",
         ":FrameworksCoreTests-helpers",
         ":FrameworksCoreTestDoubles-sources",
diff --git a/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml b/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml
index 866e1a9..5029212 100644
--- a/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml
+++ b/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml
@@ -14,105 +14,150 @@
   ~ limitations under the License
   -->
 
-<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:id="@+id/horizontal_scroll_view">
+    android:orientation="vertical">
 
-    <LinearLayout
+    <HorizontalScrollView
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal">
+        android:layout_height="match_parent"
+        android:id="@+id/horizontal_scroll_view">
 
-        <View
-            android:background="#F00"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
 
-        <View
-            android:background="#880"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#F00"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#0F0"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#880"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#088"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#0F0"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#00F"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#088"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#808"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#00F"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#F00"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#808"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#880"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#F00"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#0F0"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#880"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#088"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#0F0"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#00F"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#088"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#808"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#00F"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#F00"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#808"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#880"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#F00"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#0F0"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#880"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#088"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#0F0"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#00F"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#088"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#808"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#00F"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-    </LinearLayout>
-</HorizontalScrollView>
+            <View
+                android:background="#808"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
+
+        </LinearLayout>
+    </HorizontalScrollView>
+
+    <view
+        class="android.widget.HorizontalScrollViewFunctionalTest$MyHorizontalScrollView"
+        android:id="@+id/my_horizontal_scroll_view"
+        android:layout_width="90dp"
+        android:layout_height="90dp"
+        android:background="#FFF"
+        android:defaultFocusHighlightEnabled="false">
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+            <View
+                android:background="#00F"
+                android:layout_width="90dp"
+                android:layout_height="50dp"/>
+            <View
+                android:background="#0FF"
+                android:layout_width="90dp"
+                android:layout_height="50dp"/>
+            <View
+                android:background="#0F0"
+                android:layout_width="90dp"
+                android:layout_height="50dp"/>
+            <View
+                android:background="#FF0"
+                android:layout_width="90dp"
+                android:layout_height="50dp"/>
+            <View
+                android:background="#F00"
+                android:layout_width="90dp"
+                android:layout_height="50dp"/>
+            <View
+                android:background="#F0F"
+                android:layout_width="90dp"
+                android:layout_height="50dp"/>
+        </LinearLayout>
+    </view>
+</LinearLayout>
\ No newline at end of file
diff --git a/core/tests/coretests/res/layout/activity_scroll_view.xml b/core/tests/coretests/res/layout/activity_scroll_view.xml
index 61fabf8..db8cd02 100644
--- a/core/tests/coretests/res/layout/activity_scroll_view.xml
+++ b/core/tests/coretests/res/layout/activity_scroll_view.xml
@@ -14,105 +14,150 @@
   ~ limitations under the License
   -->
 
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:id="@+id/scroll_view">
+    android:orientation="vertical">
 
-    <LinearLayout
+    <ScrollView
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical">
+        android:layout_height="match_parent"
+        android:id="@+id/scroll_view">
 
-        <View
-            android:background="#F00"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
 
-        <View
-            android:background="#880"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#F00"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#0F0"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#880"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#088"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#0F0"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#00F"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#088"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#808"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#00F"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#F00"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#808"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#880"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#F00"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#0F0"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#880"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#088"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#0F0"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#00F"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#088"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#808"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#00F"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#F00"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#808"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#880"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#F00"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#0F0"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#880"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#088"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#0F0"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#00F"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#088"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-        <View
-            android:background="#808"
-            android:layout_width="100dp"
-            android:layout_height="100dp" />
+            <View
+                android:background="#00F"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
 
-    </LinearLayout>
-</ScrollView>
+            <View
+                android:background="#808"
+                android:layout_width="100dp"
+                android:layout_height="100dp" />
+
+        </LinearLayout>
+    </ScrollView>
+
+    <view
+        class="android.widget.ScrollViewFunctionalTest$MyScrollView"
+        android:id="@+id/my_scroll_view"
+        android:layout_width="90dp"
+        android:layout_height="90dp"
+        android:background="#FFF"
+        android:defaultFocusHighlightEnabled="false">
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+            <View
+                android:background="#00F"
+                android:layout_width="90dp"
+                android:layout_height="50dp"/>
+            <View
+                android:background="#0FF"
+                android:layout_width="90dp"
+                android:layout_height="50dp"/>
+            <View
+                android:background="#0F0"
+                android:layout_width="90dp"
+                android:layout_height="50dp"/>
+            <View
+                android:background="#FF0"
+                android:layout_width="90dp"
+                android:layout_height="50dp"/>
+            <View
+                android:background="#F00"
+                android:layout_width="90dp"
+                android:layout_height="50dp"/>
+            <View
+                android:background="#F0F"
+                android:layout_width="90dp"
+                android:layout_height="50dp"/>
+        </LinearLayout>
+    </view>
+</LinearLayout>
diff --git a/packages/SystemUI/compose/features/AndroidManifest.xml b/core/tests/coretests/res/layout/view_velocity_test.xml
similarity index 61%
rename from packages/SystemUI/compose/features/AndroidManifest.xml
rename to core/tests/coretests/res/layout/view_velocity_test.xml
index c1a9ec5..98154a4 100644
--- a/packages/SystemUI/compose/features/AndroidManifest.xml
+++ b/core/tests/coretests/res/layout/view_velocity_test.xml
@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2022 The Android Open Source Project
+<!-- Copyright (C) 2024 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -15,7 +14,12 @@
      limitations under the License.
 -->
 
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.systemui.compose.features">
-
-</manifest>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/frameLayout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <View
+        android:id="@+id/moving_view"
+        android:layout_width="50dp"
+        android:layout_height="50dp" />
+</FrameLayout>
diff --git a/core/tests/coretests/src/android/accessibilityservice/BrailleDisplayControllerImplTest.java b/core/tests/coretests/src/android/accessibilityservice/BrailleDisplayControllerImplTest.java
index aaa199d..e8b295b 100644
--- a/core/tests/coretests/src/android/accessibilityservice/BrailleDisplayControllerImplTest.java
+++ b/core/tests/coretests/src/android/accessibilityservice/BrailleDisplayControllerImplTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.hardware.usb.UsbDevice;
 import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -58,6 +59,7 @@
     @Rule
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
+    private AccessibilityService mAccessibilityService;
     private BrailleDisplayController mBrailleDisplayController;
 
     @Mock
@@ -76,12 +78,13 @@
     @Before
     public void test() {
         MockitoAnnotations.initMocks(this);
-        AccessibilityService accessibilityService = spy(new TestAccessibilityService());
-        doReturn((Executor) Runnable::run).when(accessibilityService).getMainExecutor();
-        doReturn(TEST_SERVICE_CONNECTION_ID).when(accessibilityService).getConnectionId();
+        mAccessibilityService = spy(new TestAccessibilityService());
+        doReturn((Executor) Runnable::run).when(mAccessibilityService).getMainExecutor();
+        doReturn(TEST_SERVICE_CONNECTION_ID).when(mAccessibilityService).getConnectionId();
         AccessibilityInteractionClient.addConnection(TEST_SERVICE_CONNECTION_ID,
                 mAccessibilityServiceConnection, /*initializeCache=*/false);
-        mBrailleDisplayController = accessibilityService.getBrailleDisplayController();
+        mBrailleDisplayController = new BrailleDisplayControllerImpl(
+                mAccessibilityService, new Object(), /*isHidrawSupported=*/true);
     }
 
     // Automated CTS tests only use the BluetoothDevice version of BrailleDisplayController#connect
@@ -104,4 +107,17 @@
         assertThrows(IllegalStateException.class,
                 () -> mBrailleDisplayController.connect(usbDevice, mBrailleDisplayCallback));
     }
+
+    @Test
+    public void connect_HidrawNotSupported_callsOnConnectionFailed() {
+        BrailleDisplayController controller = new BrailleDisplayControllerImpl(
+                mAccessibilityService, new Object(), /*isHidrawSupported=*/false);
+        UsbDevice usbDevice = Mockito.mock(UsbDevice.class);
+
+        controller.connect(usbDevice, mBrailleDisplayCallback);
+
+        verify(mBrailleDisplayCallback).onConnectionFailed(
+                BrailleDisplayController.BrailleDisplayCallback.FLAG_ERROR_CANNOT_ACCESS);
+        verifyZeroInteractions(mAccessibilityServiceConnection);
+    }
 }
diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
index 5ac99db..89c2b3c 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
@@ -255,7 +255,7 @@
             assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor());
             assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
             assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
-            assertEquals(td1.getStatusBarAppearance(), td2.getStatusBarAppearance());
+            assertEquals(td1.getSystemBarsAppearance(), td2.getSystemBarsAppearance());
             assertEquals(td1.getResizeMode(), td2.getResizeMode());
             assertEquals(td1.getMinWidth(), td2.getMinWidth());
             assertEquals(td1.getMinHeight(), td2.getMinHeight());
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index d95834f..927c67c 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -21,16 +21,22 @@
 import static android.content.Intent.ACTION_VIEW;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 
+import static com.android.window.flags.Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import android.annotation.NonNull;
@@ -46,6 +52,7 @@
 import android.app.servertransaction.ActivityRelaunchItem;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.ClientTransactionItem;
+import android.app.servertransaction.ClientTransactionListenerController;
 import android.app.servertransaction.ConfigurationChangeItem;
 import android.app.servertransaction.NewIntentItem;
 import android.app.servertransaction.ResumeActivityItem;
@@ -60,7 +67,9 @@
 import android.hardware.display.VirtualDisplay;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.DisplayMetrics;
 import android.util.MergedConfiguration;
 import android.view.Display;
@@ -81,11 +90,14 @@
 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;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 /**
@@ -103,11 +115,17 @@
     // few sequence numbers the framework used to launch the test activity.
     private static final int BASE_SEQ = 10000000;
 
-    @Rule
+    @Rule(order = 0)
     public final ActivityTestRule<TestActivity> mActivityTestRule =
             new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */,
                     false /* launchActivity */);
 
+    @Rule(order = 1)
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
+    @Mock
+    private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener;
+
     private WindowTokenClientController mOriginalWindowTokenClientController;
     private Configuration mOriginalAppConfig;
 
@@ -115,6 +133,8 @@
 
     @Before
     public void setup() {
+        MockitoAnnotations.initMocks(this);
+
         // Keep track of the original controller, so that it can be used to restore in tearDown()
         // when there is override in some test cases.
         mOriginalWindowTokenClientController = WindowTokenClientController.getInstance();
@@ -129,6 +149,8 @@
             mCreatedVirtualDisplays = null;
         }
         WindowTokenClientController.overrideForTesting(mOriginalWindowTokenClientController);
+        ClientTransactionListenerController.getInstance()
+                .unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener);
         InstrumentationRegistry.getInstrumentation().runOnMainSync(
                 () -> restoreConfig(ActivityThread.currentActivityThread(), mOriginalAppConfig));
     }
@@ -247,7 +269,7 @@
             newConfig.smallestScreenWidthDp++;
             transaction = newTransaction(activityThread);
             transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain(
-                    activity.getActivityToken(), newConfig));
+                    activity.getActivityToken(), newConfig, new ActivityWindowInfo()));
             appThread.scheduleTransaction(transaction);
             InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
@@ -395,11 +417,13 @@
             olderConfig.seq = seq + 1;
 
             final ActivityClientRecord r = getActivityClientRecord(activity);
-            activityThread.handleActivityConfigurationChanged(r, olderConfig, INVALID_DISPLAY);
+            activityThread.handleActivityConfigurationChanged(r, olderConfig, INVALID_DISPLAY,
+                    new ActivityWindowInfo());
             assertEquals(numOfConfig, activity.mNumOfConfigChanges);
             assertEquals(olderConfig.orientation, activity.mConfig.orientation);
 
-            activityThread.handleActivityConfigurationChanged(r, newerConfig, INVALID_DISPLAY);
+            activityThread.handleActivityConfigurationChanged(r, newerConfig, INVALID_DISPLAY,
+                    new ActivityWindowInfo());
             assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
             assertEquals(newerConfig.orientation, activity.mConfig.orientation);
         });
@@ -417,7 +441,7 @@
             config.orientation = ORIENTATION_PORTRAIT;
 
             activityThread.handleActivityConfigurationChanged(getActivityClientRecord(activity),
-                    config, INVALID_DISPLAY);
+                    config, INVALID_DISPLAY, new ActivityWindowInfo());
         });
 
         final IApplicationThread appThread = activityThread.getApplicationThread();
@@ -453,11 +477,11 @@
 
         transaction = newTransaction(activityThread);
         transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain(
-                activity.getActivityToken(), activityConfigLandscape));
+                activity.getActivityToken(), activityConfigLandscape, new ActivityWindowInfo()));
         transaction.addTransactionItem(ConfigurationChangeItem.obtain(
                 processConfigPortrait, DEVICE_ID_INVALID));
         transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain(
-                activity.getActivityToken(), activityConfigPortrait));
+                activity.getActivityToken(), activityConfigPortrait, new ActivityWindowInfo()));
         appThread.scheduleTransaction(transaction);
 
         activity.mTestLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS);
@@ -488,7 +512,7 @@
             config.orientation = ORIENTATION_PORTRAIT;
 
             activityThread.handleActivityConfigurationChanged(getActivityClientRecord(activity),
-                    config, INVALID_DISPLAY);
+                    config, INVALID_DISPLAY, new ActivityWindowInfo());
         });
 
         final int numOfConfig = activity.mNumOfConfigChanges;
@@ -618,7 +642,7 @@
             activityThread.updatePendingActivityConfiguration(activity.getActivityToken(),
                     newActivityConfig);
             activityThread.handleActivityConfigurationChanged(r, newActivityConfig,
-                    INVALID_DISPLAY);
+                    INVALID_DISPLAY, new ActivityWindowInfo());
 
             assertEquals("Virtual display orientation must not change when activity"
                             + " configuration orientation changes.",
@@ -781,10 +805,105 @@
         verify(windowTokenClientController).onWindowContextWindowRemoved(clientToken);
     }
 
+    @Test
+    public void testActivityWindowInfoChanged_activityLaunch() {
+        mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+        ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
+                mActivityWindowInfoListener);
+
+        final Activity activity = mActivityTestRule.launchActivity(new Intent());
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        final ActivityClientRecord activityClientRecord = getActivityClientRecord(activity);
+
+        verify(mActivityWindowInfoListener).accept(activityClientRecord.token,
+                activityClientRecord.getActivityWindowInfo());
+    }
+
+    @Test
+    public void testActivityWindowInfoChanged_activityRelaunch() throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+        ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
+                mActivityWindowInfoListener);
+
+        final Activity activity = mActivityTestRule.launchActivity(new Intent());
+        final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
+        appThread.scheduleTransaction(newRelaunchResumeTransaction(activity));
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        final ActivityClientRecord activityClientRecord = getActivityClientRecord(activity);
+
+        // The same ActivityWindowInfo won't trigger duplicated callback.
+        verify(mActivityWindowInfoListener).accept(activityClientRecord.token,
+                activityClientRecord.getActivityWindowInfo());
+
+        final Configuration currentConfig = activity.getResources().getConfiguration();
+        final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
+        activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000),
+                new Rect(0, 0, 1000, 1000));
+        final ActivityRelaunchItem relaunchItem = ActivityRelaunchItem.obtain(
+                activity.getActivityToken(), null, null, 0,
+                new MergedConfiguration(currentConfig, currentConfig),
+                false /* preserveWindow */, activityWindowInfo);
+        final ClientTransaction transaction = newTransaction(activity);
+        transaction.addTransactionItem(relaunchItem);
+        appThread.scheduleTransaction(transaction);
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        verify(mActivityWindowInfoListener).accept(activityClientRecord.token,
+                activityWindowInfo);
+    }
+
+    @Test
+    public void testActivityWindowInfoChanged_activityConfigurationChanged()
+            throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+        ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
+                mActivityWindowInfoListener);
+
+        final Activity activity = mActivityTestRule.launchActivity(new Intent());
+        final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        clearInvocations(mActivityWindowInfoListener);
+        final Configuration config = new Configuration(activity.getResources().getConfiguration());
+        config.seq++;
+        final Rect taskBounds = new Rect(0, 0, 1000, 2000);
+        final Rect taskFragmentBounds = new Rect(0, 0, 1000, 1000);
+        final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
+        activityWindowInfo.set(true /* isEmbedded */, taskBounds, taskFragmentBounds);
+        final ActivityConfigurationChangeItem activityConfigurationChangeItem =
+                ActivityConfigurationChangeItem.obtain(
+                        activity.getActivityToken(), config, activityWindowInfo);
+        final ClientTransaction transaction = newTransaction(activity);
+        transaction.addTransactionItem(activityConfigurationChangeItem);
+        appThread.scheduleTransaction(transaction);
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        verify(mActivityWindowInfoListener).accept(activity.getActivityToken(),
+                activityWindowInfo);
+
+        clearInvocations(mActivityWindowInfoListener);
+        final ActivityWindowInfo activityWindowInfo2 = new ActivityWindowInfo();
+        activityWindowInfo2.set(true /* isEmbedded */, taskBounds, taskFragmentBounds);
+        config.seq++;
+        final ActivityConfigurationChangeItem activityConfigurationChangeItem2 =
+                ActivityConfigurationChangeItem.obtain(
+                        activity.getActivityToken(), config, activityWindowInfo2);
+        final ClientTransaction transaction2 = newTransaction(activity);
+        transaction2.addTransactionItem(activityConfigurationChangeItem2);
+        appThread.scheduleTransaction(transaction);
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        // The same ActivityWindowInfo won't trigger duplicated callback.
+        verify(mActivityWindowInfoListener, never()).accept(any(), any());
+    }
+
     /**
      * Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord,
-     * Configuration, int)} to try to push activity configuration to the activity for the given
-     * sequence number.
+     * Configuration, int, ActivityWindowInfo)} to try to push activity configuration to the
+     * activity for the given sequence number.
      * <p>
      * It uses orientation to push the configuration and it tries a different orientation if the
      * first attempt doesn't make through, to rule out the possibility that the previous
@@ -803,7 +922,8 @@
         Configuration config = new Configuration();
         config.orientation = ORIENTATION_PORTRAIT;
         config.seq = seq;
-        activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY);
+        activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY,
+                new ActivityWindowInfo());
 
         if (activity.mNumOfConfigChanges > numOfConfig) {
             return config.seq;
@@ -812,7 +932,8 @@
         config = new Configuration();
         config.orientation = ORIENTATION_LANDSCAPE;
         config.seq = seq + 1;
-        activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY);
+        activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY,
+                new ActivityWindowInfo());
 
         return config.seq;
     }
@@ -867,7 +988,7 @@
     @NonNull
     private static ClientTransaction newStopTransaction(@NonNull Activity activity) {
         final StopActivityItem stopStateRequest = StopActivityItem.obtain(
-                activity.getActivityToken(), 0 /* configChanges */);
+                activity.getActivityToken());
 
         final ClientTransaction transaction = newTransaction(activity);
         transaction.addTransactionItem(stopStateRequest);
@@ -879,7 +1000,7 @@
     private static ClientTransaction newActivityConfigTransaction(@NonNull Activity activity,
             @NonNull Configuration config) {
         final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(
-                activity.getActivityToken(), config);
+                activity.getActivityToken(), config, new ActivityWindowInfo());
 
         final ClientTransaction transaction = newTransaction(activity);
         transaction.addTransactionItem(item);
diff --git a/core/tests/coretests/src/android/app/assist/AssistStructureTest.java b/core/tests/coretests/src/android/app/assist/AssistStructureTest.java
index abeb08c..1f2788c 100644
--- a/core/tests/coretests/src/android/app/assist/AssistStructureTest.java
+++ b/core/tests/coretests/src/android/app/assist/AssistStructureTest.java
@@ -266,8 +266,8 @@
         assertThat(view.getViewRootImpl()).isNotNull();
         ViewNodeBuilder viewStructure = new ViewNodeBuilder();
         viewStructure.setAutofillId(view.getAutofillId());
-        viewStructure.setCredentialManagerRequest(view.getCredentialManagerRequest(),
-                view.getCredentialManagerCallback());
+        viewStructure.setPendingCredentialRequest(view.getPendingCredentialRequest(),
+                view.getPendingCredentialCallback());
         view.onProvideAutofillStructure(viewStructure, /* flags= */ 0);
         ViewNodeParcelable viewNodeParcelable = new ViewNodeParcelable(viewStructure.getViewNode());
 
@@ -289,17 +289,20 @@
 
         assertThat(view.getViewRootImpl()).isNotNull();
         ViewNodeBuilder viewStructure = new ViewNodeBuilder();
-        viewStructure.setCredentialManagerRequest(view.getCredentialManagerRequest(),
-                view.getCredentialManagerCallback());
+        if (view.getPendingCredentialRequest() != null
+                && view.getPendingCredentialCallback() != null) {
+            viewStructure.setPendingCredentialRequest(view.getPendingCredentialRequest(),
+                    view.getPendingCredentialCallback());
+        }
 
-        assertEquals(viewStructure.getCredentialManagerRequest(), GET_CREDENTIAL_REQUEST);
-        assertEquals(viewStructure.getCredentialManagerCallback(),
+        assertEquals(viewStructure.getPendingCredentialRequest(), GET_CREDENTIAL_REQUEST);
+        assertEquals(viewStructure.getPendingCredentialCallback(),
                 GET_CREDENTIAL_REQUEST_CALLBACK);
 
         viewStructure.clearCredentialManagerRequest();
 
-        assertNull(viewStructure.getCredentialManagerRequest());
-        assertNull(viewStructure.getCredentialManagerCallback());
+        assertNull(viewStructure.getPendingCredentialRequest());
+        assertNull(viewStructure.getPendingCredentialCallback());
     }
 
     @Test
@@ -386,14 +389,14 @@
         EditText view = new EditText(mContext);
         view.setText("Big Hint in Little View");
         view.setAutofillHints(BIG_STRING);
-        view.setCredentialManagerRequest(GET_CREDENTIAL_REQUEST, GET_CREDENTIAL_REQUEST_CALLBACK);
+        view.setPendingCredentialRequest(GET_CREDENTIAL_REQUEST, GET_CREDENTIAL_REQUEST_CALLBACK);
         return view;
     }
 
     private EditText newCredentialView() {
         EditText view = new EditText(mContext);
         view.setText("Credential Request");
-        view.setCredentialManagerRequest(GET_CREDENTIAL_REQUEST, GET_CREDENTIAL_REQUEST_CALLBACK);
+        view.setPendingCredentialRequest(GET_CREDENTIAL_REQUEST, GET_CREDENTIAL_REQUEST_CALLBACK);
         return view;
     }
 
@@ -421,8 +424,8 @@
         assertThat(view.getAutofillId()).isNotNull();
         assertThat(view.getText().toString()).isEqualTo("Big Hint in Little View");
 
-        assertThat(view.getCredentialManagerRequest()).isEqualTo(GET_CREDENTIAL_REQUEST);
-        assertThat(view.getCredentialManagerCallback()).isEqualTo(GET_CREDENTIAL_REQUEST_CALLBACK);
+        assertThat(view.getPendingCredentialRequest()).isEqualTo(GET_CREDENTIAL_REQUEST);
+        assertThat(view.getPendingCredentialCallback()).isEqualTo(GET_CREDENTIAL_REQUEST_CALLBACK);
     }
 
     /**
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
index 30545f9..9907397 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
@@ -107,7 +107,7 @@
     @Test
     public void testActivityConfigurationChangeItem_getContextToUpdate() {
         final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem
-                .obtain(mActivityToken, mConfiguration);
+                .obtain(mActivityToken, mConfiguration, new ActivityWindowInfo());
         final Context context = item.getContextToUpdate(mHandler);
 
         assertEquals(mActivity, context);
@@ -136,7 +136,7 @@
     @Test
     public void testDestroyActivityItem_preExecute() {
         final DestroyActivityItem item = DestroyActivityItem
-                .obtain(mActivityToken, false /* finished */, 123 /* configChanges */);
+                .obtain(mActivityToken, false /* finished */);
         item.preExecute(mHandler);
 
         assertEquals(1, mActivitiesToBeDestroyed.size());
@@ -146,7 +146,7 @@
     @Test
     public void testDestroyActivityItem_postExecute() {
         final DestroyActivityItem item = DestroyActivityItem
-                .obtain(mActivityToken, false /* finished */, 123 /* configChanges */);
+                .obtain(mActivityToken, false /* finished */);
         item.preExecute(mHandler);
         item.postExecute(mHandler, mPendingActions);
 
@@ -156,11 +156,11 @@
     @Test
     public void testDestroyActivityItem_execute() {
         final DestroyActivityItem item = DestroyActivityItem
-                .obtain(mActivityToken, false /* finished */, 123 /* configChanges */);
+                .obtain(mActivityToken, false /* finished */);
         item.execute(mHandler, mActivityClientRecord, mPendingActions);
 
         verify(mHandler).handleDestroyActivity(eq(mActivityClientRecord), eq(false) /* finishing */,
-                eq(123) /* configChanges */, eq(false) /* getNonConfigInstance */, any());
+                eq(false) /* getNonConfigInstance */, any());
     }
 
     @Test
@@ -177,7 +177,7 @@
     @Test
     public void testMoveToDisplayItem_getContextToUpdate() {
         final MoveToDisplayItem item = MoveToDisplayItem
-                .obtain(mActivityToken, DEFAULT_DISPLAY, mConfiguration);
+                .obtain(mActivityToken, DEFAULT_DISPLAY, mConfiguration, new ActivityWindowInfo());
         final Context context = item.getContextToUpdate(mHandler);
 
         assertEquals(mActivity, context);
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index 213fd7b..77d31a5 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -22,21 +22,29 @@
 
 import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;
 
+import static org.mockito.ArgumentMatchers.any;
+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.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerGlobal;
 import android.hardware.display.IDisplayManager;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.DisplayInfo;
+import android.window.ActivityWindowInfo;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.window.flags.Flags;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -44,6 +52,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.function.BiConsumer;
+
 /**
  * Tests for {@link ClientTransactionListenerController}.
  *
@@ -62,6 +72,10 @@
     private IDisplayManager mIDisplayManager;
     @Mock
     private DisplayManager.DisplayListener mListener;
+    @Mock
+    private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener;
+    @Mock
+    private IBinder mActivityToken;
 
     private DisplayManagerGlobal mDisplayManager;
     private Handler mHandler;
@@ -91,4 +105,24 @@
 
         verify(mListener).onDisplayChanged(123);
     }
+
+    @Test
+    public void testActivityWindowInfoChangedListener() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+        mController.registerActivityWindowInfoChangedListener(mActivityWindowInfoListener);
+        final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
+        activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000),
+                new Rect(0, 0, 1000, 1000));
+        mController.onActivityWindowInfoChanged(mActivityToken, activityWindowInfo);
+
+        verify(mActivityWindowInfoListener).accept(mActivityToken, activityWindowInfo);
+
+        clearInvocations(mActivityWindowInfoListener);
+        mController.unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener);
+
+        mController.onActivityWindowInfoChanged(mActivityToken, activityWindowInfo);
+
+        verify(mActivityWindowInfoListener, never()).accept(any(), any());
+    }
 }
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index a8466bb..584fe16 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -82,7 +82,8 @@
 
     @Test
     public void testRecycleActivityConfigurationChangeItem() {
-        testRecycle(() -> ActivityConfigurationChangeItem.obtain(mActivityToken, config()));
+        testRecycle(() -> ActivityConfigurationChangeItem.obtain(mActivityToken, config(),
+                new ActivityWindowInfo()));
     }
 
     @Test
@@ -97,7 +98,7 @@
 
     @Test
     public void testRecycleDestroyActivityItem() {
-        testRecycle(() -> DestroyActivityItem.obtain(mActivityToken, true, 117));
+        testRecycle(() -> DestroyActivityItem.obtain(mActivityToken, true));
     }
 
     @Test
@@ -157,7 +158,8 @@
 
     @Test
     public void testRecycleMoveToDisplayItem() {
-        testRecycle(() -> MoveToDisplayItem.obtain(mActivityToken, 4, config()));
+        testRecycle(() -> MoveToDisplayItem.obtain(mActivityToken, 4, config(),
+                new ActivityWindowInfo()));
     }
 
     @Test
@@ -167,7 +169,7 @@
 
     @Test
     public void testRecyclePauseActivityItemItem() {
-        testRecycle(() -> PauseActivityItem.obtain(mActivityToken, true, true, 5, true, true));
+        testRecycle(() -> PauseActivityItem.obtain(mActivityToken, true, true, true, true));
     }
 
     @Test
@@ -183,7 +185,7 @@
 
     @Test
     public void testRecycleStopItem() {
-        testRecycle(() -> StopActivityItem.obtain(mActivityToken, 4));
+        testRecycle(() -> StopActivityItem.obtain(mActivityToken));
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index adb6f2a..935bc75 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -306,7 +306,7 @@
         final IBinder token = mock(IBinder.class);
         final ClientTransaction destroyTransaction = ClientTransaction.obtain(null /* client */);
         destroyTransaction.addTransactionItem(
-                DestroyActivityItem.obtain(token, false /* finished */, 0 /* configChanges */));
+                DestroyActivityItem.obtain(token, false /* finished */));
         destroyTransaction.preExecute(mTransactionHandler);
         // The activity should be added to to-be-destroyed container.
         assertEquals(1, activitiesToBeDestroyed.size());
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 9743e84..d451fe5 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -95,8 +95,11 @@
     @Test
     public void testActivityConfigChange() {
         // Write to parcel
+        final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
+        activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 500, 1000),
+                new Rect(0, 0, 500, 500));
         ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(
-                mActivityToken, config());
+                mActivityToken, config(), activityWindowInfo);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
@@ -110,8 +113,11 @@
     @Test
     public void testMoveToDisplay() {
         // Write to parcel
+        final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
+        activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 500, 1000),
+                new Rect(0, 0, 500, 500));
         MoveToDisplayItem item = MoveToDisplayItem.obtain(mActivityToken, 4 /* targetDisplayId */,
-                config());
+                config(), activityWindowInfo);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
@@ -149,8 +155,7 @@
 
     @Test
     public void testDestroy() {
-        DestroyActivityItem item = DestroyActivityItem.obtain(mActivityToken, true /* finished */,
-                135 /* configChanges */);
+        DestroyActivityItem item = DestroyActivityItem.obtain(mActivityToken, true /* finished */);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
@@ -238,8 +243,7 @@
     public void testPause() {
         // Write to parcel
         PauseActivityItem item = PauseActivityItem.obtain(mActivityToken, true /* finished */,
-                true /* userLeaving */, 135 /* configChanges */, true /* dontReport */,
-                true /* autoEnteringPip */);
+                true /* userLeaving */, true /* dontReport */, true /* autoEnteringPip */);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
@@ -266,7 +270,7 @@
     @Test
     public void testStop() {
         // Write to parcel
-        StopActivityItem item = StopActivityItem.obtain(mActivityToken, 14 /* configChanges */);
+        StopActivityItem item = StopActivityItem.obtain(mActivityToken);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
@@ -297,10 +301,9 @@
         // Write to parcel
         NewIntentItem callback1 = NewIntentItem.obtain(mActivityToken, new ArrayList<>(), true);
         ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
-                mActivityToken, config());
+                mActivityToken, config(), new ActivityWindowInfo());
 
-        StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken,
-                78 /* configChanges */);
+        StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken);
 
         ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
         transaction.addTransactionItem(callback1);
@@ -324,7 +327,7 @@
         // Write to parcel
         NewIntentItem callback1 = NewIntentItem.obtain(mActivityToken, new ArrayList<>(), true);
         ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
-                mActivityToken, config());
+                mActivityToken, config(), new ActivityWindowInfo());
 
         ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
         transaction.addTransactionItem(callback1);
@@ -345,8 +348,7 @@
         mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
 
         // Write to parcel
-        StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken,
-                78 /* configChanges */);
+        StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken);
 
         ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
         transaction.addTransactionItem(lifecycleRequest);
diff --git a/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
index 9dce899..da40f2a 100644
--- a/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
+++ b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
@@ -29,8 +29,8 @@
 import android.content.res.Configuration;
 import android.os.Parcel;
 import android.os.PersistableBundle;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
diff --git a/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java b/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java
index fae7148..3618543 100644
--- a/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java
+++ b/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java
@@ -18,8 +18,7 @@
 
 import static junit.framework.Assert.fail;
 
-import android.test.suitebuilder.annotation.SmallTest;
-
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.ArrayUtils;
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index 4a9cb71..0c1e879 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -18,34 +18,52 @@
 
 import android.annotation.NonNull;
 import android.app.ResourcesManager;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.LocaleList;
 import android.platform.test.annotations.Postsubmit;
+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 android.util.DisplayMetrics;
 import android.util.TypedValue;
 import android.view.Display;
 import android.view.DisplayAdjustments;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import junit.framework.TestCase;
 
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
 @Postsubmit
+@RunWith(AndroidJUnit4.class)
 public class ResourcesManagerTest extends TestCase {
     private static final int SECONDARY_DISPLAY_ID = 1;
     private static final String APP_ONE_RES_DIR = "app_one.apk";
     private static final String APP_ONE_RES_SPLIT_DIR = "app_one_split.apk";
     private static final String APP_TWO_RES_DIR = "app_two.apk";
     private static final String LIB_RES_DIR = "lib.apk";
+    private static final String TEST_LIB = "com.android.frameworks.coretests.bdr_helper_app1";
 
     private ResourcesManager mResourcesManager;
     private Map<Integer, DisplayMetrics> mDisplayMetricsMap;
+    private PackageManager mPackageManager;
 
-    @Override
-    protected void setUp() throws Exception {
+    @Before
+    public void setUp() throws Exception {
         super.setUp();
 
         mDisplayMetricsMap = new HashMap<>();
@@ -93,8 +111,14 @@
                 return mDisplayMetricsMap.get(displayId);
             }
         };
+
+        mPackageManager = InstrumentationRegistry.getContext().getPackageManager();
     }
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Test
     @SmallTest
     public void testMultipleCallsWithIdenticalParametersCacheReference() {
         Resources resources = mResourcesManager.getResources(
@@ -109,6 +133,7 @@
         assertSame(resources.getImpl(), newResources.getImpl());
     }
 
+    @Test
     @SmallTest
     public void testMultipleCallsWithDifferentParametersReturnDifferentReferences() {
         Resources resources = mResourcesManager.getResources(
@@ -125,6 +150,7 @@
         assertNotSame(resources, newResources);
     }
 
+    @Test
     @SmallTest
     public void testAddingASplitCreatesANewImpl() {
         Resources resources1 = mResourcesManager.getResources(
@@ -142,6 +168,7 @@
         assertNotSame(resources1.getImpl(), resources2.getImpl());
     }
 
+    @Test
     @SmallTest
     public void testUpdateConfigurationUpdatesAllAssetManagers() {
         Resources resources1 = mResourcesManager.getResources(
@@ -187,6 +214,7 @@
         assertEquals(expectedConfig, resources3.getConfiguration());
     }
 
+    @Test
     @SmallTest
     public void testTwoActivitiesWithIdenticalParametersShareImpl() {
         Binder activity1 = new Binder();
@@ -208,6 +236,7 @@
         assertSame(resources1.getImpl(), resources2.getImpl());
     }
 
+    @Test
     @SmallTest
     public void testThemesGetUpdatedWithNewImpl() {
         Binder activity1 = new Binder();
@@ -237,6 +266,7 @@
         assertTrue(value.data != 0);
     }
 
+    @Test
     @SmallTest
     public void testMultipleResourcesForOneActivityGetUpdatedWhenActivityBaseUpdates() {
         Binder activity1 = new Binder();
@@ -286,6 +316,7 @@
         assertEquals(expectedConfig2, resources2.getConfiguration());
     }
 
+    @Test
     @SmallTest
     public void testChangingActivityDisplayDoesntOverrideDisplayRequestedByResources() {
         Binder activity = new Binder();
@@ -322,4 +353,101 @@
         assertEquals(mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).widthPixels,
                 defaultDisplayResources.getDisplayMetrics().widthPixels);
     }
+
+    @Test
+    @SmallTest
+    @RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS)
+    public void testExistingResourcesAfterResourcePathsRegistration()
+             throws PackageManager.NameNotFoundException {
+        // Inject ResourcesManager instance from this test to the ResourcesManager class so that all
+        // the static method can interact with this test smoothly.
+        ResourcesManager oriResourcesManager = ResourcesManager.getInstance();
+        ResourcesManager.setInstance(mResourcesManager);
+
+        // Create a Resources before register resources' paths for a package.
+        Resources resources = mResourcesManager.getResources(
+                null, APP_ONE_RES_DIR, null, null, null, null, null, null,
+                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
+        assertNotNull(resources);
+        ResourcesImpl oriResImpl = resources.getImpl();
+
+        ApplicationInfo appInfo = mPackageManager.getApplicationInfo(TEST_LIB, 0);
+        Resources.registerResourcePaths(TEST_LIB, appInfo);
+
+        assertNotSame(oriResImpl, resources.getImpl());
+
+        String[] resourcePaths = appInfo.getAllApkPaths();
+        resourcePaths = removeDuplicates(resourcePaths);
+        ApkAssets[] loadedAssets = resources.getAssets().getApkAssets();
+        assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets));
+
+        // Package resources' paths should be cached in ResourcesManager.
+        assertEquals(Arrays.toString(resourcePaths), Arrays.toString(ResourcesManager.getInstance()
+                .getSharedLibAssetsMap().get(TEST_LIB).getAllAssetPaths()));
+
+        // Revert the ResourcesManager instance back.
+        ResourcesManager.setInstance(oriResourcesManager);
+    }
+
+    @Test
+    @SmallTest
+    @RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS)
+    public void testNewResourcesAfterResourcePathsRegistration()
+            throws PackageManager.NameNotFoundException {
+        // Inject ResourcesManager instance from this test to the ResourcesManager class so that all
+        // the static method can interact with this test smoothly.
+        ResourcesManager oriResourcesManager = ResourcesManager.getInstance();
+        ResourcesManager.setInstance(mResourcesManager);
+
+        ApplicationInfo appInfo = mPackageManager.getApplicationInfo(TEST_LIB, 0);
+        Resources.registerResourcePaths(TEST_LIB, appInfo);
+
+        // Create a Resources after register resources' paths for a package.
+        Resources resources = mResourcesManager.getResources(
+                null, APP_ONE_RES_DIR, null, null, null, null, null, null,
+                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
+        assertNotNull(resources);
+
+        String[] resourcePaths = appInfo.getAllApkPaths();
+        resourcePaths = removeDuplicates(resourcePaths);
+        ApkAssets[] loadedAssets = resources.getAssets().getApkAssets();
+        assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets));
+
+        // Package resources' paths should be cached in ResourcesManager.
+        assertEquals(Arrays.toString(resourcePaths), Arrays.toString(ResourcesManager.getInstance()
+                .getSharedLibAssetsMap().get(TEST_LIB).getAllAssetPaths()));
+
+        // Revert the ResourcesManager instance back.
+        ResourcesManager.setInstance(oriResourcesManager);
+    }
+
+    private static boolean allResourcePathsLoaded(String[] resourcePaths, ApkAssets[] loadedAsset) {
+        for (int i = 0; i < resourcePaths.length; i++) {
+            if (!resourcePaths[i].endsWith(".apk")) {
+                continue;
+            }
+            boolean found = false;
+            for (int j = 0; j < loadedAsset.length; j++) {
+                if (loadedAsset[j].getAssetPath().equals(resourcePaths[i])) {
+                    found = true;
+                }
+            }
+            if (!found) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static String[] removeDuplicates(String[] paths) {
+        var pathList = new ArrayList<String>();
+        var pathSet = new ArraySet<String>();
+        final int pathsLen = paths.length;
+        for (int i = 0; i < pathsLen; i++) {
+            if (pathSet.add(paths[i])) {
+                pathList.add(paths[i]);
+            }
+        }
+        return pathList.toArray(new String[0]);
+    }
 }
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index 3ee565f..2905a5a 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -31,6 +31,7 @@
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.test.AndroidTestCase;
 import android.util.Log;
+import android.util.Printer;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -46,12 +47,15 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Vector;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Phaser;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -404,40 +408,137 @@
         assertFalse(allowed);
     }
 
-    /** Return true if the path is in the list of strings. */
-    private boolean isConcurrent(String path) throws Exception {
-        path = new File(path).toPath().toRealPath().toString();
-        return SQLiteDatabase.getConcurrentDatabasePaths().contains(path);
+    /** Dumpsys information about a single database. */
+
+    /**
+     * Collect and parse dumpsys output.  This is not a full parser.  It is only enough to support
+     * the unit tests.
+     */
+    private static class Dumpsys {
+        // Regular expressions for parsing the output.  Reportedly, regular expressions are
+        // expensive, so these are created only if a dumpsys object is created.
+        private static final Object sLock = new Object();
+        static Pattern mPool;
+        static Pattern mConnection;
+        static Pattern mEntry;
+        static Pattern mSingleWord;
+        static Pattern mNone;
+
+        // The raw strings read from dumpsys.  Once loaded, this list never changes.
+        final ArrayList<String> mRaw = new ArrayList<>();
+
+        // Parsed dumpsys.  This contains only the bits that are being tested.
+        static class Connection {
+            ArrayList<String> mRecent = new ArrayList<>();
+            ArrayList<String> mLong = new ArrayList<>();
+        }
+        static class Database {
+            String mPath;
+            ArrayList<Connection> mConnection = new ArrayList<>();
+        }
+        ArrayList<Database> mDatabase;
+        ArrayList<String> mConcurrent;
+
+        Dumpsys() {
+            SQLiteDebug.dump(
+                new Printer() { public void println(String x) { mRaw.add(x); } },
+                new String[0]);
+            parse();
+        }
+
+        /** Parse the raw text. Return true if no errors were detected. */
+        boolean parse() {
+            initialize();
+
+            // Reset the parsed information.  This method may be called repeatedly.
+            mDatabase = new ArrayList<>();
+            mConcurrent = new ArrayList<>();
+
+            Database current = null;
+            Connection connection = null;
+            Matcher matcher;
+            for (int i = 0; i < mRaw.size(); i++) {
+                final String line = mRaw.get(i);
+                matcher = mPool.matcher(line);
+                if (matcher.lookingAt()) {
+                    current = new Database();
+                    mDatabase.add(current);
+                    current.mPath = matcher.group(1);
+                    continue;
+                }
+                matcher = mConnection.matcher(line);
+                if (matcher.lookingAt()) {
+                    connection = new Connection();
+                    current.mConnection.add(connection);
+                    continue;
+                }
+
+                if (line.contains("Most recently executed operations")) {
+                    i += readTable(connection.mRecent, i, mEntry);
+                    continue;
+                }
+
+                if (line.contains("Operations exceeding 2000ms")) {
+                    i += readTable(connection.mLong, i, mEntry);
+                    continue;
+                }
+                if (line.contains("Concurrently opened database files")) {
+                    i += readTable(mConcurrent, i, mSingleWord);
+                    continue;
+                }
+            }
+            return true;
+        }
+
+        /**
+         * Read a series of lines following a header.  Return the number of lines read.  The input
+         * line number is the number of the header.
+         */
+        private int readTable(List<String> s, int header, Pattern p) {
+            // Special case: if the first line is "<none>" then there are no more lines to the
+            // table.
+            if (lookingAt(header+1, mNone)) return 1;
+
+            int i;
+            for (i = header + 1; i < mRaw.size() && lookingAt(i, p); i++) {
+                s.add(mRaw.get(i).trim());
+            }
+            return i - header;
+        }
+
+        /** Return true if the n'th raw line matches the pattern. */
+        boolean lookingAt(int n, Pattern p) {
+            return p.matcher(mRaw.get(n)).lookingAt();
+        }
+
+        /** Compile the regular expressions the first time. */
+        private static void initialize() {
+            synchronized (sLock) {
+                if (mPool != null) return;
+                mPool = Pattern.compile("Connection pool for (\\S+):");
+                mConnection = Pattern.compile("\\s+Connection #(\\d+):");
+                mEntry = Pattern.compile("\\s+(\\d+): ");
+                mSingleWord = Pattern.compile("  (\\S+)$");
+                mNone = Pattern.compile("\\s+<none>$");
+            }
+        }
     }
 
     @Test
-    public void testDuplicateDatabases() throws Exception {
-        // The two database paths in this test are assumed not to have been opened earlier in this
-        // process.
+    public void testDumpsys() throws Exception {
+        Dumpsys dumpsys = new Dumpsys();
 
-        // A database path that will be opened twice.
-        final String dbName = "never-used-db.db";
-        final File dbFile = mContext.getDatabasePath(dbName);
-        final String dbPath = dbFile.getPath();
+        assertEquals(1, dumpsys.mDatabase.size());
+        // Note: cannot test mConcurrent because that attribute is not hermitic with respect to
+        // the tests.
 
-        // A database path that will be opened only once.
-        final String okName = "never-used-ok.db";
-        final File okFile = mContext.getDatabasePath(okName);
-        final String okPath = okFile.getPath();
+        Dumpsys.Database db = dumpsys.mDatabase.get(0);
 
-        SQLiteDatabase db1 = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
-        assertFalse(isConcurrent(dbPath));
-        SQLiteDatabase db2 = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
-        assertTrue(isConcurrent(dbPath));
-        db1.close();
-        assertTrue(isConcurrent(dbPath));
-        db2.close();
-        assertTrue(isConcurrent(dbPath));
+        // Work with normalized paths.
+        String wantPath = mDatabaseFile.toPath().toRealPath().toString();
+        String realPath = new File(db.mPath).toPath().toRealPath().toString();
+        assertEquals(wantPath, realPath);
 
-        SQLiteDatabase db3 = SQLiteDatabase.openOrCreateDatabase(okFile, null);
-        db3.close();
-        db3 = SQLiteDatabase.openOrCreateDatabase(okFile, null);
-        assertFalse(isConcurrent(okPath));
-        db3.close();
+        assertEquals(1, db.mConnection.size());
     }
 }
diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
index 34f5841..3a872b5 100644
--- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
@@ -18,7 +18,6 @@
 
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS;
-import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -36,15 +35,12 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Resources;
-import android.hardware.biometrics.BiometricPrompt;
 import android.os.CancellationSignal;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
 
 import com.android.internal.R;
 
@@ -62,7 +58,6 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.Executor;
 
 @Presubmit
 @RunWith(MockitoJUnitRunner.class)
@@ -83,8 +78,6 @@
     @Mock
     private FaceManager.AuthenticationCallback mAuthCallback;
     @Mock
-    private BiometricPrompt.AuthenticationCallback mBioAuthCallback;
-    @Mock
     private FaceManager.EnrollmentCallback mEnrollmentCallback;
     @Mock
     private FaceManager.FaceDetectionCallback mFaceDetectionCallback;
@@ -98,16 +91,13 @@
     private TestLooper mLooper;
     private Handler mHandler;
     private FaceManager mFaceManager;
-    private Executor mExecutor;
 
     @Before
     public void setUp() throws Exception {
         mLooper = new TestLooper();
         mHandler = new Handler(mLooper.getLooper());
-        mExecutor = new HandlerExecutor(mHandler);
 
         when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
-        when(mContext.getMainExecutor()).thenReturn(mExecutor);
         when(mContext.getOpPackageName()).thenReturn(PACKAGE_NAME);
         when(mContext.getAttributionTag()).thenReturn(ATTRIBUTION_TAG);
         when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
@@ -169,19 +159,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(FLAG_FACE_BACKGROUND_AUTHENTICATION)
-    public void authenticateInBackground_errorWhenUnavailable() throws Exception {
-        when(mService.authenticateInBackground(any(), anyLong(), any(), any()))
-                .thenThrow(new RemoteException());
-
-        mFaceManager.authenticateInBackground(mExecutor, null, new CancellationSignal(),
-                mBioAuthCallback);
-        mLooper.dispatchAll();
-
-        verify(mBioAuthCallback).onAuthenticationError(eq(FACE_ERROR_HW_UNAVAILABLE), any());
-    }
-
-    @Test
     public void enrollment_errorWhenFaceEnrollmentExists() throws RemoteException {
         when(mResources.getInteger(R.integer.config_faceMaxTemplatesPerUser)).thenReturn(1);
         when(mService.getEnrolledFaces(anyInt(), anyInt(), anyString()))
diff --git a/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java b/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java
index da3a465..b61104d 100644
--- a/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java
+++ b/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java
@@ -27,7 +27,8 @@
 import android.hardware.biometrics.face.SensorProps;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
 import org.junit.Rule;
diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java
index 613089c..f058c16 100644
--- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java
+++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java
@@ -27,7 +27,8 @@
 import android.hardware.biometrics.fingerprint.SensorProps;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -38,7 +39,6 @@
 
 import java.util.function.Function;
 
-
 @Presubmit
 @SmallTest
 public class FingerprintSensorConfigurationsTest {
diff --git a/core/tests/coretests/src/android/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java
index cb281ff..b9a12ad 100644
--- a/core/tests/coretests/src/android/os/PowerManagerTest.java
+++ b/core/tests/coretests/src/android/os/PowerManagerTest.java
@@ -32,6 +32,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.RavenwoodFlagsValueProvider;
 import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.InstrumentationRegistry;
@@ -86,7 +87,8 @@
 
     // Required for RequiresFlagsEnabled and RequiresFlagsDisabled annotations to take effect.
     @Rule
-    public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isUnderRavenwood() ? null
+    public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood()
+            ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule()
             : DeviceFlagsValueProvider.createCheckFlagsRule();
 
     /**
diff --git a/core/tests/coretests/src/android/os/WorkDurationUnitTest.java b/core/tests/coretests/src/android/os/WorkDurationUnitTest.java
index c70da6e..fcdc590 100644
--- a/core/tests/coretests/src/android/os/WorkDurationUnitTest.java
+++ b/core/tests/coretests/src/android/os/WorkDurationUnitTest.java
@@ -22,6 +22,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.RavenwoodFlagsValueProvider;
 import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -40,7 +41,8 @@
 
     // Required for RequiresFlagsEnabled and RequiresFlagsDisabled annotations to take effect.
     @Rule
-    public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isUnderRavenwood() ? null
+    public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood()
+            ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule()
             : DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Before
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 8c93fbb..48ba526 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -24,8 +24,10 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.AdditionalMatchers.and;
+import static org.mockito.AdditionalMatchers.not;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
@@ -36,6 +38,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.view.WindowManager.BadTokenException;
 import android.view.WindowManager.LayoutParams;
+import android.view.inputmethod.ImeTracker;
 import android.widget.TextView;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -96,12 +99,12 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             // test if setVisibility can show IME
             mImeConsumer.onWindowFocusGained(true);
-            mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
+            mController.show(WindowInsets.Type.ime(), true /* fromIme */, ImeTracker.Token.empty());
             mController.cancelExistingAnimations();
             assertTrue((mController.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0);
 
             // test if setVisibility can hide IME
-            mController.hide(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
+            mController.hide(WindowInsets.Type.ime(), true /* fromIme */, ImeTracker.Token.empty());
             mController.cancelExistingAnimations();
             assertFalse((mController.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0);
         });
@@ -114,8 +117,9 @@
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             // Request IME visible before control is available.
+            final var statsToken = ImeTracker.Token.empty();
             mImeConsumer.onWindowFocusGained(true);
-            mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
+            mController.show(WindowInsets.Type.ime(), true /* fromIme */, statsToken);
 
             // set control and verify visibility is applied.
             InsetsSourceControl control = new InsetsSourceControl(ID_IME,
@@ -124,10 +128,10 @@
             // IME show animation should be triggered when control becomes available.
             verify(mController).applyAnimation(
                     eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */,
-                    any() /* statsToken */);
+                    eq(statsToken));
             verify(mController, never()).applyAnimation(
                     eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(true) /* fromIme */,
-                    any() /* statsToken */);
+                    eq(statsToken));
         });
     }
 
@@ -152,9 +156,9 @@
             // Request IME visible before control is available.
             mImeConsumer.onWindowFocusGained(hasWindowFocus);
             final boolean imeVisible = hasWindowFocus && hasViewFocus;
+            final var statsToken = ImeTracker.Token.empty();
             if (imeVisible) {
-                mController.show(WindowInsets.Type.ime(), true /* fromIme */,
-                        null /* statsToken */);
+                mController.show(WindowInsets.Type.ime(), true /* fromIme */, statsToken);
             }
 
             // set control and verify visibility is applied.
@@ -168,23 +172,25 @@
                 // and expect skip animation state after getAndClearSkipAnimationOnce invoked.
                 mController.onControlsChanged(new InsetsSourceControl[]{ control });
                 verify(control).getAndClearSkipAnimationOnce();
+                // This ends up creating a new request when we gain control,
+                // so the statsToken won't match.
                 verify(mController).applyAnimation(eq(WindowInsets.Type.ime()),
                         eq(true) /* show */, eq(false) /* fromIme */,
-                        eq(expectSkipAnim) /* skipAnim */, eq(null) /* statsToken */);
+                        eq(expectSkipAnim) /* skipAnim */, and(not(eq(statsToken)), notNull()));
             }
 
             // If previously hasViewFocus is false, verify when requesting the IME visible next
             // time will not skip animation.
             if (!hasViewFocus) {
-                mController.show(WindowInsets.Type.ime(), true /* fromIme */,
-                        null /* statsToken */);
+                final var statsTokenNext = ImeTracker.Token.empty();
+                mController.show(WindowInsets.Type.ime(), true /* fromIme */, statsTokenNext);
                 mController.onControlsChanged(new InsetsSourceControl[]{ control });
                 // Verify IME show animation should be triggered when control becomes available and
                 // the animation will be skipped by getAndClearSkipAnimationOnce invoked.
                 verify(control).getAndClearSkipAnimationOnce();
                 verify(mController).applyAnimation(eq(WindowInsets.Type.ime()),
                         eq(true) /* show */, eq(true) /* fromIme */,
-                        eq(false) /* skipAnim */, eq(null) /* statsToken */);
+                        eq(false) /* skipAnim */, eq(statsTokenNext));
             }
         });
     }
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 1568174..316e191 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -256,7 +256,7 @@
             mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener);
             mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
             // since there is no focused view, forcefully make IME visible.
-            mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
+            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
             // When using the animation thread, this must not invoke onReady()
             mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
         });
@@ -273,14 +273,14 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
             // since there is no focused view, forcefully make IME visible.
-            mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
+            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
             mController.show(all());
             // quickly jump to final state by cancelling it.
             mController.cancelExistingAnimations();
-            final @InsetsType int types = navigationBars() | statusBars() | ime();
+            @InsetsType final int types = navigationBars() | statusBars() | ime();
             assertEquals(types, mController.getRequestedVisibleTypes() & types);
 
-            mController.hide(ime(), true /* fromIme */, null /* statsToken */);
+            mController.hide(ime(), true /* fromIme */, ImeTracker.Token.empty());
             mController.hide(all());
             mController.cancelExistingAnimations();
             assertEquals(0, mController.getRequestedVisibleTypes() & types);
@@ -295,10 +295,10 @@
         mController.onControlsChanged(new InsetsSourceControl[] { ime });
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
-            mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
+            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
             mController.cancelExistingAnimations();
             assertTrue(isRequestedVisible(mController, ime()));
-            mController.hide(ime(), true /* fromIme */, null /* statsToken */);
+            mController.hide(ime(), true /* fromIme */, ImeTracker.Token.empty());
             mController.cancelExistingAnimations();
             assertFalse(isRequestedVisible(mController, ime()));
             mController.getSourceConsumer(ID_IME, ime()).onWindowFocusLost();
@@ -465,7 +465,7 @@
             assertFalse(mController.getState().peekSource(ID_IME).isVisible());
 
             // Pretend IME is calling
-            mController.show(ime(), true /* fromIme */, null /* statsToken */);
+            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
 
             // Gaining control shortly after
             mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
@@ -489,7 +489,7 @@
             mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
 
             // Pretend IME is calling
-            mController.show(ime(), true /* fromIme */, null /* statsToken */);
+            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
 
             assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
             mController.cancelExistingAnimations();
@@ -567,7 +567,7 @@
             verify(listener, never()).onReady(any(), anyInt());
 
             // Pretend that IME is calling.
-            mController.show(ime(), true /* fromIme */, null /* statsToken */);
+            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
 
             // Ready gets deferred until next predraw
             mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
@@ -651,7 +651,7 @@
             mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
 
             // Pretend IME is calling
-            mController.show(ime(), true /* fromIme */, null /* statsToken */);
+            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
 
             InsetsState copy = new InsetsState(mController.getState(), true /* copySources */);
             copy.peekSource(ID_IME).setFrame(0, 1, 2, 3);
@@ -851,7 +851,7 @@
 
             // Showing invisible ime should only causes insets change once.
             clearInvocations(mTestHost);
-            mController.show(ime(), true /* fromIme */, null /* statsToken */);
+            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
             verify(mTestHost, times(1)).notifyInsetsChanged();
 
             // Sending the same insets state should not cause insets change.
@@ -918,7 +918,7 @@
             assertNull(imeInsetsConsumer.getControl());
 
             // Verify IME requested visibility should be updated to IME consumer from controller.
-            mController.show(ime(), true /* fromIme */, null /* statsToken */);
+            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
             assertTrue(isRequestedVisible(mController, ime()));
 
             mController.hide(ime());
diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index 936f4d7..61e05da 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -291,6 +291,17 @@
     }
 
     @Test
+    public void testCalculateBoundingRects_noBoundingRectsAndFrameNotAtOrigin_createsSingleRect() {
+        mSource.setFrame(new Rect(100, 100, 1200, 200));
+        mSource.setBoundingRects(null);
+
+        final Rect[] rects = mSource.calculateBoundingRects(new Rect(100, 100, 1100, 1100), false);
+
+        assertEquals(1, rects.length);
+        assertEquals(new Rect(0, 0, 1000, 100), rects[0]);
+    }
+
+    @Test
     public void testCalculateBoundingRects_noBoundingRectsAndLargerFrame_singleRectFitsRelFrame() {
         mSource.setFrame(new Rect(0, 0, 1000, 100));
         mSource.setBoundingRects(null);
diff --git a/core/tests/coretests/src/android/view/RoundedCornersTest.java b/core/tests/coretests/src/android/view/RoundedCornersTest.java
index 07ef33a..ec665ad 100644
--- a/core/tests/coretests/src/android/view/RoundedCornersTest.java
+++ b/core/tests/coretests/src/android/view/RoundedCornersTest.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.content.res.Configuration.SCREENLAYOUT_ROUND_NO;
+import static android.content.res.Configuration.SCREENLAYOUT_ROUND_YES;
 import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
 import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
 import static android.view.RoundedCorner.POSITION_TOP_LEFT;
@@ -28,22 +30,47 @@
 import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.nullValue;
 import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
 
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.platform.test.annotations.Presubmit;
+import android.util.DisplayMetrics;
 import android.util.Pair;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.R;
+
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 @Presubmit
 public class RoundedCornersTest {
 
+    @Mock
+    DisplayMetrics mMockDisplayMetrics;
+    @Mock
+    Resources mMockResources;
+    @Mock TypedArray mMockTypedArray;
+    Configuration mConfiguration;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mConfiguration = Configuration.EMPTY;
+    }
+
     final RoundedCorners mRoundedCorners = new RoundedCorners(
             new RoundedCorner(POSITION_TOP_LEFT, 10, 10, 10),
             new RoundedCorner(POSITION_TOP_RIGHT, 10, 190, 10),
@@ -199,4 +226,63 @@
         assertThat(RoundedCorners.fromRadii(radius, 200, 300),
                 not(sameInstance(cached)));
     }
+
+    @Test
+    public void testGetRoundedCornerRadius_withRoundDevice_usesDisplayRadiusAsDefault() {
+        final int displayWidth = 400;
+        mConfiguration.screenLayout = SCREENLAYOUT_ROUND_YES;
+        mMockDisplayMetrics.widthPixels = displayWidth;
+
+        when(mMockResources.getConfiguration()).thenReturn(mConfiguration);
+        when(mMockResources.getDisplayMetrics()).thenReturn(mMockDisplayMetrics);
+        when(mMockResources.getStringArray(R.array.config_displayUniqueIdArray))
+                .thenReturn(new String[]{"0"});
+        when(mMockTypedArray.length()).thenReturn(0);
+        when(mMockResources.obtainTypedArray(anyInt())).thenReturn(mMockTypedArray);
+        when(mMockResources.getDimensionPixelSize(R.dimen.rounded_corner_radius))
+                .thenReturn(0);
+
+
+        int radius = RoundedCorners.getRoundedCornerRadius(mMockResources, "0");
+        assertEquals((displayWidth / 2), radius);
+    }
+
+    @Test
+    public void testGetRoundedCornerRadius_withRoundDevice_usesOverlayIfProvided() {
+        final int displayWidth = 400;
+        final int overlayValue = 199;
+        mConfiguration.screenLayout = SCREENLAYOUT_ROUND_YES;
+        mMockDisplayMetrics.widthPixels = displayWidth;
+
+        when(mMockResources.getConfiguration()).thenReturn(mConfiguration);
+        when(mMockResources.getDisplayMetrics()).thenReturn(mMockDisplayMetrics);
+        when(mMockResources.getStringArray(R.array.config_displayUniqueIdArray))
+                .thenReturn(new String[]{"0"});
+        when(mMockTypedArray.length()).thenReturn(0);
+        when(mMockResources.obtainTypedArray(anyInt())).thenReturn(mMockTypedArray);
+        when(mMockResources.getDimensionPixelSize(R.dimen.rounded_corner_radius))
+                .thenReturn(overlayValue);
+
+        int radius = RoundedCorners.getRoundedCornerRadius(mMockResources, "0");
+        assertEquals(overlayValue, radius);
+    }
+
+    @Test
+    public void testGetRoundedCornerRadius_withNonRoundDevice_noDisplayDefault() {
+        final int displayWidth = 400;
+        mConfiguration.screenLayout = SCREENLAYOUT_ROUND_NO;
+        mMockDisplayMetrics.widthPixels = displayWidth;
+
+        when(mMockResources.getConfiguration()).thenReturn(mConfiguration);
+        when(mMockResources.getDisplayMetrics()).thenReturn(mMockDisplayMetrics);
+        when(mMockResources.getStringArray(R.array.config_displayUniqueIdArray))
+                .thenReturn(new String[]{"0"});
+        when(mMockTypedArray.length()).thenReturn(0);
+        when(mMockResources.obtainTypedArray(anyInt())).thenReturn(mMockTypedArray);
+        when(mMockResources.getDimensionPixelSize(R.dimen.rounded_corner_radius))
+                .thenReturn(0);
+
+        int radius = RoundedCorners.getRoundedCornerRadius(mMockResources, "0");
+        assertEquals(0, radius);
+    }
 }
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
new file mode 100644
index 0000000..90a8c5c
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ViewFrameRateTest {
+
+    @Rule
+    public ActivityTestRule<ViewCaptureTestActivity> mActivityRule = new ActivityTestRule<>(
+            ViewCaptureTestActivity.class);
+
+    private Activity mActivity;
+    private View mMovingView;
+    private ViewRootImpl mViewRoot;
+
+    @Before
+    public void setUp() throws Throwable {
+        mActivity = mActivityRule.getActivity();
+        mActivityRule.runOnUiThread(() -> {
+            mActivity.setContentView(R.layout.view_velocity_test);
+            mMovingView = mActivity.findViewById(R.id.moving_view);
+        });
+        ViewParent parent = mActivity.getWindow().getDecorView().getParent();
+        while (parent instanceof View) {
+            parent = parent.getParent();
+        }
+        mViewRoot = (ViewRootImpl) parent;
+    }
+
+    @UiThreadTest
+    @Test
+    @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+    public void frameRateChangesWhenContentMoves() {
+        mMovingView.offsetLeftAndRight(100);
+        float frameRate = mViewRoot.getPreferredFrameRate();
+        assertTrue(frameRate > 0);
+    }
+
+    @UiThreadTest
+    @Test
+    @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+    public void firstFrameNoMovement() {
+        assertEquals(0f, mViewRoot.getPreferredFrameRate(), 0f);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+    public void touchBoostDisable() throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            long now = SystemClock.uptimeMillis();
+            MotionEvent down = MotionEvent.obtain(
+                    /* downTime */ now,
+                    /* eventTime */ now,
+                    /* action */ MotionEvent.ACTION_DOWN,
+                    /* x */ 0f,
+                    /* y */ 0f,
+                    /* metaState */ 0
+            );
+            mActivity.dispatchTouchEvent(down);
+            mMovingView.offsetLeftAndRight(10);
+        });
+        mActivityRule.runOnUiThread(() -> {
+            mMovingView.invalidate();
+        });
+
+        mActivityRule.runOnUiThread(() -> {
+            assertFalse(mViewRoot.getIsTouchBoosting());
+        });
+    }
+
+    private void waitForFrameRateCategoryToSettle() throws Throwable {
+        for (int i = 0; i < 5; i++) {
+            final CountDownLatch drawLatch = new CountDownLatch(1);
+
+            // Now that it is small, any invalidation should have a normal category
+            mActivityRule.runOnUiThread(() -> {
+                mMovingView.invalidate();
+                mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch::countDown);
+            });
+
+            assertTrue(drawLatch.await(1, TimeUnit.SECONDS));
+        }
+    }
+
+    @Test
+    public void noVelocityUsesCategorySmall() throws Throwable {
+        final CountDownLatch drawLatch1 = new CountDownLatch(1);
+        mActivityRule.runOnUiThread(() -> {
+            float density = mActivity.getResources().getDisplayMetrics().density;
+            ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
+            layoutParams.height = (int) (40 * density);
+            layoutParams.width = (int) (40 * density);
+            mMovingView.setLayoutParams(layoutParams);
+            mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
+        });
+
+        assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
+        waitForFrameRateCategoryToSettle();
+
+        // Now that it is small, any invalidation should have a normal category
+        mActivityRule.runOnUiThread(() -> {
+            mMovingView.invalidate();
+            assertEquals(Surface.FRAME_RATE_CATEGORY_NORMAL,
+                    mViewRoot.getPreferredFrameRateCategory());
+        });
+    }
+
+    @Test
+    public void noVelocityUsesCategoryNarrowWidth() throws Throwable {
+        final CountDownLatch drawLatch1 = new CountDownLatch(1);
+        mActivityRule.runOnUiThread(() -> {
+            float density = mActivity.getResources().getDisplayMetrics().density;
+            ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
+            layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
+            layoutParams.width = (int) (10 * density);
+            mMovingView.setLayoutParams(layoutParams);
+            mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
+        });
+
+        assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
+        waitForFrameRateCategoryToSettle();
+
+        // Now that it is small, any invalidation should have a normal category
+        mActivityRule.runOnUiThread(() -> {
+            mMovingView.invalidate();
+            assertEquals(Surface.FRAME_RATE_CATEGORY_NORMAL,
+                    mViewRoot.getPreferredFrameRateCategory());
+        });
+    }
+
+    @Test
+    public void noVelocityUsesCategoryNarrowHeight() throws Throwable {
+        final CountDownLatch drawLatch1 = new CountDownLatch(1);
+        mActivityRule.runOnUiThread(() -> {
+            float density = mActivity.getResources().getDisplayMetrics().density;
+            ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
+            layoutParams.height = (int) (10 * density);
+            layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+            mMovingView.setLayoutParams(layoutParams);
+            mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
+        });
+
+        assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
+        waitForFrameRateCategoryToSettle();
+
+        // Now that it is small, any invalidation should have a normal category
+        mActivityRule.runOnUiThread(() -> {
+            mMovingView.invalidate();
+            assertEquals(Surface.FRAME_RATE_CATEGORY_NORMAL,
+                    mViewRoot.getPreferredFrameRateCategory());
+        });
+    }
+
+    @Test
+    public void noVelocityUsesCategoryLargeWidth() throws Throwable {
+        final CountDownLatch drawLatch1 = new CountDownLatch(1);
+        mActivityRule.runOnUiThread(() -> {
+            float density = mActivity.getResources().getDisplayMetrics().density;
+            ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
+            layoutParams.height = (int) (40 * density);
+            layoutParams.width = (int) Math.ceil(41 * density);
+            mMovingView.setLayoutParams(layoutParams);
+            mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
+        });
+
+        assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
+        waitForFrameRateCategoryToSettle();
+
+        // Now that it is small, any invalidation should have a high category
+        mActivityRule.runOnUiThread(() -> {
+            mMovingView.invalidate();
+            assertEquals(Surface.FRAME_RATE_CATEGORY_HIGH,
+                    mViewRoot.getPreferredFrameRateCategory());
+        });
+    }
+
+    @Test
+    public void noVelocityUsesCategoryLargeHeight() throws Throwable {
+        final CountDownLatch drawLatch1 = new CountDownLatch(1);
+        mActivityRule.runOnUiThread(() -> {
+            float density = mActivity.getResources().getDisplayMetrics().density;
+            ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
+            layoutParams.height = (int) Math.ceil(41 * density);
+            layoutParams.width = (int) (40 * density);
+            mMovingView.setLayoutParams(layoutParams);
+            mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
+        });
+
+        assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
+        waitForFrameRateCategoryToSettle();
+
+        // Now that it is small, any invalidation should have a high category
+        mActivityRule.runOnUiThread(() -> {
+            mMovingView.invalidate();
+            assertEquals(Surface.FRAME_RATE_CATEGORY_HIGH,
+                    mViewRoot.getPreferredFrameRateCategory());
+        });
+    }
+}
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 7c58de6..2544fcb 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -19,6 +19,7 @@
 import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
 import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
 import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY;
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
 import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
@@ -451,7 +452,7 @@
         ViewRootImpl viewRootImpl = new ViewRootImpl(sContext, display);
 
         boolean result = viewRootImpl.performHapticFeedback(
-                HapticFeedbackConstants.CONTEXT_CLICK, true);
+                HapticFeedbackConstants.CONTEXT_CLICK, true, false /* fromIme */);
 
         assertThat(result).isFalse();
     }
@@ -796,6 +797,38 @@
     }
 
     /**
+     * When velocity of a View is not equal to 0, we call setFrameRateCategory with HIGH.
+     * Also, we shouldn't call setFrameRate.
+     */
+    @Test
+    @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY, FLAG_VIEW_VELOCITY_API})
+    public void votePreferredFrameRate_voteFrameRateCategory_velocityToHigh() {
+        View view = new View(sContext);
+        WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+        wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+        wmlp.width = 1;
+        wmlp.height = 1;
+
+        sInstrumentation.runOnMainSync(() -> {
+            WindowManager wm = sContext.getSystemService(WindowManager.class);
+            wm.addView(view, wmlp);
+        });
+        sInstrumentation.waitForIdleSync();
+
+        ViewRootImpl viewRootImpl = view.getViewRootImpl();
+
+        sInstrumentation.runOnMainSync(() -> {
+            assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
+            view.setFrameContentVelocity(100);
+            view.invalidate();
+            assertTrue(viewRootImpl.getPreferredFrameRate() > 0);
+        });
+        sInstrumentation.waitForIdleSync();
+        assertEquals(viewRootImpl.getLastPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+        assertEquals(viewRootImpl.getLastPreferredFrameRate(), 0 , 0.1);
+    }
+
+    /**
      * We should boost the frame rate if the value of mInsetsAnimationRunning is true.
      */
     @Test
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
index e2f2554..1013bf5 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
@@ -16,13 +16,24 @@
 
 package android.view.accessibility;
 
+import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME;
+
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.TestCase.assertFalse;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -34,6 +45,8 @@
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
 import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.RemoteException;
 import android.os.UserHandle;
 
 import androidx.annotation.NonNull;
@@ -41,6 +54,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.R;
+import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
 import com.android.internal.util.IntPair;
 import com.android.server.accessibility.test.MessageCapturingHandler;
 
@@ -54,6 +68,8 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.Executors;
 
 /**
@@ -263,6 +279,75 @@
         verify(mMockService).unregisterProxyForDisplay(proxy.getDisplayId());
     }
 
+    @Test
+    public void getA11yFeatureToTileMap_catchRemoteExceptionAndRethrow() throws Exception {
+        AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+        doThrow(new RemoteException(new SecurityException()))
+                .when(mMockService)
+                .getA11yFeatureToTileMap(anyInt());
+
+        Throwable rethrownException = assertThrows(RuntimeException.class,
+                () -> manager.getA11yFeatureToTileMap(UserHandle.USER_CURRENT));
+        assertThat(rethrownException.getCause().getCause()).isInstanceOf(SecurityException.class);
+    }
+
+    @Test
+    public void getA11yFeatureToTileMap_verifyServiceMethodCalled() throws Exception {
+        AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(
+                COLOR_INVERSION_COMPONENT_NAME.flattenToString(),
+                COLOR_INVERSION_TILE_COMPONENT_NAME);
+        bundle.putParcelable(
+                DALTONIZER_COMPONENT_NAME.flattenToString(),
+                DALTONIZER_TILE_COMPONENT_NAME);
+        when(mMockService.getA11yFeatureToTileMap(UserHandle.USER_CURRENT)).thenReturn(bundle);
+
+        assertThat(manager.getA11yFeatureToTileMap(UserHandle.USER_CURRENT))
+                .containsExactlyEntriesIn(Map.of(
+                        COLOR_INVERSION_COMPONENT_NAME, COLOR_INVERSION_TILE_COMPONENT_NAME,
+                        DALTONIZER_COMPONENT_NAME, DALTONIZER_TILE_COMPONENT_NAME
+                ));
+        verify(mMockService).getA11yFeatureToTileMap(UserHandle.USER_CURRENT);
+    }
+
+    @Test
+    public void enableShortcutsForTargets_catchRemoteExceptionAndRethrow() throws Exception {
+        AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+        doThrow(new RemoteException(new SecurityException()))
+                .when(mMockService)
+                .enableShortcutsForTargets(anyBoolean(), anyInt(), anyList(), anyInt());
+
+        Throwable rethrownException = assertThrows(RuntimeException.class,
+                () -> manager.enableShortcutsForTargets(
+                        /* enable= */ false,
+                        UserShortcutType.HARDWARE,
+                        Set.of(DALTONIZER_COMPONENT_NAME.flattenToString()),
+                        UserHandle.USER_CURRENT
+                ));
+        assertThat(rethrownException.getCause().getCause()).isInstanceOf(SecurityException.class);
+    }
+
+    @Test
+    public void enableShortcutsForTargets_verifyServiceMethodCalled() throws Exception {
+        AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+        int shortcutTypes = UserShortcutType.HARDWARE | UserShortcutType.TRIPLETAP;
+
+        manager.enableShortcutsForTargets(
+                /* enable= */ false,
+                shortcutTypes,
+                Set.of(DALTONIZER_COMPONENT_NAME.flattenToString()),
+                UserHandle.USER_CURRENT
+        );
+
+        verify(mMockService).enableShortcutsForTargets(
+                /* enable= */ false,
+                shortcutTypes,
+                List.of(DALTONIZER_COMPONENT_NAME.flattenToString()),
+                UserHandle.USER_CURRENT
+        );
+    }
+
     private class MyAccessibilityProxy extends AccessibilityDisplayProxy {
         MyAccessibilityProxy(int displayId,
                 @NonNull List<AccessibilityServiceInfo> serviceInfos) {
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index 51eb41c..b60b806f 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -472,6 +472,26 @@
     }
 
     @Test
+    public void onTouchEvent_doesNothing_viewDisabled() {
+        mTestView1.setEnabled(false);
+
+        final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
+        final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
+        MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
+        mHandwritingInitiator.onTouchEvent(stylusEvent1);
+
+        final int x2 = x1 + mHandwritingSlop * 2;
+        final int y2 = y1;
+
+        MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
+        mHandwritingInitiator.onTouchEvent(stylusEvent2);
+
+        // HandwritingInitiator will not request focus if it is disabled.
+        verify(mTestView1, never()).requestFocus();
+        verify(mHandwritingInitiator, never()).startHandwriting(mTestView1);
+    }
+
+    @Test
     public void onTouchEvent_focusView_inputConnectionAlreadyBuilt_stylusMoveOnce_withinHWArea() {
         if (!mInitiateWithoutConnection) {
             mHandwritingInitiator.onInputConnectionCreated(mTestView1);
diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
index df212eb..cd38bd6 100644
--- a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
+++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
@@ -16,11 +16,17 @@
 
 package android.widget;
 
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.AttributeSet;
 import android.util.PollingCheck;
 
 import androidx.test.filters.MediumTest;
@@ -43,14 +49,21 @@
 public class HorizontalScrollViewFunctionalTest {
     private HorizontalScrollViewActivity mActivity;
     private HorizontalScrollView mHorizontalScrollView;
+    private MyHorizontalScrollView mMyHorizontalScrollView;
     @Rule
     public ActivityTestRule<HorizontalScrollViewActivity> mActivityRule = new ActivityTestRule<>(
             HorizontalScrollViewActivity.class);
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Before
     public void setUp() throws Exception {
         mActivity = mActivityRule.getActivity();
         mHorizontalScrollView = mActivity.findViewById(R.id.horizontal_scroll_view);
+        mMyHorizontalScrollView =
+                (MyHorizontalScrollView) mActivity.findViewById(R.id.my_horizontal_scroll_view);
     }
 
     @Test
@@ -79,6 +92,22 @@
         assertEquals(maxScroll, mHorizontalScrollView.getScrollX());
     }
 
+    @Test
+    @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+    public void testSetVelocity() throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            mMyHorizontalScrollView.setFrameContentVelocity(0);
+        });
+        // set setFrameContentVelocity shouldn't do anything.
+        assertEquals(mMyHorizontalScrollView.isSetVelocityCalled, false);
+
+        mActivityRule.runOnUiThread(() -> {
+            mMyHorizontalScrollView.fling(100);
+        });
+        // set setFrameContentVelocity should be called when fling.
+        assertEquals(mMyHorizontalScrollView.isSetVelocityCalled, true);
+    }
+
     static class WatchedEdgeEffect extends EdgeEffect {
         public CountDownLatch onAbsorbLatch = new CountDownLatch(1);
 
@@ -92,5 +121,29 @@
             onAbsorbLatch.countDown();
         }
     }
+
+    public static class MyHorizontalScrollView extends ScrollView {
+
+        public boolean isSetVelocityCalled;
+
+        public MyHorizontalScrollView(Context context) {
+            super(context);
+        }
+
+        public MyHorizontalScrollView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        public MyHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) {
+            super(context, attrs, defStyle);
+        }
+
+        @Override
+        public void setFrameContentVelocity(float pixelsPerSecond) {
+            if (pixelsPerSecond != 0) {
+                isSetVelocityCalled = true;
+            }
+        }
+    }
 }
 
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
index 4f722ce..6ab77dc 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
@@ -355,7 +355,7 @@
         }
 
         @Override
-        public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() {
+        public RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize) {
             RemoteViews.RemoteCollectionItems.Builder itemsBuilder =
                     new RemoteViews.RemoteCollectionItems.Builder();
             itemsBuilder.setHasStableIds(hasStableIds())
diff --git a/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
index 109c808..a60b2a13 100644
--- a/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
+++ b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
@@ -16,11 +16,17 @@
 
 package android.widget;
 
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.AttributeSet;
 import android.util.PollingCheck;
 
 import androidx.test.filters.MediumTest;
@@ -43,14 +49,20 @@
 public class ScrollViewFunctionalTest {
     private ScrollViewActivity mActivity;
     private ScrollView mScrollView;
+    private MyScrollView mMyScrollView;
     @Rule
     public ActivityTestRule<ScrollViewActivity> mActivityRule = new ActivityTestRule<>(
             ScrollViewActivity.class);
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Before
     public void setUp() throws Exception {
         mActivity = mActivityRule.getActivity();
         mScrollView = mActivity.findViewById(R.id.scroll_view);
+        mMyScrollView = (MyScrollView) mActivity.findViewById(R.id.my_scroll_view);
     }
 
     @Test
@@ -79,6 +91,22 @@
         assertEquals(maxScroll, mScrollView.getScrollY());
     }
 
+    @Test
+    @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+    public void testSetVelocity() throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            mMyScrollView.setFrameContentVelocity(0);
+        });
+        // set setFrameContentVelocity shouldn't do anything.
+        assertEquals(mMyScrollView.isSetVelocityCalled, false);
+
+        mActivityRule.runOnUiThread(() -> {
+            mMyScrollView.fling(100);
+        });
+        // set setFrameContentVelocity should be called when fling.
+        assertEquals(mMyScrollView.isSetVelocityCalled, true);
+    }
+
     static class WatchedEdgeEffect extends EdgeEffect {
         public CountDownLatch onAbsorbLatch = new CountDownLatch(1);
 
@@ -92,5 +120,29 @@
             onAbsorbLatch.countDown();
         }
     }
+
+    public static class MyScrollView extends ScrollView {
+
+        public boolean isSetVelocityCalled;
+
+        public MyScrollView(Context context) {
+            super(context);
+        }
+
+        public MyScrollView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        public MyScrollView(Context context, AttributeSet attrs, int defStyle) {
+            super(context, attrs, defStyle);
+        }
+
+        @Override
+        public void setFrameContentVelocity(float pixelsPerSecond) {
+            if (pixelsPerSecond != 0) {
+                isSetVelocityCalled = true;
+            }
+        }
+    }
 }
 
diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
index 58cfc66..43e6227 100644
--- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
@@ -53,6 +53,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 
 import androidx.test.InstrumentationRegistry;
@@ -93,6 +94,9 @@
     private static final String TYPE_PLAIN_TEXT = "text/plain";
 
     private static UserInfo MANAGED_PROFILE_INFO = new UserInfo();
+    private static UserInfo PRIVATE_PROFILE_INFO = new UserInfo(12, "Private", null,
+            UserInfo.FLAG_PROFILE, UserManager.USER_TYPE_PROFILE_PRIVATE);
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     static {
         MANAGED_PROFILE_INFO.id = 10;
@@ -131,6 +135,7 @@
 
     @Before
     public void setup() {
+
         MockitoAnnotations.initMocks(this);
         mContext = InstrumentationRegistry.getTargetContext();
         sInjector = spy(new TestInjector());
@@ -632,6 +637,54 @@
                 logMakerCaptor.getValue().getSubtype());
     }
 
+    @Test
+    public void shouldForwardToParent_telephony_privateProfile() throws Exception {
+        mSetFlagsRule.enableFlags(
+                android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_INTENT_REDIRECTION);
+
+        sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME;
+        when(mIPm.canForwardTo(
+                any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true);
+
+        List<UserInfo> profiles = new ArrayList<>();
+        profiles.add(CURRENT_USER_INFO);
+        profiles.add(PRIVATE_PROFILE_INFO);
+        when(mUserManager.getProfiles(anyInt())).thenReturn(profiles);
+        when(mUserManager.getProfileParent(anyInt())).thenReturn(CURRENT_USER_INFO);
+        Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class);
+        intent.setAction(Intent.ACTION_DIAL);
+        IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent);
+        verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
+        assertEquals(activity.getStartActivityIntent().getAction(), intent.getAction());
+        assertEquals(activity.getUserIdActivityLaunchedIn(), CURRENT_USER_INFO.id);
+    }
+
+    @Test
+    public void shouldForwardToParent_mms_privateProfile() throws Exception {
+        mSetFlagsRule.enableFlags(
+                android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_INTENT_REDIRECTION);
+
+        sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME;
+        when(mIPm.canForwardTo(
+                any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true);
+
+        List<UserInfo> profiles = new ArrayList<>();
+        profiles.add(CURRENT_USER_INFO);
+        profiles.add(PRIVATE_PROFILE_INFO);
+        when(mUserManager.getProfiles(anyInt())).thenReturn(profiles);
+        when(mUserManager.getProfileParent(anyInt())).thenReturn(CURRENT_USER_INFO);
+        Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class);
+        intent.setAction(Intent.ACTION_SEND);
+        intent.setType(TYPE_PLAIN_TEXT);
+        IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent);
+        verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
+        assertEquals(activity.getStartActivityIntent().getAction(), intent.getAction());
+        assertEquals(activity.getStartActivityIntent().getType(), intent.getType());
+        assertEquals(activity.getUserIdActivityLaunchedIn(), CURRENT_USER_INFO.id);
+    }
+
     private void setupShouldSkipDisclosureTest() throws RemoteException {
         sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME;
         sActivityName = "MyTestActivity";
@@ -688,6 +741,14 @@
         protected MetricsLogger getMetricsLogger() {
             return mMetricsLogger;
         }
+
+        Intent getStartActivityIntent() {
+            return mStartActivityIntent;
+        }
+
+        int getUserIdActivityLaunchedIn() {
+            return mUserIdActivityLaunchedIn;
+        }
     }
 
     public class TestInjector implements IntentForwarderActivity.Injector {
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index b209c7c..cb8754a 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -1162,7 +1162,8 @@
     @Test
     public void testTriggerFromPrivateProfile_withoutWorkProfile() throws RemoteException {
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         markPrivateProfileUserAvailable();
         Intent sendIntent = createSendImageIntent();
         List<ResolvedComponentInfo> privateResolvedComponentInfos =
@@ -1183,7 +1184,8 @@
     @Test
     public void testTriggerFromPrivateProfile_withWorkProfilePresent(){
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         ResolverActivity.ENABLE_TABBED_VIEW = false;
         markPrivateProfileUserAvailable();
         markWorkProfileUserAvailable();
@@ -1205,7 +1207,8 @@
     @Test
     public void testPrivateProfile_triggerFromPrimaryUser_withWorkProfilePresent(){
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         markPrivateProfileUserAvailable();
         markWorkProfileUserAvailable();
         Intent sendIntent = createSendImageIntent();
@@ -1228,7 +1231,8 @@
     @Test
     public void testPrivateProfile_triggerFromWorkProfile(){
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         markPrivateProfileUserAvailable();
         markWorkProfileUserAvailable();
         Intent sendIntent = createSendImageIntent();
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
index dcef0a7..0897726 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
@@ -16,20 +16,31 @@
 
 package android.hardware.devicestate;
 
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
 
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 import android.os.Parcel;
 
 import androidx.test.filters.SmallTest;
 
+import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.util.List;
+import java.util.Set;
+
 /**
  * Unit tests for {@link DeviceStateInfo}.
  * <p/>
@@ -38,11 +49,34 @@
 @RunWith(JUnit4.class)
 @SmallTest
 public final class DeviceStateInfoTest {
+
+    private static final DeviceState DEVICE_STATE_0 = new DeviceState(
+            new DeviceState.Configuration.Builder(0, "STATE_0")
+                    .setSystemProperties(
+                            Set.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
+                                    PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY))
+                    .setPhysicalProperties(
+                            Set.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED))
+                    .build());
+    private static final DeviceState DEVICE_STATE_1 = new DeviceState(
+            new DeviceState.Configuration.Builder(1, "STATE_1")
+                    .setSystemProperties(
+                            Set.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY))
+                    .setPhysicalProperties(
+                            Set.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN))
+                    .build());
+    private static final DeviceState DEVICE_STATE_2 = new DeviceState(
+            new DeviceState.Configuration.Builder(2, "STATE_2")
+                    .setSystemProperties(
+                            Set.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY))
+                    .build());
+
     @Test
     public void create() {
-        final int[] supportedStates = new int[] { 0, 1, 2 };
-        final int baseState = 0;
-        final int currentState = 2;
+        final List<DeviceState> supportedStates = List.of(DEVICE_STATE_0, DEVICE_STATE_1,
+                DEVICE_STATE_2);
+        final DeviceState baseState = DEVICE_STATE_0;
+        final DeviceState currentState = DEVICE_STATE_2;
 
         final DeviceStateInfo info = new DeviceStateInfo(supportedStates, baseState, currentState);
         assertNotNull(info.supportedStates);
@@ -53,27 +87,30 @@
 
     @Test
     public void equals() {
-        final int[] supportedStates = new int[] { 0, 1, 2 };
-        final int baseState = 0;
-        final int currentState = 2;
+        final List<DeviceState> supportedStates = List.of(DEVICE_STATE_0, DEVICE_STATE_1,
+                DEVICE_STATE_2);
+        final DeviceState baseState = DEVICE_STATE_0;
+        final DeviceState currentState = DEVICE_STATE_2;
 
         final DeviceStateInfo info = new DeviceStateInfo(supportedStates, baseState, currentState);
-        assertTrue(info.equals(info));
+        Assert.assertEquals(info, info);
 
         final DeviceStateInfo sameInfo = new DeviceStateInfo(supportedStates, baseState,
                 currentState);
-        assertTrue(info.equals(sameInfo));
+        Assert.assertEquals(info, sameInfo);
 
-        final DeviceStateInfo differentInfo = new DeviceStateInfo(new int[]{ 0, 2}, baseState,
+        final DeviceStateInfo differentInfo = new DeviceStateInfo(
+                List.of(DEVICE_STATE_0, DEVICE_STATE_2), baseState,
                 currentState);
-        assertFalse(info.equals(differentInfo));
+        assertNotEquals(info, differentInfo);
     }
 
     @Test
     public void diff_sameObject() {
-        final int[] supportedStates = new int[] { 0, 1, 2 };
-        final int baseState = 0;
-        final int currentState = 2;
+        final List<DeviceState> supportedStates = List.of(DEVICE_STATE_0, DEVICE_STATE_1,
+                DEVICE_STATE_2);
+        final DeviceState baseState = DEVICE_STATE_0;
+        final DeviceState currentState = DEVICE_STATE_2;
 
         final DeviceStateInfo info = new DeviceStateInfo(supportedStates, baseState, currentState);
         assertEquals(0, info.diff(info));
@@ -81,8 +118,10 @@
 
     @Test
     public void diff_differentSupportedStates() {
-        final DeviceStateInfo info = new DeviceStateInfo(new int[] { 1 }, 0, 0);
-        final DeviceStateInfo otherInfo = new DeviceStateInfo(new int[] { 2 }, 0, 0);
+        final DeviceStateInfo info = new DeviceStateInfo(List.of(DEVICE_STATE_1), DEVICE_STATE_0,
+                DEVICE_STATE_0);
+        final DeviceStateInfo otherInfo = new DeviceStateInfo(List.of(DEVICE_STATE_2),
+                DEVICE_STATE_0, DEVICE_STATE_0);
         final int diff = info.diff(otherInfo);
         assertTrue((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0);
         assertFalse((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0);
@@ -91,8 +130,10 @@
 
     @Test
     public void diff_differentNonOverrideState() {
-        final DeviceStateInfo info = new DeviceStateInfo(new int[] { 1 }, 1, 0);
-        final DeviceStateInfo otherInfo = new DeviceStateInfo(new int[] { 1 }, 2, 0);
+        final DeviceStateInfo info = new DeviceStateInfo(List.of(DEVICE_STATE_1), DEVICE_STATE_1,
+                DEVICE_STATE_0);
+        final DeviceStateInfo otherInfo = new DeviceStateInfo(List.of(DEVICE_STATE_1),
+                DEVICE_STATE_2, DEVICE_STATE_0);
         final int diff = info.diff(otherInfo);
         assertFalse((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0);
         assertTrue((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0);
@@ -101,8 +142,10 @@
 
     @Test
     public void diff_differentState() {
-        final DeviceStateInfo info = new DeviceStateInfo(new int[] { 1 }, 0, 1);
-        final DeviceStateInfo otherInfo = new DeviceStateInfo(new int[] { 1 }, 0, 2);
+        final DeviceStateInfo info = new DeviceStateInfo(List.of(DEVICE_STATE_1), DEVICE_STATE_0,
+                DEVICE_STATE_1);
+        final DeviceStateInfo otherInfo = new DeviceStateInfo(List.of(DEVICE_STATE_1),
+                DEVICE_STATE_0, DEVICE_STATE_2);
         final int diff = info.diff(otherInfo);
         assertFalse((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0);
         assertFalse((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0);
@@ -111,9 +154,10 @@
 
     @Test
     public void writeToParcel() {
-        final int[] supportedStates = new int[] { 0, 1, 2 };
-        final int nonOverrideState = 0;
-        final int state = 2;
+        final List<DeviceState> supportedStates = List.of(DEVICE_STATE_0, DEVICE_STATE_1,
+                DEVICE_STATE_2);
+        final DeviceState nonOverrideState = DEVICE_STATE_0;
+        final DeviceState state = DEVICE_STATE_2;
         final DeviceStateInfo originalInfo =
                 new DeviceStateInfo(supportedStates, nonOverrideState, state);
 
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
index 03c38cc..ee238c0 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
@@ -41,6 +41,7 @@
 import org.junit.runners.JUnit4;
 
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -51,8 +52,10 @@
 @RunWith(JUnit4.class)
 @SmallTest
 public final class DeviceStateManagerGlobalTest {
-    private static final int DEFAULT_DEVICE_STATE = 0;
-    private static final int OTHER_DEVICE_STATE = 1;
+    private static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(
+            new DeviceState.Configuration.Builder(0 /* identifier */, "" /* name */).build());
+    private static final DeviceState OTHER_DEVICE_STATE = new DeviceState(
+            new DeviceState.Configuration.Builder(1 /* identifier */, "" /* name */).build());
 
     private TestDeviceStateManagerService mService;
     private DeviceStateManagerGlobal mDeviceStateManagerGlobal;
@@ -76,41 +79,37 @@
                 ConcurrentUtils.DIRECT_EXECUTOR);
 
         // Verify initial callbacks
-        verify(callback1).onSupportedStatesChanged(eq(mService.getSupportedStates()));
-        verify(callback1).onBaseStateChanged(eq(mService.getBaseState()));
-        verify(callback1).onStateChanged(eq(mService.getMergedState()));
-        verify(callback2).onSupportedStatesChanged(eq(mService.getSupportedStates()));
-        verify(callback2).onBaseStateChanged(eq(mService.getBaseState()));
-        verify(callback2).onStateChanged(eq(mService.getMergedState()));
+        verify(callback1).onSupportedStatesChanged(eq(mService.getSupportedDeviceStates()));
+        verify(callback1).onDeviceStateChanged(eq(mService.getMergedState()));
+        verify(callback2).onDeviceStateChanged(eq(mService.getMergedState()));
 
         reset(callback1);
         reset(callback2);
 
         // Change the supported states and verify callback
-        mService.setSupportedStates(new int[]{ DEFAULT_DEVICE_STATE });
-        verify(callback1).onSupportedStatesChanged(eq(mService.getSupportedStates()));
-        verify(callback2).onSupportedStatesChanged(eq(mService.getSupportedStates()));
-        mService.setSupportedStates(new int[]{ DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE });
+        mService.setSupportedStates(List.of(DEFAULT_DEVICE_STATE));
+        verify(callback1).onSupportedStatesChanged(eq(mService.getSupportedDeviceStates()));
+        verify(callback2).onSupportedStatesChanged(eq(mService.getSupportedDeviceStates()));
+        mService.setSupportedStates(List.of(DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE));
 
         reset(callback1);
         reset(callback2);
 
         // Change the base state and verify callback
         mService.setBaseState(OTHER_DEVICE_STATE);
-        verify(callback1).onBaseStateChanged(eq(mService.getBaseState()));
-        verify(callback1).onStateChanged(eq(mService.getMergedState()));
-        verify(callback2).onBaseStateChanged(eq(mService.getBaseState()));
-        verify(callback2).onStateChanged(eq(mService.getMergedState()));
+        verify(callback1).onDeviceStateChanged(eq(mService.getMergedState()));
+        verify(callback2).onDeviceStateChanged(eq(mService.getMergedState()));
 
         reset(callback1);
         reset(callback2);
 
         // Change the requested state and verify callback
-        DeviceStateRequest request = DeviceStateRequest.newBuilder(DEFAULT_DEVICE_STATE).build();
+        DeviceStateRequest request = DeviceStateRequest.newBuilder(
+                DEFAULT_DEVICE_STATE.getIdentifier()).build();
         mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */);
 
-        verify(callback1).onStateChanged(eq(mService.getMergedState()));
-        verify(callback2).onStateChanged(eq(mService.getMergedState()));
+        verify(callback1).onDeviceStateChanged(eq(mService.getMergedState()));
+        verify(callback2).onDeviceStateChanged(eq(mService.getMergedState()));
     }
 
     @Test
@@ -121,14 +120,13 @@
                 ConcurrentUtils.DIRECT_EXECUTOR);
 
         // Verify initial callbacks
-        verify(callback).onSupportedStatesChanged(eq(mService.getSupportedStates()));
-        verify(callback).onBaseStateChanged(eq(mService.getBaseState()));
-        verify(callback).onStateChanged(eq(mService.getMergedState()));
+        verify(callback).onSupportedStatesChanged(eq(mService.getSupportedDeviceStates()));
+        verify(callback).onDeviceStateChanged(eq(mService.getMergedState()));
         reset(callback);
 
         mDeviceStateManagerGlobal.unregisterDeviceStateCallback(callback);
 
-        mService.setSupportedStates(new int[]{OTHER_DEVICE_STATE});
+        mService.setSupportedStates(List.of(OTHER_DEVICE_STATE));
         mService.setBaseState(OTHER_DEVICE_STATE);
         verifyZeroInteractions(callback);
     }
@@ -139,18 +137,19 @@
         mDeviceStateManagerGlobal.registerDeviceStateCallback(callback,
                 ConcurrentUtils.DIRECT_EXECUTOR);
 
-        verify(callback).onStateChanged(eq(mService.getBaseState()));
+        verify(callback).onDeviceStateChanged(eq(mService.getBaseState()));
         reset(callback);
 
-        DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build();
+        DeviceStateRequest request = DeviceStateRequest.newBuilder(
+                OTHER_DEVICE_STATE.getIdentifier()).build();
         mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */);
 
-        verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE));
+        verify(callback).onDeviceStateChanged(eq(OTHER_DEVICE_STATE));
         reset(callback);
 
         mDeviceStateManagerGlobal.cancelStateRequest();
 
-        verify(callback).onStateChanged(eq(mService.getBaseState()));
+        verify(callback).onDeviceStateChanged(eq(mService.getBaseState()));
     }
 
     @Test
@@ -159,22 +158,20 @@
         mDeviceStateManagerGlobal.registerDeviceStateCallback(callback,
                 ConcurrentUtils.DIRECT_EXECUTOR);
 
-        verify(callback).onBaseStateChanged(eq(mService.getBaseState()));
-        verify(callback).onStateChanged(eq(mService.getBaseState()));
+        verify(callback).onDeviceStateChanged(eq(mService.getBaseState()));
         reset(callback);
 
-        DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build();
+        DeviceStateRequest request = DeviceStateRequest.newBuilder(
+                OTHER_DEVICE_STATE.getIdentifier()).build();
         mDeviceStateManagerGlobal.requestBaseStateOverride(request, null /* executor */,
                 null /* callback */);
 
-        verify(callback).onBaseStateChanged(eq(OTHER_DEVICE_STATE));
-        verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE));
+        verify(callback).onDeviceStateChanged(eq(OTHER_DEVICE_STATE));
         reset(callback);
 
         mDeviceStateManagerGlobal.cancelBaseStateOverride();
 
-        verify(callback).onBaseStateChanged(eq(mService.getBaseState()));
-        verify(callback).onStateChanged(eq(mService.getBaseState()));
+        verify(callback).onDeviceStateChanged(eq(mService.getBaseState()));
     }
 
     @Test
@@ -183,44 +180,43 @@
         mDeviceStateManagerGlobal.registerDeviceStateCallback(callback,
                 ConcurrentUtils.DIRECT_EXECUTOR);
 
-        verify(callback).onBaseStateChanged(eq(mService.getBaseState()));
-        verify(callback).onStateChanged(eq(mService.getBaseState()));
+        verify(callback).onDeviceStateChanged(eq(mService.getBaseState()));
         reset(callback);
 
-        DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build();
+        DeviceStateRequest request = DeviceStateRequest.newBuilder(
+                OTHER_DEVICE_STATE.getIdentifier()).build();
         mDeviceStateManagerGlobal.requestBaseStateOverride(request, null /* executor */,
                 null /* callback */);
 
-        verify(callback).onBaseStateChanged(eq(OTHER_DEVICE_STATE));
-        verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE));
+        verify(callback).onDeviceStateChanged(eq(OTHER_DEVICE_STATE));
         assertEquals(OTHER_DEVICE_STATE, mService.getBaseState());
         reset(callback);
 
         DeviceStateRequest secondRequest = DeviceStateRequest.newBuilder(
-                DEFAULT_DEVICE_STATE).build();
+                DEFAULT_DEVICE_STATE.getIdentifier()).build();
 
         mDeviceStateManagerGlobal.requestState(secondRequest, null, null);
 
         assertEquals(OTHER_DEVICE_STATE, mService.getBaseState());
-        verify(callback).onStateChanged(eq(DEFAULT_DEVICE_STATE));
+        verify(callback).onDeviceStateChanged(eq(DEFAULT_DEVICE_STATE));
         reset(callback);
 
         mDeviceStateManagerGlobal.cancelStateRequest();
 
-        verify(callback).onStateChanged(OTHER_DEVICE_STATE);
+        verify(callback).onDeviceStateChanged(OTHER_DEVICE_STATE);
         reset(callback);
 
         mDeviceStateManagerGlobal.cancelBaseStateOverride();
 
-        verify(callback).onBaseStateChanged(DEFAULT_DEVICE_STATE);
-        verify(callback).onStateChanged(DEFAULT_DEVICE_STATE);
+        verify(callback).onDeviceStateChanged(DEFAULT_DEVICE_STATE);
     }
 
     @Test
     public void verifyDeviceStateRequestCallbacksCalled() {
         DeviceStateRequest.Callback callback = mock(TestDeviceStateRequestCallback.class);
 
-        DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build();
+        DeviceStateRequest request = DeviceStateRequest.newBuilder(
+                OTHER_DEVICE_STATE.getIdentifier()).build();
         mDeviceStateManagerGlobal.requestState(request,
                 ConcurrentUtils.DIRECT_EXECUTOR /* executor */,
                 callback /* callback */);
@@ -257,12 +253,14 @@
             }
         }
 
-        private int[] mSupportedStates = new int[] { DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE };
-        private int mBaseState = DEFAULT_DEVICE_STATE;
+        private List<DeviceState> mSupportedDeviceStates = List.of(DEFAULT_DEVICE_STATE,
+                OTHER_DEVICE_STATE);
+
+        private DeviceState mBaseState = DEFAULT_DEVICE_STATE;
         private Request mRequest;
         private Request mBaseStateRequest;
 
-        private Set<IDeviceStateManagerCallback> mCallbacks = new HashSet<>();
+        private final Set<IDeviceStateManagerCallback> mCallbacks = new HashSet<>();
 
         TestDeviceStateManagerService(FakePermissionEnforcer enforcer) {
             super(enforcer);
@@ -270,10 +268,15 @@
 
         private DeviceStateInfo getInfo() {
             final int mergedBaseState = mBaseStateRequest == null
-                    ? mBaseState : mBaseStateRequest.state;
+                    ? mBaseState.getIdentifier() : mBaseStateRequest.state;
             final int mergedState = mRequest == null
                     ? mergedBaseState : mRequest.state;
-            return new DeviceStateInfo(mSupportedStates, mergedBaseState, mergedState);
+
+            final DeviceState baseState = new DeviceState(
+                    new DeviceState.Configuration.Builder(mergedBaseState, "" /* name */).build());
+            final DeviceState state = new DeviceState(
+                    new DeviceState.Configuration.Builder(mergedState, "" /* name */).build());
+            return new DeviceStateInfo(mSupportedDeviceStates, baseState, state);
         }
 
         private void notifyDeviceStateInfoChanged() {
@@ -392,25 +395,25 @@
             onStateRequestOverlayDismissed_enforcePermission();
         }
 
-        public void setSupportedStates(int[] states) {
-            mSupportedStates = states;
+        public void setSupportedStates(List<DeviceState> states) {
+            mSupportedDeviceStates = states;
             notifyDeviceStateInfoChanged();
         }
 
-        public int[] getSupportedStates() {
-            return mSupportedStates;
+        public List<DeviceState> getSupportedDeviceStates() {
+            return mSupportedDeviceStates;
         }
 
-        public void setBaseState(int state) {
+        public void setBaseState(DeviceState state) {
             mBaseState = state;
             notifyDeviceStateInfoChanged();
         }
 
-        public int getBaseState() {
+        public DeviceState getBaseState() {
             return getInfo().baseState;
         }
 
-        public int getMergedState() {
+        public DeviceState getMergedState() {
             return getInfo().currentState;
         }
     }
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
index c287721..78d4324 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
@@ -16,19 +16,27 @@
 
 package android.hardware.devicestate;
 
-import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE_IDENTIFIER;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS;
 import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE_IDENTIFIER;
 
 import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.assertTrue;
 
+import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
 
+import junit.framework.Assert;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
 /**
  * Unit tests for {@link android.hardware.devicestate.DeviceState}.
  * <p/>
@@ -39,33 +47,68 @@
 public final class DeviceStateTest {
     @Test
     public void testConstruct() {
-        final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE_IDENTIFIER /* identifier */,
-                "TEST_CLOSED" /* name */, DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS /* flags */);
+        DeviceState.Configuration config = new DeviceState.Configuration.Builder(
+                MINIMUM_DEVICE_STATE_IDENTIFIER, "TEST_CLOSED")
+                .setSystemProperties(
+                        new HashSet<>(List.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS)))
+                .build();
+        final DeviceState state = new DeviceState(config);
         assertEquals(state.getIdentifier(), MINIMUM_DEVICE_STATE_IDENTIFIER);
         assertEquals(state.getName(), "TEST_CLOSED");
-        assertEquals(state.getFlags(), DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS);
+        assertTrue(state.hasProperty(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS));
     }
 
     @Test
-    public void testConstruct_nullName() {
-        final DeviceState state = new DeviceState(MAXIMUM_DEVICE_STATE_IDENTIFIER /* identifier */,
-                null /* name */, 0/* flags */);
-        assertEquals(state.getIdentifier(), MAXIMUM_DEVICE_STATE_IDENTIFIER);
-        assertNull(state.getName());
-        assertEquals(state.getFlags(), 0);
+    public void testHasProperties() {
+        DeviceState.Configuration config = new DeviceState.Configuration.Builder(
+                MINIMUM_DEVICE_STATE_IDENTIFIER, "TEST")
+                .setSystemProperties(new HashSet<>(List.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
+                        PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST)))
+                .build();
+
+        final DeviceState state = new DeviceState(config);
+
+        assertTrue(state.hasProperty(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS));
+        assertTrue(state.hasProperty(PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST));
+        assertTrue(state.hasProperties(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
+                PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST));
     }
 
     @Test
-    public void testConstruct_tooLargeIdentifier() {
-        assertThrows(IllegalArgumentException.class,
-                () -> new DeviceState(MAXIMUM_DEVICE_STATE_IDENTIFIER + 1 /* identifier */,
-                        null /* name */, 0 /* flags */));
+    public void writeToParcel() {
+        final DeviceState originalState = new DeviceState(
+                new DeviceState.Configuration.Builder(0, "TEST_STATE")
+                        .setSystemProperties(Set.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
+                                PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST))
+                        .setPhysicalProperties(
+                                Set.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED))
+                        .build());
+
+        final Parcel parcel = Parcel.obtain();
+        originalState.getConfiguration().writeToParcel(parcel, 0 /* flags */);
+        parcel.setDataPosition(0);
+
+        final DeviceState.Configuration stateConfiguration =
+                DeviceState.Configuration.CREATOR.createFromParcel(parcel);
+
+        Assert.assertEquals(originalState, new DeviceState(stateConfiguration));
     }
 
     @Test
-    public void testConstruct_tooSmallIdentifier() {
-        assertThrows(IllegalArgumentException.class,
-                () -> new DeviceState(MINIMUM_DEVICE_STATE_IDENTIFIER - 1 /* identifier */,
-                        null /* name */, 0 /* flags */));
+    public void writeToParcel_noPhysicalProperties() {
+        final DeviceState originalState = new DeviceState(
+                new DeviceState.Configuration.Builder(0, "TEST_STATE")
+                        .setSystemProperties(Set.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
+                                PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST))
+                        .build());
+
+        final Parcel parcel = Parcel.obtain();
+        originalState.getConfiguration().writeToParcel(parcel, 0 /* flags */);
+        parcel.setDataPosition(0);
+
+        final DeviceState.Configuration stateConfiguration =
+                DeviceState.Configuration.CREATOR.createFromParcel(parcel);
+
+        Assert.assertEquals(originalState, new DeviceState(stateConfiguration));
     }
 }
diff --git a/core/tests/featureflagtests/src/android/util/FeatureFlagUtilsTest.java b/core/tests/featureflagtests/src/android/util/FeatureFlagUtilsTest.java
index 3160428..cb8b0db 100644
--- a/core/tests/featureflagtests/src/android/util/FeatureFlagUtilsTest.java
+++ b/core/tests/featureflagtests/src/android/util/FeatureFlagUtilsTest.java
@@ -24,9 +24,9 @@
 import android.content.Context;
 import android.os.SystemProperties;
 import android.provider.Settings;
-import android.test.suitebuilder.annotation.SmallTest;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 66be05f..ed641e0 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -309,17 +309,17 @@
 
         private void pauseActivity(ActivityClientRecord r) {
             mThread.handlePauseActivity(r, false /* finished */,
-                    false /* userLeaving */, 0 /* configChanges */, false /* autoEnteringPip */,
+                    false /* userLeaving */, false /* autoEnteringPip */,
                     null /* pendingActions */, "test");
         }
 
         private void stopActivity(ActivityClientRecord r) {
-            mThread.handleStopActivity(r, 0 /* configChanges */,
+            mThread.handleStopActivity(r,
                     new PendingTransactionActions(), false /* finalStateRequest */, "test");
         }
 
         private void destroyActivity(ActivityClientRecord r) {
-            mThread.handleDestroyActivity(r, true /* finishing */, 0 /* configChanges */,
+            mThread.handleDestroyActivity(r, true /* finishing */,
                     false /* getNonConfigInstance */, "test");
         }
 
diff --git a/core/tests/systemproperties/Android.bp b/core/tests/systemproperties/Android.bp
index 21aa3c44..ed52ccc 100644
--- a/core/tests/systemproperties/Android.bp
+++ b/core/tests/systemproperties/Android.bp
@@ -14,9 +14,9 @@
     dxflags: ["--core-library"],
     static_libs: [
         "android-common",
-        "frameworks-core-util-lib",
         "androidx.test.rules",
         "androidx.test.ext.junit",
+        "frameworks-core-util-lib",
         "ravenwood-junit",
     ],
     libs: [
diff --git a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
index ea65de0..d98120f 100644
--- a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
+++ b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
@@ -24,7 +24,8 @@
 import static org.junit.Assert.fail;
 
 import android.platform.test.ravenwood.RavenwoodRule;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import org.junit.Rule;
 import org.junit.Test;
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 238a3e1..1410950 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -72,6 +72,12 @@
     src: "enhanced-confirmation.xml",
 }
 
+prebuilt_etc {
+    name: "package-shareduid-allowlist.xml",
+    sub_dir: "sysconfig",
+    src: "package-shareduid-allowlist.xml",
+}
+
 // Privapp permission whitelist files
 
 prebuilt_etc {
diff --git a/data/etc/CleanSpec.mk b/data/etc/CleanSpec.mk
index 783a7ed..fd38d27 100644
--- a/data/etc/CleanSpec.mk
+++ b/data/etc/CleanSpec.mk
@@ -43,6 +43,8 @@
 #$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
 #$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
 #$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/sysconfig/package-shareduid-allowlist.xml)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/etc/sysconfig/package-shareduid-allowlist.xml)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/permissions/com.android.carrierconfig.xml)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/etc/permissions/com.android.carrierconfig.xml)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/permissions/com.android.emergency.xml)
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index ce2543a..bca741f 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -88,5 +88,8 @@
         <permission name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS" />
         <permission name="android.permission.READ_SEARCH_INDEXABLES" />
         <permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"/>
+        <permission name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS" />
+        <permission name="android.permission.CONTROL_UI_TRACING" />
+        <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND" />
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/package-shareduid-allowlist.xml b/data/etc/package-shareduid-allowlist.xml
new file mode 100644
index 0000000..2401d4a
--- /dev/null
+++ b/data/etc/package-shareduid-allowlist.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+ -->
+
+<!--
+This XML defines an allowlist for packages that want to join a particular shared-uid.
+If a non-system package that is signed with platform signature, is trying to join a particular
+shared-uid, and not in this list, the installation will fail.
+
+- The "package" XML attribute refers to the app's package name.
+- The "shareduid" XML attribute refers to the shared uid name.
+
+Example usage
+    1. <allow-package-shareduid package="com.example.app" shareduid="android.uid.system"/>
+        Indicates that a package - com.example.app, will be able to join android.uid.system.
+    2. <allow-package-shareduid package="oem.example.app" shareduid="oem.uid.custom"/>
+        Indicates that a package - oem.example.app, will be able to join oem.uid.custom.
+-->
+
+<config>
+    <allow-package-shareduid package="android.test.settings" shareduid="android.uid.system" />
+</config>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 4edfb09..0f12438 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -455,7 +455,7 @@
         <permission name="android.permission.USE_BIOMETRIC" />
         <permission name="android.permission.TEST_BIOMETRIC" />
         <permission name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" />
-        <permission name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
+        <permission name="android.permission.MANAGE_BIOMETRIC_DIALOG" />
         <!-- Permissions required for CTS test - CtsContactsProviderTestCases -->
         <permission name="android.contacts.permission.MANAGE_SIM_ACCOUNTS" />
         <!-- Permissions required for CTS test - CtsHdmiCecHostTestCases -->
@@ -584,6 +584,8 @@
         <permission name="android.permission.DOMAIN_VERIFICATION_AGENT"/>
         <!-- Permission required for CTS test CtsInputTestCases -->
         <permission name="android.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW" />
+        <!-- Permission required for CTS test - PackageManagerShellCommandInstallTest -->
+        <permission name="android.permission.EMERGENCY_INSTALL_PACKAGES" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index e8c7a53..0231d3a 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1,5 +1,5 @@
 {
-  "version": "1.0.0",
+  "version": "2.0.0",
   "messages": {
     "7286191062634870297": {
       "message": "Binding proc %s with config %s",
diff --git a/data/fonts/font_fallback.xml b/data/fonts/font_fallback.xml
index 15ea15a..53024ab 100644
--- a/data/fonts/font_fallback.xml
+++ b/data/fonts/font_fallback.xml
@@ -792,14 +792,10 @@
         </font>
     </family>
     <family lang="ja">
-        <font weight="400" style="normal">
-            NotoSerifHentaigana-EL.ttf
+        <font postScriptName="NotoSerifHentaigana-ExtraLight" supportedAxes="wght">
+            NotoSerifHentaigana.ttf
             <axis tag="wght" stylevalue="400"/>
         </font>
-        <font weight="700" style="normal">
-            NotoSerifHentaigana-EL.ttf
-          <axis tag="wght" stylevalue="700"/>
-        </font>
     </family>
     <family lang="ko">
         <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Regular">
diff --git a/data/fonts/font_fallback_cjkvf.xml b/data/fonts/font_fallback_cjkvf.xml
index c1ca67e..ac1b064 100644
--- a/data/fonts/font_fallback_cjkvf.xml
+++ b/data/fonts/font_fallback_cjkvf.xml
@@ -804,14 +804,10 @@
         </font>
     </family>
     <family lang="ja">
-        <font weight="400" style="normal">
-            NotoSerifHentaigana-EL.ttf
+        <font postScriptName="NotoSerifHentaigana-ExtraLight" supportedAxes="wght">
+            NotoSerifHentaigana.ttf
             <axis tag="wght" stylevalue="400"/>
         </font>
-        <font weight="700" style="normal">
-            NotoSerifHentaigana-EL.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
     </family>
     <family lang="ko">
         <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index b23f005..d1aa8e9 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -1433,12 +1433,12 @@
         </font>
     </family>
     <family lang="ja">
-        <font weight="400" style="normal">
-            NotoSerifHentaigana-EL.ttf
+        <font weight="400" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight">
+            NotoSerifHentaigana.ttf
             <axis tag="wght" stylevalue="400"/>
         </font>
-        <font weight="700" style="normal">
-            NotoSerifHentaigana-EL.ttf
+        <font weight="700" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight">
+            NotoSerifHentaigana.ttf
             <axis tag="wght" stylevalue="700"/>
         </font>
     </family>
diff --git a/data/fonts/fonts_cjkvf.xml b/data/fonts/fonts_cjkvf.xml
index 1ab71ae..9545ae7 100644
--- a/data/fonts/fonts_cjkvf.xml
+++ b/data/fonts/fonts_cjkvf.xml
@@ -1532,12 +1532,12 @@
         </font>
     </family>
     <family lang="ja">
-        <font weight="400" style="normal">
-            NotoSerifHentaigana-EL.ttf
+        <font weight="400" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight">
+            NotoSerifHentaigana.ttf
             <axis tag="wght" stylevalue="400"/>
         </font>
-        <font weight="700" style="normal">
-            NotoSerifHentaigana-EL.ttf
+        <font weight="700" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight">
+            NotoSerifHentaigana.ttf
             <axis tag="wght" stylevalue="700"/>
         </font>
     </family>
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index a7acaf9..a2a0f49 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -333,6 +333,11 @@
         nDrawPath(mNativeCanvasWrapper, path.readOnlyNI(), paint.getNativeInstance());
     }
 
+    public void drawRegion(@NonNull Region region, @NonNull Paint paint) {
+        throwIfHasHwFeaturesInSwMode(paint);
+        nDrawRegion(mNativeCanvasWrapper, region.mNativeRegion, paint.getNativeInstance());
+    }
+
     public void drawPoint(float x, float y, @NonNull Paint paint) {
         throwIfHasHwFeaturesInSwMode(paint);
         nDrawPoint(mNativeCanvasWrapper, x, y, paint.getNativeInstance());
diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java
index 4e88b0e..5b1fa7b 100644
--- a/graphics/java/android/graphics/BaseRecordingCanvas.java
+++ b/graphics/java/android/graphics/BaseRecordingCanvas.java
@@ -290,6 +290,11 @@
     }
 
     @Override
+    public void drawRegion(@NonNull Region region, @NonNull Paint paint) {
+        nDrawRegion(mNativeCanvasWrapper, region.mNativeRegion, paint.getNativeInstance());
+    }
+
+    @Override
     public final void drawPicture(@NonNull Picture picture) {
         picture.endRecording();
         int restoreCount = save();
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index b6ce9b6..e03a1da 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -789,10 +789,8 @@
      * @param m The 4x4 matrix to preconcatenate with the current matrix
      */
     @FlaggedApi(Flags.FLAG_MATRIX_44)
-    public void concat44(@Nullable Matrix44 m) {
-        if (m != null) {
-            nConcat(mNativeCanvasWrapper, m.mBackingArray);
-        }
+    public void concat(@Nullable Matrix44 m) {
+        if (m != null) nConcat(mNativeCanvasWrapper, m.mBackingArray);
     }
 
     /**
@@ -1933,6 +1931,17 @@
     }
 
     /**
+     * Draws the given region using the given paint.
+     *
+     * @param region The region to be drawn
+     * @param paint The paint used to draw the region
+     */
+    @FlaggedApi(Flags.FLAG_DRAW_REGION)
+    public void drawRegion(@NonNull Region region, @NonNull Paint paint) {
+        super.drawRegion(region, paint);
+    }
+
+    /**
      * Helper for drawPoints() for drawing a single point.
      */
     public void drawPoint(float x, float y, @NonNull Paint paint) {
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index 20e393e..940cd93 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -25,6 +25,8 @@
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
+import android.hardware.DataSpace;
+import android.hardware.HardwareBuffer;
 import android.hardware.OverlayProperties;
 import android.hardware.display.DisplayManager;
 import android.os.IBinder;
@@ -1417,7 +1419,14 @@
             nInitDisplayInfo(largestWidth, largestHeight, defaultDisplay.getRefreshRate(),
                     wideColorDataspace, defaultDisplay.getAppVsyncOffsetNanos(),
                     defaultDisplay.getPresentationDeadlineNanos(),
-                    overlayProperties.isFp16SupportedForHdr(),
+                    overlayProperties.isCombinationSupported(
+                            DataSpace.DATASPACE_SCRGB, HardwareBuffer.RGBA_FP16),
+                    overlayProperties.isCombinationSupported(
+                            DataSpace.pack(
+                                    DataSpace.STANDARD_DCI_P3,
+                                    DataSpace.TRANSFER_SRGB,
+                                    DataSpace.RANGE_EXTENDED),
+                            HardwareBuffer.RGBA_10101010),
                     overlayProperties.isMixedColorSpacesSupported());
 
             mDisplayInitialized = true;
@@ -1603,7 +1612,8 @@
 
     private static native void nInitDisplayInfo(int width, int height, float refreshRate,
             int wideColorDataspace, long appVsyncOffsetNanos, long presentationDeadlineNanos,
-            boolean supportsFp16ForHdr, boolean nInitDisplayInfo);
+            boolean supportsFp16ForHdr, boolean isRgba10101010SupportedForHdr,
+            boolean nSupportMixedColorSpaces);
 
     private static native void nSetDrawingEnabled(boolean drawingEnabled);
 
diff --git a/graphics/java/android/graphics/Matrix44.java b/graphics/java/android/graphics/Matrix44.java
index 7cc0eb7..a99e201 100644
--- a/graphics/java/android/graphics/Matrix44.java
+++ b/graphics/java/android/graphics/Matrix44.java
@@ -17,6 +17,7 @@
 package android.graphics;
 
 import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 
 import com.android.graphics.hwui.flags.Flags;
@@ -98,11 +99,11 @@
     /**
      * Gets the value at the matrix's row and column.
      *
-     * @param row An integer from 0 to 4 indicating the row of the value to get
-     * @param col An integer from 0 to 4 indicating the column of the value to get
+     * @param row An integer from 0 to 3 indicating the row of the value to get
+     * @param col An integer from 0 to 3 indicating the column of the value to get
      */
     @FlaggedApi(Flags.FLAG_MATRIX_44)
-    public float get(int row, int col) {
+    public float get(@IntRange(from = 0, to = 3) int row, @IntRange(from = 0, to = 3) int col) {
         if (row >= 0 && row < 4 && col >= 0 && col < 4) {
             return mBackingArray[row * 4 + col];
         }
@@ -112,12 +113,13 @@
     /**
      * Sets the value at the matrix's row and column to the provided value.
      *
-     * @param row An integer from 0 to 4 indicating the row of the value to change
-     * @param col An integer from 0 to 4 indicating the column of the value to change
+     * @param row An integer from 0 to 3 indicating the row of the value to change
+     * @param col An integer from 0 to 3 indicating the column of the value to change
      * @param val The value the element at the specified index will be set to
      */
     @FlaggedApi(Flags.FLAG_MATRIX_44)
-    public void set(int row, int col, float val) {
+    public void set(@IntRange(from = 0, to = 3) int row, @IntRange(from = 0, to = 3) int col,
+            float val) {
         if (row >= 0 && row < 4 && col >= 0 && col < 4) {
             mBackingArray[row * 4 + col] = val;
         } else {
diff --git a/graphics/java/android/graphics/pdf/PdfEditor.java b/graphics/java/android/graphics/pdf/PdfEditor.java
index 3cd709e..69e1982 100644
--- a/graphics/java/android/graphics/pdf/PdfEditor.java
+++ b/graphics/java/android/graphics/pdf/PdfEditor.java
@@ -25,7 +25,9 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
+
 import dalvik.system.CloseGuard;
+
 import libcore.io.IoUtils;
 
 import java.io.IOException;
@@ -37,6 +39,12 @@
  */
 public final class PdfEditor {
 
+    /**
+     * Any call the native pdfium code has to be single threaded as the library does not support
+     * parallel use.
+     */
+    private static final Object sPdfiumLock = new Object();
+
     private final CloseGuard mCloseGuard = CloseGuard.get();
 
     private long mNativeDocument;
@@ -79,7 +87,7 @@
         }
         mInput = input;
 
-        synchronized (PdfRenderer.sPdfiumLock) {
+        synchronized (sPdfiumLock) {
             mNativeDocument = nativeOpen(mInput.getFd(), size);
             try {
                 mPageCount = nativeGetPageCount(mNativeDocument);
@@ -112,7 +120,7 @@
         throwIfClosed();
         throwIfPageNotInDocument(pageIndex);
 
-        synchronized (PdfRenderer.sPdfiumLock) {
+        synchronized (sPdfiumLock) {
             mPageCount = nativeRemovePage(mNativeDocument, pageIndex);
         }
     }
@@ -138,12 +146,12 @@
             Point size = new Point();
             getPageSize(pageIndex, size);
 
-            synchronized (PdfRenderer.sPdfiumLock) {
+            synchronized (sPdfiumLock) {
                 nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.ni(),
                         0, 0, size.x, size.y);
             }
         } else {
-            synchronized (PdfRenderer.sPdfiumLock) {
+            synchronized (sPdfiumLock) {
                 nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.ni(),
                         clip.left, clip.top, clip.right, clip.bottom);
             }
@@ -161,7 +169,7 @@
         throwIfOutSizeNull(outSize);
         throwIfPageNotInDocument(pageIndex);
 
-        synchronized (PdfRenderer.sPdfiumLock) {
+        synchronized (sPdfiumLock) {
             nativeGetPageSize(mNativeDocument, pageIndex, outSize);
         }
     }
@@ -177,7 +185,7 @@
         throwIfOutMediaBoxNull(outMediaBox);
         throwIfPageNotInDocument(pageIndex);
 
-        synchronized (PdfRenderer.sPdfiumLock) {
+        synchronized (sPdfiumLock) {
             return nativeGetPageMediaBox(mNativeDocument, pageIndex, outMediaBox);
         }
     }
@@ -193,7 +201,7 @@
         throwIfMediaBoxNull(mediaBox);
         throwIfPageNotInDocument(pageIndex);
 
-        synchronized (PdfRenderer.sPdfiumLock) {
+        synchronized (sPdfiumLock) {
             nativeSetPageMediaBox(mNativeDocument, pageIndex, mediaBox);
         }
     }
@@ -209,7 +217,7 @@
         throwIfOutCropBoxNull(outCropBox);
         throwIfPageNotInDocument(pageIndex);
 
-        synchronized (PdfRenderer.sPdfiumLock) {
+        synchronized (sPdfiumLock) {
             return nativeGetPageCropBox(mNativeDocument, pageIndex, outCropBox);
         }
     }
@@ -225,7 +233,7 @@
         throwIfCropBoxNull(cropBox);
         throwIfPageNotInDocument(pageIndex);
 
-        synchronized (PdfRenderer.sPdfiumLock) {
+        synchronized (sPdfiumLock) {
             nativeSetPageCropBox(mNativeDocument, pageIndex, cropBox);
         }
     }
@@ -238,7 +246,7 @@
     public boolean shouldScaleForPrinting() {
         throwIfClosed();
 
-        synchronized (PdfRenderer.sPdfiumLock) {
+        synchronized (sPdfiumLock) {
             return nativeScaleForPrinting(mNativeDocument);
         }
     }
@@ -255,7 +263,7 @@
         try {
             throwIfClosed();
 
-            synchronized (PdfRenderer.sPdfiumLock) {
+            synchronized (sPdfiumLock) {
                 nativeWrite(mNativeDocument, output.getFd());
             }
         } finally {
@@ -287,7 +295,7 @@
 
     private void doClose() {
         if (mNativeDocument != 0) {
-            synchronized (PdfRenderer.sPdfiumLock) {
+            synchronized (sPdfiumLock) {
                 nativeClose(mNativeDocument);
             }
             mNativeDocument = 0;
diff --git a/graphics/java/android/graphics/pdf/PdfRenderer.java b/graphics/java/android/graphics/pdf/PdfRenderer.java
deleted file mode 100644
index 4666963..0000000
--- a/graphics/java/android/graphics/pdf/PdfRenderer.java
+++ /dev/null
@@ -1,502 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.graphics.pdf;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.compat.annotation.UnsupportedAppUsage;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
-
-import com.android.internal.util.Preconditions;
-
-import dalvik.system.CloseGuard;
-
-import libcore.io.IoUtils;
-
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * <p>
- * This class enables rendering a PDF document. This class is not thread safe.
- * </p>
- * <p>
- * If you want to render a PDF, you create a renderer and for every page you want
- * to render, you open the page, render it, and close the page. After you are done
- * with rendering, you close the renderer. After the renderer is closed it should not
- * be used anymore. Note that the pages are rendered one by one, i.e. you can have
- * only a single page opened at any given time.
- * </p>
- * <p>
- * A typical use of the APIs to render a PDF looks like this:
- * </p>
- * <pre>
- * // create a new renderer
- * PdfRenderer renderer = new PdfRenderer(getSeekableFileDescriptor());
- *
- * // let us just render all pages
- * final int pageCount = renderer.getPageCount();
- * for (int i = 0; i < pageCount; i++) {
- *     Page page = renderer.openPage(i);
- *
- *     // say we render for showing on the screen
- *     page.render(mBitmap, null, null, Page.RENDER_MODE_FOR_DISPLAY);
- *
- *     // do stuff with the bitmap
- *
- *     // close the page
- *     page.close();
- * }
- *
- * // close the renderer
- * renderer.close();
- * </pre>
- *
- * <h3>Print preview and print output</h3>
- * <p>
- * If you are using this class to rasterize a PDF for printing or show a print
- * preview, it is recommended that you respect the following contract in order
- * to provide a consistent user experience when seeing a preview and printing,
- * i.e. the user sees a preview that is the same as the printout.
- * </p>
- * <ul>
- * <li>
- * Respect the property whether the document would like to be scaled for printing
- * as per {@link #shouldScaleForPrinting()}.
- * </li>
- * <li>
- * When scaling a document for printing the aspect ratio should be preserved.
- * </li>
- * <li>
- * Do not inset the content with any margins from the {@link android.print.PrintAttributes}
- * as the application is responsible to render it such that the margins are respected.
- * </li>
- * <li>
- * If document page size is greater than the printed media size the content should
- * be anchored to the upper left corner of the page for left-to-right locales and
- * top right corner for right-to-left locales.
- * </li>
- * </ul>
- *
- * @see #close()
- */
-public final class PdfRenderer implements AutoCloseable {
-    /**
-     * Any call the native pdfium code has to be single threaded as the library does not support
-     * parallel use.
-     */
-    final static Object sPdfiumLock = new Object();
-
-    private final CloseGuard mCloseGuard = CloseGuard.get();
-
-    private final Point mTempPoint = new Point();
-
-    private long mNativeDocument;
-
-    private final int mPageCount;
-
-    private ParcelFileDescriptor mInput;
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private Page mCurrentPage;
-
-    /** @hide */
-    @IntDef({
-        Page.RENDER_MODE_FOR_DISPLAY,
-        Page.RENDER_MODE_FOR_PRINT
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface RenderMode {}
-
-    /**
-     * Creates a new instance.
-     * <p>
-     * <strong>Note:</strong> The provided file descriptor must be <strong>seekable</strong>,
-     * i.e. its data being randomly accessed, e.g. pointing to a file.
-     * </p>
-     * <p>
-     * <strong>Note:</strong> This class takes ownership of the passed in file descriptor
-     * and is responsible for closing it when the renderer is closed.
-     * </p>
-     * <p>
-     * If the file is from an untrusted source it is recommended to run the renderer in a separate,
-     * isolated process with minimal permissions to limit the impact of security exploits.
-     * </p>
-     *
-     * @param input Seekable file descriptor to read from.
-     *
-     * @throws java.io.IOException If an error occurs while reading the file.
-     * @throws java.lang.SecurityException If the file requires a password or
-     *         the security scheme is not supported.
-     */
-    public PdfRenderer(@NonNull ParcelFileDescriptor input) throws IOException {
-        if (input == null) {
-            throw new NullPointerException("input cannot be null");
-        }
-
-        final long size;
-        try {
-            Os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
-            size = Os.fstat(input.getFileDescriptor()).st_size;
-        } catch (ErrnoException ee) {
-            throw new IllegalArgumentException("file descriptor not seekable");
-        }
-        mInput = input;
-
-        synchronized (sPdfiumLock) {
-            mNativeDocument = nativeCreate(mInput.getFd(), size);
-            try {
-                mPageCount = nativeGetPageCount(mNativeDocument);
-            } catch (Throwable t) {
-                nativeClose(mNativeDocument);
-                mNativeDocument = 0;
-                throw t;
-            }
-        }
-
-        mCloseGuard.open("close");
-    }
-
-    /**
-     * Closes this renderer. You should not use this instance
-     * after this method is called.
-     */
-    public void close() {
-        throwIfClosed();
-        throwIfPageOpened();
-        doClose();
-    }
-
-    /**
-     * Gets the number of pages in the document.
-     *
-     * @return The page count.
-     */
-    public int getPageCount() {
-        throwIfClosed();
-        return mPageCount;
-    }
-
-    /**
-     * Gets whether the document prefers to be scaled for printing.
-     * You should take this info account if the document is rendered
-     * for printing and the target media size differs from the page
-     * size.
-     *
-     * @return If to scale the document.
-     */
-    public boolean shouldScaleForPrinting() {
-        throwIfClosed();
-
-        synchronized (sPdfiumLock) {
-            return nativeScaleForPrinting(mNativeDocument);
-        }
-    }
-
-    /**
-     * Opens a page for rendering.
-     *
-     * @param index The page index.
-     * @return A page that can be rendered.
-     *
-     * @see android.graphics.pdf.PdfRenderer.Page#close() PdfRenderer.Page.close()
-     */
-    public Page openPage(int index) {
-        throwIfClosed();
-        throwIfPageOpened();
-        throwIfPageNotInDocument(index);
-        mCurrentPage = new Page(index);
-        return mCurrentPage;
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            if (mCloseGuard != null) {
-                mCloseGuard.warnIfOpen();
-            }
-
-            doClose();
-        } finally {
-            super.finalize();
-        }
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private void doClose() {
-        if (mCurrentPage != null) {
-            mCurrentPage.close();
-            mCurrentPage = null;
-        }
-
-        if (mNativeDocument != 0) {
-            synchronized (sPdfiumLock) {
-                nativeClose(mNativeDocument);
-            }
-            mNativeDocument = 0;
-        }
-
-        if (mInput != null) {
-            IoUtils.closeQuietly(mInput);
-            mInput = null;
-        }
-        mCloseGuard.close();
-    }
-
-    private void throwIfClosed() {
-        if (mInput == null) {
-            throw new IllegalStateException("Already closed");
-        }
-    }
-
-    private void throwIfPageOpened() {
-        if (mCurrentPage != null) {
-            throw new IllegalStateException("Current page not closed");
-        }
-    }
-
-    private void throwIfPageNotInDocument(int pageIndex) {
-        if (pageIndex < 0 || pageIndex >= mPageCount) {
-            throw new IllegalArgumentException("Invalid page index");
-        }
-    }
-
-    /**
-     * This class represents a PDF document page for rendering.
-     */
-    public final class Page implements AutoCloseable {
-
-        private final CloseGuard mCloseGuard = CloseGuard.get();
-
-        /**
-         * Mode to render the content for display on a screen.
-         */
-        public static final int RENDER_MODE_FOR_DISPLAY = 1;
-
-        /**
-         * Mode to render the content for printing.
-         */
-        public static final int RENDER_MODE_FOR_PRINT = 2;
-
-        private final int mIndex;
-        private final int mWidth;
-        private final int mHeight;
-
-        private long mNativePage;
-
-        private Page(int index) {
-            Point size = mTempPoint;
-            synchronized (sPdfiumLock) {
-                mNativePage = nativeOpenPageAndGetSize(mNativeDocument, index, size);
-            }
-            mIndex = index;
-            mWidth = size.x;
-            mHeight = size.y;
-            mCloseGuard.open("close");
-        }
-
-        /**
-         * Gets the page index.
-         *
-         * @return The index.
-         */
-        public int getIndex() {
-            return  mIndex;
-        }
-
-        /**
-         * Gets the page width in points (1/72").
-         *
-         * @return The width in points.
-         */
-        public int getWidth() {
-            return mWidth;
-        }
-
-        /**
-         * Gets the page height in points (1/72").
-         *
-         * @return The height in points.
-         */
-        public int getHeight() {
-            return mHeight;
-        }
-
-        /**
-         * Renders a page to a bitmap.
-         * <p>
-         * You may optionally specify a rectangular clip in the bitmap bounds. No rendering
-         * outside the clip will be performed, hence it is your responsibility to initialize
-         * the bitmap outside the clip.
-         * </p>
-         * <p>
-         * You may optionally specify a matrix to transform the content from page coordinates
-         * which are in points (1/72") to bitmap coordinates which are in pixels. If this
-         * matrix is not provided this method will apply a transformation that will fit the
-         * whole page to the destination clip if provided or the destination bitmap if no
-         * clip is provided.
-         * </p>
-         * <p>
-         * The clip and transformation are useful for implementing tile rendering where the
-         * destination bitmap contains a portion of the image, for example when zooming.
-         * Another useful application is for printing where the size of the bitmap holding
-         * the page is too large and a client can render the page in stripes.
-         * </p>
-         * <p>
-         * <strong>Note: </strong> The destination bitmap format must be
-         * {@link Config#ARGB_8888 ARGB}.
-         * </p>
-         * <p>
-         * <strong>Note: </strong> The optional transformation matrix must be affine as per
-         * {@link android.graphics.Matrix#isAffine() Matrix.isAffine()}. Hence, you can specify
-         * rotation, scaling, translation but not a perspective transformation.
-         * </p>
-         *
-         * @param destination Destination bitmap to which to render.
-         * @param destClip Optional clip in the bitmap bounds.
-         * @param transform Optional transformation to apply when rendering.
-         * @param renderMode The render mode.
-         *
-         * @see #RENDER_MODE_FOR_DISPLAY
-         * @see #RENDER_MODE_FOR_PRINT
-         */
-        public void render(@NonNull Bitmap destination, @Nullable Rect destClip,
-                           @Nullable Matrix transform, @RenderMode int renderMode) {
-            if (mNativePage == 0) {
-                throw new NullPointerException();
-            }
-
-            destination = Preconditions.checkNotNull(destination, "bitmap null");
-
-            if (destination.getConfig() != Config.ARGB_8888) {
-                throw new IllegalArgumentException("Unsupported pixel format");
-            }
-
-            if (destClip != null) {
-                if (destClip.left < 0 || destClip.top < 0
-                        || destClip.right > destination.getWidth()
-                        || destClip.bottom > destination.getHeight()) {
-                    throw new IllegalArgumentException("destBounds not in destination");
-                }
-            }
-
-            if (transform != null && !transform.isAffine()) {
-                 throw new IllegalArgumentException("transform not affine");
-            }
-
-            if (renderMode != RENDER_MODE_FOR_PRINT && renderMode != RENDER_MODE_FOR_DISPLAY) {
-                throw new IllegalArgumentException("Unsupported render mode");
-            }
-
-            if (renderMode == RENDER_MODE_FOR_PRINT && renderMode == RENDER_MODE_FOR_DISPLAY) {
-                throw new IllegalArgumentException("Only single render mode supported");
-            }
-
-            final int contentLeft = (destClip != null) ? destClip.left : 0;
-            final int contentTop = (destClip != null) ? destClip.top : 0;
-            final int contentRight = (destClip != null) ? destClip.right
-                    : destination.getWidth();
-            final int contentBottom = (destClip != null) ? destClip.bottom
-                    : destination.getHeight();
-
-            // If transform is not set, stretch page to whole clipped area
-            if (transform == null) {
-                int clipWidth = contentRight - contentLeft;
-                int clipHeight = contentBottom - contentTop;
-
-                transform = new Matrix();
-                transform.postScale((float)clipWidth / getWidth(),
-                        (float)clipHeight / getHeight());
-                transform.postTranslate(contentLeft, contentTop);
-            }
-
-            // FIXME: This code is planned to be outside the UI rendering module, so it should not
-            // be able to access native instances from Bitmap, Matrix, etc.
-            final long transformPtr = transform.ni();
-
-            synchronized (sPdfiumLock) {
-                nativeRenderPage(mNativeDocument, mNativePage, destination.getNativeInstance(),
-                        contentLeft, contentTop, contentRight, contentBottom, transformPtr,
-                        renderMode);
-            }
-        }
-
-        /**
-         * Closes this page.
-         *
-         * @see android.graphics.pdf.PdfRenderer#openPage(int)
-         */
-        @Override
-        public void close() {
-            throwIfClosed();
-            doClose();
-        }
-
-        @Override
-        protected void finalize() throws Throwable {
-            try {
-                if (mCloseGuard != null) {
-                    mCloseGuard.warnIfOpen();
-                }
-
-                doClose();
-            } finally {
-                super.finalize();
-            }
-        }
-
-        private void doClose() {
-            if (mNativePage != 0) {
-                synchronized (sPdfiumLock) {
-                    nativeClosePage(mNativePage);
-                }
-                mNativePage = 0;
-            }
-
-            mCloseGuard.close();
-            mCurrentPage = null;
-        }
-
-        private void throwIfClosed() {
-            if (mNativePage == 0) {
-                throw new IllegalStateException("Already closed");
-            }
-        }
-    }
-
-    private static native long nativeCreate(int fd, long size);
-    private static native void nativeClose(long documentPtr);
-    private static native int nativeGetPageCount(long documentPtr);
-    private static native boolean nativeScaleForPrinting(long documentPtr);
-    private static native void nativeRenderPage(long documentPtr, long pagePtr, long bitmapHandle,
-            int clipLeft, int clipTop, int clipRight, int clipBottom, long transformPtr,
-            int renderMode);
-    private static native long nativeOpenPageAndGetSize(long documentPtr, int pageIndex,
-            Point outSize);
-    private static native void nativeClosePage(long pagePtr);
-}
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 6da0719..884268a 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -29,11 +29,13 @@
 import com.android.internal.util.Preconditions;
 
 import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.NeverInline;
 
 import libcore.util.NativeAllocationRegistry;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Locale;
 import java.util.Objects;
 
 /**
@@ -85,6 +87,30 @@
         return mChars;
     }
 
+    private void rangeCheck(int start, int end) {
+        if (start < 0 || start > end || end > mChars.length) {
+            throwRangeError(start, end);
+        }
+    }
+
+    @NeverInline
+    private void throwRangeError(int start, int end) {
+        throw new IllegalArgumentException(String.format(Locale.US,
+            "start(%d) end(%d) length(%d) out of bounds", start, end, mChars.length));
+    }
+
+    private void offsetCheck(int offset) {
+        if (offset < 0 || offset >= mChars.length) {
+            throwOffsetError(offset);
+        }
+    }
+
+    @NeverInline
+    private void throwOffsetError(int offset) {
+        throw new IllegalArgumentException(String.format(Locale.US,
+            "offset (%d) length(%d) out of bounds", offset, mChars.length));
+    }
+
     /**
      * Returns the width of a given range.
      *
@@ -93,12 +119,7 @@
      */
     public @FloatRange(from = 0.0) @Px float getWidth(
             @IntRange(from = 0) int start, @IntRange(from = 0) int end) {
-        Preconditions.checkArgument(0 <= start && start <= mChars.length,
-                "start(%d) must be 0 <= start <= %d", start, mChars.length);
-        Preconditions.checkArgument(0 <= end && end <= mChars.length,
-                "end(%d) must be 0 <= end <= %d", end, mChars.length);
-        Preconditions.checkArgument(start <= end,
-                "start(%d) is larger than end(%d)", start, end);
+        rangeCheck(start, end);
         return nGetWidth(mNativePtr, start, end);
     }
 
@@ -120,12 +141,7 @@
      */
     public void getBounds(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
             @NonNull Rect rect) {
-        Preconditions.checkArgument(0 <= start && start <= mChars.length,
-                "start(%d) must be 0 <= start <= %d", start, mChars.length);
-        Preconditions.checkArgument(0 <= end && end <= mChars.length,
-                "end(%d) must be 0 <= end <= %d", end, mChars.length);
-        Preconditions.checkArgument(start <= end,
-                "start(%d) is larger than end(%d)", start, end);
+        rangeCheck(start, end);
         Preconditions.checkNotNull(rect);
         nGetBounds(mNativePtr, mChars, start, end, rect);
     }
@@ -139,12 +155,7 @@
      */
     public void getFontMetricsInt(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
             @NonNull Paint.FontMetricsInt outMetrics) {
-        Preconditions.checkArgument(0 <= start && start <= mChars.length,
-                "start(%d) must be 0 <= start <= %d", start, mChars.length);
-        Preconditions.checkArgument(0 <= end && end <= mChars.length,
-                "end(%d) must be 0 <= end <= %d", end, mChars.length);
-        Preconditions.checkArgument(start <= end,
-                "start(%d) is larger than end(%d)", start, end);
+        rangeCheck(start, end);
         Objects.requireNonNull(outMetrics);
 
         long packed = nGetExtent(mNativePtr, mChars, start, end);
@@ -160,8 +171,7 @@
      * @param offset an offset of the character.
      */
     public @FloatRange(from = 0.0f) @Px float getCharWidthAt(@IntRange(from = 0) int offset) {
-        Preconditions.checkArgument(0 <= offset && offset < mChars.length,
-                "offset(%d) is larger than text length %d" + offset, mChars.length);
+        offsetCheck(offset);
         return nGetCharWidthAt(mNativePtr, offset);
     }
 
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 11b8271..bd9abec 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -21,12 +21,14 @@
 import android.os.StrictMode;
 
 /**
- * @hide This should not be made public in its present form because it
- * assumes that private and secret key bytes are available and would
- * preclude the use of hardware crypto.
+ * This class provides some constants and helper methods related to Android's Keystore service.
+ * This class was originally much larger, but its functionality was superseded by other classes.
+ * It now just contains a few remaining pieces for which the users haven't been updated yet.
+ * You may be looking for {@link java.security.KeyStore} instead.
+ *
+ * @hide
  */
 public class KeyStore {
-    private static final String TAG = "KeyStore";
 
     // ResponseCodes - see system/security/keystore/include/keystore/keystore.h
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -42,50 +44,6 @@
         return KEY_STORE;
     }
 
-    /** @hide */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public byte[] get(String key) {
-        return null;
-    }
-
-    /** @hide */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public boolean delete(String key) {
-        return false;
-    }
-
-    /**
-     * List uids of all keys that are auth bound to the current user.
-     * Only system is allowed to call this method.
-     * @hide
-     * @deprecated This function always returns null.
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public int[] listUidsOfAuthBoundKeys() {
-        return null;
-    }
-
-
-    /**
-     * @hide
-     * @deprecated This function has no effect.
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public boolean unlock(String password) {
-        return false;
-    }
-
-    /**
-     *
-     * @return
-     * @deprecated This function always returns true.
-     * @hide
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    public boolean isEmpty() {
-        return true;
-    }
-
     /**
      * Add an authentication record to the keystore authorization table.
      *
@@ -105,13 +63,4 @@
     public void onDeviceOffBody() {
         AndroidKeyStoreMaintenance.onDeviceOffBody();
     }
-
-    /**
-     * Returns a {@link KeyStoreException} corresponding to the provided keystore/keymaster error
-     * code.
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static KeyStoreException getKeyStoreException(int errorCode) {
-        return new KeyStoreException(-10000, "Should not be called.");
-    }
 }
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
index 62fe54f..ef03d3a 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
@@ -19,9 +19,9 @@
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.security.KeyStore;
 
 import java.io.IOException;
+import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
@@ -47,13 +47,13 @@
     }
 
     /**
-     * Gets the {@link KeyStore} operation handle corresponding to the provided JCA crypto
+     * Gets the Android KeyStore operation handle corresponding to the provided JCA crypto
      * primitive.
      *
      * <p>The following primitives are supported: {@link Cipher} and {@link Mac}.
      *
-     * @return KeyStore operation handle or {@code 0} if the provided primitive's KeyStore operation
-     *         is not in progress.
+     * @return Android KeyStore operation handle or {@code 0} if the provided primitive's Android
+     *         KeyStore operation is not in progress.
      *
      * @throws IllegalArgumentException if the provided primitive is not supported or is not backed
      *         by AndroidKeyStore provider.
@@ -67,10 +67,10 @@
     }
 
     /**
-     * Returns an {@code AndroidKeyStore} {@link java.security.KeyStore}} of the specified UID.
-     * The {@code KeyStore} contains keys and certificates owned by that UID. Such cross-UID
-     * access is permitted to a few system UIDs and only to a few other UIDs (e.g., Wi-Fi, VPN)
-     * all of which are system.
+     * Returns an {@code AndroidKeyStore} {@link KeyStore} of the specified UID. The {@code
+     * KeyStore} contains keys and certificates owned by that UID. Such cross-UID access is
+     * permitted to a few system UIDs and only to a few other UIDs (e.g., Wi-Fi, VPN) all of which
+     * are system.
      *
      * <p>Note: the returned {@code KeyStore} is already initialized/loaded. Thus, there is
      * no need to invoke {@code load} on it.
@@ -84,12 +84,12 @@
      */
     @SystemApi
     @NonNull
-    public static java.security.KeyStore getKeyStoreForUid(int uid)
+    public static KeyStore getKeyStoreForUid(int uid)
             throws KeyStoreException, NoSuchProviderException {
-        final java.security.KeyStore.LoadStoreParameter loadParameter =
+        final KeyStore.LoadStoreParameter loadParameter =
                 new android.security.keystore2.AndroidKeyStoreLoadStoreParameter(
                         KeyProperties.legacyUidToNamespace(uid));
-        java.security.KeyStore result = java.security.KeyStore.getInstance(PROVIDER_NAME);
+        KeyStore result = KeyStore.getInstance(PROVIDER_NAME);
         try {
             result.load(loadParameter);
         } catch (NoSuchAlgorithmException | CertificateException | IOException e) {
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 244fe30..7aecfd8 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -910,7 +910,7 @@
     /**
      * Returns whether this key is critical to the device encryption flow.
      *
-     * @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION
+     * @see Builder#setCriticalToDeviceEncryption(boolean)
      * @hide
      */
     public boolean isCriticalToDeviceEncryption() {
diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java
index f50efd2..5cffe46 100644
--- a/keystore/java/android/security/keystore/KeyInfo.java
+++ b/keystore/java/android/security/keystore/KeyInfo.java
@@ -16,6 +16,7 @@
 
 package android.security.keystore;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
@@ -81,6 +82,7 @@
     private final @KeyProperties.AuthEnum int mUserAuthenticationType;
     private final boolean mUserAuthenticationRequirementEnforcedBySecureHardware;
     private final boolean mUserAuthenticationValidWhileOnBody;
+    private final boolean mUnlockedDeviceRequired;
     private final boolean mTrustedUserPresenceRequired;
     private final boolean mInvalidatedByBiometricEnrollment;
     private final boolean mUserConfirmationRequired;
@@ -107,6 +109,7 @@
             @KeyProperties.AuthEnum int userAuthenticationType,
             boolean userAuthenticationRequirementEnforcedBySecureHardware,
             boolean userAuthenticationValidWhileOnBody,
+            boolean unlockedDeviceRequired,
             boolean trustedUserPresenceRequired,
             boolean invalidatedByBiometricEnrollment,
             boolean userConfirmationRequired,
@@ -132,6 +135,7 @@
         mUserAuthenticationRequirementEnforcedBySecureHardware =
                 userAuthenticationRequirementEnforcedBySecureHardware;
         mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
+        mUnlockedDeviceRequired = unlockedDeviceRequired;
         mTrustedUserPresenceRequired = trustedUserPresenceRequired;
         mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment;
         mUserConfirmationRequired = userConfirmationRequired;
@@ -275,6 +279,20 @@
     }
 
     /**
+     * Returns {@code true} if the key is authorized to be used only when the device is unlocked.
+     *
+     * <p>This authorization applies only to secret key and private key operations. Public key
+     * operations are not restricted.
+     *
+     * @see KeyGenParameterSpec.Builder#setUnlockedDeviceRequired(boolean)
+     * @see KeyProtection.Builder#setUnlockedDeviceRequired(boolean)
+     */
+    @FlaggedApi(android.security.Flags.FLAG_KEYINFO_UNLOCKED_DEVICE_REQUIRED)
+    public boolean isUnlockedDeviceRequired() {
+        return mUnlockedDeviceRequired;
+    }
+
+    /**
      * Returns {@code true} if the key is authorized to be used only for messages confirmed by the
      * user.
      *
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index 2495d1a..31b4a5e 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -569,7 +569,7 @@
     /**
      * Return whether this key is critical to the device encryption flow.
      *
-     * @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION
+     * @see Builder#setCriticalToDeviceEncryption(boolean)
      * @hide
      */
     public boolean isCriticalToDeviceEncryption() {
@@ -1105,9 +1105,10 @@
          * Set whether this key is critical to the device encryption flow
          *
          * This is a special flag only available to system servers to indicate the current key
-         * is part of the device encryption flow.
+         * is part of the device encryption flow. Setting this flag causes the key to not
+         * be cryptographically bound to the LSKF even if the key is otherwise authentication
+         * bound.
          *
-         * @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION
          * @hide
          */
         public Builder setCriticalToDeviceEncryption(boolean critical) {
diff --git a/keystore/java/android/security/keystore/KeyStoreCryptoOperation.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperation.java
index 2c709ae..c42c9e4 100644
--- a/keystore/java/android/security/keystore/KeyStoreCryptoOperation.java
+++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperation.java
@@ -16,18 +16,16 @@
 
 package android.security.keystore;
 
-import android.security.KeyStore;
-
 /**
- * Cryptographic operation backed by {@link KeyStore}.
+ * Cryptographic operation backed by Android KeyStore.
  *
  * @hide
  */
 public interface KeyStoreCryptoOperation {
     /**
-     * Gets the KeyStore operation handle of this crypto operation.
+     * Gets the Android KeyStore operation handle of this crypto operation.
      *
-     * @return handle or {@code 0} if the KeyStore operation is not in progress.
+     * @return handle or {@code 0} if the Android KeyStore operation is not in progress.
      */
     long getOperationHandle();
 }
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java
index a8dd7f3..8eca67f 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java
@@ -16,7 +16,6 @@
 
 package android.security.keystore2;
 
-import android.security.KeyStore;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyInfo;
 
@@ -39,8 +38,6 @@
  */
 public class AndroidKeyStoreKeyFactorySpi extends KeyFactorySpi {
 
-    private final KeyStore mKeyStore = KeyStore.getInstance();
-
     @Override
     protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpecClass)
             throws InvalidKeySpecException {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
index d204f13..99100de 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
@@ -17,7 +17,6 @@
 package android.security.keystore2;
 
 import android.annotation.NonNull;
-import android.security.KeyStore;
 import android.security.KeyStore2;
 import android.security.KeyStoreSecurityLevel;
 import android.security.keymaster.KeymasterDefs;
@@ -161,13 +160,13 @@
     }
 
     /**
-     * Gets the {@link KeyStore} operation handle corresponding to the provided JCA crypto
+     * Gets the Android KeyStore operation handle corresponding to the provided JCA crypto
      * primitive.
      *
      * <p>The following primitives are supported: {@link Cipher}, {@link Signature} and {@link Mac}.
      *
-     * @return KeyStore operation handle or {@code 0} if the provided primitive's KeyStore operation
-     *         is not in progress.
+     * @return Android KeyStore operation handle or {@code 0} if the provided primitive's Android
+     *         KeyStore operation is not in progress.
      *
      * @throws IllegalArgumentException if the provided primitive is not supported or is not backed
      *         by AndroidKeyStore provider.
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java
index 97592b4..2223091 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.security.GateKeeper;
-import android.security.KeyStore;
 import android.security.keymaster.KeymasterArguments;
 import android.security.keymaster.KeymasterDefs;
 import android.security.keystore.KeyGenParameterSpec;
@@ -46,8 +45,6 @@
  */
 public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi {
 
-    private final KeyStore mKeyStore = KeyStore.getInstance();
-
     @Override
     protected KeySpec engineGetKeySpec(SecretKey key,
             @SuppressWarnings("rawtypes") Class keySpecClass) throws InvalidKeySpecException {
@@ -93,6 +90,7 @@
         long userAuthenticationValidityDurationSeconds = 0;
         boolean userAuthenticationRequired = true;
         boolean userAuthenticationValidWhileOnBody = false;
+        boolean unlockedDeviceRequired = false;
         boolean trustedUserPresenceRequired = false;
         boolean trustedUserConfirmationRequired = false;
         int remainingUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT;
@@ -184,6 +182,9 @@
                                     + userAuthenticationValidityDurationSeconds + " seconds");
                         }
                         break;
+                    case KeymasterDefs.KM_TAG_UNLOCKED_DEVICE_REQUIRED:
+                        unlockedDeviceRequired = true;
+                        break;
                     case KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY:
                         userAuthenticationValidWhileOnBody =
                                 KeyStore2ParameterUtils.isSecureHardware(a.securityLevel);
@@ -257,6 +258,7 @@
                         : keymasterSwEnforcedUserAuthenticators,
                 userAuthenticationRequirementEnforcedBySecureHardware,
                 userAuthenticationValidWhileOnBody,
+                unlockedDeviceRequired,
                 trustedUserPresenceRequired,
                 invalidatedByBiometricEnrollment,
                 trustedUserConfirmationRequired,
diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java
index 07d6a69..5bd98bc 100644
--- a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java
+++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java
@@ -16,12 +16,11 @@
 
 package android.security.keystore2;
 
-import android.security.KeyStore;
 import android.security.KeyStoreException;
 
 /**
- * Helper for streaming a crypto operation's input and output via {@link KeyStore} service's
- * {@code update} and {@code finish} operations.
+ * Helper for streaming a crypto operation's input and output via KeyStore service's {@code update}
+ * and {@code finish} operations.
  *
  * <p>The helper abstracts away to issues that need to be solved in most code that uses KeyStore's
  * update and finish operations. Firstly, KeyStore's update operation can consume only a limited
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
index 88fd461..fa35b63 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
@@ -16,7 +16,7 @@
 
 package androidx.window.common;
 
-import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
 
 import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
 import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE;
@@ -69,14 +69,14 @@
      * example is activated via public API and can be active in both the "open" and "half folded"
      * device states.
      */
-    private int mCurrentDeviceState = INVALID_DEVICE_STATE;
+    private int mCurrentDeviceState = INVALID_DEVICE_STATE_IDENTIFIER;
 
     /**
      * Base device state received via
      * {@link DeviceStateManager.DeviceStateCallback#onBaseStateChanged(int)}.
      * "Base" in this context means the "physical" state of the device.
      */
-    private int mCurrentBaseDeviceState = INVALID_DEVICE_STATE;
+    private int mCurrentBaseDeviceState = INVALID_DEVICE_STATE_IDENTIFIER;
 
     @NonNull
     private final RawFoldingFeatureProducer mRawFoldSupplier;
@@ -177,7 +177,7 @@
         if (hasListeners()) {
             mRawFoldSupplier.addDataChangedCallback(this::notifyFoldingFeatureChange);
         } else {
-            mCurrentDeviceState = INVALID_DEVICE_STATE;
+            mCurrentDeviceState = INVALID_DEVICE_STATE_IDENTIFIER;
             mRawFoldSupplier.removeDataChangedCallback(this::notifyFoldingFeatureChange);
         }
     }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index b315f94..d31bf2a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -16,7 +16,7 @@
 
 package androidx.window.extensions.area;
 
-import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
 
 import android.app.Activity;
 import android.content.Context;
@@ -79,7 +79,7 @@
     private int mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE;
 
     @GuardedBy("mLock")
-    private int mCurrentDeviceState = INVALID_DEVICE_STATE;
+    private int mCurrentDeviceState = INVALID_DEVICE_STATE_IDENTIFIER;
     @GuardedBy("mLock")
     private int[] mCurrentSupportedDeviceStates;
 
@@ -143,7 +143,7 @@
             mRearDisplayStatusListeners.add(consumer);
 
             // If current device state is still invalid, the initial value has not been provided.
-            if (mCurrentDeviceState == INVALID_DEVICE_STATE) {
+            if (mCurrentDeviceState == INVALID_DEVICE_STATE_IDENTIFIER) {
                 return;
             }
             consumer.accept(getCurrentRearDisplayModeStatus());
@@ -308,7 +308,7 @@
             mRearDisplayPresentationStatusListeners.add(consumer);
 
             // If current device state is still invalid, the initial value has not been provided
-            if (mCurrentDeviceState == INVALID_DEVICE_STATE) {
+            if (mCurrentDeviceState == INVALID_DEVICE_STATE_IDENTIFIER) {
                 return;
             }
             @WindowAreaStatus int currentStatus = getCurrentRearDisplayPresentationModeStatus();
@@ -467,7 +467,7 @@
 
     @GuardedBy("mLock")
     private int getCurrentRearDisplayModeStatus() {
-        if (mRearDisplayState == INVALID_DEVICE_STATE) {
+        if (mRearDisplayState == INVALID_DEVICE_STATE_IDENTIFIER) {
             return WindowAreaComponent.STATUS_UNSUPPORTED;
         }
 
@@ -495,7 +495,7 @@
 
     @GuardedBy("mLock")
     private void updateRearDisplayStatusListeners(@WindowAreaStatus int windowAreaStatus) {
-        if (mRearDisplayState == INVALID_DEVICE_STATE) {
+        if (mRearDisplayState == INVALID_DEVICE_STATE_IDENTIFIER) {
             return;
         }
         synchronized (mLock) {
@@ -507,7 +507,7 @@
 
     @GuardedBy("mLock")
     private int getCurrentRearDisplayPresentationModeStatus() {
-        if (mConcurrentDisplayState == INVALID_DEVICE_STATE) {
+        if (mConcurrentDisplayState == INVALID_DEVICE_STATE_IDENTIFIER) {
             return WindowAreaComponent.STATUS_UNSUPPORTED;
         }
 
@@ -530,7 +530,7 @@
     @GuardedBy("mLock")
     private void updateRearDisplayPresentationStatusListeners(
             @WindowAreaStatus int windowAreaStatus) {
-        if (mConcurrentDisplayState == INVALID_DEVICE_STATE) {
+        if (mConcurrentDisplayState == INVALID_DEVICE_STATE_IDENTIFIER) {
             return;
         }
         RearDisplayPresentationStatus consumerValue = new RearDisplayPresentationStatus(
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index 08b7bb8..39cface 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -201,7 +201,7 @@
             return null;
         }
         return new SplitInfo(primaryActivityStack, secondaryActivityStack,
-                mCurrentSplitAttributes, mToken);
+                mCurrentSplitAttributes, SplitInfo.Token.createFromBinder(mToken));
     }
 
     static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) {
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 ae3a854..1abda42 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -35,6 +35,7 @@
 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED;
 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
 
+import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_ACTIVITY_STACK_TOKEN;
 import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG;
 import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
 import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
@@ -55,6 +56,7 @@
 import android.app.ActivityThread;
 import android.app.Application;
 import android.app.Instrumentation;
+import android.app.servertransaction.ClientTransactionListenerController;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -102,6 +104,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
 
 /**
  * Main controller class that manages split states and presentation.
@@ -112,10 +115,6 @@
     static final boolean ENABLE_SHELL_TRANSITIONS =
             SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
 
-    // TODO(b/295993745): remove after prebuilt library is updated.
-    private static final String KEY_ACTIVITY_STACK_TOKEN =
-            "androidx.window.extensions.embedding.ActivityStackToken";
-
     @VisibleForTesting
     @GuardedBy("mLock")
     final SplitPresenter mPresenter;
@@ -181,6 +180,20 @@
 
     private final List<ActivityStack> mLastReportedActivityStacks = new ArrayList<>();
 
+    /** WM Jetpack set callback for {@link EmbeddedActivityWindowInfo}. */
+    @GuardedBy("mLock")
+    @Nullable
+    private Pair<Executor, Consumer<EmbeddedActivityWindowInfo>>
+            mEmbeddedActivityWindowInfoCallback;
+
+    /** Listener registered to {@link ClientTransactionListenerController}. */
+    @GuardedBy("mLock")
+    @Nullable
+    private final BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener =
+            Flags.activityWindowInfoFlag()
+                    ? this::onActivityWindowInfoChanged
+                    : null;
+
     private final Handler mHandler;
     final Object mLock = new Object();
     private final ActivityStartMonitor mActivityStartMonitor;
@@ -554,7 +567,7 @@
     }
 
     @Override
-    public void updateActivityStackAttributes(@NonNull IBinder activityStackToken,
+    public void updateActivityStackAttributes(@NonNull ActivityStack.Token activityStackToken,
                                               @NonNull ActivityStackAttributes attributes) {
         if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
             return;
@@ -563,7 +576,7 @@
         Objects.requireNonNull(attributes);
 
         synchronized (mLock) {
-            final TaskFragmentContainer container = getContainer(activityStackToken);
+            final TaskFragmentContainer container = getContainer(activityStackToken.getRawToken());
             if (container == null) {
                 Log.w(TAG, "Cannot find TaskFragmentContainer for token:" + activityStackToken);
                 return;
@@ -583,13 +596,14 @@
 
     @Override
     @Nullable
-    public ParentContainerInfo getParentContainerInfo(@NonNull IBinder activityStackToken) {
+    public ParentContainerInfo getParentContainerInfo(
+            @NonNull ActivityStack.Token activityStackToken) {
         if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
             return null;
         }
         Objects.requireNonNull(activityStackToken);
         synchronized (mLock) {
-            final TaskFragmentContainer container = getContainer(activityStackToken);
+            final TaskFragmentContainer container = getContainer(activityStackToken.getRawToken());
             if (container == null) {
                 return null;
             }
@@ -601,7 +615,7 @@
 
     @Override
     @Nullable
-    public IBinder getActivityStackToken(@NonNull String tag) {
+    public ActivityStack.Token getActivityStackToken(@NonNull String tag) {
         if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
             return null;
         }
@@ -612,7 +626,8 @@
             if (taskFragmentContainer == null) {
                 return null;
             }
-            return taskFragmentContainer.getTaskFragmentToken();
+            return ActivityStack.Token.createFromBinder(taskFragmentContainer
+                    .getTaskFragmentToken());
         }
     }
 
@@ -2457,6 +2472,13 @@
     }
 
     @VisibleForTesting
+    @Nullable
+    ActivityThread.ActivityClientRecord getActivityClientRecord(@NonNull Activity activity) {
+        return ActivityThread.currentActivityThread()
+                .getActivityClient(activity.getActivityToken());
+    }
+
+    @VisibleForTesting
     ActivityStartMonitor getActivityStartMonitor() {
         return mActivityStartMonitor;
     }
@@ -2469,8 +2491,7 @@
     @VisibleForTesting
     @Nullable
     IBinder getTaskFragmentTokenFromActivityClientRecord(@NonNull Activity activity) {
-        final ActivityThread.ActivityClientRecord record = ActivityThread.currentActivityThread()
-                .getActivityClient(activity.getActivityToken());
+        final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity);
         return record != null ? record.mTaskFragmentToken : null;
     }
 
@@ -2761,8 +2782,10 @@
             // TODO(b/232042367): Consolidate the activity create handling so that we can handle
             // cross-process the same as normal.
 
-            IBinder activityStackToken = options.getBinder(KEY_ACTIVITY_STACK_TOKEN);
-            if (activityStackToken != null) {
+            final Bundle bundle = options.getBundle(KEY_ACTIVITY_STACK_TOKEN);
+            if (bundle != null) {
+                final IBinder activityStackToken = ActivityStack.Token.readFromBundle(bundle)
+                        .getRawToken();
                 // Put activityStack token to #KEY_LAUNCH_TASK_FRAGMENT_TOKEN to launch the activity
                 // into the taskFragment associated with the token.
                 options.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN, activityStackToken);
@@ -2875,17 +2898,102 @@
         }
     }
 
+    @Override
+    public void setEmbeddedActivityWindowInfoCallback(@NonNull Executor executor,
+            @NonNull Consumer<EmbeddedActivityWindowInfo> callback) {
+        if (!Flags.activityWindowInfoFlag()) {
+            return;
+        }
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        synchronized (mLock) {
+            if (mEmbeddedActivityWindowInfoCallback == null) {
+                ClientTransactionListenerController.getInstance()
+                        .registerActivityWindowInfoChangedListener(getActivityWindowInfoListener());
+            }
+            mEmbeddedActivityWindowInfoCallback = new Pair<>(executor, callback);
+        }
+    }
+
+    @Override
+    public void clearEmbeddedActivityWindowInfoCallback() {
+        if (!Flags.activityWindowInfoFlag()) {
+            return;
+        }
+        synchronized (mLock) {
+            if (mEmbeddedActivityWindowInfoCallback == null) {
+                return;
+            }
+            mEmbeddedActivityWindowInfoCallback = null;
+            ClientTransactionListenerController.getInstance()
+                    .unregisterActivityWindowInfoChangedListener(getActivityWindowInfoListener());
+        }
+    }
+
+    @VisibleForTesting
+    @GuardedBy("mLock")
     @Nullable
-    private static ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) {
+    BiConsumer<IBinder, ActivityWindowInfo> getActivityWindowInfoListener() {
+        return mActivityWindowInfoListener;
+    }
+
+    @Nullable
+    @Override
+    public EmbeddedActivityWindowInfo getEmbeddedActivityWindowInfo(@NonNull Activity activity) {
+        if (!Flags.activityWindowInfoFlag()) {
+            return null;
+        }
+        synchronized (mLock) {
+            final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
+            return activityWindowInfo != null
+                    ? translateActivityWindowInfo(activity, activityWindowInfo)
+                    : null;
+        }
+    }
+
+    @VisibleForTesting
+    void onActivityWindowInfoChanged(@NonNull IBinder activityToken,
+            @NonNull ActivityWindowInfo activityWindowInfo) {
+        synchronized (mLock) {
+            if (mEmbeddedActivityWindowInfoCallback == null) {
+                return;
+            }
+            final Executor executor = mEmbeddedActivityWindowInfoCallback.first;
+            final Consumer<EmbeddedActivityWindowInfo> callback =
+                    mEmbeddedActivityWindowInfoCallback.second;
+
+            final Activity activity = getActivity(activityToken);
+            if (activity == null) {
+                return;
+            }
+            final EmbeddedActivityWindowInfo info = translateActivityWindowInfo(
+                    activity, activityWindowInfo);
+
+            executor.execute(() -> callback.accept(info));
+        }
+    }
+
+    @Nullable
+    private ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) {
         if (activity.isFinishing()) {
             return null;
         }
-        final ActivityThread.ActivityClientRecord record =
-                ActivityThread.currentActivityThread()
-                        .getActivityClient(activity.getActivityToken());
+        final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity);
         return record != null ? record.getActivityWindowInfo() : null;
     }
 
+    @NonNull
+    private static EmbeddedActivityWindowInfo translateActivityWindowInfo(
+            @NonNull Activity activity, @NonNull ActivityWindowInfo activityWindowInfo) {
+        final boolean isEmbedded = activityWindowInfo.isEmbedded();
+        final Rect activityBounds = new Rect(activity.getResources().getConfiguration()
+                .windowConfiguration.getBounds());
+        final Rect taskBounds = new Rect(activityWindowInfo.getTaskBounds());
+        final Rect activityStackBounds = new Rect(activityWindowInfo.getTaskFragmentBounds());
+        return new EmbeddedActivityWindowInfo(activity, isEmbedded, activityBounds, taskBounds,
+                activityStackBounds);
+    }
+
     /**
      * If the two rules have the same presentation, and the calculated {@link SplitAttributes}
      * matches the {@link SplitAttributes} of {@link SplitContainer}, we can reuse the same
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 2f2da8c..b53b9c5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -387,7 +387,7 @@
 
         // Sets the dim area when the two TaskFragments are adjacent.
         final boolean dimOnTask = !isStacked
-                && splitAttributes.getWindowAttributes().getDimArea() == DIM_AREA_ON_TASK
+                && splitAttributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK
                 && Flags.fullscreenDimFlag();
         setTaskFragmentDimOnTask(wct, primaryContainer.getTaskFragmentToken(), dimOnTask);
         setTaskFragmentDimOnTask(wct, secondaryContainer.getTaskFragmentToken(), dimOnTask);
@@ -590,7 +590,7 @@
         final boolean isFillParent = relativeBounds.isEmpty();
         final boolean isIsolatedNavigated = !isFillParent && container.isOverlay();
         final boolean dimOnTask = !isFillParent
-                && attributes.getWindowAttributes().getDimArea() == DIM_AREA_ON_TASK
+                && attributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK
                 && Flags.fullscreenDimFlag();
         final IBinder fragmentToken = container.getTaskFragmentToken();
 
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 6fe8e50..a6bf99d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -367,7 +367,8 @@
         if (activities == null) {
             return null;
         }
-        return new ActivityStack(activities, isEmpty(), mToken, mOverlayTag);
+        return new ActivityStack(activities, isEmpty(),
+                ActivityStack.Token.createFromBinder(mToken), mOverlayTag);
     }
 
     /** Adds the activity that will be reparented to this container. */
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 34d43ad..28fbadb 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -399,7 +399,8 @@
                         new ActivityStackAttributes.Builder().build()));
 
         assertThrows(NullPointerException.class, () ->
-                mSplitController.updateActivityStackAttributes(new Binder(), null));
+                mSplitController.updateActivityStackAttributes(
+                        ActivityStack.Token.createFromBinder(new Binder()), null));
 
         verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any(), any());
     }
@@ -408,7 +409,8 @@
     public void testUpdateActivityStackAttributes_nullContainer_earlyReturn() {
         final TaskFragmentContainer container = mSplitController.newContainer(mActivity,
                 mActivity.getTaskId());
-        mSplitController.updateActivityStackAttributes(container.getTaskFragmentToken(),
+        mSplitController.updateActivityStackAttributes(
+                ActivityStack.Token.createFromBinder(container.getTaskFragmentToken()),
                 new ActivityStackAttributes.Builder().build());
 
         verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any(), any());
@@ -418,7 +420,8 @@
     public void testUpdateActivityStackAttributes_notOverlay_earlyReturn() {
         final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
 
-        mSplitController.updateActivityStackAttributes(container.getTaskFragmentToken(),
+        mSplitController.updateActivityStackAttributes(
+                ActivityStack.Token.createFromBinder(container.getTaskFragmentToken()),
                 new ActivityStackAttributes.Builder().build());
 
         verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any(), any());
@@ -431,7 +434,8 @@
         final ActivityStackAttributes attrs = new ActivityStackAttributes.Builder().build();
         final IBinder token = container.getTaskFragmentToken();
 
-        mSplitController.updateActivityStackAttributes(token, attrs);
+        mSplitController.updateActivityStackAttributes(ActivityStack.Token.createFromBinder(token),
+                attrs);
 
         verify(mSplitPresenter).applyActivityStackAttributes(any(), eq(container), eq(attrs),
                 any());
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 b60943a..bdeeb73 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
@@ -72,6 +72,8 @@
 import android.annotation.NonNull;
 import android.app.Activity;
 import android.app.ActivityOptions;
+import android.app.ActivityThread;
+import android.app.servertransaction.ClientTransactionListenerController;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -83,9 +85,11 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.ArraySet;
 import android.view.WindowInsets;
 import android.view.WindowMetrics;
+import android.window.ActivityWindowInfo;
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentOrganizer;
 import android.window.TaskFragmentParentInfo;
@@ -99,7 +103,10 @@
 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.ArgumentCaptor;
@@ -110,6 +117,8 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 /**
@@ -127,6 +136,9 @@
     private static final Intent PLACEHOLDER_INTENT = new Intent().setComponent(
             new ComponentName("test", "placeholder"));
 
+    @Rule
+    public final SetFlagsRule mSetFlagRule = new SetFlagsRule();
+
     private Activity mActivity;
     @Mock
     private Resources mActivityResources;
@@ -138,6 +150,13 @@
     private Handler mHandler;
     @Mock
     private WindowLayoutComponentImpl mWindowLayoutComponent;
+    @Mock
+    private ActivityWindowInfo mActivityWindowInfo;
+    @Mock
+    private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener;
+    @Mock
+    private androidx.window.extensions.core.util.function.Consumer<EmbeddedActivityWindowInfo>
+            mEmbeddedActivityWindowInfoCallback;
 
     private SplitController mSplitController;
     private SplitPresenter mSplitPresenter;
@@ -1437,7 +1456,7 @@
     @Test
     public void testUpdateSplitAttributes_nullParams_throwException() {
         assertThrows(NullPointerException.class,
-                () -> mSplitController.updateSplitAttributes(null, SPLIT_ATTRIBUTES));
+                () -> mSplitController.updateSplitAttributes((IBinder) null, SPLIT_ATTRIBUTES));
 
         final SplitContainer splitContainer = mock(SplitContainer.class);
         final IBinder token = new Binder();
@@ -1529,6 +1548,73 @@
                 .getTopNonFinishingActivity(), secondaryActivity);
     }
 
+    @Test
+    public void testIsActivityEmbedded() {
+        mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+        assertFalse(mSplitController.isActivityEmbedded(mActivity));
+
+        doReturn(true).when(mActivityWindowInfo).isEmbedded();
+
+        assertTrue(mSplitController.isActivityEmbedded(mActivity));
+    }
+
+    @Test
+    public void testGetEmbeddedActivityWindowInfo() {
+        mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+        final boolean isEmbedded = true;
+        final Rect activityBounds = mActivity.getResources().getConfiguration().windowConfiguration
+                .getBounds();
+        final Rect taskBounds = new Rect(0, 0, 1000, 2000);
+        final Rect activityStackBounds = new Rect(0, 0, 500, 2000);
+        doReturn(isEmbedded).when(mActivityWindowInfo).isEmbedded();
+        doReturn(taskBounds).when(mActivityWindowInfo).getTaskBounds();
+        doReturn(activityStackBounds).when(mActivityWindowInfo).getTaskFragmentBounds();
+
+        final EmbeddedActivityWindowInfo expected = new EmbeddedActivityWindowInfo(mActivity,
+                isEmbedded, activityBounds, taskBounds, activityStackBounds);
+        assertEquals(expected, mSplitController.getEmbeddedActivityWindowInfo(mActivity));
+    }
+
+    @Test
+    public void testSetEmbeddedActivityWindowInfoCallback() {
+        mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+        final ClientTransactionListenerController controller = ClientTransactionListenerController
+                .getInstance();
+        spyOn(controller);
+        doNothing().when(controller).registerActivityWindowInfoChangedListener(any());
+        doReturn(mActivityWindowInfoListener).when(mSplitController)
+                .getActivityWindowInfoListener();
+        final Executor executor = Runnable::run;
+
+        // Register to ClientTransactionListenerController
+        mSplitController.setEmbeddedActivityWindowInfoCallback(executor,
+                mEmbeddedActivityWindowInfoCallback);
+
+        verify(controller).registerActivityWindowInfoChangedListener(mActivityWindowInfoListener);
+        verify(mEmbeddedActivityWindowInfoCallback, never()).accept(any());
+
+        // Test onActivityWindowInfoChanged triggered.
+        mSplitController.onActivityWindowInfoChanged(mActivity.getActivityToken(),
+                mActivityWindowInfo);
+
+        verify(mEmbeddedActivityWindowInfoCallback).accept(any());
+
+        // Unregister to ClientTransactionListenerController
+        mSplitController.clearEmbeddedActivityWindowInfoCallback();
+
+        verify(controller).unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener);
+
+        // Test onActivityWindowInfoChanged triggered as no-op after clear callback.
+        clearInvocations(mEmbeddedActivityWindowInfoCallback);
+        mSplitController.onActivityWindowInfoChanged(mActivity.getActivityToken(),
+                mActivityWindowInfo);
+
+        verify(mEmbeddedActivityWindowInfoCallback, never()).accept(any());
+    }
+
     /** Creates a mock activity in the organizer process. */
     private Activity createMockActivity() {
         return createMockActivity(TASK_ID);
@@ -1537,13 +1623,17 @@
     /** Creates a mock activity in the organizer process. */
     private Activity createMockActivity(int taskId) {
         final Activity activity = mock(Activity.class);
+        final ActivityThread.ActivityClientRecord activityClientRecord =
+                mock(ActivityThread.ActivityClientRecord.class);
         doReturn(mActivityResources).when(activity).getResources();
         final IBinder activityToken = new Binder();
         doReturn(activityToken).when(activity).getActivityToken();
         doReturn(activity).when(mSplitController).getActivity(activityToken);
+        doReturn(activityClientRecord).when(mSplitController).getActivityClientRecord(activity);
         doReturn(taskId).when(activity).getTaskId();
         doReturn(new ActivityInfo()).when(activity).getActivityInfo();
         doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
+        doReturn(mActivityWindowInfo).when(activityClientRecord).getActivityWindowInfo();
         return activity;
     }
 
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 0ecf1f8..8829d1b 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -212,76 +212,3 @@
     plugins: ["dagger2-compiler"],
     use_resource_processor: true,
 }
-
-android_app {
-    name: "WindowManagerShellRobolectric",
-    platform_apis: true,
-    static_libs: [
-        "WindowManager-Shell",
-    ],
-    manifest: "multivalentTests/AndroidManifestRobolectric.xml",
-    use_resource_processor: true,
-}
-
-android_robolectric_test {
-    name: "WMShellRobolectricTests",
-    instrumentation_for: "WindowManagerShellRobolectric",
-    upstream: true,
-    java_resource_dirs: [
-        "multivalentTests/robolectric/config",
-    ],
-    srcs: [
-        "multivalentTests/src/**/*.kt",
-    ],
-    // TODO(b/323188766): Include BubbleStackViewTest once the robolectric issue is fixed.
-    exclude_srcs: ["multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt"],
-    static_libs: [
-        "junit",
-        "androidx.test.runner",
-        "androidx.test.rules",
-        "androidx.test.ext.junit",
-        "mockito-robolectric-prebuilt",
-        "mockito-kotlin2",
-        "truth",
-    ],
-}
-
-android_test {
-    name: "WMShellMultivalentTestsOnDevice",
-    srcs: [
-        "multivalentTests/src/**/*.kt",
-    ],
-    static_libs: [
-        "WindowManager-Shell",
-        "junit",
-        "androidx.test.runner",
-        "androidx.test.rules",
-        "androidx.test.ext.junit",
-        "frameworks-base-testutils",
-        "mockito-kotlin2",
-        "mockito-target-extended-minus-junit4",
-        "truth",
-        "platform-test-annotations",
-        "platform-test-rules",
-    ],
-    libs: [
-        "android.test.base",
-        "android.test.runner",
-    ],
-    jni_libs: [
-        "libdexmakerjvmtiagent",
-        "libstaticjvmtiagent",
-    ],
-    kotlincflags: ["-Xjvm-default=all"],
-    optimize: {
-        enabled: false,
-    },
-    test_suites: ["device-tests"],
-    platform_apis: true,
-    certificate: "platform",
-    aaptflags: [
-        "--extra-packages",
-        "com.android.wm.shell",
-    ],
-    manifest: "multivalentTests/AndroidManifest.xml",
-}
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 0967f4e..b61dda4 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -8,14 +8,6 @@
 }
 
 flag {
-    name: "enable_desktop_windowing"
-    namespace: "multitasking"
-    description: "Enables desktop windowing"
-    bug: "304778354"
-    is_fixed_read_only: true
-}
-
-flag {
     name: "enable_split_contextual"
     namespace: "multitasking"
     description: "Enables invoking split contextually"
diff --git a/libs/WindowManager/Shell/multivalentTests/Android.bp b/libs/WindowManager/Shell/multivalentTests/Android.bp
new file mode 100644
index 0000000..1686d0d
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/Android.bp
@@ -0,0 +1,97 @@
+// 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 {
+    // 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"],
+    default_team: "trendy_team_multitasking_windowing",
+}
+
+android_app {
+    name: "WindowManagerShellRobolectric",
+    platform_apis: true,
+    static_libs: [
+        "WindowManager-Shell",
+    ],
+    manifest: "AndroidManifestRobolectric.xml",
+    use_resource_processor: true,
+}
+
+android_robolectric_test {
+    name: "WMShellRobolectricTests",
+    instrumentation_for: "WindowManagerShellRobolectric",
+    upstream: true,
+    java_resource_dirs: [
+        "robolectric/config",
+    ],
+    srcs: [
+        "src/**/*.kt",
+    ],
+    // TODO(b/323188766): Include BubbleStackViewTest once the robolectric issue is fixed.
+    exclude_srcs: ["src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt"],
+    static_libs: [
+        "junit",
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "mockito-robolectric-prebuilt",
+        "mockito-kotlin2",
+        "truth",
+    ],
+    auto_gen_config: true,
+}
+
+android_test {
+    name: "WMShellMultivalentTestsOnDevice",
+    srcs: [
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "WindowManager-Shell",
+        "junit",
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "frameworks-base-testutils",
+        "mockito-kotlin2",
+        "mockito-target-extended-minus-junit4",
+        "truth",
+        "platform-test-annotations",
+        "platform-test-rules",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
+    kotlincflags: ["-Xjvm-default=all"],
+    optimize: {
+        enabled: false,
+    },
+    test_suites: ["device-tests"],
+    platform_apis: true,
+    certificate: "platform",
+    aaptflags: [
+        "--extra-packages",
+        "com.android.wm.shell",
+    ],
+    manifest: "AndroidManifest.xml",
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
index 9cd14fca..e422198 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
@@ -28,6 +28,7 @@
 import androidx.test.filters.SmallTest
 import com.android.wm.shell.R
 import com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT
+import com.android.wm.shell.common.bubbles.BubbleBarLocation
 import com.google.common.truth.Truth.assertThat
 import com.google.common.util.concurrent.MoreExecutors.directExecutor
 import org.junit.Before
@@ -486,6 +487,32 @@
                 positioner.screenRect.width() - paddings[0] - paddings[2])
     }
 
+    @Test
+    fun testIsBubbleBarOnLeft_defaultsToRight() {
+        positioner.bubbleBarLocation = BubbleBarLocation.DEFAULT
+        assertThat(positioner.isBubbleBarOnLeft).isFalse()
+
+        // Check that left and right return expected position
+        positioner.bubbleBarLocation = BubbleBarLocation.LEFT
+        assertThat(positioner.isBubbleBarOnLeft).isTrue()
+        positioner.bubbleBarLocation = BubbleBarLocation.RIGHT
+        assertThat(positioner.isBubbleBarOnLeft).isFalse()
+    }
+
+    @Test
+    fun testIsBubbleBarOnLeft_rtlEnabled_defaultsToLeft() {
+        positioner.update(defaultDeviceConfig.copy(isRtl = true))
+
+        positioner.bubbleBarLocation = BubbleBarLocation.DEFAULT
+        assertThat(positioner.isBubbleBarOnLeft).isTrue()
+
+        // Check that left and right return expected position
+        positioner.bubbleBarLocation = BubbleBarLocation.LEFT
+        assertThat(positioner.isBubbleBarOnLeft).isTrue()
+        positioner.bubbleBarLocation = BubbleBarLocation.RIGHT
+        assertThat(positioner.isBubbleBarOnLeft).isFalse()
+    }
+
     private val defaultYPosition: Float
         /**
          * Calculates the Y position bubbles should be placed based on the config. Based on the
diff --git a/libs/WindowManager/Shell/res/drawable/circular_progress.xml b/libs/WindowManager/Shell/res/drawable/circular_progress.xml
index 9482645..294b1f0 100644
--- a/libs/WindowManager/Shell/res/drawable/circular_progress.xml
+++ b/libs/WindowManager/Shell/res/drawable/circular_progress.xml
@@ -25,7 +25,7 @@
             <shape
                 android:shape="ring"
                 android:thickness="3dp"
-                android:innerRadius="17dp"
+                android:innerRadius="14dp"
                 android:useLevel="true">
             </shape>
         </rotate>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
index 02b7075..e5fe1b54 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
@@ -15,12 +15,12 @@
   ~ limitations under the License.
   -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="48dp"
-    android:height="48dp"
+    android:width="24dp"
+    android:height="24dp"
     android:tint="?attr/colorControlNormal"
     android:viewportHeight="960"
     android:viewportWidth="960">
     <path
-        android:fillColor="@android:color/white"
-        android:pathData="M180,840Q156,840 138,822Q120,804 120,780L120,180Q120,156 138,138Q156,120 180,120L780,120Q804,120 822,138Q840,156 840,180L840,780Q840,804 822,822Q804,840 780,840L180,840ZM180,780L780,780Q780,780 780,780Q780,780 780,780L780,277L180,277L180,780Q180,780 180,780Q180,780 180,780Z" />
+        android:fillColor="@android:color/black"
+        android:pathData="M160,800Q127,800 103.5,776.5Q80,753 80,720L80,240Q80,207 103.5,183.5Q127,160 160,160L800,160Q833,160 856.5,183.5Q880,207 880,240L880,720Q880,753 856.5,776.5Q833,800 800,800L160,800ZM160,720L800,720Q800,720 800,720Q800,720 800,720L800,320L160,320L160,720Q160,720 160,720Q160,720 160,720Z"/>
 </vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_header_ic_close.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_header_ic_close.xml
new file mode 100644
index 0000000..ff49edb
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_header_ic_close.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml
deleted file mode 100644
index 7c4f499..0000000
--- a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="20dp"
-    android:height="20dp"
-    android:viewportWidth="20"
-    android:viewportHeight="20">
-  <path
-      android:pathData="M15.701,14.583L18.567,17.5L17.425,18.733L14.525,15.833L12.442,17.917V12.5H17.917L15.701,14.583ZM15.833,5.833H17.5V7.5H15.833V5.833ZM17.5,4.167H15.833V2.567C16.75,2.567 17.5,3.333 17.5,4.167ZM12.5,2.5H14.167V4.167H12.5V2.5ZM15.833,9.167H17.5V10.833H15.833V9.167ZM7.5,17.5H5.833V15.833H7.5V17.5ZM4.167,7.5H2.5V5.833H4.167V7.5ZM4.167,2.567V4.167H2.5C2.5,3.333 3.333,2.567 4.167,2.567ZM4.167,14.167H2.5V12.5H4.167V14.167ZM7.5,4.167H5.833V2.5H7.5V4.167ZM10.833,4.167H9.167V2.5H10.833V4.167ZM10.833,17.5H9.167V15.833H10.833V17.5ZM4.167,10.833H2.5V9.167H4.167V10.833ZM4.167,17.567C3.25,17.567 2.5,16.667 2.5,15.833H4.167V17.567Z"
-      android:fillColor="#1C1C14"/>
-</vector>
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 d1b1af3..fa18e2b 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
@@ -16,6 +16,7 @@
   -->
 <com.android.wm.shell.windowdecor.WindowDecorLinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/desktop_mode_caption"
     android:layout_width="match_parent"
@@ -27,17 +28,20 @@
         android:id="@+id/open_menu_button"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
+        android:tint="?androidprv:attr/materialColorOnSurface"
+        android:background="?android:selectableItemBackground"
         android:orientation="horizontal"
         android:clickable="true"
         android:focusable="true"
-        android:paddingStart="6dp"
-        android:paddingEnd="8dp">
+        android:paddingStart="12dp">
         <ImageView
             android:id="@+id/application_icon"
             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" />
+            android:contentDescription="@string/app_icon_text"
+            android:layout_marginStart="6dp"
+            android:scaleType="centerCrop"/>
 
         <TextView
             android:id="@+id/application_name"
@@ -50,8 +54,7 @@
             android:lineHeight="20dp"
             android:layout_gravity="center_vertical"
             android:layout_weight="1"
-            android:paddingStart="8dp"
-            android:paddingEnd="8dp"
+            android:layout_marginStart="8dp"
             tools:text="Gmail"/>
 
         <ImageButton
@@ -64,6 +67,7 @@
             android:scaleType="fitCenter"
             android:clickable="false"
             android:focusable="false"
+            android:layout_marginHorizontal="8dp"
             android:layout_gravity="center_vertical"/>
 
     </LinearLayout>
@@ -76,19 +80,26 @@
 
     <com.android.wm.shell.windowdecor.MaximizeButtonView
         android:id="@+id/maximize_button_view"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="end"/>
+        android:layout_width="44dp"
+        android:layout_height="40dp"
+        android:layout_gravity="end"
+        android:layout_marginHorizontal="8dp"
+        android:paddingHorizontal="5dp"
+        android:paddingVertical="3dp"
+        android:clickable="true"
+        android:focusable="true"/>
 
     <ImageButton
         android:id="@+id/close_window"
-        android:layout_width="40dp"
+        android:layout_width="44dp"
         android:layout_height="40dp"
-        android:padding="4dp"
+        android:paddingHorizontal="10dp"
+        android:paddingVertical="8dp"
         android:layout_marginEnd="8dp"
+        android:tint="?androidprv:attr/materialColorOnSurface"
+        android:background="?android:selectableItemBackgroundBorderless"
         android:contentDescription="@string/close_button_text"
-        android:src="@drawable/decor_close_button_dark"
-        android:scaleType="fitCenter"
-        android:gravity="end"
-        android:background="@null"/>
+        android:src="@drawable/desktop_mode_header_ic_close"
+        android:scaleType="centerCrop"
+        android:gravity="end"/>
 </com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file
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 c6f85a0..fca2fe4 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
@@ -134,15 +134,6 @@
             android:drawableStart="@drawable/desktop_mode_ic_handle_menu_screenshot"
             android:drawableTint="?androidprv:attr/materialColorOnSurface"
             style="@style/DesktopModeHandleMenuActionButton"/>
-
-        <Button
-            android:id="@+id/select_button"
-            android:contentDescription="@string/select_text"
-            android:text="@string/select_text"
-            android:drawableStart="@drawable/desktop_mode_ic_handle_menu_select"
-            android:drawableTint="?androidprv:attr/materialColorOnSurface"
-            style="@style/DesktopModeHandleMenuActionButton"/>
-
     </LinearLayout>
 </LinearLayout>
 
diff --git a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
index bb6efce..296c8956 100644
--- a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
+++ b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
@@ -14,23 +14,25 @@
   ~ limitations under the License.
   -->
 
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
     <ProgressBar
         android:id="@+id/progress_bar"
         style="?android:attr/progressBarStyleHorizontal"
         android:progressDrawable="@drawable/circular_progress"
-        android:layout_width="40dp"
-        android:layout_height="40dp"
+        android:layout_width="34dp"
+        android:layout_height="34dp"
         android:indeterminate="false"
         android:visibility="invisible"/>
 
     <ImageButton
         android:id="@+id/maximize_window"
-        android:layout_width="40dp"
-        android:layout_height="40dp"
-        android:padding="9dp"
+        android:layout_width="34dp"
+        android:layout_height="34dp"
+        android:padding="5dp"
         android:contentDescription="@string/maximize_button_text"
+        android:tint="?androidprv:attr/materialColorOnSurface"
+        android:background="?android:selectableItemBackgroundBorderless"
         android:src="@drawable/decor_desktop_mode_maximize_button_dark"
-        android:scaleType="fitCenter"
-        android:background="@drawable/rounded_button"/>
+        android:scaleType="fitCenter" />
 </merge>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index a883e08..b54f9cf 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -109,7 +109,7 @@
     <string name="app_icon_text" msgid="2823268023931811747">"Icône de l\'application"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Plein écran"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Mode Bureau"</string>
-    <string name="split_screen_text" msgid="1396336058129570886">"Écran partagé"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Écran divisé"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Plus"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Flottant"</string>
     <string name="select_text" msgid="5139083974039906583">"Sélectionner"</string>
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 8baaf2f..c68b0be 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -145,4 +145,10 @@
 
     <!-- Whether CompatUIController is enabled -->
     <bool name="config_enableCompatUIController">true</bool>
+
+    <!-- Whether pointer pilfer is required to start back animation. -->
+    <bool name="config_backAnimationRequiresPointerPilfer">true</bool>
+
+    <!-- Whether desktop mode is supported on the current device  -->
+    <bool name="config_isDesktopModeSupported">false</bool>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index cbfa74e..7dd3961 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -101,6 +101,10 @@
     <dimen name="split_divider_bar_width">10dp</dimen>
     <dimen name="split_divider_corner_size">42dp</dimen>
 
+    <!-- The distance from the edge of the screen to invoke splitscreen when the user is dragging
+         an intent that can be launched into split. -->
+    <dimen name="drag_launchable_intent_edge_margin">48dp</dimen>
+
     <!-- One-Handed Mode -->
     <!-- Threshold for dragging distance to enable one-handed mode -->
     <dimen name="gestures_onehanded_drag_threshold">20dp</dimen>
@@ -430,15 +434,22 @@
     <!-- (32 dp buttons + 10dp margins) * 3 buttons-->
     <dimen name="caption_right_buttons_width">126dp</dimen>
 
-    <!-- 2 buttons * 48dp button size. -->
-    <dimen name="desktop_mode_right_edge_buttons_width">96dp</dimen>
+    <!-- 2 buttons * 44dp button size + 16dp total margins. -->
+    <dimen name="desktop_mode_right_edge_buttons_width">104dp</dimen>
 
     <!-- 22dp padding + 24dp app icon + 16dp expand button.
          Text varies in size, we will calculate that width separately. -->
     <dimen name="desktop_mode_app_details_width_minus_text">62dp</dimen>
 
-    <!-- 22dp padding + 24dp app icon + 16dp expand button + 86dp text (max) -->
-    <dimen name="desktop_mode_app_details_max_width">148dp</dimen>
+    <!-- When custom headers are requested, this is the width of the left-aligned region that is
+         taken up by caption elements and extra margins. The customizable region starts at the
+         end of this area. -->
+    <dimen name="desktop_mode_customizable_caption_margin_start">84dp</dimen>
+
+    <!-- When custom headers are requested, this is the width of the right-aligned region that is
+         taken up by caption elements and extra margins. The customizable region ends at the
+         start of this area. -->
+    <dimen name="desktop_mode_customizable_caption_margin_end">152dp</dimen>
 
     <!-- The width of the maximize menu in desktop mode. -->
     <dimen name="desktop_mode_maximize_menu_width">287dp</dimen>
@@ -486,7 +497,7 @@
     <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>
+    <dimen name="desktop_mode_caption_icon_radius">24dp</dimen>
 
     <!-- The radius of the caption menu shadow. -->
     <dimen name="desktop_mode_handle_menu_shadow_radius">2dp</dimen>
@@ -499,10 +510,6 @@
     split select if dragged until the touch input is within the range. -->
     <dimen name="desktop_mode_transition_area_width">32dp</dimen>
 
-    <!-- The height of the area at the top of the screen where a freeform task will transition to
-    fullscreen if dragged until the top bound of the task is within the area. -->
-    <dimen name="desktop_mode_transition_area_height">16dp</dimen>
-
     <!-- The width of the area where a desktop task will transition to fullscreen. -->
     <dimen name="desktop_mode_fullscreen_from_desktop_width">80dp</dimen>
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
index 93893e3..ef9bf00 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
@@ -51,7 +51,7 @@
         final ILogger logger = pw::println;
         switch (args[0]) {
             case "status": {
-                if (android.tracing.Flags.perfettoProtolog()) {
+                if (android.tracing.Flags.perfettoProtologTracing()) {
                     pw.println("(Deprecated) legacy command. Use Perfetto commands instead.");
                     return false;
                 }
@@ -59,7 +59,7 @@
                 return true;
             }
             case "start": {
-                if (android.tracing.Flags.perfettoProtolog()) {
+                if (android.tracing.Flags.perfettoProtologTracing()) {
                     pw.println("(Deprecated) legacy command. Use Perfetto commands instead.");
                     return false;
                 }
@@ -67,7 +67,7 @@
                 return true;
             }
             case "stop": {
-                if (android.tracing.Flags.perfettoProtolog()) {
+                if (android.tracing.Flags.perfettoProtologTracing()) {
                     pw.println("(Deprecated) legacy command. Use Perfetto commands instead.");
                     return false;
                 }
@@ -101,7 +101,7 @@
                 return mShellProtoLog.stopLoggingToLogcat(groups, logger) == 0;
             }
             case "save-for-bugreport": {
-                if (android.tracing.Flags.perfettoProtolog()) {
+                if (android.tracing.Flags.perfettoProtologTracing()) {
                     pw.println("(Deprecated) legacy command");
                     return false;
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 539832e..d44033c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -523,8 +523,8 @@
     /**
      * Whether we should use jump cut for the change transition.
      * This normally happens when opening a new secondary with the existing primary using a
-     * different split layout. This can be complicated, like from horizontal to vertical split with
-     * new split pairs.
+     * different split layout (ratio or direction). This can be complicated, like from horizontal to
+     * vertical split with new split pairs.
      * Uses a jump cut animation to simplify.
      */
     private boolean shouldUseJumpCutForChangeTransition(@NonNull TransitionInfo info) {
@@ -553,8 +553,8 @@
         }
 
         // Check if the transition contains both opening and closing windows.
-        boolean hasOpeningWindow = false;
-        boolean hasClosingWindow = false;
+        final List<TransitionInfo.Change> openChanges = new ArrayList<>();
+        final List<TransitionInfo.Change> closeChanges = new ArrayList<>();
         for (TransitionInfo.Change change : info.getChanges()) {
             if (changingChanges.contains(change)) {
                 continue;
@@ -564,10 +564,30 @@
                 // No-op if it will be covered by the changing parent window.
                 continue;
             }
-            hasOpeningWindow |= TransitionUtil.isOpeningType(change.getMode());
-            hasClosingWindow |= TransitionUtil.isClosingType(change.getMode());
+            if (TransitionUtil.isOpeningType(change.getMode())) {
+                openChanges.add(change);
+            } else if (TransitionUtil.isClosingType(change.getMode())) {
+                closeChanges.add(change);
+            }
         }
-        return hasOpeningWindow && hasClosingWindow;
+        if (openChanges.isEmpty() || closeChanges.isEmpty()) {
+            // Only skip if the transition contains both open and close.
+            return false;
+        }
+        if (changingChanges.size() != 1 || openChanges.size() != 1 || closeChanges.size() != 1) {
+            // Skip when there are too many windows involved.
+            return true;
+        }
+        final TransitionInfo.Change changingChange = changingChanges.get(0);
+        final TransitionInfo.Change openChange = openChanges.get(0);
+        final TransitionInfo.Change closeChange = closeChanges.get(0);
+        if (changingChange.getStartAbsBounds().equals(openChange.getEndAbsBounds())
+                && changingChange.getEndAbsBounds().equals(closeChange.getStartAbsBounds())) {
+            // Don't skip if the transition is a simple shifting without split direction or ratio
+            // change. For example, A|B -> B|C.
+            return false;
+        }
+        return true;
     }
 
     /** Updates the changes to end states in {@code startTransaction} for jump cut animation. */
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 2606fb6..9bd8531 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
@@ -64,6 +64,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.view.AppearanceRegion;
+import com.android.wm.shell.R;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
@@ -115,6 +116,7 @@
     private boolean mShouldStartOnNextMoveEvent = false;
     private boolean mOnBackStartDispatched = false;
     private boolean mPointerPilfered = false;
+    private final boolean mRequirePointerPilfer;
 
     private final FlingAnimationUtils mFlingAnimationUtils;
 
@@ -220,6 +222,8 @@
         mActivityTaskManager = activityTaskManager;
         mContext = context;
         mContentResolver = contentResolver;
+        mRequirePointerPilfer =
+                context.getResources().getBoolean(R.bool.config_backAnimationRequiresPointerPilfer);
         mBgHandler = bgHandler;
         shellInit.addInitCallback(this::onInit, this);
         mAnimationBackground = backAnimationBackground;
@@ -560,7 +564,9 @@
     private void tryDispatchOnBackStarted(
             IOnBackInvokedCallback callback,
             BackMotionEvent backEvent) {
-        if (mOnBackStartDispatched || callback == null || !mPointerPilfered) {
+        if (mOnBackStartDispatched
+                || callback == null
+                || (!mPointerPilfered && mRequirePointerPilfer)) {
             return;
         }
         dispatchOnBackStarted(callback, backEvent);
@@ -1006,6 +1012,8 @@
         pw.println(prefix + "  mBackGestureStarted=" + mBackGestureStarted);
         pw.println(prefix + "  mPostCommitAnimationInProgress=" + mPostCommitAnimationInProgress);
         pw.println(prefix + "  mShouldStartOnNextMoveEvent=" + mShouldStartOnNextMoveEvent);
+        pw.println(prefix + "  mPointerPilfered=" + mPointerPilfered);
+        pw.println(prefix + "  mRequirePointerPilfer=" + mRequirePointerPilfer);
         pw.println(prefix + "  mCurrentTracker state:");
         mCurrentTracker.dump(pw, prefix + "    ");
         pw.println(prefix + "  mQueuedTracker state:");
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 15350fb..9585842 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
@@ -103,6 +103,7 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.annotations.ShellBackgroundThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
 import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.onehanded.OneHandedController;
@@ -708,6 +709,30 @@
         return mBubbleProperties.isBubbleBarEnabled() && mBubblePositioner.isLargeScreen();
     }
 
+    /**
+     * Returns current {@link BubbleBarLocation} if bubble bar is being used.
+     * Otherwise returns <code>null</code>
+     */
+    @Nullable
+    public BubbleBarLocation getBubbleBarLocation() {
+        if (canShowAsBubbleBar()) {
+            return mBubblePositioner.getBubbleBarLocation();
+        }
+        return null;
+    }
+
+    /**
+     * Update bubble bar location and trigger and update to listeners
+     */
+    public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
+        if (canShowAsBubbleBar()) {
+            mBubblePositioner.setBubbleBarLocation(bubbleBarLocation);
+            BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
+            bubbleBarUpdate.bubbleBarLocation = bubbleBarLocation;
+            mBubbleStateListener.onBubbleStateChange(bubbleBarUpdate);
+        }
+    }
+
     /** Whether this userId belongs to the current user. */
     private boolean isCurrentProfile(int userId) {
         return userId == UserHandle.USER_ALL
@@ -1179,7 +1204,7 @@
      */
     @VisibleForTesting
     public void expandStackAndSelectBubbleFromLauncher(String key, Rect bubbleBarBounds) {
-        mBubblePositioner.setBubbleBarPosition(bubbleBarBounds);
+        mBubblePositioner.setBubbleBarBounds(bubbleBarBounds);
 
         if (BubbleOverflow.KEY.equals(key)) {
             mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow());
@@ -1799,11 +1824,12 @@
         @Override
         public void removeBubble(Bubble removedBubble) {
             if (mLayerView != null) {
-                mLayerView.removeBubble(removedBubble);
-                if (!mBubbleData.hasBubbles() && !isStackExpanded()) {
-                    mLayerView.setVisibility(INVISIBLE);
-                    removeFromWindowManagerMaybe();
-                }
+                mLayerView.removeBubble(removedBubble, () -> {
+                    if (!mBubbleData.hasBubbles() && !isStackExpanded()) {
+                        mLayerView.setVisibility(INVISIBLE);
+                        removeFromWindowManagerMaybe();
+                    }
+                });
             }
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 6c2f925..61f0ed2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -165,7 +165,7 @@
          * used when {@link BubbleController#isShowingAsBubbleBar()} is true.
          */
         BubbleBarUpdate getInitialState() {
-            BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
+            BubbleBarUpdate bubbleBarUpdate = BubbleBarUpdate.createInitialState();
             bubbleBarUpdate.shouldShowEducation = shouldShowEducation;
             for (int i = 0; i < bubbles.size(); i++) {
                 bubbleBarUpdate.currentBubbleList.add(bubbles.get(i).asBubbleBarBubble());
@@ -255,7 +255,9 @@
      * Returns a bubble bar update populated with the current list of active bubbles.
      */
     public BubbleBarUpdate getInitialStateForBubbleBar() {
-        return mStateChange.getInitialState();
+        BubbleBarUpdate initialState = mStateChange.getInitialState();
+        initialState.bubbleBarLocation = mPositioner.getBubbleBarLocation();
+        return initialState;
     }
 
     public void setSuppressionChangedListener(Bubbles.BubbleMetadataFlagListener listener) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index a5853d6..b215b61 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -32,6 +32,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.launcher3.icons.IconNormalizer;
 import com.android.wm.shell.R;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
 
 /**
  * Keeps track of display size, configuration, and specific bubble sizes. One place for all
@@ -95,6 +96,7 @@
     private PointF mRestingStackPosition;
 
     private boolean mShowingInBubbleBar;
+    private BubbleBarLocation mBubbleBarLocation = BubbleBarLocation.DEFAULT;
     private final Rect mBubbleBarBounds = new Rect();
 
     public BubblePositioner(Context context, WindowManager windowManager) {
@@ -797,14 +799,36 @@
         mShowingInBubbleBar = showingInBubbleBar;
     }
 
+    public void setBubbleBarLocation(BubbleBarLocation location) {
+        mBubbleBarLocation = location;
+    }
+
+    public BubbleBarLocation getBubbleBarLocation() {
+        return mBubbleBarLocation;
+    }
+
+    /**
+     * @return <code>true</code> when bubble bar is on the left and <code>false</code> when on right
+     */
+    public boolean isBubbleBarOnLeft() {
+        return mBubbleBarLocation.isOnLeft(mDeviceConfig.isRtl());
+    }
+
     /**
      * Sets the position of the bubble bar in display coordinates.
      */
-    public void setBubbleBarPosition(Rect bubbleBarBounds) {
+    public void setBubbleBarBounds(Rect bubbleBarBounds) {
         mBubbleBarBounds.set(bubbleBarBounds);
     }
 
     /**
+     * Returns the display coordinates of the bubble bar.
+     */
+    public Rect getBubbleBarBounds() {
+        return mBubbleBarBounds;
+    }
+
+    /**
      * How wide the expanded view should be when showing from the bubble bar.
      */
     public int getExpandedViewWidthForBubbleBar(boolean isOverflow) {
@@ -831,11 +855,4 @@
     public int getBubbleBarExpandedViewPadding() {
         return mExpandedViewPadding;
     }
-
-    /**
-     * Returns the display coordinates of the bubble bar.
-     */
-    public Rect getBubbleBarBounds() {
-        return mBubbleBarBounds;
-    }
 }
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 8fd6ffe..23bdd08 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
@@ -717,11 +717,6 @@
 
             // Hide the stack after a delay, if needed.
             updateTemporarilyInvisibleAnimation(false /* hideImmediately */);
-
-            if (mShouldReorderBubblesAfterGestureCompletes) {
-                mShouldReorderBubblesAfterGestureCompletes = false;
-                updateBubbleOrderInternal(mBubbleData.getBubbles(), true);
-            }
         }
     };
 
@@ -2479,11 +2474,12 @@
         // Let the expanded animation controller know that it shouldn't animate child adds/reorders
         // since we're about to animate collapsed.
         mExpandedAnimationController.notifyPreparingToCollapse();
-
+        final PointF collapsePosition = mStackAnimationController
+                .getStackPositionAlongNearestHorizontalEdge();
         updateOverflowDotVisibility(false /* expanding */);
         final Runnable collapseBackToStack = () ->
                 mExpandedAnimationController.collapseBackToStack(
-                        mStackAnimationController.getStackPositionAlongNearestHorizontalEdge(),
+                        collapsePosition,
                         /* fadeBubblesDuringCollapse= */ mRemovingLastBubbleWhileExpanded,
                         () -> {
                             mBubbleContainer.setActiveController(mStackAnimationController);
@@ -2506,7 +2502,8 @@
             }
             mExpandedViewAnimationController.reset();
         };
-        mExpandedViewAnimationController.animateCollapse(collapseBackToStack, after);
+        mExpandedViewAnimationController.animateCollapse(collapseBackToStack, after,
+                collapsePosition);
         if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
             // When the animation completes, we should no longer be showing the content.
             // This won't actually update content visibility immediately, if we are currently
@@ -2732,6 +2729,12 @@
                 ev.getAction() != MotionEvent.ACTION_UP
                         && ev.getAction() != MotionEvent.ACTION_CANCEL;
 
+        // If there is a deferred reorder action, and the gesture is over, run it now.
+        if (mShouldReorderBubblesAfterGestureCompletes && !mIsGestureInProgress) {
+            mShouldReorderBubblesAfterGestureCompletes = false;
+            updateBubbleOrderInternal(mBubbleData.getBubbles(), false);
+        }
+
         return dispatched;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java
index 8a33780..4175529 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java
@@ -15,6 +15,8 @@
  */
 package com.android.wm.shell.bubbles.animation;
 
+import android.graphics.PointF;
+
 import com.android.wm.shell.bubbles.BubbleExpandedView;
 
 /**
@@ -55,8 +57,9 @@
      * @param startStackCollapse runnable that is triggered when bubbles can start moving back to
      *                           their collapsed location
      * @param after              runnable to run after animation is complete
+     * @param collapsePosition the position on screen the stack will collapse to
      */
-    void animateCollapse(Runnable startStackCollapse, Runnable after);
+    void animateCollapse(Runnable startStackCollapse, Runnable after, PointF collapsePosition);
 
     /**
      * Animate the view back to fully expanded state.
@@ -69,6 +72,22 @@
     void animateForImeVisibilityChange(boolean visible);
 
     /**
+     * Whether this controller should also animate the expansion for the bubble
+     */
+    boolean shouldAnimateExpansion();
+
+    /**
+     * Animate the expansion of the bubble.
+     *
+     * @param startDelayMillis how long to delay starting the expansion animation
+     * @param after runnable to run after the animation is complete
+     * @param collapsePosition the position on screen the stack will collapse to (and expand from)
+     * @param bubblePosition the position of the bubble on screen that the view is associated with
+     */
+    void animateExpansion(long startDelayMillis, Runnable after, PointF collapsePosition,
+            PointF bubblePosition);
+
+    /**
      * Reset the view to fully expanded state
      */
     void reset();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
index e43609f..aa4129a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
@@ -28,6 +28,7 @@
 import android.animation.ValueAnimator;
 import android.annotation.SuppressLint;
 import android.content.Context;
+import android.graphics.PointF;
 import android.view.HapticFeedbackConstants;
 import android.view.ViewConfiguration;
 
@@ -187,9 +188,11 @@
     }
 
     @Override
-    public void animateCollapse(Runnable startStackCollapse, Runnable after) {
-        ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate collapse swipeVel=%f minFlingVel=%d",
-                mSwipeUpVelocity,  mMinFlingVelocity);
+    public void animateCollapse(Runnable startStackCollapse, Runnable after,
+            PointF collapsePosition) {
+        ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate collapse swipeVel=%f minFlingVel=%d"
+                        + " collapsePosition=%f,%f", mSwipeUpVelocity, mMinFlingVelocity,
+                collapsePosition.x, collapsePosition.y);
         if (mExpandedView != null) {
             // Mark it as animating immediately to avoid updates to the view before animation starts
             mExpandedView.setAnimating(true);
@@ -274,6 +277,17 @@
     }
 
     @Override
+    public boolean shouldAnimateExpansion() {
+        return false;
+    }
+
+    @Override
+    public void animateExpansion(long startDelayMillis, Runnable after, PointF collapsePosition,
+            PointF bubblePosition) {
+        // TODO - animate
+    }
+
+    @Override
     public void reset() {
         ProtoLog.d(WM_SHELL_BUBBLES, "reset expandedView collapsed state");
         if (mExpandedView == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index 8946f41..9eb9632 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -477,7 +477,7 @@
     private Point getExpandedViewRestPosition(Size size) {
         final int padding = mPositioner.getBubbleBarExpandedViewPadding();
         Point point = new Point();
-        if (mLayerView.isOnLeft()) {
+        if (mPositioner.isBubbleBarOnLeft()) {
             point.x = mPositioner.getInsets().left + padding;
         } else {
             point.x = mPositioner.getAvailableRect().width() - size.getWidth() - padding;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index 7d37d60..056598b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -19,6 +19,8 @@
 import android.annotation.SuppressLint
 import android.view.MotionEvent
 import android.view.View
+import com.android.wm.shell.bubbles.BubblePositioner
+import com.android.wm.shell.common.bubbles.BubbleBarLocation
 import com.android.wm.shell.common.bubbles.DismissView
 import com.android.wm.shell.common.bubbles.RelativeTouchListener
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject
@@ -29,7 +31,8 @@
     private val expandedView: BubbleBarExpandedView,
     private val dismissView: DismissView,
     private val animationHelper: BubbleBarAnimationHelper,
-    private val onDismissed: () -> Unit
+    private val bubblePositioner: BubblePositioner,
+    private val dragListener: DragListener
 ) {
 
     var isStuckToDismiss: Boolean = false
@@ -45,11 +48,11 @@
         magnetizedExpandedView.magnetListener = MagnetListener()
         magnetizedExpandedView.animateStuckToTarget =
             {
-                    target: MagnetizedObject.MagneticTarget,
-                    _: Float,
-                    _: Float,
-                    _: Boolean,
-                    after: (() -> Unit)? ->
+                target: MagnetizedObject.MagneticTarget,
+                _: Float,
+                _: Float,
+                _: Boolean,
+                after: (() -> Unit)? ->
                 animationHelper.animateIntoTarget(target, after)
             }
 
@@ -73,13 +76,34 @@
         }
     }
 
+    /** Listener to receive callback about dragging events */
+    interface DragListener {
+        /**
+         * Bubble bar [BubbleBarLocation] has changed as a result of dragging the expanded view.
+         *
+         * Triggered when drag gesture passes the middle of the screen and before touch up.
+         * Can be triggered multiple times per gesture.
+         *
+         * @param location new location of the bubble bar as a result of the ongoing drag operation
+         */
+        fun onLocationChanged(location: BubbleBarLocation)
+
+        /** Expanded view has been released in the dismiss target */
+        fun onReleasedInDismiss()
+    }
+
     private inner class HandleDragListener : RelativeTouchListener() {
 
         private var isMoving = false
+        private var screenCenterX: Int = -1
+        private var isOnLeft = false
 
         override fun onDown(v: View, ev: MotionEvent): Boolean {
             // While animating, don't allow new touch events
-            return !expandedView.isAnimating
+            if (expandedView.isAnimating) return false
+            screenCenterX = bubblePositioner.screenRect.centerX()
+            isOnLeft = bubblePositioner.isBubbleBarOnLeft
+            return true
         }
 
         override fun onMove(
@@ -97,6 +121,14 @@
             expandedView.translationX = expandedViewInitialTranslationX + dx
             expandedView.translationY = expandedViewInitialTranslationY + dy
             dismissView.show()
+
+            if (isOnLeft && ev.rawX > screenCenterX) {
+                isOnLeft = false
+                dragListener.onLocationChanged(BubbleBarLocation.RIGHT)
+            } else if (!isOnLeft && ev.rawX < screenCenterX) {
+                isOnLeft = true
+                dragListener.onLocationChanged(BubbleBarLocation.LEFT)
+            }
         }
 
         override fun onUp(
@@ -113,6 +145,7 @@
         }
 
         override fun onCancel(v: View, ev: MotionEvent, viewInitialX: Float, viewInitialY: Float) {
+            isStuckToDismiss = false
             finishDrag()
         }
 
@@ -127,30 +160,29 @@
 
     private inner class MagnetListener : MagnetizedObject.MagnetListener {
         override fun onStuckToTarget(
-                target: MagnetizedObject.MagneticTarget,
-                draggedObject: MagnetizedObject<*>
+            target: MagnetizedObject.MagneticTarget,
+            draggedObject: MagnetizedObject<*>
         ) {
             isStuckToDismiss = true
         }
 
         override fun onUnstuckFromTarget(
-                target: MagnetizedObject.MagneticTarget,
-                draggedObject: MagnetizedObject<*>,
-                velX: Float,
-                velY: Float,
-                wasFlungOut: Boolean
+            target: MagnetizedObject.MagneticTarget,
+            draggedObject: MagnetizedObject<*>,
+            velX: Float,
+            velY: Float,
+            wasFlungOut: Boolean
         ) {
             isStuckToDismiss = false
             animationHelper.animateUnstuckFromDismissView(target)
         }
 
         override fun onReleasedInTarget(
-                target: MagnetizedObject.MagneticTarget,
-                draggedObject: MagnetizedObject<*>
+            target: MagnetizedObject.MagneticTarget,
+            draggedObject: MagnetizedObject<*>
         ) {
-            onDismissed()
+            dragListener.onReleasedInDismiss()
             dismissView.hide()
         }
     }
 }
-
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 78a41f7..3fb9f63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -33,6 +33,8 @@
 import android.view.WindowManager;
 import android.widget.FrameLayout;
 
+import androidx.annotation.NonNull;
+
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.Bubble;
 import com.android.wm.shell.bubbles.BubbleController;
@@ -42,6 +44,8 @@
 import com.android.wm.shell.bubbles.BubbleViewProvider;
 import com.android.wm.shell.bubbles.DeviceConfig;
 import com.android.wm.shell.bubbles.DismissViewUtils;
+import com.android.wm.shell.bubbles.bar.BubbleBarExpandedViewDragController.DragListener;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
 import com.android.wm.shell.common.bubbles.DismissView;
 
 import kotlin.Unit;
@@ -155,12 +159,6 @@
         return mIsExpanded;
     }
 
-    // TODO(b/313661121) - when dragging is implemented, check user setting first
-    /** Whether the expanded view is positioned on the left or right side of the screen. */
-    public boolean isOnLeft() {
-        return getLayoutDirection() == LAYOUT_DIRECTION_RTL;
-    }
-
     /** Shows the expanded view of the provided bubble. */
     public void showExpandedView(BubbleViewProvider b) {
         BubbleBarExpandedView expandedView = b.getBubbleBarExpandedView();
@@ -207,15 +205,23 @@
                 }
             });
 
+            DragListener dragListener = new DragListener() {
+                @Override
+                public void onLocationChanged(@NonNull BubbleBarLocation location) {
+                    mBubbleController.setBubbleBarLocation(location);
+                }
+
+                @Override
+                public void onReleasedInDismiss() {
+                    mBubbleController.dismissBubble(mExpandedBubble.getKey(), DISMISS_USER_GESTURE);
+                }
+            };
             mDragController = new BubbleBarExpandedViewDragController(
                     mExpandedView,
                     mDismissView,
                     mAnimationHelper,
-                    () -> {
-                        mBubbleController.dismissBubble(mExpandedBubble.getKey(),
-                                DISMISS_USER_GESTURE);
-                        return Unit.INSTANCE;
-                    });
+                    mPositioner,
+                    dragListener);
 
             addView(mExpandedView, new LayoutParams(width, height, Gravity.LEFT));
         }
@@ -242,13 +248,17 @@
     }
 
     /** Removes the given {@code bubble}. */
-    public void removeBubble(Bubble bubble) {
+    public void removeBubble(Bubble bubble, Runnable endAction) {
+        Runnable cleanUp = () -> {
+            bubble.cleanupViews();
+            endAction.run();
+        };
         if (mBubbleData.getBubbles().isEmpty()) {
             // we're removing the last bubble. collapse the expanded view and cleanup bubble views
             // at the end.
-            collapse(bubble::cleanupViews);
+            collapse(cleanUp);
         } else {
-            bubble.cleanupViews();
+            cleanUp.run();
         }
     }
 
@@ -264,6 +274,9 @@
      */
     public void collapse(@Nullable Runnable endAction) {
         if (!mIsExpanded) {
+            if (endAction != null) {
+                endAction.run();
+            }
             return;
         }
         mIsExpanded = false;
@@ -345,7 +358,7 @@
         lp.width = width;
         lp.height = height;
         mExpandedView.setLayoutParams(lp);
-        if (isOnLeft()) {
+        if (mPositioner.isBubbleBarOnLeft()) {
             mExpandedView.setX(mPositioner.getInsets().left + padding);
         } else {
             mExpandedView.setX(mPositioner.getAvailableRect().width() - width - padding);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 2ea4316..ad01d0f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -20,12 +20,12 @@
 import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_END;
 import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_START;
 import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
-import static android.view.inputmethod.ImeTracker.TOKEN_NONE;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.res.Configuration;
@@ -51,6 +51,7 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.wm.shell.sysui.ShellInit;
 
 import java.util.ArrayList;
@@ -122,7 +123,8 @@
         }
         if (mDisplayController.getDisplayLayout(displayId).rotation()
                 != pd.mRotation && isImeShowing(displayId)) {
-            pd.startAnimation(true, false /* forceRestart */, null /* statsToken */);
+            pd.startAnimation(true, false /* forceRestart */,
+                    SoftInputShowHideReason.DISPLAY_CONFIGURATION_CHANGED);
         }
     }
 
@@ -257,7 +259,8 @@
             mInsetsState.set(insetsState, true /* copySources */);
             if (mImeShowing && !Objects.equals(oldFrame, newFrame) && newSourceVisible) {
                 if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation");
-                startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */);
+                startAnimation(mImeShowing, true /* forceRestart */,
+                        SoftInputShowHideReason.DISPLAY_INSETS_CHANGED);
             }
         }
 
@@ -291,7 +294,8 @@
                     final boolean positionChanged =
                             !imeSourceControl.getSurfacePosition().equals(lastSurfacePosition);
                     if (positionChanged) {
-                        startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */);
+                        startAnimation(mImeShowing, true /* forceRestart */,
+                                SoftInputShowHideReason.DISPLAY_CONTROLS_CHANGED);
                     }
                 } else {
                     if (!haveSameLeash(mImeSourceControl, imeSourceControl)) {
@@ -384,7 +388,20 @@
         }
 
         private void startAnimation(final boolean show, final boolean forceRestart,
-                @Nullable ImeTracker.Token statsToken) {
+                @SoftInputShowHideReason int reason) {
+            final var imeSource = mInsetsState.peekSource(InsetsSource.ID_IME);
+            if (imeSource == null || mImeSourceControl == null) {
+                return;
+            }
+            final var statsToken = ImeTracker.forLogging().onStart(
+                    show ? ImeTracker.TYPE_SHOW : ImeTracker.TYPE_HIDE, ImeTracker.ORIGIN_WM_SHELL,
+                    reason, false /* fromUser */);
+
+            startAnimation(show, forceRestart, statsToken);
+        }
+
+        private void startAnimation(final boolean show, final boolean forceRestart,
+                @NonNull final ImeTracker.Token statsToken) {
             final InsetsSource imeSource = mInsetsState.peekSource(InsetsSource.ID_IME);
             if (imeSource == null || mImeSourceControl == null) {
                 ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
@@ -458,7 +475,7 @@
             ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
             mAnimation.addListener(new AnimatorListenerAdapter() {
                 private boolean mCancelled = false;
-                @Nullable
+                @NonNull
                 private final ImeTracker.Token mStatsToken = statsToken;
 
                 @Override
@@ -484,7 +501,7 @@
                     }
                     if (DEBUG_IME_VISIBILITY) {
                         EventLog.writeEvent(IMF_IME_REMOTE_ANIM_START,
-                                statsToken != null ? statsToken.getTag() : TOKEN_NONE,
+                                mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE,
                                 mDisplayId, mAnimationDirection, alpha, startY , endY,
                                 Objects.toString(mImeSourceControl.getLeash()),
                                 Objects.toString(mImeSourceControl.getInsetsHint()),
@@ -500,7 +517,8 @@
                     mCancelled = true;
                     if (DEBUG_IME_VISIBILITY) {
                         EventLog.writeEvent(IMF_IME_REMOTE_ANIM_CANCEL,
-                                statsToken != null ? statsToken.getTag() : TOKEN_NONE, mDisplayId,
+                                mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE,
+                                mDisplayId,
                                 Objects.toString(mImeSourceControl.getInsetsHint()));
                     }
                 }
@@ -528,7 +546,7 @@
                     }
                     if (DEBUG_IME_VISIBILITY) {
                         EventLog.writeEvent(IMF_IME_REMOTE_ANIM_END,
-                                statsToken != null ? statsToken.getTag() : TOKEN_NONE,
+                                mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE,
                                 mDisplayId, mAnimationDirection, endY,
                                 Objects.toString(mImeSourceControl.getLeash()),
                                 Objects.toString(mImeSourceControl.getInsetsHint()),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index 9bdda14..ca06024 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -277,8 +277,7 @@
          *
          * @param types {@link InsetsType} to show
          * @param fromIme true if this request originated from IME (InputMethodService).
-         * @param statsToken the token tracking the current IME show request
-         *                   or {@code null} otherwise.
+         * @param statsToken the token tracking the current IME request or {@code null} otherwise.
          */
         default void showInsets(@InsetsType int types, boolean fromIme,
                 @Nullable ImeTracker.Token statsToken) {}
@@ -288,8 +287,7 @@
          *
          * @param types {@link InsetsType} to hide
          * @param fromIme true if this request originated from IME (InputMethodService).
-         * @param statsToken the token tracking the current IME hide request
-         *                   or {@code null} otherwise.
+         * @param statsToken the token tracking the current IME request or {@code null} otherwise.
          */
         default void hideInsets(@InsetsType int types, boolean fromIme,
                 @Nullable ImeTracker.Token statsToken) {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
index 4c0281d..e261d92 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
@@ -16,6 +16,8 @@
 
 package com.android.wm.shell.common;
 
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL;
+
 import android.annotation.BinderThread;
 import android.annotation.NonNull;
 import android.os.RemoteException;
@@ -26,6 +28,7 @@
 import android.window.WindowContainerTransactionCallback;
 import android.window.WindowOrganizer;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.transition.LegacyTransitions;
 
 import java.util.ArrayList;
@@ -204,6 +207,7 @@
         @Override
         public void onTransactionReady(int id,
                 @NonNull SurfaceControl.Transaction t) {
+            ProtoLog.v(WM_SHELL, "SyncTransactionQueue.onTransactionReady(): syncId=%d", id);
             mMainExecutor.execute(() -> {
                 synchronized (mQueue) {
                     if (mId != id) {
@@ -223,6 +227,8 @@
                             Slog.e(TAG, "Error sending callback to legacy transition: " + mId, e);
                         }
                     } else {
+                        ProtoLog.v(WM_SHELL,
+                                "SyncTransactionQueue.onTransactionReady(): syncId=%d apply", id);
                         t.apply();
                         t.close();
                     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.kt
new file mode 100644
index 0000000..f0bdfde
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common.bubbles
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * The location of the bubble bar.
+ */
+enum class BubbleBarLocation : Parcelable {
+    /**
+     * Place bubble bar at the default location for the chosen system language.
+     * If an RTL language is used, it is on the left. Otherwise on the right.
+     */
+    DEFAULT,
+    /** Default bubble bar location is overridden. Place bubble bar on the left. */
+    LEFT,
+    /** Default bubble bar location is overridden. Place bubble bar on the right. */
+    RIGHT;
+
+    /**
+     * Returns whether bubble bar is pinned to the left edge or right edge.
+     */
+    fun isOnLeft(isRtl: Boolean): Boolean {
+        if (this == DEFAULT) {
+            return isRtl
+        }
+        return this == LEFT
+    }
+
+    override fun describeContents(): Int {
+        return 0
+    }
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeString(name)
+    }
+
+    companion object {
+        @JvmField
+        val CREATOR = object : Parcelable.Creator<BubbleBarLocation> {
+            override fun createFromParcel(parcel: Parcel): BubbleBarLocation {
+                return parcel.readString()?.let { valueOf(it) } ?: DEFAULT
+            }
+
+            override fun newArray(size: Int) = arrayOfNulls<BubbleBarLocation>(size)
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
index fc627a8..e5f6c37 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
@@ -33,6 +33,7 @@
 
     public static final String BUNDLE_KEY = "update";
 
+    public final boolean initialState;
     public boolean expandedChanged;
     public boolean expanded;
     public boolean shouldShowEducation;
@@ -46,6 +47,8 @@
     public String suppressedBubbleKey;
     @Nullable
     public String unsupressedBubbleKey;
+    @Nullable
+    public BubbleBarLocation bubbleBarLocation;
 
     // This is only populated if bubbles have been removed.
     public List<RemovedBubble> removedBubbles = new ArrayList<>();
@@ -56,10 +59,17 @@
     // This is only populated the first time a listener is connected so it gets the current state.
     public List<BubbleInfo> currentBubbleList = new ArrayList<>();
 
+
     public BubbleBarUpdate() {
+        this(false);
+    }
+
+    private BubbleBarUpdate(boolean initialState) {
+        this.initialState = initialState;
     }
 
     public BubbleBarUpdate(Parcel parcel) {
+        initialState = parcel.readBoolean();
         expandedChanged = parcel.readBoolean();
         expanded = parcel.readBoolean();
         shouldShowEducation = parcel.readBoolean();
@@ -75,6 +85,8 @@
         parcel.readStringList(bubbleKeysInOrder);
         currentBubbleList = parcel.readParcelableList(new ArrayList<>(),
                 BubbleInfo.class.getClassLoader());
+        bubbleBarLocation = parcel.readParcelable(BubbleBarLocation.class.getClassLoader(),
+                BubbleBarLocation.class);
     }
 
     /**
@@ -89,12 +101,15 @@
                 || !bubbleKeysInOrder.isEmpty()
                 || suppressedBubbleKey != null
                 || unsupressedBubbleKey != null
-                || !currentBubbleList.isEmpty();
+                || !currentBubbleList.isEmpty()
+                || bubbleBarLocation != null;
     }
 
     @Override
     public String toString() {
-        return "BubbleBarUpdate{ expandedChanged=" + expandedChanged
+        return "BubbleBarUpdate{"
+                + " initialState=" + initialState
+                + " expandedChanged=" + expandedChanged
                 + " expanded=" + expanded
                 + " selectedBubbleKey=" + selectedBubbleKey
                 + " shouldShowEducation=" + shouldShowEducation
@@ -105,6 +120,7 @@
                 + " removedBubbles=" + removedBubbles
                 + " bubbles=" + bubbleKeysInOrder
                 + " currentBubbleList=" + currentBubbleList
+                + " bubbleBarLocation=" + bubbleBarLocation
                 + " }";
     }
 
@@ -115,6 +131,7 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeBoolean(initialState);
         parcel.writeBoolean(expandedChanged);
         parcel.writeBoolean(expanded);
         parcel.writeBoolean(shouldShowEducation);
@@ -126,6 +143,16 @@
         parcel.writeParcelableList(removedBubbles, flags);
         parcel.writeStringList(bubbleKeysInOrder);
         parcel.writeParcelableList(currentBubbleList, flags);
+        parcel.writeParcelable(bubbleBarLocation, flags);
+    }
+
+    /**
+     * Create update for initial set of values.
+     * <p>
+     * Used when bubble bar is newly created.
+     */
+    public static BubbleBarUpdate createInitialState() {
+        return new BubbleBarUpdate(true);
     }
 
     @NonNull
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index f801b0d..a87116e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -75,7 +75,6 @@
     private SurfaceControlViewHost mViewHost;
     private DividerHandleView mHandle;
     private DividerRoundedCorner mCorners;
-    private View mBackground;
     private int mTouchElevation;
 
     private VelocityTracker mVelocityTracker;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS
index 7237d2b..37ccd15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS
@@ -1,2 +1,4 @@
 # WM shell sub-modules splitscreen owner
 chenghsiuchang@google.com
+jeremysim@google.com
+peanutbutter@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index 2b10377..dae62ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -138,8 +138,10 @@
         mViewHost.setView(rootLayout, lp);
     }
 
-    /** Releases the surfaces for split decor. */
-    public void release(SurfaceControl.Transaction t) {
+    /**
+     * Cancels any currently running animations.
+     */
+    public void cancelRunningAnimations() {
         if (mFadeAnimator != null) {
             if (mFadeAnimator.isRunning()) {
                 mFadeAnimator.cancel();
@@ -152,6 +154,11 @@
             }
             mScreenshotAnimator = null;
         }
+    }
+
+    /** Releases the surfaces for split decor. */
+    public void release(SurfaceControl.Transaction t) {
+        cancelRunningAnimations();
         if (mViewHost != null) {
             mViewHost.release();
             mViewHost = null;
@@ -277,7 +284,7 @@
                 }
 
                 @Override
-                public void onAnimationEnd(@androidx.annotation.NonNull Animator animation) {
+                public void onAnimationEnd(@NonNull Animator animation) {
                     mRunningAnimationCount--;
                     animT.remove(mScreenshot);
                     animT.apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 53caddb..6b2d544 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -510,16 +510,18 @@
         }
     }
 
-    /** Updates divide position and split bounds base on the ratio within root bounds. */
+    /**
+     * Updates divide position and split bounds base on the ratio within root bounds. Falls back
+     * to middle position if the provided SnapTarget is not supported.
+     */
     public void setDivideRatio(@PersistentSnapPosition int snapPosition) {
         final DividerSnapAlgorithm.SnapTarget snapTarget = mDividerSnapAlgorithm.findSnapTarget(
                 snapPosition);
 
-        if (snapTarget == null) {
-            throw new IllegalArgumentException("No SnapTarget for position " + snapPosition);
-        }
-
-        setDividePosition(snapTarget.position, false /* applyLayoutChange */);
+        setDividePosition(snapTarget != null
+                ? snapTarget.position
+                : mDividerSnapAlgorithm.getMiddleTarget().position,
+                false /* applyLayoutChange */);
     }
 
     /** Resets divider position. */
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 8b2ec0a..8d489e1 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
@@ -846,8 +846,10 @@
     static ShellController provideShellController(Context context,
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
+            DisplayInsetsController displayInsetsController,
             @ShellMainThread ShellExecutor mainExecutor) {
-        return new ShellController(context, shellInit, shellCommandHandler, mainExecutor);
+        return new ShellController(context, shellInit, shellCommandHandler,
+                displayInsetsController, mainExecutor);
     }
 
     //
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 f757e1c..04f0f44 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
@@ -57,6 +57,8 @@
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
 import com.android.wm.shell.dagger.pip.PipModule;
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
+import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -201,9 +203,11 @@
     @Provides
     static WindowDecorViewModel provideWindowDecorViewModel(
             Context context,
+            @ShellMainThread ShellExecutor mainExecutor,
             @ShellMainThread Handler mainHandler,
             @ShellMainThread Choreographer mainChoreographer,
             ShellInit shellInit,
+            IWindowManager windowManager,
             ShellCommandHandler shellCommandHandler,
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
@@ -216,10 +220,12 @@
         if (DesktopModeStatus.isEnabled()) {
             return new DesktopModeWindowDecorViewModel(
                     context,
+                    mainExecutor,
                     mainHandler,
                     mainChoreographer,
                     shellInit,
                     shellCommandHandler,
+                    windowManager,
                     taskOrganizer,
                     displayController,
                     shellController,
@@ -505,6 +511,7 @@
             ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
             DragToDesktopTransitionHandler dragToDesktopTransitionHandler,
             @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
+            DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver,
             LaunchAdjacentController launchAdjacentController,
             RecentsTransitionHandler recentsTransitionHandler,
             MultiInstanceHelper multiInstanceHelper,
@@ -514,7 +521,8 @@
                 displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
                 dragAndDropController, transitions, enterDesktopTransitionHandler,
                 exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler,
-                dragToDesktopTransitionHandler, desktopModeTaskRepository, launchAdjacentController,
+                dragToDesktopTransitionHandler, desktopModeTaskRepository,
+                desktopModeLoggerTransitionObserver, launchAdjacentController,
                 recentsTransitionHandler, multiInstanceHelper, mainExecutor);
     }
 
@@ -558,6 +566,22 @@
         return new DesktopModeTaskRepository();
     }
 
+    @WMSingleton
+    @Provides
+    static DesktopModeLoggerTransitionObserver provideDesktopModeLoggerTransitionObserver(
+            ShellInit shellInit,
+            Transitions transitions,
+            DesktopModeEventLogger desktopModeEventLogger) {
+        return new DesktopModeLoggerTransitionObserver(
+                shellInit, transitions, desktopModeEventLogger);
+    }
+
+    @WMSingleton
+    @Provides
+    static DesktopModeEventLogger provideDesktopModeEventLogger() {
+        return new DesktopModeEventLogger();
+    }
+
     //
     // Drag and drop
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index 8305fa6..838603f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -51,4 +51,9 @@
     /** Called when requested to go to desktop mode from the current focused app. */
     void enterDesktop(int displayId);
 
+    /** Called when requested to go to fullscreen from the current focused desktop app. */
+    void moveFocusedTaskToFullscreen(int displayId);
+
+    /** Called when requested to go to split screen from the current focused desktop app. */
+    void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
new file mode 100644
index 0000000..a10c7c0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityTaskManager.INVALID_TASK_ID
+import android.app.TaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.os.IBinder
+import android.util.SparseArray
+import android.view.SurfaceControl
+import android.view.WindowManager
+import android.window.TransitionInfo
+import androidx.annotation.VisibleForTesting
+import androidx.core.util.containsKey
+import androidx.core.util.forEach
+import androidx.core.util.isEmpty
+import androidx.core.util.isNotEmpty
+import androidx.core.util.plus
+import androidx.core.util.putAll
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.InstanceIdSequence
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.TransitionUtil
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.util.KtProtoLog
+
+/**
+ * A [Transitions.TransitionObserver] that observes transitions and the proposed changes to log
+ * appropriate desktop mode session log events. This observes transitions related to desktop mode
+ * and other transitions that originate both within and outside shell.
+ */
+class DesktopModeLoggerTransitionObserver(
+    shellInit: ShellInit,
+    private val transitions: Transitions,
+    private val desktopModeEventLogger: DesktopModeEventLogger
+) : Transitions.TransitionObserver {
+
+    private val idSequence: InstanceIdSequence by lazy { InstanceIdSequence(Int.MAX_VALUE) }
+
+    init {
+        if (Transitions.ENABLE_SHELL_TRANSITIONS && DesktopModeStatus.isEnabled()) {
+            shellInit.addInitCallback(this::onInit, this)
+        }
+    }
+
+    // A sparse array of visible freeform tasks and taskInfos
+    private val visibleFreeformTaskInfos: SparseArray<TaskInfo> = SparseArray()
+
+    // Caching the taskInfos to handle canceled recents animations, if we identify that the recents
+    // animation was cancelled, we restore these tasks to calculate the post-Transition state
+    private val tasksSavedForRecents: SparseArray<TaskInfo> = SparseArray()
+
+    // The instanceId for the current logging session
+    private var loggerInstanceId: InstanceId? = null
+
+    private val isSessionActive: Boolean
+        get() = loggerInstanceId != null
+
+    private fun setSessionInactive() {
+        loggerInstanceId = null
+    }
+
+    fun onInit() {
+        transitions.registerObserver(this)
+    }
+
+    override fun onTransitionReady(
+        transition: IBinder,
+        info: TransitionInfo,
+        startTransaction: SurfaceControl.Transaction,
+        finishTransaction: SurfaceControl.Transaction
+    ) {
+        // this was a new recents animation
+        if (info.isRecentsTransition() && tasksSavedForRecents.isEmpty()) {
+            KtProtoLog.v(
+                WM_SHELL_DESKTOP_MODE,
+                "DesktopModeLogger: Recents animation running, saving tasks for later"
+            )
+            // TODO (b/326391303) - avoid logging session exit if we can identify a cancelled
+            // recents animation
+
+            // when recents animation is running, all freeform tasks are sent TO_BACK temporarily
+            // if the user ends up at home, we need to update the visible freeform tasks
+            // if the user cancels the animation, the subsequent transition is NONE
+            // if the user opens a new task, the subsequent transition is OPEN with flag
+            tasksSavedForRecents.putAll(visibleFreeformTaskInfos)
+        }
+
+        // figure out what the new state of freeform tasks would be post transition
+        var postTransitionVisibleFreeformTasks = getPostTransitionVisibleFreeformTaskInfos(info)
+
+        // A canceled recents animation is followed by a TRANSIT_NONE transition with no flags, if
+        // that's the case, we might have accidentally logged a session exit and would need to
+        // revaluate again. Add all the tasks back.
+        // This will start a new desktop mode session.
+        if (
+            info.type == WindowManager.TRANSIT_NONE &&
+                info.flags == 0 &&
+                tasksSavedForRecents.isNotEmpty()
+        ) {
+            KtProtoLog.v(
+                WM_SHELL_DESKTOP_MODE,
+                "DesktopModeLogger: Canceled recents animation, restoring tasks"
+            )
+            // restore saved tasks in the updated set and clear for next use
+            postTransitionVisibleFreeformTasks += tasksSavedForRecents
+            tasksSavedForRecents.clear()
+        }
+
+        // identify if we need to log any changes and update the state of visible freeform tasks
+        identifyLogEventAndUpdateState(
+            transitionInfo = info,
+            preTransitionVisibleFreeformTasks = visibleFreeformTaskInfos,
+            postTransitionVisibleFreeformTasks = postTransitionVisibleFreeformTasks
+        )
+    }
+
+    override fun onTransitionStarting(transition: IBinder) {}
+
+    override fun onTransitionMerged(merged: IBinder, playing: IBinder) {}
+
+    override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {}
+
+    private fun getPostTransitionVisibleFreeformTaskInfos(
+        info: TransitionInfo
+    ): SparseArray<TaskInfo> {
+        // device is sleeping, so no task will be visible anymore
+        if (info.type == WindowManager.TRANSIT_SLEEP) {
+            return SparseArray()
+        }
+
+        // filter changes involving freeform tasks or tasks that were cached in previous state
+        val changesToFreeformWindows =
+            info.changes
+                .filter { it.taskInfo != null && it.requireTaskInfo().taskId != INVALID_TASK_ID }
+                .filter {
+                    it.requireTaskInfo().isFreeformWindow() ||
+                        visibleFreeformTaskInfos.containsKey(it.requireTaskInfo().taskId)
+                }
+
+        val postTransitionFreeformTasks: SparseArray<TaskInfo> = SparseArray()
+        // start off by adding all existing tasks
+        postTransitionFreeformTasks.putAll(visibleFreeformTaskInfos)
+
+        // the combined set of taskInfos we are interested in this transition change
+        for (change in changesToFreeformWindows) {
+            val taskInfo = change.requireTaskInfo()
+
+            // check if this task existed as freeform window in previous cached state and it's now
+            // changing window modes
+            if (
+                visibleFreeformTaskInfos.containsKey(taskInfo.taskId) &&
+                    visibleFreeformTaskInfos.get(taskInfo.taskId).isFreeformWindow() &&
+                    !taskInfo.isFreeformWindow()
+            ) {
+                postTransitionFreeformTasks.remove(taskInfo.taskId)
+                // no need to evaluate new visibility of this task, since it's no longer a freeform
+                // window
+                continue
+            }
+
+            // check if the task is visible after this change, otherwise remove it
+            if (isTaskVisibleAfterChange(change)) {
+                postTransitionFreeformTasks.put(taskInfo.taskId, taskInfo)
+            } else {
+                postTransitionFreeformTasks.remove(taskInfo.taskId)
+            }
+        }
+
+        KtProtoLog.v(
+            WM_SHELL_DESKTOP_MODE,
+            "DesktopModeLogger: taskInfo map after processing changes %s",
+            postTransitionFreeformTasks.size()
+        )
+
+        return postTransitionFreeformTasks
+    }
+
+    /**
+     * Look at the [TransitionInfo.Change] and figure out if this task will be visible after this
+     * change is processed
+     */
+    private fun isTaskVisibleAfterChange(change: TransitionInfo.Change): Boolean =
+        when {
+            TransitionUtil.isOpeningType(change.mode) -> true
+            TransitionUtil.isClosingType(change.mode) -> false
+            // change mode TRANSIT_CHANGE is only for visible to visible transitions
+            change.mode == WindowManager.TRANSIT_CHANGE -> true
+            else -> false
+        }
+
+    /**
+     * Log the appropriate log event based on the new state of TasksInfos and previously cached
+     * state and update it
+     */
+    private fun identifyLogEventAndUpdateState(
+        transitionInfo: TransitionInfo,
+        preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
+        postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>
+    ) {
+        if (
+            postTransitionVisibleFreeformTasks.isEmpty() &&
+                preTransitionVisibleFreeformTasks.isNotEmpty() &&
+                isSessionActive
+        ) {
+            // Sessions is finishing, log task updates followed by an exit event
+            identifyAndLogTaskUpdates(
+                loggerInstanceId!!.id,
+                preTransitionVisibleFreeformTasks,
+                postTransitionVisibleFreeformTasks
+            )
+
+            desktopModeEventLogger.logSessionExit(
+                loggerInstanceId!!.id,
+                getExitReason(transitionInfo)
+            )
+
+            setSessionInactive()
+        } else if (
+            postTransitionVisibleFreeformTasks.isNotEmpty() &&
+                preTransitionVisibleFreeformTasks.isEmpty() &&
+                !isSessionActive
+        ) {
+            // Session is starting, log enter event followed by task updates
+            loggerInstanceId = idSequence.newInstanceId()
+            desktopModeEventLogger.logSessionEnter(
+                loggerInstanceId!!.id,
+                getEnterReason(transitionInfo)
+            )
+
+            identifyAndLogTaskUpdates(
+                loggerInstanceId!!.id,
+                preTransitionVisibleFreeformTasks,
+                postTransitionVisibleFreeformTasks
+            )
+        } else if (isSessionActive) {
+            // Session is neither starting, nor finishing, log task updates if there are any
+            identifyAndLogTaskUpdates(
+                loggerInstanceId!!.id,
+                preTransitionVisibleFreeformTasks,
+                postTransitionVisibleFreeformTasks
+            )
+        }
+
+        // update the state to the new version
+        visibleFreeformTaskInfos.clear()
+        visibleFreeformTaskInfos.putAll(postTransitionVisibleFreeformTasks)
+    }
+
+    // TODO(b/326231724) - Add logging around taskInfoChanges Updates
+    /** Compare the old and new state of taskInfos and identify and log the changes */
+    private fun identifyAndLogTaskUpdates(
+        sessionId: Int,
+        preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
+        postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>
+    ) {
+        // find new tasks that were added
+        postTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
+            if (!preTransitionVisibleFreeformTasks.containsKey(taskId)) {
+                desktopModeEventLogger.logTaskAdded(sessionId, buildTaskUpdateForTask(taskInfo))
+            }
+        }
+
+        // find old tasks that were removed
+        preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
+            if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) {
+                desktopModeEventLogger.logTaskRemoved(sessionId, buildTaskUpdateForTask(taskInfo))
+            }
+        }
+    }
+
+    // TODO(b/326231724: figure out how to get taskWidth and taskHeight from TaskInfo
+    private fun buildTaskUpdateForTask(taskInfo: TaskInfo): TaskUpdate {
+        val taskUpdate = TaskUpdate(taskInfo.taskId, taskInfo.userId)
+        // add task x, y if available
+        taskInfo.positionInParent?.let { taskUpdate.copy(taskX = it.x, taskY = it.y) }
+
+        return taskUpdate
+    }
+
+    /** Get [EnterReason] for this session enter */
+    private fun getEnterReason(transitionInfo: TransitionInfo): EnterReason {
+        // TODO(b/326231756) - Add support for missing enter reasons
+        return when (transitionInfo.type) {
+            WindowManager.TRANSIT_WAKE -> EnterReason.SCREEN_ON
+            Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP -> EnterReason.APP_HANDLE_DRAG
+            Transitions.TRANSIT_MOVE_TO_DESKTOP -> EnterReason.APP_HANDLE_MENU_BUTTON
+            WindowManager.TRANSIT_OPEN -> EnterReason.APP_FREEFORM_INTENT
+            else -> EnterReason.UNKNOWN_ENTER
+        }
+    }
+
+    /** Get [ExitReason] for this session exit */
+    private fun getExitReason(transitionInfo: TransitionInfo): ExitReason {
+        // TODO(b/326231756) - Add support for missing exit reasons
+        return when {
+            transitionInfo.type == WindowManager.TRANSIT_SLEEP -> ExitReason.SCREEN_OFF
+            transitionInfo.type == WindowManager.TRANSIT_CLOSE -> ExitReason.TASK_FINISHED
+            transitionInfo.type == Transitions.TRANSIT_EXIT_DESKTOP_MODE -> ExitReason.DRAG_TO_EXIT
+            transitionInfo.isRecentsTransition() -> ExitReason.RETURN_HOME_OR_OVERVIEW
+            else -> ExitReason.UNKNOWN_EXIT
+        }
+    }
+
+    /** Adds tasks to the saved copy of freeform taskId, taskInfo. Only used for testing. */
+    @VisibleForTesting
+    fun addTaskInfosToCachedMap(taskInfo: TaskInfo) {
+        visibleFreeformTaskInfos.set(taskInfo.taskId, taskInfo)
+    }
+
+    @VisibleForTesting fun getLoggerSessionId(): Int? = loggerInstanceId?.id
+
+    @VisibleForTesting
+    fun setLoggerSessionId(id: Int) {
+        loggerInstanceId = InstanceId.fakeInstanceId(id)
+    }
+
+    private fun TransitionInfo.Change.requireTaskInfo(): RunningTaskInfo {
+        return this.taskInfo ?: throw IllegalStateException("Expected TaskInfo in the Change")
+    }
+
+    private fun TaskInfo.isFreeformWindow(): Boolean {
+        return this.windowingMode == WINDOWING_MODE_FREEFORM
+    }
+
+    private fun TransitionInfo.isRecentsTransition(): Boolean {
+        return this.type == WindowManager.TRANSIT_TO_FRONT &&
+            this.flags == WindowManager.TRANSIT_FLAG_IS_RECENTS
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 88949b2a..494d893 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -16,23 +16,19 @@
 
 package com.android.wm.shell.desktopmode;
 
+import android.annotation.NonNull;
+import android.content.Context;
 import android.os.SystemProperties;
 
-import com.android.wm.shell.Flags;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
+import com.android.wm.shell.R;
 
 /**
  * Constants for desktop mode feature
  */
 public class DesktopModeStatus {
 
-    private static final boolean ENABLE_DESKTOP_WINDOWING = Flags.enableDesktopWindowing();
-
-    /**
-     * Flag to indicate whether desktop mode proto is available on the device
-     */
-    private static final boolean IS_PROTO2_ENABLED = SystemProperties.getBoolean(
-            "persist.wm.debug.desktop_mode_2", false);
-
     /**
      * Flag to indicate whether task resizing is veiled.
      */
@@ -75,16 +71,16 @@
             "persist.wm.debug.desktop_use_rounded_corners", true);
 
     /**
-     * Return {@code true} is desktop windowing proto 2 is enabled
+     * Flag to indicate whether to restrict desktop mode to supported devices.
+     */
+    private static final boolean ENFORCE_DEVICE_RESTRICTIONS = SystemProperties.getBoolean(
+            "persist.wm.debug.desktop_mode_enforce_device_restrictions", true);
+
+    /**
+     * Return {@code true} if desktop windowing is enabled
      */
     public static boolean isEnabled() {
-        // Check for aconfig flag first
-        if (ENABLE_DESKTOP_WINDOWING) {
-            return true;
-        }
-        // Fall back to sysprop flag
-        // TODO(b/304778354): remove sysprop once desktop aconfig flag supports dynamic overriding
-        return IS_PROTO2_ENABLED;
+        return Flags.enableDesktopWindowingMode();
     }
 
     /**
@@ -118,4 +114,27 @@
     public static boolean useRoundedCorners() {
         return USE_ROUNDED_CORNERS;
     }
+
+    /**
+     * Return {@code true} if desktop mode should be restricted to supported devices.
+     */
+    @VisibleForTesting
+    public static boolean enforceDeviceRestrictions() {
+        return ENFORCE_DEVICE_RESTRICTIONS;
+    }
+
+    /**
+     * Return {@code true} if the current device supports desktop mode.
+     */
+    @VisibleForTesting
+    public static boolean isDesktopModeSupported(@NonNull Context context) {
+        return context.getResources().getBoolean(R.bool.config_isDesktopModeSupported);
+    }
+
+    /**
+     * Return {@code true} if desktop mode can be entered on the current device.
+     */
+    public static boolean canEnterDesktopMode(@NonNull Context context) {
+        return !enforceDeviceRestrictions() || isDesktopModeSupported(context);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 7091c4b..fb0ed15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -98,6 +98,7 @@
      * Based on the coordinates of the current drag event, determine which indicator type we should
      * display, including no visible indicator.
      */
+    @NonNull
     IndicatorType updateIndicatorType(PointF inputCoordinates, int windowingMode) {
         final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
         // If we are in freeform, we don't want a visible indicator in the "freeform" drag zone.
@@ -136,18 +137,18 @@
     Region calculateFullscreenRegion(DisplayLayout layout,
             @WindowConfiguration.WindowingMode int windowingMode, int captionHeight) {
         final Region region = new Region();
-        int edgeTransitionHeight = mContext.getResources().getDimensionPixelSize(
-                com.android.wm.shell.R.dimen.desktop_mode_transition_area_height);
+        int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
+                ? 2 * layout.stableInsets().top
+                : mContext.getResources().getDimensionPixelSize(
+                        com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height);
         // A thin, short Rect at the top of the screen.
         if (windowingMode == WINDOWING_MODE_FREEFORM) {
             int fromFreeformWidth = mContext.getResources().getDimensionPixelSize(
                     com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_width);
-            int fromFreeformHeight = mContext.getResources().getDimensionPixelSize(
-                    com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height);
             region.union(new Rect((layout.width() / 2) - (fromFreeformWidth / 2),
                     -captionHeight,
                     (layout.width() / 2) + (fromFreeformWidth / 2),
-                    fromFreeformHeight));
+                    transitionHeight));
         }
         // A screen-wide, shorter Rect if the task is in fullscreen or split.
         if (windowingMode == WINDOWING_MODE_FULLSCREEN
@@ -155,7 +156,7 @@
             region.union(new Rect(0,
                     -captionHeight,
                     layout.width(),
-                    edgeTransitionHeight));
+                    transitionHeight));
         }
         return region;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index dcffb2d..dd8c1a0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -102,6 +102,7 @@
         ToggleResizeDesktopTaskTransitionHandler,
         private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler,
         private val desktopModeTaskRepository: DesktopModeTaskRepository,
+        private val desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver,
         private val launchAdjacentController: LaunchAdjacentController,
         private val recentsTransitionHandler: RecentsTransitionHandler,
         private val multiInstanceHelper: MultiInstanceHelper,
@@ -141,7 +142,7 @@
 
     private val transitionAreaHeight
         get() = context.resources.getDimensionPixelSize(
-                com.android.wm.shell.R.dimen.desktop_mode_transition_area_height
+                com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height
         )
 
     private val transitionAreaWidth
@@ -306,6 +307,12 @@
             task: RunningTaskInfo,
             wct: WindowContainerTransaction = WindowContainerTransaction()
     ) {
+        if (!DesktopModeStatus.canEnterDesktopMode(context)) {
+            KtProtoLog.w(
+                WM_SHELL_DESKTOP_MODE, "DesktopTasksController: Cannot enter desktop, " +
+                        "display does not meet minimum size requirements")
+            return
+        }
         KtProtoLog.v(
             WM_SHELL_DESKTOP_MODE,
             "DesktopTasksController: moveToDesktop taskId=%d",
@@ -381,6 +388,12 @@
         }
     }
 
+    /** Enter fullscreen by moving the focused freeform task in given `displayId` to fullscreen. */
+    fun enterFullscreen(displayId: Int) {
+        getFocusedFreeformTask(displayId)
+                ?.let { moveToFullscreenWithAnimation(it, it.positionInParent) }
+    }
+
     /** Move a desktop app to split screen. */
     fun moveToSplit(task: RunningTaskInfo) {
         KtProtoLog.v(
@@ -405,23 +418,9 @@
                     splitScreenController.getStageOfTask(taskInfo.taskId),
                     EXIT_REASON_DESKTOP_MODE
             )
-            getOtherSplitTask(taskInfo.taskId)?.let { otherTaskInfo ->
-                wct.removeTask(otherTaskInfo.token)
-            }
         }
     }
 
-    private fun getOtherSplitTask(taskId: Int): RunningTaskInfo? {
-        val remainingTaskPosition: Int =
-                if (splitScreenController.getSplitPosition(taskId)
-                        == SPLIT_POSITION_BOTTOM_OR_RIGHT) {
-                    SPLIT_POSITION_TOP_OR_LEFT
-                } else {
-                    SPLIT_POSITION_BOTTOM_OR_RIGHT
-                }
-        return splitScreenController.getTaskInfo(remainingTaskPosition)
-    }
-
     /**
      * The second part of the animated drag to desktop transition, called after
      * [startDragToDesktop].
@@ -568,30 +567,7 @@
      * @param position the portion of the screen (RIGHT or LEFT) we want to snap the task to.
      */
     fun snapToHalfScreen(taskInfo: RunningTaskInfo, position: SnapPosition) {
-        val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
-
-        val stableBounds = Rect()
-        displayLayout.getStableBounds(stableBounds)
-
-        val destinationWidth = stableBounds.width() / 2
-        val destinationBounds = when (position) {
-            SnapPosition.LEFT -> {
-                Rect(
-                        stableBounds.left,
-                        stableBounds.top,
-                        stableBounds.left + destinationWidth,
-                        stableBounds.bottom
-                )
-            }
-            SnapPosition.RIGHT -> {
-                Rect(
-                        stableBounds.right - destinationWidth,
-                        stableBounds.top,
-                        stableBounds.right,
-                        stableBounds.bottom
-                )
-            }
-        }
+        val destinationBounds = getSnapBounds(taskInfo, position)
 
         if (destinationBounds == taskInfo.configuration.windowConfiguration.bounds) return
 
@@ -612,8 +588,35 @@
         outBounds.set(0, 0, desiredWidth, desiredHeight)
         // Center the task in screen bounds
         outBounds.offset(
-                screenBounds.centerX() - outBounds.centerX(),
-                screenBounds.centerY() - outBounds.centerY())
+            screenBounds.centerX() - outBounds.centerX(),
+            screenBounds.centerY() - outBounds.centerY())
+    }
+
+    private fun getSnapBounds(taskInfo: RunningTaskInfo, position: SnapPosition): Rect {
+        val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return Rect()
+
+        val stableBounds = Rect()
+        displayLayout.getStableBounds(stableBounds)
+
+        val destinationWidth = stableBounds.width() / 2
+        return when (position) {
+            SnapPosition.LEFT -> {
+                Rect(
+                    stableBounds.left,
+                    stableBounds.top,
+                    stableBounds.left + destinationWidth,
+                    stableBounds.bottom
+                )
+            }
+            SnapPosition.RIGHT -> {
+                Rect(
+                    stableBounds.right - destinationWidth,
+                    stableBounds.top,
+                    stableBounds.right,
+                    stableBounds.bottom
+                )
+            }
+        }
     }
 
     /**
@@ -649,7 +652,7 @@
             ?.let { homeTask -> wct.reorder(homeTask.getToken(), true /* onTop */) }
     }
 
-    private fun releaseVisualIndicator() {
+    fun releaseVisualIndicator() {
         val t = SurfaceControl.Transaction()
         visualIndicator?.releaseVisualIndicator(t)
         visualIndicator = null
@@ -869,12 +872,28 @@
         wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
     }
 
+    /** Enter split by using the focused desktop task in given `displayId`. */
+    fun enterSplit(
+        displayId: Int,
+        leftOrTop: Boolean
+    ) {
+        getFocusedFreeformTask(displayId)?.let { requestSplit(it, leftOrTop) }
+    }
+
+    private fun getFocusedFreeformTask(displayId: Int): RunningTaskInfo? {
+        return shellTaskOrganizer.getRunningTasks(displayId)
+                .find { taskInfo -> taskInfo.isFocused &&
+                        taskInfo.windowingMode == WINDOWING_MODE_FREEFORM }
+    }
+
     /**
      * Requests a task be transitioned from desktop to split select. Applies needed windowing
      * changes if this transition is enabled.
      */
+    @JvmOverloads
     fun requestSplit(
-        taskInfo: RunningTaskInfo
+        taskInfo: RunningTaskInfo,
+        leftOrTop: Boolean = false,
     ) {
         val windowingMode = taskInfo.windowingMode
         if (windowingMode == WINDOWING_MODE_FULLSCREEN || windowingMode == WINDOWING_MODE_FREEFORM
@@ -882,7 +901,8 @@
             val wct = WindowContainerTransaction()
             addMoveToSplitChanges(wct, taskInfo)
             splitScreenController.requestEnterSplitSelect(taskInfo, wct,
-                SPLIT_POSITION_BOTTOM_OR_RIGHT, taskInfo.configuration.windowConfiguration.bounds)
+                if (leftOrTop) SPLIT_POSITION_TOP_OR_LEFT else SPLIT_POSITION_BOTTOM_OR_RIGHT,
+                taskInfo.configuration.windowConfiguration.bounds)
         }
     }
 
@@ -930,16 +950,13 @@
         taskSurface: SurfaceControl,
         inputX: Float,
         taskTop: Float
-    ) {
+    ): DesktopModeVisualIndicator.IndicatorType {
         // If the visual indicator does not exist, create it.
-        if (visualIndicator == null) {
-            visualIndicator = DesktopModeVisualIndicator(
-                syncQueue, taskInfo, displayController, context, taskSurface,
-                rootTaskDisplayAreaOrganizer)
-        }
-        // Then, update the indicator type.
-        val indicator = visualIndicator ?: return
-        indicator.updateIndicatorType(PointF(inputX, taskTop), taskInfo.windowingMode)
+        val indicator = visualIndicator ?: DesktopModeVisualIndicator(
+            syncQueue, taskInfo, displayController, context, taskSurface,
+            rootTaskDisplayAreaOrganizer)
+        if (visualIndicator == null) visualIndicator = indicator
+        return indicator.updateIndicatorType(PointF(inputX, taskTop), taskInfo.windowingMode)
     }
 
     /**
@@ -959,20 +976,28 @@
         if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
             return
         }
-        if (taskBounds.top <= transitionAreaHeight) {
-            moveToFullscreenWithAnimation(taskInfo, position)
-            return
-        }
-        if (inputCoordinate.x <= transitionAreaWidth) {
-            releaseVisualIndicator()
-            snapToHalfScreen(taskInfo, SnapPosition.LEFT)
-            return
-        }
-        if (inputCoordinate.x >= (displayController.getDisplayLayout(taskInfo.displayId)?.width()
-            ?.minus(transitionAreaWidth) ?: return)) {
-            releaseVisualIndicator()
-            snapToHalfScreen(taskInfo, SnapPosition.RIGHT)
-            return
+
+        val indicator = visualIndicator ?: return
+        val indicatorType = indicator.updateIndicatorType(
+            PointF(inputCoordinate.x, taskBounds.top.toFloat()),
+            taskInfo.windowingMode
+        )
+        when (indicatorType) {
+            DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
+                moveToFullscreenWithAnimation(taskInfo, position)
+            }
+            DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
+                releaseVisualIndicator()
+                snapToHalfScreen(taskInfo, SnapPosition.LEFT)
+            }
+            DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
+                releaseVisualIndicator()
+                snapToHalfScreen(taskInfo, SnapPosition.RIGHT)
+            }
+            DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
+            DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR -> {
+                releaseVisualIndicator()
+            }
         }
         // A freeform drag-move ended, remove the indicator immediately.
         releaseVisualIndicator()
@@ -985,14 +1010,28 @@
      * @param y height of drag, to be checked against status bar height.
      */
     fun onDragPositioningEndThroughStatusBar(
+            inputCoordinates: PointF,
             taskInfo: RunningTaskInfo,
             freeformBounds: Rect
     ) {
-        finalizeDragToDesktop(taskInfo, freeformBounds)
-    }
-
-    private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int {
-        return displayController.getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
+        val indicator = visualIndicator ?: return
+        val indicatorType = indicator
+            .updateIndicatorType(inputCoordinates, taskInfo.windowingMode)
+        when (indicatorType) {
+            DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
+                finalizeDragToDesktop(taskInfo, freeformBounds)
+            }
+            DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR,
+                    DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
+                cancelDragToDesktop(taskInfo)
+            }
+            DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
+                finalizeDragToDesktop(taskInfo, getSnapBounds(taskInfo, SnapPosition.LEFT))
+            }
+            DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
+                finalizeDragToDesktop(taskInfo, getSnapBounds(taskInfo, SnapPosition.RIGHT))
+            }
+        }
     }
 
     /**
@@ -1108,6 +1147,18 @@
                 this@DesktopTasksController.enterDesktop(displayId)
             }
         }
+
+        override fun moveFocusedTaskToFullscreen(displayId: Int) {
+            mainExecutor.execute {
+                this@DesktopTasksController.enterFullscreen(displayId)
+            }
+        }
+
+        override fun moveFocusedTaskToStageSplit(displayId: Int, leftOrTop: Boolean) {
+            mainExecutor.execute {
+                this@DesktopTasksController.enterSplit(displayId, leftOrTop)
+            }
+        }
     }
 
     /** The interface for calls from outside the host process. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 1afbdf9..7da1b23 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -59,7 +59,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.launcher3.icons.IconProvider;
@@ -316,12 +315,11 @@
                     return false;
                 }
                 // TODO(b/290391688): Also update the session data with task stack changes
-                InstanceId loggerSessionId = mLogger.logStart(event);
-                pd.activeDragCount++;
-                pd.dragSession = new DragSession(mContext, ActivityTaskManager.getInstance(),
+                pd.dragSession = new DragSession(ActivityTaskManager.getInstance(),
                         mDisplayController.getDisplayLayout(displayId), event.getClipData());
                 pd.dragSession.update();
-                pd.dragLayout.prepare(pd.dragSession, loggerSessionId);
+                pd.activeDragCount++;
+                pd.dragLayout.prepare(pd.dragSession, mLogger.logStart(pd.dragSession));
                 setDropTargetWindowVisibility(pd, View.VISIBLE);
                 notifyListeners(l -> {
                     l.onDragStarted();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java
index 2a7dd5a..75b126c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java
@@ -53,17 +53,21 @@
     /**
      * Logs the start of a drag.
      */
-    public InstanceId logStart(DragEvent event) {
-        final ClipDescription description = event.getClipDescription();
-        final ClipData data = event.getClipData();
-        final ClipData.Item item = data.getItemAt(0);
-        mInstanceId = item.getIntent().getParcelableExtra(
-                ClipDescription.EXTRA_LOGGING_INSTANCE_ID);
+    public InstanceId logStart(DragSession session) {
+        mInstanceId = session.appData != null
+                ? session.appData.getParcelableExtra(ClipDescription.EXTRA_LOGGING_INSTANCE_ID,
+                        InstanceId.class)
+                : null;
         if (mInstanceId == null) {
             mInstanceId = mIdSequence.newInstanceId();
         }
-        mActivityInfo = item.getActivityInfo();
-        log(getStartEnum(description), mActivityInfo);
+        mActivityInfo = session.activityInfo;
+        if (session.appData != null) {
+            log(getStartEnum(session.getClipDescription()), mActivityInfo);
+        } else {
+            // TODO(b/255649902): Update this once we have a new enum
+            log(DragAndDropUiEventEnum.GLOBAL_APP_DRAG_START_ACTIVITY, mActivityInfo);
+        }
         return mInstanceId;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index a31a773..eb82da8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -29,6 +29,8 @@
 import static android.content.Intent.EXTRA_SHORTCUT_ID;
 import static android.content.Intent.EXTRA_TASK_ID;
 import static android.content.Intent.EXTRA_USER;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 
 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;
@@ -52,9 +54,11 @@
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.util.Log;
 import android.util.Slog;
 
 import androidx.annotation.IntDef;
@@ -63,8 +67,10 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.logging.InstanceId;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import java.lang.annotation.Retention;
@@ -104,7 +110,9 @@
     void start(DragSession session, InstanceId loggerSessionId) {
         mLoggerSessionId = loggerSessionId;
         mSession = session;
-        RectF disallowHitRegion = (RectF) mSession.dragData.getExtra(EXTRA_DISALLOW_HIT_REGION);
+        RectF disallowHitRegion = mSession.appData != null
+                ? (RectF) mSession.appData.getExtra(EXTRA_DISALLOW_HIT_REGION)
+                : null;
         if (disallowHitRegion == null) {
             mDisallowHitRegion.setEmpty();
         } else {
@@ -223,7 +231,7 @@
     }
 
     @VisibleForTesting
-    void handleDrop(Target target, ClipData data) {
+    void handleDrop(Target target) {
         if (target == null || !mTargets.contains(target)) {
             return;
         }
@@ -238,41 +246,77 @@
             mSplitScreen.onDroppedToSplit(position, mLoggerSessionId);
         }
 
-        final ClipDescription description = data.getDescription();
-        final Intent dragData = mSession.dragData;
-        startClipDescription(description, dragData, position);
+        if (mSession.appData != null) {
+            launchApp(mSession, position);
+        } else {
+            launchIntent(mSession, position);
+        }
     }
 
-    private void startClipDescription(ClipDescription description, Intent intent,
-            @SplitPosition int position) {
+    /**
+     * Launches an app provided by SysUI.
+     */
+    private void launchApp(DragSession session, @SplitPosition int position) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching app data at position=%d",
+                position);
+        final ClipDescription description = session.getClipDescription();
         final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK);
         final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
         final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic();
         baseActivityOpts.setDisallowEnterPictureInPictureWhileLaunching(true);
         final Bundle opts = baseActivityOpts.toBundle();
-        if (intent.hasExtra(EXTRA_ACTIVITY_OPTIONS)) {
-            opts.putAll(intent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS));
+        if (session.appData.hasExtra(EXTRA_ACTIVITY_OPTIONS)) {
+            opts.putAll(session.appData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS));
         }
         // Put BAL flags to avoid activity start aborted.
         opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
         opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
-        final UserHandle user = intent.getParcelableExtra(EXTRA_USER);
+        final UserHandle user = session.appData.getParcelableExtra(EXTRA_USER);
 
         if (isTask) {
-            final int taskId = intent.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
+            final int taskId = session.appData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
             mStarter.startTask(taskId, position, opts);
         } else if (isShortcut) {
-            final String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
-            final String id = intent.getStringExtra(EXTRA_SHORTCUT_ID);
+            final String packageName = session.appData.getStringExtra(EXTRA_PACKAGE_NAME);
+            final String id = session.appData.getStringExtra(EXTRA_SHORTCUT_ID);
             mStarter.startShortcut(packageName, id, position, opts, user);
         } else {
-            final PendingIntent launchIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT);
+            final PendingIntent launchIntent =
+                    session.appData.getParcelableExtra(EXTRA_PENDING_INTENT);
+            if (Build.IS_DEBUGGABLE) {
+                if (!user.equals(launchIntent.getCreatorUserHandle())) {
+                    Log.e(TAG, "Expected app intent's EXTRA_USER to match pending intent user");
+                }
+            }
             mStarter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */,
                     position, opts);
         }
     }
 
     /**
+     * Launches an intent sender provided by an application.
+     */
+    private void launchIntent(DragSession session, @SplitPosition int position) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching intent at position=%d",
+                position);
+        final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic();
+        baseActivityOpts.setDisallowEnterPictureInPictureWhileLaunching(true);
+        // TODO(b/255649902): Rework this so that SplitScreenController can always use the options
+        // instead of a fillInIntent since it's assuming that the PendingIntent is mutable
+        baseActivityOpts.setPendingIntentLaunchFlags(FLAG_ACTIVITY_NEW_TASK
+                | FLAG_ACTIVITY_MULTIPLE_TASK);
+
+        final Bundle opts = baseActivityOpts.toBundle();
+        // Put BAL flags to avoid activity start aborted.
+        opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
+        opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
+
+        mStarter.startIntent(session.launchableIntent,
+                session.launchableIntent.getCreatorUserHandle().getIdentifier(),
+                null /* fillIntent */, position, opts);
+    }
+
+    /**
      * Interface for actually committing the task launches.
      */
     public interface Starter {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 619f624..ecb53dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -22,6 +22,8 @@
 import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS;
 import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
 
 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;
@@ -38,12 +40,15 @@
 import android.app.StatusBarManager;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Insets;
 import android.graphics.Rect;
+import android.graphics.Region;
 import android.graphics.drawable.Drawable;
 import android.view.DragEvent;
 import android.view.SurfaceControl;
+import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.WindowInsets.Type;
 import android.widget.LinearLayout;
@@ -65,7 +70,8 @@
 /**
  * Coordinates the visible drop targets for the current drag within a single display.
  */
-public class DragLayout extends LinearLayout {
+public class DragLayout extends LinearLayout
+        implements ViewTreeObserver.OnComputeInternalInsetsListener {
 
     // While dragging the status bar is hidden.
     private static final int HIDE_STATUS_BAR_FLAGS = StatusBarManager.DISABLE_NOTIFICATION_ICONS
@@ -90,7 +96,9 @@
 
     private int mDisplayMargin;
     private int mDividerSize;
+    private int mLaunchIntentEdgeMargin;
     private Insets mInsets = Insets.NONE;
+    private Region mTouchableRegion;
 
     private boolean mIsShowing;
     private boolean mHasDropped;
@@ -106,10 +114,11 @@
         mStatusBarManager = context.getSystemService(StatusBarManager.class);
         mLastConfiguration.setTo(context.getResources().getConfiguration());
 
-        mDisplayMargin = context.getResources().getDimensionPixelSize(
-                R.dimen.drop_layout_display_margin);
-        mDividerSize = context.getResources().getDimensionPixelSize(
-                R.dimen.split_divider_bar_width);
+        final Resources res = context.getResources();
+        mDisplayMargin = res.getDimensionPixelSize(R.dimen.drop_layout_display_margin);
+        mDividerSize = res.getDimensionPixelSize(R.dimen.split_divider_bar_width);
+        mLaunchIntentEdgeMargin =
+                res.getDimensionPixelSize(R.dimen.drag_launchable_intent_edge_margin);
 
         // Always use LTR because we assume dropZoneView1 is on the left and 2 is on the right when
         // showing the highlight.
@@ -131,6 +140,66 @@
     }
 
     @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mTouchableRegion = Region.obtain();
+        getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+        mTouchableRegion.recycle();
+    }
+
+    @Override
+    public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inOutInfo) {
+        if (mSession != null && mSession.launchableIntent != null) {
+            inOutInfo.touchableRegion.set(mTouchableRegion);
+            inOutInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        updateTouchableRegion();
+    }
+
+    /**
+     * Updates the touchable region, this should be called after any configuration changes have
+     * been applied.
+     */
+    private void updateTouchableRegion() {
+        mTouchableRegion.setEmpty();
+        if (mSession != null && mSession.launchableIntent != null) {
+            final int width = getMeasuredWidth();
+            final int height = getMeasuredHeight();
+            if (mIsLeftRightSplit) {
+                mTouchableRegion.union(
+                        new Rect(0, 0, mInsets.left + mLaunchIntentEdgeMargin, height));
+                mTouchableRegion.union(
+                        new Rect(width - mInsets.right - mLaunchIntentEdgeMargin, 0, width,
+                                height));
+            } else {
+                mTouchableRegion.union(
+                        new Rect(0, 0, width, mInsets.top + mLaunchIntentEdgeMargin));
+                mTouchableRegion.union(
+                        new Rect(0, height - mInsets.bottom - mLaunchIntentEdgeMargin, width,
+                                height));
+            }
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+                    "Updating drag layout width=%d height=%d touchable region=%s",
+                    width, height, mTouchableRegion);
+
+            // Reapply insets to update the touchable region
+            requestApplyInsets();
+        }
+    }
+
+
+    @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
         mInsets = insets.getInsets(Type.tappableElement() | Type.displayCutout());
         recomputeDropTargets();
@@ -164,6 +233,7 @@
             mDropZoneView2.onThemeChange();
         }
         mLastConfiguration.setTo(newConfig);
+        requestLayout();
     }
 
     private void updateContainerMarginsForSingleTask() {
@@ -242,6 +312,7 @@
             mSplitScreenController.getStageBounds(topOrLeftBounds, bottomOrRightBounds);
             updateDropZoneSizes(topOrLeftBounds, bottomOrRightBounds);
         }
+        requestLayout();
     }
 
     private void updateDropZoneSizesForSingleTask() {
@@ -392,7 +463,7 @@
         mHasDropped = true;
 
         // Process the drop
-        mPolicy.handleDrop(mCurrentTarget, event.getClipData());
+        mPolicy.handleDrop(mCurrentTarget);
 
         // Start animating the drop UI out with the drag surface
         hide(event, dropCompleteCallback);
@@ -505,5 +576,7 @@
         pw.println(innerPrefix + "mIsShowing=" + mIsShowing);
         pw.println(innerPrefix + "mHasDropped=" + mHasDropped);
         pw.println(innerPrefix + "mCurrentTarget=" + mCurrentTarget);
+        pw.println(innerPrefix + "mInsets=" + mInsets);
+        pw.println(innerPrefix + "mTouchableRegion=" + mTouchableRegion);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
index 353d702..8f1bc59 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
@@ -21,12 +21,15 @@
 
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
 import android.app.WindowConfiguration;
 import android.content.ClipData;
-import android.content.Context;
+import android.content.ClipDescription;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 
+import androidx.annotation.Nullable;
+
 import com.android.wm.shell.common.DisplayLayout;
 
 import java.util.List;
@@ -39,7 +42,18 @@
     private final ClipData mInitialDragData;
 
     final DisplayLayout displayLayout;
-    Intent dragData;
+    // The activity info associated with the activity in the appData or the launchableIntent
+    @Nullable
+    ActivityInfo activityInfo;
+    // The intent bundle that includes data about an app-type drag that is started by
+    // Launcher/SysUI.  Only one of appDragData OR launchableIntent will be non-null for a session.
+    @Nullable
+    Intent appData;
+    // A launchable intent that is specified in the ClipData directly.
+    // Only one of appDragData OR launchableIntent will be non-null for a session.
+    @Nullable
+    PendingIntent launchableIntent;
+    // Stores the current running task at the time that the drag was initiated
     ActivityManager.RunningTaskInfo runningTaskInfo;
     @WindowConfiguration.WindowingMode
     int runningTaskWinMode = WINDOWING_MODE_UNDEFINED;
@@ -47,7 +61,7 @@
     int runningTaskActType = ACTIVITY_TYPE_STANDARD;
     boolean dragItemSupportsSplitscreen;
 
-    DragSession(Context context, ActivityTaskManager activityTaskManager,
+    DragSession(ActivityTaskManager activityTaskManager,
             DisplayLayout dispLayout, ClipData data) {
         mActivityTaskManager = activityTaskManager;
         mInitialDragData = data;
@@ -55,6 +69,14 @@
     }
 
     /**
+     * Returns the clip description associated with the drag.
+     * @return
+     */
+    ClipDescription getClipDescription() {
+        return mInitialDragData.getDescription();
+    }
+
+    /**
      * Updates the session data based on the current state of the system.
      */
     void update() {
@@ -67,9 +89,11 @@
             runningTaskActType = task.getActivityType();
         }
 
-        final ActivityInfo info = mInitialDragData.getItemAt(0).getActivityInfo();
-        dragItemSupportsSplitscreen = info == null
-                || ActivityInfo.isResizeableMode(info.resizeMode);
-        dragData = mInitialDragData.getItemAt(0).getIntent();
+        activityInfo = mInitialDragData.getItemAt(0).getActivityInfo();
+        // TODO: This should technically check & respect config_supportsNonResizableMultiWindow
+        dragItemSupportsSplitscreen = activityInfo == null
+                || ActivityInfo.isResizeableMode(activityInfo.resizeMode);
+        appData = mInitialDragData.getItemAt(0).getIntent();
+        launchableIntent = DragUtils.getLaunchIntent(mInitialDragData);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
index f7bcc94..24f8e18 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
@@ -36,8 +36,21 @@
      * Returns whether we can handle this particular drag.
      */
     public static boolean canHandleDrag(DragEvent event) {
-        return event.getClipData().getItemCount() > 0
-                && (isAppDrag(event.getClipDescription()));
+        if (event.getClipData().getItemCount() <= 0) {
+            // No clip data, ignore this drag
+            return false;
+        }
+        if (isAppDrag(event.getClipDescription())) {
+            // Clip data contains an app drag initiated from SysUI, handle it
+            return true;
+        }
+        if (com.android.window.flags.Flags.delegateUnhandledDrags()
+                && getLaunchIntent(event) != null) {
+            // Clip data contains a launchable intent drag, handle it
+            return true;
+        }
+        // Otherwise ignore
+        return false;
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index c7daf56..87e372c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -63,6 +63,7 @@
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.os.SystemProperties;
+import android.util.Rational;
 import android.view.Choreographer;
 import android.view.Display;
 import android.view.Surface;
@@ -126,6 +127,8 @@
             SystemProperties.getInt(
                     "persist.wm.debug.extra_content_overlay_fade_out_delay_ms", 400);
 
+    private static final float PIP_ASPECT_RATIO_MISMATCH_THRESHOLD = 0.005f;
+
     private final Context mContext;
     private final SyncTransactionQueue mSyncTransactionQueue;
     private final PipBoundsState mPipBoundsState;
@@ -767,6 +770,37 @@
                     mPictureInPictureParams.getTitle());
             mPipParamsChangedForwarder.notifySubtitleChanged(
                     mPictureInPictureParams.getSubtitle());
+
+            if (mPictureInPictureParams.hasSourceBoundsHint()
+                    && mPictureInPictureParams.hasSetAspectRatio()) {
+                Rational sourceRectHintAspectRatio = new Rational(
+                        mPictureInPictureParams.getSourceRectHint().width(),
+                        mPictureInPictureParams.getSourceRectHint().height());
+                if (sourceRectHintAspectRatio.compareTo(
+                        mPictureInPictureParams.getAspectRatio()) != 0) {
+                    ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                            "Aspect ratio of source rect hint (%d/%d) does not match the provided "
+                                    + "aspect ratio value (%d/%d). Consider matching them for "
+                                    + "improved animation. Future releases might override the "
+                                    + "value to match.",
+                            mPictureInPictureParams.getSourceRectHint().width(),
+                            mPictureInPictureParams.getSourceRectHint().height(),
+                            mPictureInPictureParams.getAspectRatio().getNumerator(),
+                            mPictureInPictureParams.getAspectRatio().getDenominator());
+                }
+                if (Math.abs(sourceRectHintAspectRatio.floatValue()
+                        - mPictureInPictureParams.getAspectRatioFloat())
+                        > PIP_ASPECT_RATIO_MISMATCH_THRESHOLD) {
+                    ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                            "Aspect ratio of source rect hint (%f) does not match the provided "
+                                    + "aspect ratio value (%f) and is above threshold of %f. "
+                                    + "Consider matching them for improved animation. Future "
+                                    + "releases might override the value to match.",
+                            sourceRectHintAspectRatio.floatValue(),
+                            mPictureInPictureParams.getAspectRatioFloat(),
+                            PIP_ASPECT_RATIO_MISMATCH_THRESHOLD);
+                }
+            }
         }
 
         mPipUiEventLoggerLogger.setTaskInfo(mTaskInfo);
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 2cdec81..139cde2 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
@@ -122,6 +122,8 @@
     private static final long PIP_KEEP_CLEAR_AREAS_DELAY =
             SystemProperties.getLong("persist.wm.debug.pip_keep_clear_areas_delay", 200);
 
+    private static final long ENABLE_TOUCH_DELAY_MS = 200L;
+
     private Context mContext;
     protected ShellExecutor mMainExecutor;
     private DisplayController mDisplayController;
@@ -152,6 +154,8 @@
     private final Runnable mMovePipInResponseToKeepClearAreasChangeCallback =
             this::onKeepClearAreasChangedCallback;
 
+    private final Runnable mEnableTouchCallback = () -> mTouchHandler.setTouchEnabled(true);
+
     private void onKeepClearAreasChangedCallback() {
         if (mIsKeyguardShowingOrAnimating) {
             // early bail out if the change was caused by keyguard showing up
@@ -979,7 +983,13 @@
         // cache current min/max size
         Point minSize = mPipBoundsState.getMinSize();
         Point maxSize = mPipBoundsState.getMaxSize();
-        mPipBoundsState.updateMinMaxSize(pictureInPictureParams.getAspectRatioFloat());
+        final float aspectRatioFloat;
+        if (pictureInPictureParams.hasSetAspectRatio()) {
+            aspectRatioFloat = pictureInPictureParams.getAspectRatioFloat();
+        } else {
+            aspectRatioFloat = mPipBoundsAlgorithm.getDefaultAspectRatio();
+        }
+        mPipBoundsState.updateMinMaxSize(aspectRatioFloat);
         final Rect entryBounds = mPipTaskOrganizer.startSwipePipToHome(componentName, activityInfo,
                 pictureInPictureParams);
         // restore min/max size, as this is referenced later in OnDisplayChangingListener and needs
@@ -1037,6 +1047,7 @@
             saveReentryState(pipBounds);
         }
         // Disable touches while the animation is running
+        mMainExecutor.removeCallbacks(mEnableTouchCallback);
         mTouchHandler.setTouchEnabled(false);
         if (mPinnedStackAnimationRecentsCallback != null) {
             mPinnedStackAnimationRecentsCallback.onPipAnimationStarted();
@@ -1067,7 +1078,7 @@
         InteractionJankMonitor.getInstance().end(CUJ_PIP_TRANSITION);
 
         // Re-enable touches after the animation completes
-        mTouchHandler.setTouchEnabled(true);
+        mMainExecutor.executeDelayed(mEnableTouchCallback, ENABLE_TOUCH_DELAY_MS);
         mTouchHandler.onPinnedStackAnimationEnded(direction);
     }
 
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
index e73a850..4c69cc3 100644
--- 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
@@ -32,13 +32,16 @@
 
 import androidx.annotation.BinderThread;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
 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.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SingleInstanceRemoteListener;
 import com.android.wm.shell.common.pip.IPip;
 import com.android.wm.shell.common.pip.IPipAnimationListener;
 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -57,15 +60,40 @@
         DisplayController.OnDisplaysChangedListener, RemoteCallable<PipController> {
     private static final String TAG = PipController.class.getSimpleName();
 
-    private Context mContext;
-    private ShellController mShellController;
-    private DisplayController mDisplayController;
-    private DisplayInsetsController mDisplayInsetsController;
-    private PipBoundsState mPipBoundsState;
-    private PipBoundsAlgorithm mPipBoundsAlgorithm;
-    private PipDisplayLayoutState mPipDisplayLayoutState;
-    private PipScheduler mPipScheduler;
-    private ShellExecutor mMainExecutor;
+    private final Context mContext;
+    private final ShellController mShellController;
+    private final DisplayController mDisplayController;
+    private final DisplayInsetsController mDisplayInsetsController;
+    private final PipBoundsState mPipBoundsState;
+    private final PipBoundsAlgorithm mPipBoundsAlgorithm;
+    private final PipDisplayLayoutState mPipDisplayLayoutState;
+    private final PipScheduler mPipScheduler;
+    private final ShellExecutor mMainExecutor;
+
+    // Wrapper for making Binder calls into PiP animation listener hosted in launcher's Recents.
+    private PipAnimationListener mPipRecentsAnimationListener;
+
+    @VisibleForTesting
+    interface PipAnimationListener {
+        /**
+         * Notifies the listener that the Pip animation is started.
+         */
+        void onPipAnimationStarted();
+
+        /**
+         * Notifies the listener about PiP resource dimensions changed.
+         * Listener can expect an immediate callback the first time they attach.
+         *
+         * @param cornerRadius the pixel value of the corner radius, zero means it's disabled.
+         * @param shadowRadius the pixel value of the shadow radius, zero means it's disabled.
+         */
+        void onPipResourceDimensionsChanged(int cornerRadius, int shadowRadius);
+
+        /**
+         * Notifies the listener that user leaves PiP by tapping on the expand button.
+         */
+        void onExpandPip();
+    }
 
     private PipController(Context context,
             ShellInit shellInit,
@@ -92,39 +120,6 @@
         }
     }
 
-    @Override
-    public Context getContext() {
-        return mContext;
-    }
-
-    @Override
-    public ShellExecutor getRemoteCallExecutor() {
-        return mMainExecutor;
-    }
-
-    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()));
-                    }
-                });
-
-        // Allow other outside processes to bind to PiP controller using the key below.
-        mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
-                this::createExternalInterface, this);
-    }
-
     /**
      * Instantiates {@link PipController}, returns {@code null} if the feature not supported.
      */
@@ -148,20 +143,70 @@
                 pipScheduler, mainExecutor);
     }
 
+    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);
+
+        mDisplayController.addDisplayWindowListener(this);
+        mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
+                new DisplayInsetsController.OnInsetsChangedListener() {
+                    @Override
+                    public void insetsChanged(InsetsState insetsState) {
+                        onDisplayChanged(mDisplayController
+                                        .getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
+                    }
+                });
+
+        // Allow other outside processes to bind to PiP controller using the key below.
+        mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
+                this::createExternalInterface, this);
+        mShellController.addConfigurationChangeListener(this);
+    }
+
     private ExternalInterfaceBinder createExternalInterface() {
         return new IPipImpl(this);
     }
 
+    //
+    // RemoteCallable implementations
+    //
+
+    @Override
+    public Context getContext() {
+        return mContext;
+    }
+
+    @Override
+    public ShellExecutor getRemoteCallExecutor() {
+        return mMainExecutor;
+    }
+
+    //
+    // ConfigurationChangeListener implementations
+    //
+
     @Override
     public void onConfigurationChanged(Configuration newConfiguration) {
         mPipDisplayLayoutState.onConfigurationChanged();
     }
 
     @Override
+    public void onDensityOrFontScaleChanged() {
+        onPipResourceDimensionsChanged();
+    }
+
+    @Override
     public void onThemeChanged() {
         onDisplayChanged(new DisplayLayout(mContext, mContext.getDisplay()));
     }
 
+    //
+    // DisplayController.OnDisplaysChangedListener implementations
+    //
+
     @Override
     public void onDisplayAdded(int displayId) {
         if (displayId != mPipDisplayLayoutState.getDisplayId()) {
@@ -182,6 +227,10 @@
         mPipDisplayLayoutState.setDisplayLayout(layout);
     }
 
+    //
+    // IPip Binder stub helpers
+    //
+
     private Rect getSwipePipToHomeBounds(ComponentName componentName, ActivityInfo activityInfo,
             PictureInPictureParams pictureInPictureParams,
             int launcherRotation, Rect hotseatKeepClearArea) {
@@ -197,18 +246,56 @@
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "onSwipePipToHomeAnimationStart: %s", componentName);
         mPipScheduler.setInSwipePipToHomeTransition(true);
+        mPipRecentsAnimationListener.onPipAnimationStarted();
         // TODO: cache the overlay if provided for reparenting later.
     }
 
+    //
+    // IPipAnimationListener Binder proxy helpers
+    //
+
+    private void setPipRecentsAnimationListener(PipAnimationListener pipAnimationListener) {
+        mPipRecentsAnimationListener = pipAnimationListener;
+        onPipResourceDimensionsChanged();
+    }
+
+    private void onPipResourceDimensionsChanged() {
+        if (mPipRecentsAnimationListener != null) {
+            mPipRecentsAnimationListener.onPipResourceDimensionsChanged(
+                    mContext.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius),
+                    mContext.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius));
+        }
+    }
+
     /**
      * The interface for calls from outside the host process.
      */
     @BinderThread
     private static class IPipImpl extends IPip.Stub implements ExternalInterfaceBinder {
         private PipController mController;
+        private final SingleInstanceRemoteListener<PipController, IPipAnimationListener> mListener;
+        private final PipAnimationListener mPipAnimationListener = new PipAnimationListener() {
+            @Override
+            public void onPipAnimationStarted() {
+                mListener.call(l -> l.onPipAnimationStarted());
+            }
+
+            @Override
+            public void onPipResourceDimensionsChanged(int cornerRadius, int shadowRadius) {
+                mListener.call(l -> l.onPipResourceDimensionsChanged(cornerRadius, shadowRadius));
+            }
+
+            @Override
+            public void onExpandPip() {
+                mListener.call(l -> l.onExpandPip());
+            }
+        };
 
         IPipImpl(PipController controller) {
             mController = controller;
+            mListener = new SingleInstanceRemoteListener<>(mController,
+                    (cntrl) -> cntrl.setPipRecentsAnimationListener(mPipAnimationListener),
+                    (cntrl) -> cntrl.setPipRecentsAnimationListener(null));
         }
 
         /**
@@ -217,6 +304,7 @@
         @Override
         public void invalidate() {
             mController = null;
+            mListener.unregister();
         }
 
         @Override
@@ -257,7 +345,14 @@
 
         @Override
         public void setPipAnimationListener(IPipAnimationListener listener) {
-            // TODO: set a proper animation listener to update the Launcher state as needed.
+            executeRemoteCallWithTaskPermission(mController, "setPipAnimationListener",
+                    (controller) -> {
+                        if (listener != null) {
+                            mListener.register(listener);
+                        } else {
+                            mListener.unregister();
+                        }
+                    });
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 895c793..6665013 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -172,7 +172,7 @@
     }
 
     void setInSwipePipToHomeTransition(boolean inSwipePipToHome) {
-        mInSwipePipToHomeTransition = true;
+        mInSwipePipToHomeTransition = inSwipePipToHome;
     }
 
     boolean isInSwipePipToHomeTransition() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index dfb0475..d15da4a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -226,7 +226,26 @@
 
         // cache the PiP task token and leash
         mPipScheduler.setPipTaskToken(mPipTaskToken);
+        SurfaceControl pipLeash = pipChange.getLeash();
 
+        PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams;
+        Rect srcRectHint = params.getSourceRectHint();
+        Rect destinationBounds = pipChange.getEndAbsBounds();
+
+        if (PipBoundsAlgorithm.isSourceRectHintValidForEnterPip(srcRectHint, destinationBounds)) {
+            float scale = (float) destinationBounds.width() / srcRectHint.width();
+            startTransaction.setWindowCrop(pipLeash, srcRectHint);
+            startTransaction.setPosition(pipLeash,
+                    destinationBounds.left - srcRectHint.left * scale,
+                    destinationBounds.top - srcRectHint.top * scale);
+
+            // Reset the scale in case we are in the multi-activity case.
+            // TO_FRONT transition already scales down the task in single-activity case, but
+            // in multi-activity case, reparenting yields new reset scales coming from pinned task.
+            startTransaction.setScale(pipLeash, scale, scale);
+        } else {
+            // TODO(b/325481148): handle the case with invalid srcRectHint (using overlay).
+        }
         startTransaction.apply();
         finishCallback.onTransitionFinished(null);
         return true;
@@ -303,6 +322,7 @@
 
         WindowContainerTransaction wct = new WindowContainerTransaction();
         wct.movePipActivityToPinnedRootTask(pipTask.token, entryBounds);
+        wct.deferConfigToTransitionEnd(pipTask.token);
         return wct;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index e421356..1c54754 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -163,14 +163,14 @@
     /**
      * Adds a split pair. This call does not validate the taskIds, only that they are not the same.
      */
-    public void addSplitPair(int taskId1, int taskId2, SplitBounds splitBounds) {
+    public boolean addSplitPair(int taskId1, int taskId2, SplitBounds splitBounds) {
         if (taskId1 == taskId2) {
-            return;
+            return false;
         }
         if (mSplitTasks.get(taskId1, INVALID_TASK_ID) == taskId2
                 && mTaskSplitBoundsMap.get(taskId1).equals(splitBounds)) {
             // If the two tasks are already paired and the bounds are the same, then skip updating
-            return;
+            return false;
         }
         // Remove any previous pairs
         removeSplitPair(taskId1);
@@ -185,6 +185,7 @@
         notifyRecentTasksChanged();
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENT_TASKS, "Add split pair: %d, %d, %s",
                 taskId1, taskId2, splitBounds);
+        return true;
     }
 
     /**
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 b5ea1b1..235456c 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
@@ -418,6 +418,8 @@
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                     "[%d] RecentsController.start", mInstanceId);
             if (mListener == null || mTransition == null) {
+                Slog.e(TAG, "Missing listener or transition, hasListener=" + (mListener != null) +
+                        " hasTransition=" + (mTransition != null));
                 cleanUp();
                 return false;
             }
@@ -531,21 +533,31 @@
                         // Put into the "below" layer space.
                         t.setLayer(change.getLeash(), layer);
                         mOpeningTasks.add(new TaskState(change, null /* leash */));
+                    } else {
+                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                                "  unhandled root taskId=%d", taskInfo.taskId);
                     }
                 } else if (TransitionUtil.isDividerBar(change)) {
                     final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
                             belowLayers - i, info, t, mLeashMap);
                     // Add this as a app and we will separate them on launcher side by window type.
                     apps.add(target);
+                } else {
+                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                            "  unhandled change taskId=%d",
+                            taskInfo != null ? taskInfo.taskId : -1);
                 }
             }
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                    "Applying transaction=%d", t.getId());
             t.apply();
             Bundle b = new Bundle(1 /*capacity*/);
             b.putParcelable(KEY_EXTRA_SPLIT_BOUNDS,
                     mRecentTasksController.getSplitBoundsForTaskId(closingSplitTaskId));
             try {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
-                        "[%d] RecentsController.start: calling onAnimationStart", mInstanceId);
+                        "[%d] RecentsController.start: calling onAnimationStart with %d apps",
+                        mInstanceId, apps.size());
                 mListener.onAnimationStart(this,
                         apps.toArray(new RemoteAnimationTarget[apps.size()]),
                         wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
index e52235f..64e26db 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
@@ -16,11 +16,14 @@
 
 package com.android.wm.shell.splitscreen;
 
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
+
 import android.content.Context;
 import android.view.SurfaceSession;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.SyncTransactionQueue;
@@ -50,6 +53,8 @@
 
     void activate(WindowContainerTransaction wct, boolean includingTopTask) {
         if (mIsActive) return;
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "activate: main stage includingTopTask=%b",
+                includingTopTask);
 
         if (includingTopTask) {
             reparentTopTask(wct);
@@ -64,6 +69,8 @@
 
     void deactivate(WindowContainerTransaction wct, boolean toTop) {
         if (!mIsActive) return;
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "deactivate: main stage toTop=%b rootTaskInfo=%s",
+                toTop, mRootTaskInfo);
         mIsActive = false;
 
         if (mRootTaskInfo == null) return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OWNERS
index 7237d2b..37ccd15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OWNERS
@@ -1,2 +1,4 @@
 # WM shell sub-modules splitscreen owner
 chenghsiuchang@google.com
+jeremysim@google.com
+peanutbutter@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
index 9903113..f5fbae5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
@@ -16,12 +16,15 @@
 
 package com.android.wm.shell.splitscreen;
 
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
+
 import android.app.ActivityManager;
 import android.content.Context;
 import android.view.SurfaceSession;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.SyncTransactionQueue;
@@ -47,6 +50,8 @@
     }
 
     boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "remove all side stage tasks: childCount=%d toTop=%b",
+                mChildrenTaskInfo.size(), toTop);
         if (mChildrenTaskInfo.size() == 0) return false;
         wct.reparentTasks(
                 mRootTaskInfo.token,
@@ -59,6 +64,8 @@
 
     boolean removeTask(int taskId, WindowContainerToken newParent, WindowContainerTransaction wct) {
         final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.get(taskId);
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "remove side stage task: task=%d exists=%b", taskId,
+                task != null);
         if (task == null) return false;
         wct.reparent(task.token, newParent, false /* onTop */);
         return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 53dd981..952e2d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -476,7 +476,9 @@
     }
 
     public void goToFullscreenFromSplit() {
-        mStageCoordinator.goToFullscreenFromSplit();
+        if (mStageCoordinator.isSplitActive()) {
+            mStageCoordinator.goToFullscreenFromSplit();
+        }
     }
 
     /** Move the specified task to fullscreen, regardless of focus state. */
@@ -806,6 +808,9 @@
     @Override
     public void startIntent(PendingIntent intent, int userId1, @Nullable Intent fillInIntent,
             @SplitPosition int position, @Nullable Bundle options) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                "startIntent(): intent=%s user=%d fillInIntent=%s position=%d", intent, userId1,
+                fillInIntent, position);
         // Flag this as a no-user-action launch to prevent sending user leaving event to the current
         // top activity since it's going to be put into another side of the split. This prevents the
         // current top activity from going into pip mode due to user leaving event.
@@ -824,6 +829,8 @@
                 .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1))
                 .orElse(null);
         if (taskInfo != null) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                    "Found suitable background task=%s", taskInfo);
             if (ENABLE_SHELL_TRANSITIONS) {
                 mStageCoordinator.startTask(taskInfo.taskId, position, options);
             } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index b60e361..1a53a1d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -25,6 +25,8 @@
 import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.FADE_DURATION;
 import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS;
 import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
 import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString;
@@ -101,6 +103,7 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback,
             @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
             @NonNull WindowContainerToken topRoot) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "playAnimation: transition=%d", info.getDebugId());
         initTransition(transition, finishTransaction, finishCallback);
 
         final TransitSession pendingTransition = getPendingTransition(transition);
@@ -123,10 +126,12 @@
         playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot, topRoot);
     }
 
-    /** Internal funcation of playAnimation. */
+    /** Internal function of playAnimation. */
     private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
             @NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "playInternalAnimation: transition=%d",
+                info.getDebugId());
         // Play some place-holder fade animations
         final boolean isEnter = isPendingEnter(transition);
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
@@ -220,6 +225,8 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback,
             @NonNull WindowContainerToken toTopRoot, @NonNull SplitDecorManager toTopDecor,
             @NonNull WindowContainerToken topRoot) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "playDragDismissAnimation: transition=%d",
+                info.getDebugId());
         initTransition(transition, finishTransaction, finishCallback);
 
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
@@ -259,6 +266,7 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback,
             @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
             @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "playResizeAnimation: transition=%d", info.getDebugId());
         initTransition(transition, finishTransaction, finishCallback);
 
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
@@ -312,13 +320,15 @@
     @Nullable
     private TransitSession getPendingTransition(IBinder transition) {
         if (isPendingEnter(transition)) {
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved enter transition");
             return mPendingEnter;
         } else if (isPendingDismiss(transition)) {
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved dismiss transition");
             return mPendingDismiss;
         } else if (isPendingResize(transition)) {
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved resize transition");
             return mPendingResize;
         }
-
         return null;
     }
 
@@ -339,7 +349,7 @@
             Transitions.TransitionHandler handler,
             int extraTransitType, boolean resizeAnim) {
         if (mPendingEnter != null) {
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
+            ProtoLog.v(WM_SHELL_TRANSITIONS, "  splitTransition "
                     + " skip to start enter split transition since it already exist. ");
             return null;
         }
@@ -355,8 +365,10 @@
         mPendingEnter = new EnterSession(
                 transition, remoteTransition, extraTransitType, resizeAnim);
 
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
+        ProtoLog.v(WM_SHELL_TRANSITIONS, "  splitTransition "
                 + " deduced Enter split screen");
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setEnterTransition: transitType=%d resize=%b",
+                extraTransitType, resizeAnim);
     }
 
     /** Starts a transition to dismiss split. */
@@ -364,7 +376,7 @@
             Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop,
             @SplitScreenController.ExitReason int reason) {
         if (mPendingDismiss != null) {
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
+            ProtoLog.v(WM_SHELL_TRANSITIONS, "  splitTransition "
                     + " skip to start dismiss split transition since it already exist. reason to "
                     + " dismiss = %s", exitReasonToString(reason));
             return null;
@@ -381,32 +393,33 @@
             @SplitScreenController.ExitReason int reason) {
         mPendingDismiss = new DismissSession(transition, reason, dismissTop);
 
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
+        ProtoLog.v(WM_SHELL_TRANSITIONS, "  splitTransition "
                         + " deduced Dismiss due to %s. toTop=%s",
                 exitReasonToString(reason), stageTypeToString(dismissTop));
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setDismissTransition: reason=%s dismissTop=%s",
+                exitReasonToString(reason), stageTypeToString(dismissTop));
     }
 
     IBinder startResizeTransition(WindowContainerTransaction wct,
             Transitions.TransitionHandler handler,
             @Nullable TransitionConsumedCallback consumedCallback,
-            @Nullable TransitionFinishedCallback finishCallback) {
+            @Nullable TransitionFinishedCallback finishCallback,
+            @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                "  splitTransition deduced Resize split screen.");
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setResizeTransition: hasPendingResize=%b",
+                mPendingResize != null);
         if (mPendingResize != null) {
+            mainDecor.cancelRunningAnimations();
+            sideDecor.cancelRunningAnimations();
             mPendingResize.cancel(null);
             mAnimations.clear();
             onFinish(null /* wct */);
         }
 
         IBinder transition = mTransitions.startTransition(TRANSIT_CHANGE, wct, handler);
-        setResizeTransition(transition, consumedCallback, finishCallback);
-        return transition;
-    }
-
-    void setResizeTransition(@NonNull IBinder transition,
-            @Nullable TransitionConsumedCallback consumedCallback,
-            @Nullable TransitionFinishedCallback finishCallback) {
         mPendingResize = new TransitSession(transition, consumedCallback, finishCallback);
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
-                + " deduced Resize split screen");
+        return transition;
     }
 
     void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
@@ -444,12 +457,15 @@
 
             mPendingEnter.onConsumed(aborted);
             mPendingEnter = null;
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for enter transition");
         } else if (isPendingDismiss(transition)) {
             mPendingDismiss.onConsumed(aborted);
             mPendingDismiss = null;
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for dismiss transition");
         } else if (isPendingResize(transition)) {
             mPendingResize.onConsumed(aborted);
             mPendingResize = null;
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for resize transition");
         }
 
         // TODO: handle transition consumed for active remote handler
@@ -462,12 +478,15 @@
         if (isPendingEnter(mAnimatingTransition)) {
             mPendingEnter.onFinished(wct, mFinishTransaction);
             mPendingEnter = null;
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for enter transition");
         } else if (isPendingDismiss(mAnimatingTransition)) {
             mPendingDismiss.onFinished(wct, mFinishTransaction);
             mPendingDismiss = null;
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for dismiss transition");
         } else if (isPendingResize(mAnimatingTransition)) {
             mPendingResize.onFinished(wct, mFinishTransaction);
             mPendingResize = null;
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for resize transition");
         }
 
         mActiveRemoteHandler = null;
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 fa14b4c..ec907fd 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
@@ -29,6 +29,7 @@
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -44,6 +45,7 @@
 import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString;
 import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
 import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
 import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
 import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
@@ -54,6 +56,7 @@
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DESKTOP_MODE;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
@@ -138,6 +141,7 @@
 import com.android.wm.shell.shared.TransitionUtil;
 import com.android.wm.shell.splitscreen.SplitScreen.StageType;
 import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
+import com.android.wm.shell.splitscreen.SplitScreenController.SplitEnterReason;
 import com.android.wm.shell.transition.DefaultMixedHandler;
 import com.android.wm.shell.transition.LegacyTransitions;
 import com.android.wm.shell.transition.Transitions;
@@ -313,6 +317,7 @@
 
         taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */);
 
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Creating main/side root task");
         mMainStage = new MainStage(
                 mContext,
                 mTaskOrganizer,
@@ -454,6 +459,8 @@
 
     boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition,
             WindowContainerTransaction wct) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "moveToStage: task=%d position=%d", task.taskId,
+                stagePosition);
         prepareEnterSplitScreen(wct, task, stagePosition, false /* resizeAnim */);
         if (ENABLE_SHELL_TRANSITIONS) {
             mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct,
@@ -474,6 +481,7 @@
     }
 
     boolean removeFromSideStage(int taskId) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "removeFromSideStage: task=%d", taskId);
         final WindowContainerTransaction wct = new WindowContainerTransaction();
 
         /**
@@ -498,11 +506,15 @@
             enteredSplitSelect |= listener.onRequestEnterSplitSelect(taskInfo, splitPosition,
                     taskBounds);
         }
-        if (enteredSplitSelect) mTaskOrganizer.applyTransaction(wct);
+        if (enteredSplitSelect) {
+            mTaskOrganizer.applyTransaction(wct);
+        }
     }
 
     void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
             Bundle options, UserHandle user) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startShortcut: pkg=%s id=%s position=%d user=%d",
+                packageName, shortcutId, position, user.getIdentifier());
         final boolean isEnteringSplit = !isSplitActive();
 
         IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@@ -564,6 +576,7 @@
 
     /** Use this method to launch an existing Task via a taskId */
     void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startTask: task=%d position=%d", taskId, position);
         mSplitRequest = new SplitRequest(taskId, position);
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
@@ -595,6 +608,8 @@
     /** Launches an activity into split. */
     void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
             @Nullable Bundle options) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startIntent: intent=%s position=%d", intent.getIntent(),
+                position);
         mSplitRequest = new SplitRequest(intent.getIntent(), position);
         if (!ENABLE_SHELL_TRANSITIONS) {
             startIntentLegacy(intent, fillInIntent, position, options);
@@ -690,6 +705,9 @@
     void startTasks(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2,
             @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
             @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+                "startTasks: task1=%d task2=%d position=%d snapPosition=%d",
+                taskId1, taskId2, splitPosition, snapPosition);
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (taskId2 == INVALID_TASK_ID) {
             if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) {
@@ -718,6 +736,9 @@
             @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
             @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
             @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+                "startIntentAndTask: intent=%s task1=%d position=%d snapPosition=%d",
+                pendingIntent.getIntent(), taskId, splitPosition, snapPosition);
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (taskId == INVALID_TASK_ID) {
             options1 = options1 != null ? options1 : new Bundle();
@@ -740,6 +761,9 @@
             int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
             @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
             InstanceId instanceId) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+                "startShortcutAndTask: shortcut=%s task1=%d position=%d snapPosition=%d",
+                shortcutInfo, taskId, splitPosition, snapPosition);
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (taskId == INVALID_TASK_ID) {
             options1 = options1 != null ? options1 : new Bundle();
@@ -797,10 +821,15 @@
 
     void startIntents(PendingIntent pendingIntent1, Intent fillInIntent1,
             @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
-            PendingIntent pendingIntent2, Intent fillInIntent2,
+            @Nullable PendingIntent pendingIntent2, Intent fillInIntent2,
             @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
             @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
             @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+                "startIntents: intent1=%s intent2=%s position=%d snapPosition=%d",
+                pendingIntent1.getIntent(),
+                (pendingIntent2 != null ? pendingIntent2.getIntent() : "null"),
+                splitPosition, snapPosition);
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (pendingIntent2 == null) {
             options1 = options1 != null ? options1 : new Bundle();
@@ -1302,6 +1331,7 @@
     }
 
     void switchSplitPosition(String reason) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "switchSplitPosition");
         final SurfaceControl.Transaction t = mTransactionPool.acquire();
         mTempRect1.setEmpty();
         final StageTaskListener topLeftStage =
@@ -1343,7 +1373,7 @@
                     });
                 });
 
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason);
+        ProtoLog.v(WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason);
         mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
                 getSideStagePosition(), mSideStage.getTopChildTaskUid(),
                 mSplitLayout.isLeftRightSplit());
@@ -1376,11 +1406,12 @@
         if (!mMainStage.isActive()) {
             return;
         }
-
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onKeyguardVisibilityChanged: showing=%b", showing);
         setDividerVisibility(!mKeyguardShowing, null);
     }
 
     void onFinishedWakingUp() {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinishedWakingUp");
         if (!mMainStage.isActive()) {
             return;
         }
@@ -1421,6 +1452,8 @@
     }
 
     void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: topTaskId=%d reason=%s active=%b",
+                toTopTaskId, exitReasonToString(exitReason), mMainStage.isActive());
         if (!mMainStage.isActive()) return;
 
         StageTaskListener childrenToTop = null;
@@ -1439,6 +1472,8 @@
 
     private void exitSplitScreen(@Nullable StageTaskListener childrenToTop,
             @ExitReason int exitReason) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: mainStageToTop=%b reason=%s active=%b",
+                childrenToTop == mMainStage, exitReasonToString(exitReason), mMainStage.isActive());
         if (!mMainStage.isActive()) return;
 
         final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -1447,6 +1482,8 @@
 
     private void applyExitSplitScreen(@Nullable StageTaskListener childrenToTop,
             WindowContainerTransaction wct, @ExitReason int exitReason) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "applyExitSplitScreen: reason=%s",
+                exitReasonToString(exitReason));
         if (!mMainStage.isActive() || mIsExiting) return;
 
         onSplitScreenExit();
@@ -1502,7 +1539,6 @@
             }
         });
 
-        Slog.i(TAG, "applyExitSplitScreen, reason = " + exitReasonToString(exitReason));
         // Log the exit
         if (childrenToTop != null) {
             logExitToStage(exitReason, childrenToTop == mMainStage);
@@ -1527,6 +1563,7 @@
      * Exits the split screen by finishing one of the tasks.
      */
     protected void exitStage(@SplitPosition int stageToClose) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitStage: stageToClose=%d", stageToClose);
         mSplitLayout.flingDividerToDismiss(stageToClose == SPLIT_POSITION_BOTTOM_OR_RIGHT,
                 EXIT_REASON_APP_FINISHED);
     }
@@ -1540,12 +1577,13 @@
         try {
             activityTaskManagerService.setFocusedTask(getTaskId(stageToFocus));
         } catch (RemoteException | NullPointerException e) {
-            ProtoLog.e(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+            ProtoLog.e(WM_SHELL_SPLIT_SCREEN,
                     "Unable to update focus on the chosen stage: %s", e.getMessage());
         }
     }
 
     private void clearRequestIfPresented() {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "clearRequestIfPresented");
         if (mSideStageListener.mVisible && mSideStageListener.mHasChildren
                 && mMainStageListener.mVisible && mSideStageListener.mHasChildren) {
             mSplitRequest = null;
@@ -1572,6 +1610,8 @@
                 // The device is folded
             case EXIT_REASON_FULLSCREEN_SHORTCUT:
                 // User has used a keyboard shortcut to go back to fullscreen from split
+            case EXIT_REASON_DESKTOP_MODE:
+                // One of the children enters desktop mode
                 return true;
             default:
                 return false;
@@ -1581,6 +1621,8 @@
     void clearSplitPairedInRecents(@ExitReason int exitReason) {
         if (!shouldBreakPairedTaskInRecents(exitReason) || !mShouldUpdateRecents) return;
 
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "clearSplitPairedInRecents: reason=%s",
+                exitReasonToString(exitReason));
         mRecentTasks.ifPresent(recentTasks -> {
             // Notify recents if we are exiting in a way that breaks the pair, and disable further
             // updates to splits in the recents until we enter split again
@@ -1597,11 +1639,13 @@
     void prepareExitSplitScreen(@StageType int stageToTop,
             @NonNull WindowContainerTransaction wct) {
         if (!mMainStage.isActive()) return;
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareExitSplitScreen: stageToTop=%d", stageToTop);
         mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
         mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN);
     }
 
     private void prepareEnterSplitScreen(WindowContainerTransaction wct) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareEnterSplitScreen");
         prepareEnterSplitScreen(wct, null /* taskInfo */, SPLIT_POSITION_UNDEFINED,
                 !mIsDropEntering);
     }
@@ -1613,6 +1657,8 @@
     void prepareEnterSplitScreen(WindowContainerTransaction wct,
             @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition,
             boolean resizeAnim) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareEnterSplitScreen: position=%d resize=%b",
+                startPosition, resizeAnim);
         onSplitScreenEnter();
         // Preemptively reset the reparenting behavior if we know that we are entering, as starting
         // split tasks with activity trampolines can inadvertently trigger the task to be
@@ -1629,6 +1675,8 @@
     private void prepareBringSplit(WindowContainerTransaction wct,
             @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition,
             boolean resizeAnim) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareBringSplit: task=%d isSplitVisible=%b",
+                taskInfo != null ? taskInfo.taskId : -1, isSplitScreenVisible());
         if (taskInfo != null) {
             wct.startTask(taskInfo.taskId,
                     resolveStartStage(STAGE_TYPE_UNDEFINED, startPosition, null, wct));
@@ -1649,6 +1697,8 @@
     private void prepareActiveSplit(WindowContainerTransaction wct,
             @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition,
             boolean resizeAnim) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareActiveSplit: task=%d isSplitVisible=%b",
+                taskInfo != null ? taskInfo.taskId : -1, isSplitScreenVisible());
         if (!ENABLE_SHELL_TRANSITIONS) {
             // Legacy transition we need to create divider here, shell transition case we will
             // create it on #finishEnterSplitScreen
@@ -1667,6 +1717,7 @@
     }
 
     private void prepareSplitLayout(WindowContainerTransaction wct, boolean resizeAnim) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareSplitLayout: resize=%b", resizeAnim);
         if (resizeAnim) {
             mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
         } else {
@@ -1686,6 +1737,7 @@
     }
 
     void finishEnterSplitScreen(SurfaceControl.Transaction finishT) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "finishEnterSplitScreen");
         mSplitLayout.update(finishT, true /* resetImePosition */);
         mMainStage.getSplitDecorManager().inflate(mContext, mMainStage.mRootLeash,
                 getMainStageBounds());
@@ -1835,12 +1887,20 @@
                     leftTopTaskId, rightBottomTaskId, mSplitLayout.calculateCurrentSnapPosition());
             if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) {
                 // Update the pair for the top tasks
-                recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId, splitBounds);
+                boolean added = recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId,
+                        splitBounds);
+                if (added) {
+                    ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+                            "updateRecentTasksSplitPair: adding split pair ltTask=%d rbTask=%d",
+                            leftTopTaskId, rightBottomTaskId);
+                }
             }
         });
     }
 
     private void sendSplitVisibilityChanged() {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "sendSplitVisibilityChanged: dividerVisible=%b",
+                mDividerVisible);
         for (int i = mListeners.size() - 1; i >= 0; --i) {
             final SplitScreen.SplitScreenListener l = mListeners.get(i);
             l.onSplitVisibilityChanged(mDividerVisible);
@@ -1855,6 +1915,7 @@
             throw new IllegalArgumentException(this + "\n Unknown task appeared: " + taskInfo);
         }
 
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: task=%s", taskInfo);
         mRootTaskInfo = taskInfo;
         mRootTaskLeash = leash;
 
@@ -1880,6 +1941,8 @@
         if (mSplitLayout != null
                 && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)
                 && mMainStage.isActive()) {
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskInfoChanged: task=%d updating",
+                    taskInfo.taskId);
             // Clear the divider remote animating flag as the divider will be re-rendered to apply
             // the new rotation config.  Don't reset the IME state since those updates are not in
             // sync with task info changes.
@@ -1892,6 +1955,7 @@
     @Override
     @CallSuper
     public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskVanished: task=%s", taskInfo);
         if (mRootTaskInfo == null) {
             throw new IllegalArgumentException(this + "\n Unknown task vanished: " + taskInfo);
         }
@@ -1911,6 +1975,8 @@
 
     @VisibleForTesting
     void onRootTaskAppeared() {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRootTaskAppeared: rootTask=%s mainRoot=%b sideRoot=%b",
+                mRootTaskInfo, mMainStageListener.mHasRootTask, mSideStageListener.mHasRootTask);
         // Wait unit all root tasks appeared.
         if (mRootTaskInfo == null
                 || !mMainStageListener.mHasRootTask
@@ -1937,6 +2003,8 @@
      * #onStageHasChildrenChanged because this would be called every time child task appeared.
      * NOTICE: This only be called on legacy transition. */
     private void onChildTaskAppeared(StageListenerImpl stageListener, int taskId) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onChildTaskAppeared: isMainStage=%b task=%d",
+                stageListener == mMainStageListener, taskId);
         // Handle entering split screen while there is a split pair running in the background.
         if (stageListener == mSideStageListener && !isSplitScreenVisible() && isSplitActive()
                 && mSplitRequest == null) {
@@ -1960,6 +2028,7 @@
     }
 
     private void onRootTaskVanished() {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRootTaskVanished");
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         mLaunchAdjacentController.clearLaunchAdjacentRoot();
         applyExitSplitScreen(null /* childrenToTop */, wct, EXIT_REASON_ROOT_TASK_VANISHED);
@@ -1990,6 +2059,8 @@
             return;
         }
 
+        // TODO Protolog
+
         // Check if it needs to dismiss split screen when both stage invisible.
         if (!mainStageVisible && mExitSplitScreenOnHide) {
             exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RETURN_HOME);
@@ -2020,14 +2091,14 @@
             return;
         }
 
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
-                "Request to %s divider bar from %s.",
-                (visible ? "show" : "hide"), Debug.getCaller());
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+                "setDividerVisibility: visible=%b keyguardShowing=%b dividerAnimating=%b caller=%s",
+                visible, mKeyguardShowing, mIsDividerRemoteAnimating, Debug.getCaller());
 
         // Defer showing divider bar after keyguard dismissed, so it won't interfere with keyguard
         // dismissing animation.
         if (visible && mKeyguardShowing) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
                     "   Defer showing divider bar due to keyguard showing.");
             return;
         }
@@ -2036,7 +2107,7 @@
         sendSplitVisibilityChanged();
 
         if (mIsDividerRemoteAnimating) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
                     "   Skip animating divider bar due to it's remote animating.");
             return;
         }
@@ -2050,12 +2121,12 @@
     private void applyDividerVisibility(@Nullable SurfaceControl.Transaction t) {
         final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
         if (dividerLeash == null) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
                     "   Skip animating divider bar due to divider leash not ready.");
             return;
         }
         if (mIsDividerRemoteAnimating) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
                     "   Skip animating divider bar due to it's remote animating.");
             return;
         }
@@ -2119,6 +2190,8 @@
     /** Callback when split roots have child or haven't under it.
      * NOTICE: This only be called on legacy transition. */
     private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onStageHasChildrenChanged: isMainStage=%b",
+                stageListener == mMainStageListener);
         final boolean hasChildren = stageListener.mHasChildren;
         final boolean isSideStage = stageListener == mSideStageListener;
         if (!hasChildren && !mIsExiting && mMainStage.isActive()) {
@@ -2170,13 +2243,15 @@
     }
 
     @Override
-    public void onSnappedToDismiss(boolean bottomOrRight, int reason) {
+    public void onSnappedToDismiss(boolean bottomOrRight, @ExitReason int exitReason) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onSnappedToDismiss: bottomOrRight=%b reason=%s",
+                bottomOrRight, exitReasonToString(exitReason));
         final boolean mainStageToTop =
                 bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
                         : mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT;
         final StageTaskListener toTopStage = mainStageToTop ? mMainStage : mSideStage;
         if (!ENABLE_SHELL_TRANSITIONS) {
-            exitSplitScreen(toTopStage, reason);
+            exitSplitScreen(toTopStage, exitReason);
             return;
         }
 
@@ -2219,6 +2294,7 @@
 
     @Override
     public void onLayoutSizeChanged(SplitLayout layout) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onLayoutSizeChanged");
         // Reset this flag every time onLayoutSizeChanged.
         mShowDecorImmediately = false;
 
@@ -2237,10 +2313,10 @@
         if (ENABLE_SHELL_TRANSITIONS) {
             mSplitLayout.setDividerInteractive(false, false, "onSplitResizeStart");
             mSplitTransitions.startResizeTransition(wct, this, (aborted) -> {
-                mSplitLayout.setDividerInteractive(true, false, "onSplitResizeConsumed");
-            }, (finishWct, t) -> {
-                mSplitLayout.setDividerInteractive(true, false, "onSplitResizeFinish");
-            });
+                        mSplitLayout.setDividerInteractive(true, false, "onSplitResizeConsumed");
+                    }, (finishWct, t) -> {
+                        mSplitLayout.setDividerInteractive(true, false, "onSplitResizeFinish");
+                    }, mMainStage.getSplitDecorManager(), mSideStage.getSplitDecorManager());
         } else {
             // Only need screenshot for legacy case because shell transition should screenshot
             // itself during transition.
@@ -2278,8 +2354,11 @@
                 mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
         final StageTaskListener bottomRightStage =
                 mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
-        return layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo,
+        boolean updated = layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo,
                 bottomRightStage.mRootTaskInfo);
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "updateWindowBounds: topLeftStage=%s bottomRightStage=%s",
+                layout.getBounds1(), layout.getBounds2());
+        return updated;
     }
 
     void updateSurfaceBounds(@Nullable SplitLayout layout, @NonNull SurfaceControl.Transaction t,
@@ -2291,6 +2370,9 @@
         (layout != null ? layout : mSplitLayout).applySurfaceChanges(t, topLeftStage.mRootLeash,
                 bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer,
                 applyResizingOffset);
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+                "updateSurfaceBounds: topLeftStage=%s bottomRightStage=%s",
+                layout.getBounds1(), layout.getBounds2());
     }
 
     @Override
@@ -2329,6 +2411,8 @@
 
     @Override
     public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setLayoutOffsetTarget: x=%d y=%d",
+                offsetX, offsetY);
         final StageTaskListener topLeftStage =
                 mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
         final StageTaskListener bottomRightStage =
@@ -2343,6 +2427,7 @@
         if (displayId != DEFAULT_DISPLAY) {
             return;
         }
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onDisplayAdded: display=%d", displayId);
         mDisplayController.addDisplayChangingController(this::onDisplayChange);
     }
 
@@ -2357,8 +2442,14 @@
 
     private void onDisplayChange(int displayId, int fromRotation, int toRotation,
             @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction wct) {
-        if (displayId != DEFAULT_DISPLAY || !mMainStage.isActive()) return;
+        if (displayId != DEFAULT_DISPLAY || !mMainStage.isActive()) {
+            return;
+        }
 
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+                "onDisplayChange: display=%d fromRot=%d toRot=%d config=%s",
+                displayId, fromRotation, toRotation,
+                newDisplayAreaInfo != null ? newDisplayAreaInfo.configuration : null);
         mSplitLayout.rotateTo(toRotation);
         if (newDisplayAreaInfo != null) {
             mSplitLayout.updateConfiguration(newDisplayAreaInfo.configuration);
@@ -2369,6 +2460,7 @@
 
     @VisibleForTesting
     void onFoldedStateChanged(boolean folded) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFoldedStateChanged: folded=%b", folded);
         mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
         if (!folded) return;
 
@@ -2439,6 +2531,8 @@
         final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
         if (triggerTask == null) {
             if (isSplitActive()) {
+                ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d display rotation",
+                        request.getDebugId());
                 // Check if the display is rotating.
                 final TransitionRequestInfo.DisplayChange displayChange =
                         request.getDisplayChange();
@@ -2467,6 +2561,8 @@
         }
 
         if (isSplitActive()) {
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d split active",
+                    request.getDebugId());
             // Try to handle everything while in split-screen, so return a WCT even if it's empty.
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  split is active so using split"
                             + "Transition to handle request. triggerTask=%d type=%s mainChildren=%d"
@@ -2541,6 +2637,8 @@
             return null;
         } else {
             if (isOpening && getStageOfTask(triggerTask) != null) {
+                ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d enter split",
+                        request.getDebugId());
                 // One task is appearing into split, prepare to enter split screen.
                 out = new WindowContainerTransaction();
                 prepareEnterSplitScreen(out);
@@ -2557,6 +2655,8 @@
      */
     public void addEnterOrExitIfNeeded(@Nullable TransitionRequestInfo request,
             @NonNull WindowContainerTransaction outWCT) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "addEnterOrExitIfNeeded: transition=%d",
+                request.getDebugId());
         final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
         if (triggerTask != null && triggerTask.displayId != mDisplayId) {
             // Skip handling task on the other display.
@@ -2591,6 +2691,7 @@
     public void mergeAnimation(IBinder transition, TransitionInfo info,
             SurfaceControl.Transaction t, IBinder mergeTarget,
             Transitions.TransitionFinishCallback finishCallback) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "mergeAnimation: transition=%d", info.getDebugId());
         mSplitTransitions.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
     }
 
@@ -2602,6 +2703,7 @@
     @Override
     public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
             @Nullable SurfaceControl.Transaction finishT) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed");
         mSplitTransitions.onTransitionConsumed(transition, aborted, finishT);
     }
 
@@ -2617,6 +2719,7 @@
             // If we're not in split-mode, just abort so something else can handle it.
             if (!mMainStage.isActive()) return false;
 
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startAnimation: transition=%d", info.getDebugId());
             mSplitLayout.setFreezeDividerWindow(false);
             final StageChangeRecord record = new StageChangeRecord();
             final int transitType = info.getType();
@@ -2674,7 +2777,7 @@
                                 + " with " + taskInfo.taskId + " before startAnimation().");
                         record.addRecord(stage, true, taskInfo.taskId);
                     }
-                } else if (isClosingType(change.getMode())) {
+                } else if (change.getMode() == TRANSIT_CLOSE) {
                     if (stage.containsTask(taskInfo.taskId)) {
                         record.addRecord(stage, false, taskInfo.taskId);
                         Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called"
@@ -2727,6 +2830,8 @@
             if (mMixedHandler.animatePendingSplitWithDisplayChange(transition, info,
                     startTransaction, finishTransaction, finishCallback)) {
                 if (mSplitTransitions.isPendingResize(transition)) {
+                    ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+                            "startAnimation: transition=%d display change", info.getDebugId());
                     // Only need to update in resize because divider exist before transition.
                     mSplitLayout.update(startTransaction, true /* resetImePosition */);
                     startTransaction.apply();
@@ -2797,6 +2902,8 @@
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startPendingAnimation: transition=%d",
+                info.getDebugId());
         boolean shouldAnimate = true;
         if (mSplitTransitions.isPendingEnter(transition)) {
             shouldAnimate = startPendingEnterAnimation(transition,
@@ -2830,6 +2937,7 @@
 
     /** Called to clean-up state and do house-keeping after the animation is done. */
     public void onTransitionAnimationComplete() {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionAnimationComplete");
         // If still playing, let it finish.
         if (!mMainStage.isActive() && !mIsExiting) {
             // Update divider state after animation so that it is still around and positioned
@@ -2842,6 +2950,8 @@
             @NonNull SplitScreenTransitions.EnterSession enterTransition,
             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
             @NonNull SurfaceControl.Transaction finishT) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startPendingEnterAnimation: enterTransition=%s",
+                enterTransition);
         // First, verify that we actually have opened apps in both splits.
         TransitionInfo.Change mainChild = null;
         TransitionInfo.Change sideChild = null;
@@ -2959,6 +3069,7 @@
     }
 
     public void goToFullscreenFromSplit() {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "goToFullscreenFromSplit");
         // If main stage is focused, toEnd = true if
         // mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT. Otherwise toEnd = false
         // If side stage is focused, toEnd = true if
@@ -2974,6 +3085,7 @@
 
     /** Move the specified task to fullscreen, regardless of focus state. */
     public void moveTaskToFullscreen(int taskId, int exitReason) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "moveTaskToFullscreen");
         boolean leftOrTop;
         if (mMainStage.containsTask(taskId)) {
             leftOrTop = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
@@ -2994,6 +3106,7 @@
      */
     public void onPipExpandToSplit(WindowContainerTransaction wct,
             ActivityManager.RunningTaskInfo taskInfo) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onPipExpandToSplit: task=%s", taskInfo);
         prepareEnterSplitScreen(wct, taskInfo, getActivateSplitPosition(taskInfo),
                 false /*resizeAnim*/);
 
@@ -3040,6 +3153,9 @@
     public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason,
             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
             @NonNull SurfaceControl.Transaction finishT) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, 
+                "prepareDismissAnimation: transition=%d toStage=%d reason=%s",
+                info.getDebugId(), toStage, exitReasonToString(dismissReason));
         // Make some noise if things aren't totally expected. These states shouldn't effect
         // transitions locally, but remotes (like Launcher) may get confused if they were
         // depending on listener callbacks. This can happen because task-organizer callbacks
@@ -3126,6 +3242,9 @@
             @NonNull SplitScreenTransitions.DismissSession dismissTransition,
             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
             @NonNull SurfaceControl.Transaction finishT) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+                "startPendingDismissAnimation: transition=%d dismissTransition=%s",
+                info.getDebugId(), dismissTransition);
         prepareDismissAnimation(dismissTransition.mDismissTop, dismissTransition.mReason, info,
                 t, finishT);
         if (dismissTransition.mDismissTop == STAGE_TYPE_UNDEFINED) {
@@ -3146,6 +3265,8 @@
 
     /** Call this when starting the open-recents animation while split-screen is active. */
     public void onRecentsInSplitAnimationStart(TransitionInfo info) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsInSplitAnimationStart: transition=%d",
+                info.getDebugId());
         if (isSplitScreenVisible()) {
             // Cache tasks on live tile.
             for (int i = 0; i < info.getChanges().size(); ++i) {
@@ -3178,6 +3299,7 @@
     /** Call this when the recents animation during split-screen finishes. */
     public void onRecentsInSplitAnimationFinish(WindowContainerTransaction finishWct,
             SurfaceControl.Transaction finishT) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsInSplitAnimationFinish");
         mPausingTasks.clear();
         // Check if the recent transition is finished by returning to the current
         // split, so we can restore the divider bar.
@@ -3203,6 +3325,7 @@
 
     /** Call this when the recents animation finishes by doing pair-to-pair switch. */
     public void onRecentsPairToPairAnimationFinish(WindowContainerTransaction finishWct) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsPairToPairAnimationFinish");
         // Pair-to-pair switch happened so here should evict the live tile from its stage.
         // Otherwise, the task will remain in stage, and occluding the new task when next time
         // user entering recents.
@@ -3284,6 +3407,7 @@
      * handled.
      */
     private void setSplitsVisible(boolean visible) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setSplitsVisible: visible=%b", visible);
         mMainStageListener.mVisible = mSideStageListener.mVisible = visible;
         mMainStageListener.mHasChildren = mSideStageListener.mHasChildren = visible;
     }
@@ -3292,6 +3416,7 @@
      * Sets drag info to be logged when splitscreen is next entered.
      */
     public void onDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onDroppedToSplit: position=%d", position);
         if (!isSplitScreenVisible()) {
             mIsDropEntering = true;
             mSkipEvictingMainStageChildren = true;
@@ -3308,7 +3433,8 @@
     /**
      * Sets info to be logged when splitscreen is next entered.
      */
-    public void onRequestToSplit(InstanceId sessionId, int enterReason) {
+    public void onRequestToSplit(InstanceId sessionId, @SplitEnterReason int enterReason) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRequestToSplit: reason=%d", enterReason);
         if (!isSplitScreenVisible() && !ENABLE_SHELL_TRANSITIONS) {
             // If split running background, exit split first.
             // Skip this on shell transition due to we could evict existing tasks on transition
@@ -3384,6 +3510,7 @@
 
         @Override
         public void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo) {
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onNoLongerSupportMultiWindow: task=%s", taskInfo);
             if (mMainStage.isActive()) {
                 final boolean isMainStage = mMainStageListener == this;
                 if (!ENABLE_SHELL_TRANSITIONS) {
@@ -3393,15 +3520,24 @@
                     return;
                 }
 
-                final int stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+                // If visible, we preserve the app and keep it running. If an app becomes
+                // unsupported in the bg, break split without putting anything on top
+                boolean splitScreenVisible = isSplitScreenVisible();
+                int stageType = STAGE_TYPE_UNDEFINED;
+                if (splitScreenVisible) {
+                    stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+                }
                 final WindowContainerTransaction wct = new WindowContainerTransaction();
                 prepareExitSplitScreen(stageType, wct);
+                clearSplitPairedInRecents(EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
                 mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType,
                         EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
                 Log.w(TAG, splitFailureMessage("onNoLongerSupportMultiWindow",
                         "app package " + taskInfo.baseActivity.getPackageName()
                         + " does not support splitscreen, or is a controlled activity type"));
-                mSplitUnsupportedToast.show();
+                if (splitScreenVisible) {
+                    mSplitUnsupportedToast.show();
+                }
             }
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index af7bf36..f33ab33 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -25,6 +25,7 @@
 import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
 import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
 import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
 
 import android.annotation.CallSuper;
@@ -44,6 +45,7 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ArrayUtils;
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.ShellTaskOrganizer;
@@ -175,6 +177,9 @@
     @Override
     @CallSuper
     public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: task=%d taskParent=%d rootTask=%d",
+                taskInfo.taskId, taskInfo.parentTaskId,
+                mRootTaskInfo != null ? mRootTaskInfo.taskId : -1);
         if (mRootTaskInfo == null) {
             mRootLeash = leash;
             mRootTaskInfo = taskInfo;
@@ -225,6 +230,9 @@
                     || !ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType())
                     || !ArrayUtils.contains(CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
                     taskInfo.getWindowingMode())) {
+                ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+                        "onTaskInfoChanged: task=%d no longer supports multiwindow",
+                        taskInfo.taskId);
                 // Leave split screen if the task no longer supports multi window or have
                 // uncontrolled task.
                 mCallbacks.onNoLongerSupportMultiWindow(taskInfo);
@@ -251,6 +259,7 @@
     @Override
     @CallSuper
     public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskVanished: task=%d", taskInfo.taskId);
         final int taskId = taskInfo.taskId;
         if (mRootTaskInfo.taskId == taskId) {
             mCallbacks.onRootTaskVanished();
@@ -333,6 +342,7 @@
     }
 
     void addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "addTask: task=%d", task.taskId);
         // Clear overridden bounds and windowing mode to make sure the child task can inherit
         // windowing mode and bounds from split root.
         wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED)
@@ -342,6 +352,7 @@
     }
 
     void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "reorderChild: task=%d onTop=%b", taskId, onTop);
         if (!containsTask(taskId)) {
             return;
         }
@@ -357,6 +368,7 @@
 
     /** Collects all the current child tasks and prepares transaction to evict them to display. */
     void evictAllChildren(WindowContainerTransaction wct) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evicting all children");
         for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
             final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
             wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
@@ -367,11 +379,13 @@
         for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
             final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
             if (taskId == taskInfo.taskId) continue;
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict other child: task=%d", taskId);
             wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
         }
     }
 
     void evictNonOpeningChildren(RemoteAnimationTarget[] apps, WindowContainerTransaction wct) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "evictNonOpeningChildren");
         final SparseArray<ActivityManager.RunningTaskInfo> toBeEvict = mChildrenTaskInfo.clone();
         for (int i = 0; i < apps.length; i++) {
             if (apps[i].mode == MODE_OPENING) {
@@ -380,6 +394,7 @@
         }
         for (int i = toBeEvict.size() - 1; i >= 0; i--) {
             final ActivityManager.RunningTaskInfo taskInfo = toBeEvict.valueAt(i);
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict non-opening child: task=%d", taskInfo.taskId);
             wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
         }
     }
@@ -388,12 +403,15 @@
         for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
             final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
             if (!taskInfo.isVisible) {
+                ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict invisible child: task=%d",
+                        taskInfo.taskId);
                 wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
             }
         }
     }
 
     void evictChildren(WindowContainerTransaction wct, int taskId) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict child: task=%d", taskId);
         final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.get(taskId);
         if (taskInfo != null) {
             wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index bfb60c0..2b12a22 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -251,9 +251,14 @@
         final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
                 ? windowInfo.targetActivityInfo
                 : taskInfo.topActivityInfo;
+        final boolean isEdgeToEdgeEnforced = PhoneWindow.isEdgeToEdgeEnforced(
+                activityInfo.applicationInfo, false /* local */, a);
+        if (isEdgeToEdgeEnforced) {
+            params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
+        }
         params.layoutInDisplayCutoutMode = a.getInt(
                 R.styleable.Window_windowLayoutInDisplayCutoutMode,
-                PhoneWindow.isEdgeToEdgeEnforced(activityInfo.applicationInfo, false /* local */, a)
+                isEdgeToEdgeEnforced
                         ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
                         : params.layoutInDisplayCutoutMode);
         params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
@@ -417,7 +422,7 @@
         final SplashViewBuilder builder = new SplashViewBuilder(context, ai);
         final SplashScreenView view = builder
                 .setWindowBGColor(themeBGColor)
-                .chooseStyle(STARTING_WINDOW_TYPE_SPLASH_SCREEN)
+                .chooseStyle(STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN)
                 .build();
         view.setNotCopyable();
         return view;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
index 31fc98b..da3aa4a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
@@ -461,25 +461,6 @@
             a.recycle();
         }
 
-        // Reset the system bar color which set by splash screen, make it align to the app.
-        void clearSystemBarColor() {
-            if (mRootView == null || !mRootView.isAttachedToWindow()) {
-                return;
-            }
-            if (mRootView.getLayoutParams() instanceof WindowManager.LayoutParams) {
-                final WindowManager.LayoutParams lp =
-                        (WindowManager.LayoutParams) mRootView.getLayoutParams();
-                if (mDrawsSystemBarBackgrounds) {
-                    lp.flags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
-                } else {
-                    lp.flags &= ~WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
-                }
-                mRootView.setLayoutParams(lp);
-            }
-            mRootView.getWindowInsetsController().setSystemBarsAppearance(
-                    mSystemBarAppearance, LIGHT_BARS_MASK);
-        }
-
         @Override
         public boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
             if (mRootView == null) {
@@ -491,7 +472,6 @@
                 removeWindowInner(mRootView, false);
                 return true;
             }
-            clearSystemBarColor();
             if (immediately
                     || mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
                 removeWindowInner(mRootView, false);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index e2be153..1ce87ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -22,6 +22,7 @@
 import static android.window.StartingWindowRemovalInfo.DEFER_MODE_ROTATION;
 
 import android.annotation.CallSuper;
+import android.annotation.NonNull;
 import android.app.TaskInfo;
 import android.app.WindowConfiguration;
 import android.content.Context;
@@ -306,7 +307,7 @@
         @CallSuper
         protected void removeImmediately() {
             mRemoveExecutor.removeCallbacks(mScheduledRunnable);
-            mRecordManager.onRecordRemoved(mTaskId);
+            mRecordManager.onRecordRemoved(this, mTaskId);
         }
     }
 
@@ -327,6 +328,11 @@
         }
 
         void addRecord(int taskId, StartingWindowRecord record) {
+            final StartingWindowRecord original = mStartingWindowRecords.get(taskId);
+            if (original != null) {
+                mTmpRemovalInfo.taskId = taskId;
+                original.removeIfPossible(mTmpRemovalInfo, true /* immediately */);
+            }
             mStartingWindowRecords.put(taskId, record);
         }
 
@@ -346,8 +352,11 @@
             removeWindow(mTmpRemovalInfo, true/* immediately */);
         }
 
-        void onRecordRemoved(int taskId) {
-            mStartingWindowRecords.remove(taskId);
+        void onRecordRemoved(@NonNull StartingWindowRecord record, int taskId) {
+            final StartingWindowRecord currentRecord = mStartingWindowRecords.get(taskId);
+            if (currentRecord == record) {
+                mStartingWindowRecords.remove(taskId);
+            }
         }
 
         StartingWindowRecord getRecord(int taskId) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java
new file mode 100644
index 0000000..a94f802
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.sysui;
+
+import android.graphics.Rect;
+
+/**
+ * Callbacks for when the Display IME changes.
+ */
+public interface DisplayImeChangeListener {
+    /**
+     * Called when the ime bounds change.
+     */
+    default void onImeBoundsChanged(int displayId, Rect bounds) {}
+
+    /**
+     * Called when the IME visibility change.
+     */
+    default void onImeVisibilityChanged(int displayId, boolean isShowing) {}
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index a7843e2..2f6edc2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -30,21 +30,28 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
+import android.graphics.Rect;
 import android.os.Bundle;
 import android.util.ArrayMap;
+import android.view.InsetsSource;
+import android.view.InsetsState;
 import android.view.SurfaceControlRegistry;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
 import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.annotations.ExternalThread;
 
 import java.io.PrintWriter;
 import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
 import java.util.function.Supplier;
 
 /**
@@ -57,6 +64,7 @@
     private final ShellInit mShellInit;
     private final ShellCommandHandler mShellCommandHandler;
     private final ShellExecutor mMainExecutor;
+    private final DisplayInsetsController mDisplayInsetsController;
     private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl();
 
     private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners =
@@ -65,6 +73,8 @@
             new CopyOnWriteArrayList<>();
     private final CopyOnWriteArrayList<UserChangeListener> mUserChangeListeners =
             new CopyOnWriteArrayList<>();
+    private final ConcurrentHashMap<DisplayImeChangeListener, Executor> mDisplayImeChangeListeners =
+            new ConcurrentHashMap<>();
 
     private ArrayMap<String, Supplier<ExternalInterfaceBinder>> mExternalInterfaceSuppliers =
             new ArrayMap<>();
@@ -73,20 +83,53 @@
 
     private Configuration mLastConfiguration;
 
+    private OnInsetsChangedListener mInsetsChangeListener = new OnInsetsChangedListener() {
+        private InsetsState mInsetsState = new InsetsState();
+
+        @Override
+        public void insetsChanged(InsetsState insetsState) {
+            if (mInsetsState == insetsState) {
+                return;
+            }
+
+            InsetsSource oldSource = mInsetsState.peekSource(InsetsSource.ID_IME);
+            boolean wasVisible = (oldSource != null && oldSource.isVisible());
+            Rect oldFrame = wasVisible ? oldSource.getFrame() : null;
+
+            InsetsSource newSource = insetsState.peekSource(InsetsSource.ID_IME);
+            boolean isVisible = (newSource != null && newSource.isVisible());
+            Rect newFrame = isVisible ? newSource.getFrame() : null;
+
+            if (wasVisible != isVisible) {
+                onImeVisibilityChanged(isVisible);
+            }
+
+            if (newFrame != null && !newFrame.equals(oldFrame)) {
+                onImeBoundsChanged(newFrame);
+            }
+
+            mInsetsState = insetsState;
+        }
+    };
+
 
     public ShellController(Context context,
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
+            DisplayInsetsController displayInsetsController,
             ShellExecutor mainExecutor) {
         mContext = context;
         mShellInit = shellInit;
         mShellCommandHandler = shellCommandHandler;
+        mDisplayInsetsController = displayInsetsController;
         mMainExecutor = mainExecutor;
         shellInit.addInitCallback(this::onInit, this);
     }
 
     private void onInit() {
         mShellCommandHandler.addDumpCallback(this::dump, this);
+        mDisplayInsetsController.addInsetsChangedListener(
+                mContext.getDisplayId(), mInsetsChangeListener);
     }
 
     /**
@@ -259,6 +302,25 @@
         }
     }
 
+    @VisibleForTesting
+    void onImeBoundsChanged(Rect bounds) {
+        ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Display Ime bounds changed");
+        mDisplayImeChangeListeners.forEach(
+                (DisplayImeChangeListener listener, Executor executor) ->
+                executor.execute(() -> listener.onImeBoundsChanged(
+                    mContext.getDisplayId(), bounds)));
+    }
+
+    @VisibleForTesting
+    void onImeVisibilityChanged(boolean isShowing) {
+        ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Display Ime visibility changed: isShowing=%b",
+                isShowing);
+        mDisplayImeChangeListeners.forEach(
+                (DisplayImeChangeListener listener, Executor executor) ->
+                executor.execute(() -> listener.onImeVisibilityChanged(
+                    mContext.getDisplayId(), isShowing)));
+    }
+
     private void handleInit() {
         SurfaceControlRegistry.createProcessInstance(mContext);
         mShellInit.init();
@@ -329,6 +391,19 @@
         }
 
         @Override
+        public void addDisplayImeChangeListener(DisplayImeChangeListener listener,
+                Executor executor) {
+            ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Adding new DisplayImeChangeListener");
+            mDisplayImeChangeListeners.put(listener, executor);
+        }
+
+        @Override
+        public void removeDisplayImeChangeListener(DisplayImeChangeListener listener) {
+            ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Removing DisplayImeChangeListener");
+            mDisplayImeChangeListeners.remove(listener);
+        }
+
+        @Override
         public boolean handleCommand(String[] args, PrintWriter pw) {
             try {
                 boolean[] result = new boolean[1];
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
index bc5dd11..bd1c64a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
@@ -25,6 +25,7 @@
 
 import java.io.PrintWriter;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * General interface for notifying the Shell of common SysUI events like configuration or keyguard
@@ -65,6 +66,18 @@
     default void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {}
 
     /**
+     * Registers a DisplayImeChangeListener to monitor for changes on Ime
+     * position and visibility.
+     */
+    default void addDisplayImeChangeListener(DisplayImeChangeListener listener,
+            Executor executor) {}
+
+    /**
+     * Removes a registered DisplayImeChangeListener.
+     */
+    default void removeDisplayImeChangeListener(DisplayImeChangeListener listener) {}
+
+    /**
      * Handles a shell command.
      */
     default boolean handleCommand(final String[] args, PrintWriter pw) {
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 c70a821..9130edf 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
@@ -470,10 +470,12 @@
                 }
 
                 final float cornerRadius;
-                if (a.hasRoundedCorners() && isTask) {
-                    // hasRoundedCorners is currently only enabled for tasks
+                if (a.hasRoundedCorners()) {
+                    final int displayId = isTask ? change.getTaskInfo().displayId
+                            : info.getRoot(TransitionUtil.rootIndexFor(change, info))
+                                    .getDisplayId();
                     final Context displayContext =
-                            mDisplayController.getDisplayContext(change.getTaskInfo().displayId);
+                            mDisplayController.getDisplayContext(displayId);
                     cornerRadius = displayContext == null ? 0
                             : ScreenDecorationsUtils.getWindowCornerRadius(displayContext);
                 } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
index 61e11e8..89b0e25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
@@ -16,6 +16,8 @@
 
 package com.android.wm.shell.transition;
 
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS;
+
 import android.annotation.NonNull;
 import android.os.RemoteException;
 import android.view.IRemoteAnimationFinishedCallback;
@@ -26,6 +28,8 @@
 import android.view.WindowManager;
 import android.window.IWindowContainerTransactionCallback;
 
+import com.android.internal.protolog.common.ProtoLog;
+
 /**
  * Utilities and interfaces for transition-like usage on top of the legacy app-transition and
  * synctransaction tools.
@@ -87,9 +91,11 @@
             @Override
             public void onTransactionReady(int id, SurfaceControl.Transaction t)
                     throws RemoteException {
+                ProtoLog.v(WM_SHELL_TRANSITIONS,
+                        "LegacyTransitions.onTransactionReady(): syncId=%d", id);
                 mSyncId = id;
                 mTransaction = t;
-                checkApply();
+                checkApply(true /* log */);
             }
         }
 
@@ -103,20 +109,29 @@
                 mWallpapers = wallpapers;
                 mNonApps = nonApps;
                 mFinishCallback = finishedCallback;
-                checkApply();
+                checkApply(false /* log */);
             }
 
             @Override
             public void onAnimationCancelled() throws RemoteException {
                 mCancelled = true;
                 mApps = mWallpapers = mNonApps = null;
-                checkApply();
+                checkApply(false /* log */);
             }
         }
 
 
-        private void checkApply() throws RemoteException {
-            if (mSyncId < 0 || (mFinishCallback == null && !mCancelled)) return;
+        private void checkApply(boolean log) throws RemoteException {
+            if (mSyncId < 0 || (mFinishCallback == null && !mCancelled)) {
+                if (log) {
+                    ProtoLog.v(WM_SHELL_TRANSITIONS, "\tSkipping hasFinishedCb=%b canceled=%b",
+                            mFinishCallback != null, mCancelled);
+                }
+                return;
+            }
+            if (log) {
+                ProtoLog.v(WM_SHELL_TRANSITIONS, "\tapply");
+            }
             mLegacyTransition.onAnimationStart(mTransit, mApps, mWallpapers,
                     mNonApps, mFinishCallback, mTransaction);
         }
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 b8a0f67..ccd0b2d 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
@@ -1409,6 +1409,8 @@
         public void onTransitionReady(IBinder iBinder, TransitionInfo transitionInfo,
                 SurfaceControl.Transaction t, SurfaceControl.Transaction finishT)
                 throws RemoteException {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady(transaction=%d)",
+                    t.getId());
             mMainExecutor.execute(() -> Transitions.this.onTransitionReady(
                     iBinder, transitionInfo, t, finishT));
         }
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 96eaa1e..91e9601 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
@@ -190,6 +190,7 @@
         mRelayoutParams.mShadowRadiusId = shadowRadiusID;
         mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
         mRelayoutParams.mSetTaskPositionAndCrop = setTaskCropAndPosition;
+        mRelayoutParams.mAllowCaptionInputFallthrough = false;
 
         relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
         // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
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 c1406d0..98ff0ee 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
@@ -22,12 +22,18 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
+import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_HOVER_ENTER;
 import static android.view.MotionEvent.ACTION_HOVER_EXIT;
+import static android.view.MotionEvent.ACTION_UP;
 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;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
 import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION;
 import static com.android.wm.shell.windowdecor.MoveToDesktopAnimator.DRAG_FREEFORM_SCALE;
 
@@ -46,9 +52,13 @@
 import android.hardware.input.InputManager;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
 import android.util.SparseArray;
 import android.view.Choreographer;
 import android.view.GestureDetector;
+import android.view.ISystemGestureExclusionListener;
+import android.view.IWindowManager;
 import android.view.InputChannel;
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
@@ -59,7 +69,6 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
@@ -72,9 +81,11 @@
 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;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
@@ -87,6 +98,7 @@
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener;
+import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
 
 import java.io.PrintWriter;
 import java.util.Optional;
@@ -101,6 +113,8 @@
     private static final String TAG = "DesktopModeWindowDecorViewModel";
 
     private final DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory;
+    private final IWindowManager mWindowManager;
+    private final ShellExecutor mMainExecutor;
     private final ActivityTaskManager mActivityTaskManager;
     private final ShellCommandHandler mShellCommandHandler;
     private final ShellTaskOrganizer mTaskOrganizer;
@@ -110,7 +124,8 @@
     private final Choreographer mMainChoreographer;
     private final DisplayController mDisplayController;
     private final SyncTransactionQueue mSyncQueue;
-    private final Optional<DesktopTasksController> mDesktopTasksController;
+    private final DesktopTasksController mDesktopTasksController;
+    private final InputManager mInputManager;
     private boolean mTransitionDragActive;
 
     private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
@@ -118,8 +133,7 @@
     private final ExclusionRegionListener mExclusionRegionListener =
             new ExclusionRegionListenerImpl();
 
-    private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId =
-            new SparseArray<>();
+    private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId;
     private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
     private final InputMonitorFactory mInputMonitorFactory;
     private TaskOperations mTaskOperations;
@@ -134,14 +148,31 @@
             new DesktopModeKeyguardChangeListener();
     private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
     private final DisplayInsetsController mDisplayInsetsController;
+    private final Region mExclusionRegion = Region.obtain();
     private boolean mInImmersiveMode;
 
+    private final ISystemGestureExclusionListener mGestureExclusionListener =
+            new ISystemGestureExclusionListener.Stub() {
+                @Override
+                public void onSystemGestureExclusionChanged(int displayId,
+                        Region systemGestureExclusion, Region systemGestureExclusionUnrestricted) {
+                    if (mContext.getDisplayId() != displayId) {
+                        return;
+                    }
+                    mMainExecutor.execute(() -> {
+                        mExclusionRegion.set(systemGestureExclusion);
+                    });
+                }
+            };
+
     public DesktopModeWindowDecorViewModel(
             Context context,
+            ShellExecutor shellExecutor,
             Handler mainHandler,
             Choreographer mainChoreographer,
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
+            IWindowManager windowManager,
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
             ShellController shellController,
@@ -153,10 +184,12 @@
     ) {
         this(
                 context,
+                shellExecutor,
                 mainHandler,
                 mainChoreographer,
                 shellInit,
                 shellCommandHandler,
+                windowManager,
                 taskOrganizer,
                 displayController,
                 shellController,
@@ -167,16 +200,19 @@
                 new DesktopModeWindowDecoration.Factory(),
                 new InputMonitorFactory(),
                 SurfaceControl.Transaction::new,
-                rootTaskDisplayAreaOrganizer);
+                rootTaskDisplayAreaOrganizer,
+                new SparseArray<>());
     }
 
     @VisibleForTesting
     DesktopModeWindowDecorViewModel(
             Context context,
+            ShellExecutor shellExecutor,
             Handler mainHandler,
             Choreographer mainChoreographer,
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
+            IWindowManager windowManager,
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
             ShellController shellController,
@@ -187,8 +223,10 @@
             DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
             InputMonitorFactory inputMonitorFactory,
             Supplier<SurfaceControl.Transaction> transactionFactory,
-            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+            SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId) {
         mContext = context;
+        mMainExecutor = shellExecutor;
         mMainHandler = mainHandler;
         mMainChoreographer = mainChoreographer;
         mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
@@ -198,12 +236,15 @@
         mDisplayInsetsController = displayInsetsController;
         mSyncQueue = syncQueue;
         mTransitions = transitions;
-        mDesktopTasksController = desktopTasksController;
+        mDesktopTasksController = desktopTasksController.get();
         mShellCommandHandler = shellCommandHandler;
+        mWindowManager = windowManager;
         mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
         mInputMonitorFactory = inputMonitorFactory;
         mTransactionFactory = transactionFactory;
         mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+        mInputManager = mContext.getSystemService(InputManager.class);
+        mWindowDecorByTaskId = windowDecorByTaskId;
 
         shellInit.addInitCallback(this::onInit, this);
     }
@@ -213,8 +254,14 @@
         mShellCommandHandler.addDumpCallback(this::dump, this);
         mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(),
                 new DesktopModeOnInsetsChangedListener());
-        mDesktopTasksController.ifPresent(c -> c.setOnTaskResizeAnimationListener(
-                new DeskopModeOnTaskResizeAnimationListener()));
+        mDesktopTasksController.setOnTaskResizeAnimationListener(
+                new DeskopModeOnTaskResizeAnimationListener());
+        try {
+            mWindowManager.registerSystemGestureExclusionListener(mGestureExclusionListener,
+                    mContext.getDisplayId());
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to register window manager callbacks", e);
+        }
     }
 
     @Override
@@ -232,7 +279,7 @@
                     DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
                     if (decor != null && DesktopModeStatus.isEnabled()
                             && decor.mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
-                        mDesktopTasksController.ifPresent(c -> c.moveToSplit(decor.mTaskInfo));
+                        mDesktopTasksController.moveToSplit(decor.mTaskInfo);
                     }
                 }
             }
@@ -299,8 +346,7 @@
 
     @Override
     public void destroyWindowDecoration(RunningTaskInfo taskInfo) {
-        final DesktopModeWindowDecoration decoration =
-                mWindowDecorByTaskId.removeReturnOld(taskInfo.taskId);
+        final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
         if (decoration == null) return;
 
         decoration.close();
@@ -308,21 +354,32 @@
         if (mEventReceiversByDisplay.contains(displayId)) {
             removeTaskFromEventReceiver(displayId);
         }
+        // Remove the decoration from the cache last because WindowDecoration#close could still
+        // issue CANCEL MotionEvents to touch listeners before its view host is released.
+        // See b/327664694.
+        mWindowDecorByTaskId.remove(taskInfo.taskId);
     }
 
     private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
             implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
             View.OnGenericMotionListener , DragDetector.MotionEventHandler {
         private static final int CLOSE_MAXIMIZE_MENU_DELAY_MS = 150;
+
         private final int mTaskId;
         private final WindowContainerToken mTaskToken;
         private final DragPositioningCallback mDragPositioningCallback;
         private final DragDetector mDragDetector;
         private final GestureDetector mGestureDetector;
 
+        /**
+         * Whether to pilfer the next motion event to send cancellations to the windows below.
+         * Useful when the caption window is spy and the gesture should be handle by the system
+         * instead of by the app for their custom header content.
+         */
+        private boolean mShouldPilferCaptionEvents;
         private boolean mIsDragging;
+        private boolean mTouchscreenInUse;
         private boolean mHasLongClicked;
-        private boolean mShouldClick;
         private int mDragPointerId = -1;
         private final Runnable mCloseMaximizeWindowRunnable;
 
@@ -343,6 +400,10 @@
 
         @Override
         public void onClick(View v) {
+            if (mIsDragging) {
+                mIsDragging = false;
+                return;
+            }
             final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
             final int id = v.getId();
             if (id == R.id.close_window) {
@@ -362,14 +423,11 @@
                     decoration.closeHandleMenu();
                 }
             } else if (id == R.id.desktop_button) {
-                if (mDesktopTasksController.isPresent()) {
-                    final WindowContainerTransaction wct = new WindowContainerTransaction();
-                    // App sometimes draws before the insets from WindowDecoration#relayout have
-                    // been added, so they must be added here
-                    mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
-                    mDesktopTasksController.get().moveToDesktop(mTaskId, wct);
-                    closeOtherSplitTask(mTaskId);
-                }
+                final WindowContainerTransaction wct = new WindowContainerTransaction();
+                // App sometimes draws before the insets from WindowDecoration#relayout have
+                // been added, so they must be added here
+                mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
+                mDesktopTasksController.moveToDesktop(mTaskId, wct);
                 decoration.closeHandleMenu();
             } else if (id == R.id.fullscreen_button) {
                 decoration.closeHandleMenu();
@@ -377,42 +435,31 @@
                     mSplitScreenController.moveTaskToFullscreen(mTaskId,
                             SplitScreenController.EXIT_REASON_DESKTOP_MODE);
                 } else {
-                    mDesktopTasksController.ifPresent(c ->
-                            c.moveToFullscreen(mTaskId));
+                    mDesktopTasksController.moveToFullscreen(mTaskId);
                 }
             } else if (id == R.id.split_screen_button) {
                 decoration.closeHandleMenu();
-                mDesktopTasksController.ifPresent(c -> {
-                    c.requestSplit(decoration.mTaskInfo);
-                });
+                mDesktopTasksController.requestSplit(decoration.mTaskInfo);
             } else if (id == R.id.collapse_menu_button) {
                 decoration.closeHandleMenu();
-            } else if (id == R.id.select_button) {
-                if (DesktopModeStatus.IS_DISPLAY_CHANGE_ENABLED) {
-                    // TODO(b/278084491): dev option to enable display switching
-                    //  remove when select is implemented
-                    mDesktopTasksController.ifPresent(c -> c.moveToNextDisplay(mTaskId));
-                }
             } else if (id == R.id.maximize_window) {
                 final RunningTaskInfo taskInfo = decoration.mTaskInfo;
                 decoration.closeHandleMenu();
                 decoration.closeMaximizeMenu();
-                mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(taskInfo));
+                mDesktopTasksController.toggleDesktopTaskSize(taskInfo);
             } else if (id == R.id.maximize_menu_maximize_button) {
                 final RunningTaskInfo taskInfo = decoration.mTaskInfo;
-                mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(taskInfo));
+                mDesktopTasksController.toggleDesktopTaskSize(taskInfo);
                 decoration.closeHandleMenu();
                 decoration.closeMaximizeMenu();
             } else if (id == R.id.maximize_menu_snap_left_button) {
                 final RunningTaskInfo taskInfo = decoration.mTaskInfo;
-                mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen(
-                        taskInfo, SnapPosition.LEFT));
+                mDesktopTasksController.snapToHalfScreen(taskInfo, SnapPosition.LEFT);
                 decoration.closeHandleMenu();
                 decoration.closeMaximizeMenu();
             } else if (id == R.id.maximize_menu_snap_right_button) {
                 final RunningTaskInfo taskInfo = decoration.mTaskInfo;
-                mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen(
-                        taskInfo, SnapPosition.RIGHT));
+                mDesktopTasksController.snapToHalfScreen(taskInfo, SnapPosition.RIGHT);
                 decoration.closeHandleMenu();
                 decoration.closeMaximizeMenu();
             }
@@ -421,6 +468,10 @@
         @Override
         public boolean onTouch(View v, MotionEvent e) {
             final int id = v.getId();
+            if ((e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN) {
+                mTouchscreenInUse = e.getActionMasked() != ACTION_UP
+                        && e.getActionMasked() != ACTION_CANCEL;
+            }
             if (id != R.id.caption_handle && id != R.id.desktop_mode_caption
                     && id != R.id.open_menu_button && id != R.id.close_window
                     && id != R.id.maximize_window) {
@@ -429,34 +480,56 @@
             final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
             moveTaskToFront(decoration.mTaskInfo);
 
+            final int actionMasked = e.getActionMasked();
+            final boolean isDown = actionMasked == MotionEvent.ACTION_DOWN;
+            final boolean isUpOrCancel = actionMasked == MotionEvent.ACTION_CANCEL
+                    || actionMasked == MotionEvent.ACTION_UP;
+            if (isDown) {
+                final boolean downInCustomizableCaptionRegion =
+                        decoration.checkTouchEventInCustomizableRegion(e);
+                final boolean downInExclusionRegion = mExclusionRegion.contains(
+                        (int) e.getRawX(), (int) e.getRawY());
+                final boolean isTransparentCaption =
+                        TaskInfoKt.isTransparentCaptionBarAppearance(decoration.mTaskInfo);
+                // The caption window may be a spy window when the caption background is
+                // transparent, which means events will fall through to the app window. Make
+                // sure to cancel these events if they do not happen in the intersection of the
+                // customizable region and what the app reported as exclusion areas, because
+                // the drag-move or other caption gestures should take priority outside those
+                // regions.
+                mShouldPilferCaptionEvents = !(downInCustomizableCaptionRegion
+                        && downInExclusionRegion && isTransparentCaption);
+            }
+
+            if (!mShouldPilferCaptionEvents) {
+                // The event will be handled by a window below.
+                return false;
+            }
+            // Otherwise pilfer so that windows below receive cancellations for this gesture, and
+            // continue normal handling as a caption gesture.
+            if (mInputManager != null) {
+                mInputManager.pilferPointers(v.getViewRootImpl().getInputToken());
+            }
+            if (isUpOrCancel) {
+                // Gesture is finished, reset state.
+                mShouldPilferCaptionEvents = false;
+            }
             if (!mHasLongClicked && id != R.id.maximize_window) {
                 decoration.closeMaximizeMenuIfNeeded(e);
             }
-
-            final long eventDuration = e.getEventTime() - e.getDownTime();
-            final boolean isTouchScreen =
-                    (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
-            final boolean shouldLongClick = isTouchScreen && id == R.id.maximize_window
-                    && !mIsDragging && !mHasLongClicked
-                    && eventDuration >= ViewConfiguration.getLongPressTimeout();
-            if (shouldLongClick) {
-                v.performLongClick();
-                mHasLongClicked = true;
-                return true;
-            }
-
             return mDragDetector.onMotionEvent(v, e);
         }
 
         @Override
         public boolean onLongClick(View v) {
             final int id = v.getId();
-            if (id == R.id.maximize_window) {
+            if (id == R.id.maximize_window && mTouchscreenInUse) {
                 final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
                 moveTaskToFront(decoration.mTaskInfo);
                 if (decoration.isMaximizeMenuActive()) {
                     decoration.closeMaximizeMenu();
                 } else {
+                    mHasLongClicked = true;
                     decoration.createMaximizeMenu();
                 }
                 return true;
@@ -481,8 +554,7 @@
             } else if (ev.getAction() == ACTION_HOVER_EXIT) {
                 if (!decoration.isMaximizeMenuActive() && id == R.id.maximize_window) {
                     decoration.onMaximizeWindowHoverExit();
-                } else if (id == R.id.maximize_window
-                        || MaximizeMenu.Companion.isMaximizeMenuView(id)) {
+                } else if (id == R.id.maximize_window || id == R.id.maximize_menu) {
                     // Close menu if not hovering over maximize menu or maximize button after a
                     // delay to give user a chance to re-enter view or to move from one maximize
                     // menu view to another.
@@ -496,7 +568,7 @@
 
         private void moveTaskToFront(RunningTaskInfo taskInfo) {
             if (!taskInfo.isFocused) {
-                mDesktopTasksController.ifPresent(c -> c.moveTaskToFront(taskInfo));
+                mDesktopTasksController.moveTaskToFront(taskInfo);
             }
         }
 
@@ -515,11 +587,9 @@
             if (mGestureDetector.onTouchEvent(e)) {
                 return true;
             }
-            if (e.getActionMasked() == MotionEvent.ACTION_CANCEL) {
-                // If a motion event is cancelled, reset mShouldClick so a click is not accidentally
-                // performed.
-                mShouldClick = false;
-            }
+            final int id = v.getId();
+            final boolean touchingButton = (id == R.id.close_window || id == R.id.maximize_window
+                    || id == R.id.open_menu_button);
             switch (e.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN: {
                     mDragPointerId = e.getPointerId(0);
@@ -527,12 +597,12 @@
                             0 /* ctrlType */, e.getRawX(0),
                             e.getRawY(0));
                     mIsDragging = false;
-                    mShouldClick = true;
                     mHasLongClicked = false;
-                    return true;
+                    // Do not consume input event if a button is touched, otherwise it would
+                    // prevent the button's ripple effect from showing.
+                    return !touchingButton;
                 }
                 case MotionEvent.ACTION_MOVE: {
-                    mShouldClick = false;
                     // If a decor's resize drag zone is active, don't also try to reposition it.
                     if (decoration.isHandlingDragResize()) break;
                     decoration.closeMaximizeMenu();
@@ -542,10 +612,10 @@
                     final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
                     final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningMove(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
-                    mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo,
+                    mDesktopTasksController.onDragPositioningMove(taskInfo,
                             decoration.mTaskSurface,
                             e.getRawX(dragPointerIdx),
-                            newTaskBounds));
+                            newTaskBounds);
                     mIsDragging = true;
                     return true;
                 }
@@ -553,11 +623,6 @@
                 case MotionEvent.ACTION_CANCEL: {
                     final boolean wasDragging = mIsDragging;
                     if (!wasDragging) {
-                        if (mShouldClick && v != null && !mHasLongClicked) {
-                            v.performClick();
-                            mShouldClick = false;
-                            return true;
-                        }
                         return false;
                     }
                     if (e.findPointerIndex(mDragPointerId) == -1) {
@@ -572,12 +637,18 @@
                             (int) (e.getRawY(dragPointerIdx) - e.getY(dragPointerIdx)));
                     final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
-                    mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
-                            position,
+                    mDesktopTasksController.onDragPositioningEnd(taskInfo, position,
                             new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
-                            newTaskBounds));
-                    mIsDragging = false;
-                    return true;
+                            newTaskBounds);
+                    if (touchingButton && !mHasLongClicked) {
+                        // We need the input event to not be consumed here to end the ripple
+                        // effect on the touched button. We will reset drag state in the ensuing
+                        // onClick call that results.
+                        return false;
+                    } else {
+                        mIsDragging = false;
+                        return true;
+                    }
                 }
             }
             return true;
@@ -596,10 +667,8 @@
                     && action != MotionEvent.ACTION_CANCEL)) {
                 return false;
             }
-            mDesktopTasksController.ifPresent(c -> {
-                final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
-                c.toggleDesktopTaskSize(decoration.mTaskInfo);
-            });
+            final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+            mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo);
             return true;
         }
     }
@@ -763,20 +832,29 @@
                     return;
                 }
                 if (mTransitionDragActive) {
+                    final DesktopModeVisualIndicator.IndicatorType indicatorType =
+                            mDesktopTasksController.updateVisualIndicator(relevantDecor.mTaskInfo,
+                                    relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY());
                     mTransitionDragActive = false;
-                    final int statusBarHeight = getStatusBarHeight(
-                            relevantDecor.mTaskInfo.displayId);
-                    if (ev.getRawY() > 2 * statusBarHeight) {
+                    if (indicatorType == TO_DESKTOP_INDICATOR
+                            || indicatorType == TO_SPLIT_LEFT_INDICATOR
+                            || indicatorType == TO_SPLIT_RIGHT_INDICATOR) {
                         if (DesktopModeStatus.isEnabled()) {
                             animateToDesktop(relevantDecor, ev);
                         }
                         mMoveToDesktopAnimator = null;
                         return;
                     } else if (mMoveToDesktopAnimator != null) {
-                        mDesktopTasksController.ifPresent(
-                                c -> c.cancelDragToDesktop(relevantDecor.mTaskInfo));
+                        mDesktopTasksController.onDragPositioningEndThroughStatusBar(
+                                new PointF(ev.getRawX(), ev.getRawY()),
+                                relevantDecor.mTaskInfo,
+                                calculateFreeformBounds(ev.getDisplayId(), DRAG_FREEFORM_SCALE));
                         mMoveToDesktopAnimator = null;
                         return;
+                    } else {
+                        // In cases where we create an indicator but do not start the
+                        // move-to-desktop animation, we need to dismiss it.
+                        mDesktopTasksController.releaseVisualIndicator();
                     }
                 }
                 relevantDecor.checkClickEvent(ev);
@@ -788,20 +866,23 @@
                     return;
                 }
                 if (mTransitionDragActive) {
-                    mDesktopTasksController.ifPresent(
-                            c -> c.updateVisualIndicator(
+                    // Do not create an indicator at all if we're not past transition height.
+                    if (ev.getRawY() < mContext.getResources().getDimensionPixelSize(com.android
+                            .wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height)
+                            && mMoveToDesktopAnimator == null) {
+                        return;
+                    }
+                    final DesktopModeVisualIndicator.IndicatorType indicatorType =
+                            mDesktopTasksController.updateVisualIndicator(
                                     relevantDecor.mTaskInfo,
-                                    relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY()));
-                    final int statusBarHeight = getStatusBarHeight(
-                            relevantDecor.mTaskInfo.displayId);
-                    if (ev.getRawY() > statusBarHeight) {
+                                    relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY());
+                    if (indicatorType != TO_FULLSCREEN_INDICATOR) {
                         if (mMoveToDesktopAnimator == null) {
                             mMoveToDesktopAnimator = new MoveToDesktopAnimator(
                                     mContext, mDragToDesktopAnimationStartBounds,
                                     relevantDecor.mTaskInfo, relevantDecor.mTaskSurface);
-                            mDesktopTasksController.ifPresent(
-                                    c -> c.startDragToDesktop(relevantDecor.mTaskInfo,
-                                            mMoveToDesktopAnimator));
+                            mDesktopTasksController.startDragToDesktop(relevantDecor.mTaskInfo,
+                                    mMoveToDesktopAnimator);
                         }
                     }
                     if (mMoveToDesktopAnimator != null) {
@@ -847,6 +928,8 @@
      * Animates a window to the center, grows to freeform size, and transitions to Desktop Mode.
      * @param relevantDecor the window decor of the task to be animated
      * @param ev the motion event that triggers the animation
+     * TODO(b/315527000): This animation needs to be adjusted to allow snap left/right cases.
+     *  Currently fullscreen -> split snap still animates to center screen before readjusting.
      */
     private void centerAndMoveToDesktopWithAnimation(DesktopModeWindowDecoration relevantDecor,
             MotionEvent ev) {
@@ -870,13 +953,12 @@
         animator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                mDesktopTasksController.ifPresent(
-                        c -> {
-                            c.onDragPositioningEndThroughStatusBar(relevantDecor.mTaskInfo,
-                                    calculateFreeformBounds(ev.getDisplayId(),
-                                            DesktopTasksController
-                                                    .DESKTOP_MODE_INITIAL_BOUNDS_SCALE));
-                        });
+                mDesktopTasksController.onDragPositioningEndThroughStatusBar(
+                        new PointF(ev.getRawX(), ev.getRawY()),
+                        relevantDecor.mTaskInfo,
+                        calculateFreeformBounds(ev.getDisplayId(),
+                                DesktopTasksController
+                                        .DESKTOP_MODE_INITIAL_BOUNDS_SCALE));
             }
         });
         animator.start();
@@ -976,8 +1058,7 @@
                 && taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED
                 && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
                 && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop()
-                && mDisplayController.getDisplayContext(taskInfo.displayId)
-                .getResources().getConfiguration().smallestScreenWidthDp >= 600;
+                && DesktopModeStatus.canEnterDesktopMode(mContext);
     }
 
     private void createWindowDecoration(
@@ -1006,7 +1087,7 @@
 
         final DragPositioningCallback dragPositioningCallback;
         final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
-                R.dimen.desktop_mode_transition_area_height);
+                R.dimen.desktop_mode_fullscreen_from_desktop_height);
         if (!DesktopModeStatus.isVeiledResizeEnabled()) {
             dragPositioningCallback =  new FluidResizeTaskPositioner(
                     mTaskOrganizer, mTransitions, windowDecoration, mDisplayController,
@@ -1041,12 +1122,6 @@
         return mSplitScreenController.getTaskInfo(remainingTaskPosition);
     }
 
-    private void closeOtherSplitTask(int taskId) {
-        if (isTaskInSplitScreen(taskId)) {
-            mTaskOperations.closeTask(getOtherSplitTask(taskId).token);
-        }
-    }
-
     private boolean isTaskInSplitScreen(int taskId) {
         return mSplitScreenController != null
                 && mSplitScreenController.isTaskInSplitScreen(taskId);
@@ -1111,12 +1186,12 @@
 
         @Override
         public void onExclusionRegionChanged(int taskId, Region region) {
-            mDesktopTasksController.ifPresent(d -> d.onExclusionRegionChanged(taskId, region));
+            mDesktopTasksController.onExclusionRegionChanged(taskId, region);
         }
 
         @Override
         public void onExclusionRegionDismissed(int taskId) {
-            mDesktopTasksController.ifPresent(d -> d.removeExclusionRegionForTask(taskId));
+            mDesktopTasksController.removeExclusionRegionForTask(taskId);
         }
     }
 
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 74f460b..c9669a7 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
@@ -56,6 +56,7 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
 import com.android.wm.shell.windowdecor.viewholder.DesktopModeAppControlsWindowDecorationViewHolder;
 import com.android.wm.shell.windowdecor.viewholder.DesktopModeFocusedWindowDecorationViewHolder;
 import com.android.wm.shell.windowdecor.viewholder.DesktopModeWindowDecorationViewHolder;
@@ -317,24 +318,24 @@
         relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode());
         relayoutParams.mCaptionWidthId = getCaptionWidthId(relayoutParams.mLayoutResId);
 
-        // The "app controls" type caption bar should report the occluding elements as bounding
-        // rects to the insets system so that apps can draw in the empty space left in the center.
         if (captionLayoutId == R.layout.desktop_mode_app_controls_window_decor) {
-            // The "app chip" section of the caption bar, it's aligned to the left and its width
-            // varies depending on the length of the app name, but we'll report its max width for
-            // now.
-            // TODO(b/316387515): consider reporting the true width after it's been laid out.
+            // If the app is requesting to customize the caption bar, allow input to fall through
+            // to the windows below so that the app can respond to input events on their custom
+            // content.
+            relayoutParams.mAllowCaptionInputFallthrough =
+                    TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo);
+            // Report occluding elements as bounding rects to the insets system so that apps can
+            // draw in the empty space in the center:
+            //   First, the "app chip" section of the caption bar (+ some extra margins).
             final RelayoutParams.OccludingCaptionElement appChipElement =
                     new RelayoutParams.OccludingCaptionElement();
-            appChipElement.mWidthResId = R.dimen.desktop_mode_app_details_max_width;
+            appChipElement.mWidthResId = R.dimen.desktop_mode_customizable_caption_margin_start;
             appChipElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.START;
             relayoutParams.mOccludingCaptionElements.add(appChipElement);
-            // The "controls" section of the caption bar (maximize, close btns). These are aligned
-            // to the right of the caption bar and have a fixed width.
-            // TODO(b/316387515): add additional padding for an exclusive drag-move region.
+            //   Then, the right-aligned section (drag space, maximize and close buttons).
             final RelayoutParams.OccludingCaptionElement controlsElement =
                     new RelayoutParams.OccludingCaptionElement();
-            controlsElement.mWidthResId = R.dimen.desktop_mode_right_edge_buttons_width;
+            controlsElement.mWidthResId = R.dimen.desktop_mode_customizable_caption_margin_end;
             controlsElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.END;
             relayoutParams.mOccludingCaptionElements.add(controlsElement);
         }
@@ -699,6 +700,13 @@
     }
 
     /**
+     * Checks whether the touch event falls inside the customizable caption region.
+     */
+    boolean checkTouchEventInCustomizableRegion(MotionEvent ev) {
+        return mResult.mCustomizableCaptionRegion.contains((int) ev.getRawX(), (int) ev.getRawY());
+    }
+
+    /**
      * Check a passed MotionEvent if a click has occurred on any button on this caption
      * Note this should only be called when a regular onClick is not possible
      * (i.e. the button was clicked through status bar layer)
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 b37dd0d..3d0dd31 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
@@ -35,7 +35,6 @@
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
 import android.view.View;
-import android.widget.Button;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.TextView;
@@ -53,6 +52,7 @@
  */
 class HandleMenu {
     private static final String TAG = "HandleMenu";
+    private static final boolean SHOULD_SHOW_MORE_ACTIONS_PILL = false;
     private final Context mContext;
     private final WindowDecoration mParentDecor;
     private WindowDecoration.AdditionalWindow mHandleMenuWindow;
@@ -185,11 +185,9 @@
      * Set up interactive elements & height of handle menu's more actions pill
      */
     private void setupMoreActionsPill(View handleMenu) {
-        final Button selectBtn = handleMenu.findViewById(R.id.select_button);
-        selectBtn.setOnClickListener(mOnClickListener);
-        final Button screenshotBtn = handleMenu.findViewById(R.id.screenshot_button);
-        // TODO: Remove once implemented.
-        screenshotBtn.setVisibility(View.GONE);
+        if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
+            handleMenu.findViewById(R.id.more_actions_pill).setVisibility(View.GONE);
+        }
     }
 
     /**
@@ -305,12 +303,15 @@
      * Determines handle menu height based on if windowing pill should be shown.
      */
     private int getHandleMenuHeight(Resources resources) {
-        int menuHeight = loadDimensionPixelSize(resources,
-                R.dimen.desktop_mode_handle_menu_height);
+        int menuHeight = loadDimensionPixelSize(resources, R.dimen.desktop_mode_handle_menu_height);
         if (!mShouldShowWindowingPill) {
             menuHeight -= loadDimensionPixelSize(resources,
                     R.dimen.desktop_mode_handle_menu_windowing_pill_height);
         }
+        if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
+            menuHeight -= loadDimensionPixelSize(resources,
+                    R.dimen.desktop_mode_handle_menu_more_actions_pill_height);
+        }
         return menuHeight;
     }
 
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 dc65855..32c2d1e 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
@@ -31,6 +31,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.Region;
 import android.os.Binder;
 import android.view.Display;
 import android.view.InsetsSource;
@@ -311,6 +312,10 @@
                 if (numOfElements == 0) {
                     boundingRects = null;
                 } else {
+                    // The customizable region can at most be equal to the caption bar.
+                    if (params.mAllowCaptionInputFallthrough) {
+                        outResult.mCustomizableCaptionRegion.set(mCaptionInsetsRect);
+                    }
                     boundingRects = new Rect[numOfElements];
                     for (int i = 0; i < numOfElements; i++) {
                         final OccludingCaptionElement element =
@@ -319,9 +324,14 @@
                                 resources.getDimensionPixelSize(element.mWidthResId);
                         boundingRects[i] =
                                 calculateBoundingRect(element, elementWidthPx, mCaptionInsetsRect);
+                        // Subtract the regions used by the caption elements, the rest is
+                        // customizable.
+                        if (params.mAllowCaptionInputFallthrough) {
+                            outResult.mCustomizableCaptionRegion.op(boundingRects[i],
+                                    Region.Op.DIFFERENCE);
+                        }
                     }
                 }
-
                 // Add this caption as an inset source.
                 wct.addInsetsSource(mTaskInfo.token,
                         mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect,
@@ -389,6 +399,11 @@
                         WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
         lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
         lp.setTrustedOverlay();
+        if (params.mAllowCaptionInputFallthrough) {
+            lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY;
+        } else {
+            lp.inputFeatures &= ~WindowManager.LayoutParams.INPUT_FEATURE_SPY;
+        }
         if (mViewHost == null) {
             mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay,
                     mCaptionWindowManager);
@@ -596,6 +611,7 @@
         int mCaptionHeightId;
         int mCaptionWidthId;
         final List<OccludingCaptionElement> mOccludingCaptionElements = new ArrayList<>();
+        boolean mAllowCaptionInputFallthrough;
 
         int mShadowRadiusId;
         int mCornerRadius;
@@ -610,6 +626,7 @@
             mCaptionHeightId = Resources.ID_NULL;
             mCaptionWidthId = Resources.ID_NULL;
             mOccludingCaptionElements.clear();
+            mAllowCaptionInputFallthrough = false;
 
             mShadowRadiusId = Resources.ID_NULL;
             mCornerRadius = 0;
@@ -637,6 +654,7 @@
         int mCaptionHeight;
         int mCaptionWidth;
         int mCaptionX;
+        final Region mCustomizableCaptionRegion = Region.obtain();
         int mWidth;
         int mHeight;
         T mRootView;
@@ -647,6 +665,7 @@
             mCaptionHeight = 0;
             mCaptionWidth = 0;
             mCaptionX = 0;
+            mCustomizableCaptionRegion.setEmpty();
             mRootView = null;
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
new file mode 100644
index 0000000..7a64a47
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor.extension
+
+import android.app.TaskInfo
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS
+import android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND
+
+val TaskInfo.isTransparentCaptionBarAppearance: Boolean
+    get() {
+        val appearance = taskDescription?.systemBarsAppearance ?: 0
+        return (appearance and APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND) != 0
+    }
+
+val TaskInfo.isLightCaptionBarAppearance: Boolean
+    get() {
+        val appearance = taskDescription?.systemBarsAppearance ?: 0
+        return (appearance and APPEARANCE_LIGHT_CAPTION_BARS) != 0
+    }
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 7e5b9bd..58bbb03 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
@@ -8,12 +8,11 @@
 import android.graphics.Color
 import android.view.View
 import android.view.View.OnLongClickListener
-import android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS
-import android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND
 import android.widget.ImageButton
 import android.widget.ImageView
 import android.widget.TextView
 import androidx.core.content.withStyledAttributes
+import androidx.core.view.isVisible
 import com.android.internal.R.attr.materialColorOnSecondaryContainer
 import com.android.internal.R.attr.materialColorOnSurface
 import com.android.internal.R.attr.materialColorSecondaryContainer
@@ -22,6 +21,8 @@
 import com.android.internal.R.attr.materialColorSurfaceDim
 import com.android.wm.shell.R
 import com.android.wm.shell.windowdecor.MaximizeButtonView
+import com.android.wm.shell.windowdecor.extension.isLightCaptionBarAppearance
+import com.android.wm.shell.windowdecor.extension.isTransparentCaptionBarAppearance
 
 /**
  * A desktop mode window decoration used when the window is floating (i.e. freeform). It hosts
@@ -76,6 +77,7 @@
         closeWindowButton.imageTintList = ColorStateList.valueOf(color)
         maximizeWindowButton.imageTintList = ColorStateList.valueOf(color)
         expandMenuButton.imageTintList = ColorStateList.valueOf(color)
+        appNameTextView.isVisible = !taskInfo.isTransparentCaptionBarAppearance
         appNameTextView.setTextColor(color)
         appIconImageView.imageAlpha = alpha
         maximizeWindowButton.imageAlpha = alpha
@@ -107,7 +109,7 @@
 
     @ColorInt
     private fun getCaptionBackgroundColor(taskInfo: RunningTaskInfo): Int {
-        if (isTransparentBackgroundRequested(taskInfo)) {
+        if (taskInfo.isTransparentCaptionBarAppearance) {
             return Color.TRANSPARENT
         }
         val materialColorAttr: Int =
@@ -133,10 +135,10 @@
     @ColorInt
     private fun getAppNameAndButtonColor(taskInfo: RunningTaskInfo): Int {
         val materialColorAttr = when {
-            isTransparentBackgroundRequested(taskInfo) &&
-                    isLightCaptionBar(taskInfo) -> materialColorOnSecondaryContainer
-            isTransparentBackgroundRequested(taskInfo) &&
-                    !isLightCaptionBar(taskInfo) -> materialColorOnSurface
+            taskInfo.isTransparentCaptionBarAppearance &&
+                    taskInfo.isLightCaptionBarAppearance -> materialColorOnSecondaryContainer
+            taskInfo.isTransparentCaptionBarAppearance &&
+                    !taskInfo.isLightCaptionBarAppearance -> materialColorOnSurface
             isDarkMode() -> materialColorOnSurface
             else -> materialColorOnSecondaryContainer
         }
@@ -167,16 +169,6 @@
                 Configuration.UI_MODE_NIGHT_YES
     }
 
-    private fun isTransparentBackgroundRequested(taskInfo: RunningTaskInfo): Boolean {
-        val appearance = taskInfo.taskDescription?.statusBarAppearance ?: 0
-        return (appearance and APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND) != 0
-    }
-
-    private fun isLightCaptionBar(taskInfo: RunningTaskInfo): Boolean {
-        val appearance = taskInfo.taskDescription?.statusBarAppearance ?: 0
-        return (appearance and APPEARANCE_LIGHT_CAPTION_BARS) != 0
-    }
-
     companion object {
         private const val TAG = "DesktopModeAppControlsWindowDecorationViewHolder"
         private const val DARK_THEME_UNFOCUSED_OPACITY = 140 // 55%
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 6dcae27..96bc4a1 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
@@ -65,7 +65,7 @@
                     taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
                     Color.valueOf(taskDescription.statusBarColor).luminance() < 0.5
                 } else {
-                    taskDescription.statusBarAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
+                    taskDescription.systemBarsAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
                 }
             } ?: false
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index ac7380f..9a1bd26 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -206,6 +206,18 @@
         }
     }
 
+    @Presubmit
+    @Test
+    fun pipLayerRemainInsideVisibleBounds() {
+        // during the transition we assert the center point is within the display bounds, since it
+        // might go outside of bounds as we resize from landscape fullscreen to destination bounds,
+        // and once the animation is over we assert that it's fully within the display bounds, at
+        // which point the device also performs orientation change from landscape to portrait
+        flicker.assertLayersVisibleRegion(pipApp.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)) {
+            regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds)
+        }
+    }
+
     /** {@inheritDoc} */
     @FlakyTest(bugId = 267424412)
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
index 89ef91e..6171074 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
@@ -19,7 +19,6 @@
 import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
-import android.tools.AndroidLoggerSetupRule
 import android.tools.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
@@ -28,7 +27,6 @@
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
-import org.junit.ClassRule
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -66,8 +64,4 @@
         primaryApp.exit(wmHelper)
         secondaryApp.exit(wmHelper)
     }
-
-    companion object {
-        @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
index 4336692..9e6a686 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
@@ -19,7 +19,6 @@
 import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
-import android.tools.AndroidLoggerSetupRule
 import android.tools.flicker.rules.ChangeDisplayOrientationRule
 import android.tools.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
@@ -30,7 +29,6 @@
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
-import org.junit.ClassRule
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -77,8 +75,4 @@
         secondaryApp.exit(wmHelper)
         sendNotificationApp.exit(wmHelper)
     }
-
-    companion object {
-        @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
index 8c7e63f..3f07be0 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
@@ -19,7 +19,6 @@
 import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
-import android.tools.AndroidLoggerSetupRule
 import android.tools.flicker.rules.ChangeDisplayOrientationRule
 import android.tools.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
@@ -30,7 +29,6 @@
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
-import org.junit.ClassRule
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -88,8 +86,4 @@
         secondaryApp.exit(wmHelper)
         tapl.enableBlockTimeout(false)
     }
-
-    companion object {
-        @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
index 2072831..5328013 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
@@ -19,7 +19,6 @@
 import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
-import android.tools.AndroidLoggerSetupRule
 import android.tools.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
@@ -29,7 +28,6 @@
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
-import org.junit.ClassRule
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -76,8 +74,4 @@
         secondaryApp.exit(wmHelper)
         tapl.enableBlockTimeout(false)
     }
-
-    companion object {
-        @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
index 09e77cc..be4035d 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
@@ -19,7 +19,6 @@
 import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
-import android.tools.AndroidLoggerSetupRule
 import android.tools.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
@@ -28,7 +27,6 @@
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
-import org.junit.ClassRule
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -72,8 +70,4 @@
         primaryApp.exit(wmHelper)
         secondaryApp.exit(wmHelper)
     }
-
-    companion object {
-        @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
index babdae1..9312c0a 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
@@ -20,7 +20,6 @@
 import android.graphics.Point
 import android.tools.NavBar
 import android.tools.Rotation
-import android.tools.AndroidLoggerSetupRule
 import android.tools.helpers.WindowUtils
 import android.tools.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
@@ -30,7 +29,6 @@
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
-import org.junit.ClassRule
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -151,8 +149,4 @@
         val LARGE_SCREEN_DP_THRESHOLD = 600
         return sizeDp.x >= LARGE_SCREEN_DP_THRESHOLD && sizeDp.y >= LARGE_SCREEN_DP_THRESHOLD
     }
-
-    companion object {
-        @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
index 3e85479..de26982 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
@@ -19,7 +19,6 @@
 import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
-import android.tools.AndroidLoggerSetupRule
 import android.tools.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
@@ -28,7 +27,6 @@
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
-import org.junit.ClassRule
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -69,8 +67,4 @@
         primaryApp.exit(wmHelper)
         secondaryApp.exit(wmHelper)
     }
-
-    companion object {
-        @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
index 655ae4e..873b019 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
@@ -19,7 +19,6 @@
 import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
-import android.tools.AndroidLoggerSetupRule
 import android.tools.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
@@ -28,7 +27,6 @@
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
-import org.junit.ClassRule
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -68,8 +66,4 @@
         primaryApp.exit(wmHelper)
         secondaryApp.exit(wmHelper)
     }
-
-    companion object {
-        @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
index 2208258..15934d0 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
@@ -19,7 +19,6 @@
 import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
-import android.tools.AndroidLoggerSetupRule
 import android.tools.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
@@ -28,7 +27,6 @@
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
-import org.junit.ClassRule
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -70,8 +68,4 @@
         primaryApp.exit(wmHelper)
         secondaryApp.exit(wmHelper)
     }
-
-    companion object {
-        @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
index 2ac63c2..79e69ae 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
@@ -19,7 +19,6 @@
 import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
-import android.tools.AndroidLoggerSetupRule
 import android.tools.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
@@ -28,7 +27,6 @@
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
-import org.junit.ClassRule
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -71,8 +69,4 @@
         thirdApp.exit(wmHelper)
         fourthApp.exit(wmHelper)
     }
-
-    companion object {
-        @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
index 35b122d..0f932d4 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
@@ -19,7 +19,6 @@
 import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
-import android.tools.AndroidLoggerSetupRule
 import android.tools.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
@@ -28,7 +27,6 @@
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
-import org.junit.ClassRule
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -68,8 +66,4 @@
         primaryApp.exit(wmHelper)
         secondaryApp.exit(wmHelper)
     }
-
-    companion object {
-        @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index fa0aba5..48e396a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -49,6 +49,8 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.bubbles.BubbleData.TimeSource;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
+import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
 
 import com.google.common.collect.ImmutableList;
 
@@ -1207,6 +1209,19 @@
         assertOverflowChangedTo(ImmutableList.of());
     }
 
+    @Test
+    public void test_getInitialStateForBubbleBar_includesInitialBubblesAndPosition() {
+        sendUpdatedEntryAtTime(mEntryA1, 1000);
+        sendUpdatedEntryAtTime(mEntryA2, 2000);
+        mPositioner.setBubbleBarLocation(BubbleBarLocation.LEFT);
+
+        BubbleBarUpdate update = mBubbleData.getInitialStateForBubbleBar();
+        assertThat(update.currentBubbleList).hasSize(2);
+        assertThat(update.currentBubbleList.get(0).getKey()).isEqualTo(mEntryA2.getKey());
+        assertThat(update.currentBubbleList.get(1).getKey()).isEqualTo(mEntryA1.getKey());
+        assertThat(update.bubbleBarLocation).isEqualTo(BubbleBarLocation.LEFT);
+    }
+
     private void verifyUpdateReceived() {
         verify(mListener).applyUpdate(mUpdateCaptor.capture());
         reset(mListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index ae39fbc..4a4c5e8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -37,6 +37,7 @@
 import com.android.wm.shell.bubbles.bar.BubbleBarLayerView
 import com.android.wm.shell.bubbles.properties.BubbleProperties
 import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayInsetsController
 import com.android.wm.shell.common.FloatingContentCoordinator
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SyncTransactionQueue
@@ -94,7 +95,8 @@
         val windowManager = context.getSystemService(WindowManager::class.java)
         val shellInit = ShellInit(mainExecutor)
         val shellCommandHandler = ShellCommandHandler()
-        val shellController = ShellController(context, shellInit, shellCommandHandler, mainExecutor)
+        val shellController = ShellController(context, shellInit, shellCommandHandler,
+					      mock<DisplayInsetsController>(), mainExecutor)
         bubblePositioner = BubblePositioner(context, windowManager)
         val bubbleData =
             BubbleData(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 01e2f98..2c0aa12 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -38,6 +38,7 @@
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.SurfaceControl;
+import android.view.inputmethod.ImeTracker;
 
 import androidx.test.filters.SmallTest;
 
@@ -51,6 +52,12 @@
 
 import java.util.concurrent.Executor;
 
+/**
+ * Tests for the display IME controller.
+ *
+ * <p> Build/Install/Run:
+ *  atest WMShellUnitTests:DisplayImeControllerTest
+ */
 @SmallTest
 public class DisplayImeControllerTest extends ShellTestCase {
 
@@ -99,13 +106,13 @@
 
     @Test
     public void showInsets_schedulesNoWorkOnExecutor() {
-        mPerDisplay.showInsets(ime(), true /* fromIme */, null /* statsToken */);
+        mPerDisplay.showInsets(ime(), true /* fromIme */, ImeTracker.Token.empty());
         verifyZeroInteractions(mExecutor);
     }
 
     @Test
     public void hideInsets_schedulesNoWorkOnExecutor() {
-        mPerDisplay.hideInsets(ime(), true /* fromIme */, null /* statsToken */);
+        mPerDisplay.hideInsets(ime(), true /* fromIme */, ImeTracker.Token.empty());
         verifyZeroInteractions(mExecutor);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
index 956f1cd..669e433 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
@@ -50,6 +50,12 @@
 
 import java.util.List;
 
+/**
+ * Tests for the display insets controller.
+ *
+ * <p> Build/Install/Run:
+ *  atest WMShellUnitTests:DisplayInsetsControllerTest
+ */
 @SmallTest
 public class DisplayInsetsControllerTest extends ShellTestCase {
 
@@ -114,9 +120,9 @@
         mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsChanged(null);
         mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsControlChanged(null, null);
         mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false,
-                null /* statsToken */);
+                ImeTracker.Token.empty());
         mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).hideInsets(0, false,
-                null /* statsToken */);
+                ImeTracker.Token.empty());
         mExecutor.flushAll();
 
         assertTrue(defaultListener.topFocusedWindowChangedCount == 1);
@@ -136,9 +142,9 @@
         mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsChanged(null);
         mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsControlChanged(null, null);
         mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false,
-                null /* statsToken */);
+                ImeTracker.Token.empty());
         mInsetsControllersByDisplayId.get(SECOND_DISPLAY).hideInsets(0, false,
-                null /* statsToken */);
+                ImeTracker.Token.empty());
         mExecutor.flushAll();
 
         assertTrue(defaultListener.topFocusedWindowChangedCount == 1);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleBarLocationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleBarLocationTest.kt
new file mode 100644
index 0000000..27e0b19
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleBarLocationTest.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common.bubbles
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.bubbles.BubbleBarLocation.DEFAULT
+import com.android.wm.shell.common.bubbles.BubbleBarLocation.LEFT
+import com.android.wm.shell.common.bubbles.BubbleBarLocation.RIGHT
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class BubbleBarLocationTest : ShellTestCase() {
+
+    @Test
+    fun isOnLeft_rtlEnabled_defaultsToLeft() {
+        assertThat(DEFAULT.isOnLeft(isRtl = true)).isTrue()
+    }
+
+    @Test
+    fun isOnLeft_rtlDisabled_defaultsToRight() {
+        assertThat(DEFAULT.isOnLeft(isRtl = false)).isFalse()
+    }
+
+    @Test
+    fun isOnLeft_left_trueForAllLanguageDirections() {
+        assertThat(LEFT.isOnLeft(isRtl = false)).isTrue()
+        assertThat(LEFT.isOnLeft(isRtl = true)).isTrue()
+    }
+
+    @Test
+    fun isOnLeft_right_falseForAllLanguageDirections() {
+        assertThat(RIGHT.isOnLeft(isRtl = false)).isFalse()
+        assertThat(RIGHT.isOnLeft(isRtl = true)).isFalse()
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
new file mode 100644
index 0000000..65117f7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS
+import android.view.WindowManager.TRANSIT_NONE
+import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_SLEEP
+import android.view.WindowManager.TRANSIT_TO_BACK
+import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.view.WindowManager.TRANSIT_WAKE
+import android.window.IWindowContainerToken
+import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
+import android.window.WindowContainerToken
+import androidx.test.filters.SmallTest
+import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.TransitionInfoBuilder
+import com.android.wm.shell.transition.Transitions
+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.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.never
+import org.mockito.kotlin.same
+import org.mockito.kotlin.times
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopModeLoggerTransitionObserverTest {
+
+    @JvmField
+    @Rule
+    val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
+            .mockStatic(DesktopModeEventLogger::class.java)
+            .mockStatic(DesktopModeStatus::class.java).build()!!
+
+    @Mock
+    lateinit var testExecutor: ShellExecutor
+    @Mock
+    private lateinit var mockShellInit: ShellInit
+    @Mock
+    private lateinit var transitions: Transitions
+
+    private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver
+    private lateinit var shellInit: ShellInit
+    private lateinit var desktopModeEventLogger: DesktopModeEventLogger
+
+    @Before
+    fun setup() {
+        Mockito.`when`(DesktopModeStatus.isEnabled()).thenReturn(true)
+        shellInit = Mockito.spy(ShellInit(testExecutor))
+        desktopModeEventLogger = mock(DesktopModeEventLogger::class.java)
+
+        transitionObserver = DesktopModeLoggerTransitionObserver(
+            mockShellInit, transitions, desktopModeEventLogger)
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            val initRunnableCaptor = ArgumentCaptor.forClass(
+                Runnable::class.java)
+            verify(mockShellInit).addInitCallback(initRunnableCaptor.capture(),
+                same(transitionObserver))
+            initRunnableCaptor.value.run()
+        } else {
+            transitionObserver.onInit()
+        }
+    }
+
+    @Test
+    fun testRegistersObserverAtInit() {
+        verify(transitions)
+                .registerObserver(same(
+                    transitionObserver))
+    }
+
+    @Test
+    fun taskCreated_notFreeformWindow_doesNotLogSessionEnterOrTaskAdded() {
+        val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+        val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+
+        callOnTransitionReady(transitionInfo)
+
+        verify(desktopModeEventLogger, never()).logSessionEnter(any(), any())
+        verify(desktopModeEventLogger, never()).logTaskAdded(any(), any())
+    }
+
+    @Test
+    fun taskCreated_FreeformWindowOpen_logSessionEnterAndTaskAdded() {
+        val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+
+        callOnTransitionReady(transitionInfo)
+        val sessionId = transitionObserver.getLoggerSessionId()
+
+        assertThat(sessionId).isNotNull()
+        verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+            eq(EnterReason.APP_FREEFORM_INTENT))
+        verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+    }
+
+    @Test
+    fun taskChanged_taskMovedToDesktopByDrag_logSessionEnterAndTaskAdded() {
+        val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        // task change is finalised when drag ends
+        val transitionInfo = TransitionInfoBuilder(
+            Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, 0).addChange(change).build()
+
+        callOnTransitionReady(transitionInfo)
+        val sessionId = transitionObserver.getLoggerSessionId()
+
+        assertThat(sessionId).isNotNull()
+        verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+            eq(EnterReason.APP_HANDLE_DRAG))
+        verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+    }
+
+    @Test
+    fun taskChanged_taskMovedToDesktopByButtonTap_logSessionEnterAndTaskAdded() {
+        val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        val transitionInfo = TransitionInfoBuilder(Transitions.TRANSIT_MOVE_TO_DESKTOP, 0)
+                .addChange(change).build()
+
+        callOnTransitionReady(transitionInfo)
+        val sessionId = transitionObserver.getLoggerSessionId()
+
+        assertThat(sessionId).isNotNull()
+        verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+            eq(EnterReason.APP_HANDLE_MENU_BUTTON))
+        verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+    }
+
+    @Test
+    fun taskChanged_existingFreeformTaskMadeVisible_logSessionEnterAndTaskAdded() {
+        val taskInfo = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
+        taskInfo.isVisibleRequested = true
+        val change = createChange(TRANSIT_CHANGE, taskInfo)
+        val transitionInfo = TransitionInfoBuilder(Transitions.TRANSIT_MOVE_TO_DESKTOP, 0)
+                .addChange(change).build()
+
+        callOnTransitionReady(transitionInfo)
+        val sessionId = transitionObserver.getLoggerSessionId()
+
+        assertThat(sessionId).isNotNull()
+        verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+            eq(EnterReason.APP_HANDLE_MENU_BUTTON))
+        verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+    }
+
+    @Test
+    fun taskToFront_screenWake_logSessionStartedAndTaskAdded() {
+        val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        val transitionInfo = TransitionInfoBuilder(TRANSIT_WAKE, 0)
+                .addChange(change).build()
+
+        callOnTransitionReady(transitionInfo)
+        val sessionId = transitionObserver.getLoggerSessionId()
+
+        assertThat(sessionId).isNotNull()
+        verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+            eq(EnterReason.SCREEN_ON))
+        verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+    }
+
+    @Test
+    fun freeformTaskVisible_screenTurnOff_logSessionExitAndTaskRemoved_sessionIdNull() {
+        val sessionId = 1
+        // add a freeform task
+        transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        transitionObserver.setLoggerSessionId(sessionId)
+
+        val transitionInfo = TransitionInfoBuilder(TRANSIT_SLEEP).build()
+        callOnTransitionReady(transitionInfo)
+
+        verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+        verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+            eq(ExitReason.SCREEN_OFF))
+        assertThat(transitionObserver.getLoggerSessionId()).isNull()
+    }
+
+    @Test
+    fun freeformTaskVisible_exitDesktopUsingDrag_logSessionExitAndTaskRemoved_sessionIdNull() {
+        val sessionId = 1
+        // add a freeform task
+        transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        transitionObserver.setLoggerSessionId(sessionId)
+
+        // window mode changing from FREEFORM to FULLSCREEN
+        val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+        val transitionInfo = TransitionInfoBuilder(Transitions.TRANSIT_EXIT_DESKTOP_MODE)
+                .addChange(change).build()
+        callOnTransitionReady(transitionInfo)
+
+        verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+        verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+            eq(ExitReason.DRAG_TO_EXIT))
+        assertThat(transitionObserver.getLoggerSessionId()).isNull()
+    }
+
+    @Test
+    fun freeformTaskVisible_exitDesktopBySwipeUp_logSessionExitAndTaskRemoved_sessionIdNull() {
+        val sessionId = 1
+        // add a freeform task
+        transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        transitionObserver.setLoggerSessionId(sessionId)
+
+        // recents transition
+        val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
+                .addChange(change).build()
+        callOnTransitionReady(transitionInfo)
+
+        verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+        verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+            eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
+        assertThat(transitionObserver.getLoggerSessionId()).isNull()
+    }
+
+    @Test
+    fun freeformTaskVisible_taskFinished_logSessionExitAndTaskRemoved_sessionIdNull() {
+        val sessionId = 1
+        // add a freeform task
+        transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        transitionObserver.setLoggerSessionId(sessionId)
+
+        // task closing
+        val change = createChange(TRANSIT_CLOSE, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+        val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE).addChange(change).build()
+        callOnTransitionReady(transitionInfo)
+
+        verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+        verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+            eq(ExitReason.TASK_FINISHED))
+        assertThat(transitionObserver.getLoggerSessionId()).isNull()
+    }
+
+    @Test
+    fun sessionExitByRecents_cancelledAnimation_sessionRestored() {
+        val sessionId = 1
+        // add a freeform task to an existing session
+        transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        transitionObserver.setLoggerSessionId(sessionId)
+
+        // recents transition sent freeform window to back
+        val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        val transitionInfo1 =
+            TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS).addChange(change)
+                    .build()
+        callOnTransitionReady(transitionInfo1)
+        verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+        verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+            eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
+        assertThat(transitionObserver.getLoggerSessionId()).isNull()
+
+        val transitionInfo2 = TransitionInfoBuilder(TRANSIT_NONE).build()
+        callOnTransitionReady(transitionInfo2)
+
+        verify(desktopModeEventLogger, times(1)).logSessionEnter(any(), any())
+        verify(desktopModeEventLogger, times(1)).logTaskAdded(any(), any())
+    }
+
+    @Test
+    fun sessionAlreadyStarted_newFreeformTaskAdded_logsTaskAdded() {
+        val sessionId = 1
+        // add an existing freeform task
+        transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        transitionObserver.setLoggerSessionId(sessionId)
+
+        // new freeform task added
+        val change = createChange(TRANSIT_OPEN, createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+        val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+        callOnTransitionReady(transitionInfo)
+
+        verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+        verify(desktopModeEventLogger, never()).logSessionEnter(any(), any())
+    }
+
+    @Test
+    fun sessionAlreadyStarted_freeformTaskRemoved_logsTaskRemoved() {
+        val sessionId = 1
+        // add two existing freeform tasks
+        transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+        transitionObserver.addTaskInfosToCachedMap(createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+        transitionObserver.setLoggerSessionId(sessionId)
+
+        // new freeform task added
+        val change = createChange(TRANSIT_CLOSE, createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+        val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build()
+        callOnTransitionReady(transitionInfo)
+
+        verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+        verify(desktopModeEventLogger, never()).logSessionExit(any(), any())
+    }
+
+    /**
+     * Simulate calling the onTransitionReady() method
+     */
+    private fun callOnTransitionReady(transitionInfo: TransitionInfo) {
+        val transition = mock(IBinder::class.java)
+        val startT = mock(
+            SurfaceControl.Transaction::class.java)
+        val finishT = mock(
+            SurfaceControl.Transaction::class.java)
+
+        transitionObserver.onTransitionReady(transition, transitionInfo, startT, finishT)
+    }
+
+    companion object {
+        fun createTaskInfo(taskId: Int, windowMode: Int): ActivityManager.RunningTaskInfo {
+            val taskInfo = ActivityManager.RunningTaskInfo()
+            taskInfo.taskId = taskId
+            taskInfo.configuration.windowConfiguration.windowingMode = windowMode
+
+            return taskInfo
+        }
+
+        fun createChange(mode: Int, taskInfo: ActivityManager.RunningTaskInfo): Change {
+            val change = Change(
+                WindowContainerToken(mock(
+                    IWindowContainerToken::class.java)),
+                mock(SurfaceControl::class.java))
+            change.mode = mode
+            change.taskInfo = taskInfo
+            return change
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index f8ce4ee..9703dce 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -56,18 +56,16 @@
             context, taskSurface, taskDisplayAreaOrganizer)
         whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
         whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height())
+        whenever(displayLayout.stableInsets()).thenReturn(STABLE_INSETS)
     }
 
     @Test
     fun testFullscreenRegionCalculation() {
         val transitionHeight = context.resources.getDimensionPixelSize(
-            R.dimen.desktop_mode_transition_area_height)
+            R.dimen.desktop_mode_fullscreen_from_desktop_height)
         val fromFreeformWidth = mContext.resources.getDimensionPixelSize(
             R.dimen.desktop_mode_fullscreen_from_desktop_width
         )
-        val fromFreeformHeight = mContext.resources.getDimensionPixelSize(
-            R.dimen.desktop_mode_fullscreen_from_desktop_height
-        )
         var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
             WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT)
         assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
@@ -77,7 +75,7 @@
             DISPLAY_BOUNDS.width() / 2 - fromFreeformWidth / 2,
             -50,
             DISPLAY_BOUNDS.width() / 2 + fromFreeformWidth / 2,
-            fromFreeformHeight))
+            2 * STABLE_INSETS.top))
         testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
             WINDOWING_MODE_MULTI_WINDOW, CAPTION_HEIGHT)
         assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
@@ -135,5 +133,12 @@
         private const val TRANSITION_AREA_WIDTH = 32
         private const val CAPTION_HEIGHT = 50
         private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
+        private const val NAVBAR_HEIGHT = 50
+        private val STABLE_INSETS = Rect(
+            DISPLAY_BOUNDS.left,
+            DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
+            DISPLAY_BOUNDS.right,
+            DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
+        )
     }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 383621b..5df9dd3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -36,6 +36,7 @@
 import android.window.WindowContainerTransaction
 import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.dx.mockito.inline.extended.ExtendedMockito.never
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
@@ -51,6 +52,7 @@
 import com.android.wm.shell.common.MultiInstanceHelper
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.common.split.SplitScreenConstants
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
@@ -85,7 +87,9 @@
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.verify
+import org.mockito.kotlin.times
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.quality.Strictness
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -110,6 +114,7 @@
     @Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler
     @Mock lateinit var dragAndDropController: DragAndDropController
     @Mock lateinit var multiInstanceHelper: MultiInstanceHelper
+    @Mock lateinit var desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver
 
     private lateinit var mockitoSession: StaticMockitoSession
     private lateinit var controller: DesktopTasksController
@@ -123,7 +128,8 @@
 
     @Before
     fun setUp() {
-        mockitoSession = mockitoSession().mockStatic(DesktopModeStatus::class.java).startMocking()
+        mockitoSession = mockitoSession().strictness(Strictness.LENIENT)
+            .spyStatic(DesktopModeStatus::class.java).startMocking()
         whenever(DesktopModeStatus.isEnabled()).thenReturn(true)
 
         shellInit = Mockito.spy(ShellInit(testExecutor))
@@ -159,6 +165,7 @@
             mToggleResizeDesktopTaskTransitionHandler,
             dragToDesktopTransitionHandler,
             desktopModeTaskRepository,
+            desktopModeLoggerTransitionObserver,
             launchAdjacentController,
             recentsTransitionHandler,
             multiInstanceHelper,
@@ -332,6 +339,45 @@
     }
 
     @Test
+    fun moveToDesktop_deviceNotSupported_doesNothing() {
+        val task = setUpFullscreenTask()
+
+        // Simulate non compatible device
+        doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+
+        controller.moveToDesktop(task)
+        verifyWCTNotExecuted()
+    }
+
+    @Test
+    fun moveToDesktop_deviceNotSupported_deviceRestrictionsOverridden_taskIsMovedToDesktop() {
+        val task = setUpFullscreenTask()
+
+        // Simulate non compatible device
+        doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+
+        // Simulate enforce device restrictions system property overridden to false
+        whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(false)
+
+        controller.moveToDesktop(task)
+
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+            .isEqualTo(WINDOWING_MODE_FREEFORM)
+    }
+
+    @Test
+    fun moveToDesktop_deviceSupported_taskIsMovedToDesktop() {
+        val task = setUpFullscreenTask()
+
+        controller.moveToDesktop(task)
+
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+            .isEqualTo(WINDOWING_MODE_FREEFORM)
+    }
+
+    @Test
     fun moveToDesktop_otherFreeformTasksBroughtToFront() {
         val homeTask = setUpHomeTask()
         val freeformTask = setUpFreeformTask()
@@ -781,6 +827,39 @@
         )
     }
 
+    @Test
+    fun moveFocusedTaskToFullscreen() {
+        val task1 = setUpFreeformTask()
+        val task2 = setUpFreeformTask()
+        val task3 = setUpFreeformTask()
+
+        task1.isFocused = false
+        task2.isFocused = true
+        task3.isFocused = false
+
+        controller.enterFullscreen(DEFAULT_DISPLAY)
+
+        val wct = getLatestExitDesktopWct()
+        assertThat(wct.changes[task2.token.asBinder()]?.windowingMode)
+                .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+    }
+
+    fun enterSplit_freeformTaskIsMovedToSplit() {
+        val task1 = setUpFreeformTask()
+        val task2 = setUpFreeformTask()
+        val task3 = setUpFreeformTask()
+
+        task1.isFocused = false
+        task2.isFocused = true
+        task3.isFocused = false
+
+        controller.enterSplit(DEFAULT_DISPLAY, false)
+
+        verify(splitScreenController).requestEnterSplitSelect(task2, any(),
+            SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT,
+            task2.configuration.windowConfiguration.bounds)
+    }
+
     private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
         val task = createFreeformTask(displayId)
         whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
@@ -799,6 +878,8 @@
 
     private fun setUpFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
         val task = createFullscreenTask(displayId)
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
         whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
         runningTasks.add(task)
         return task
@@ -806,6 +887,8 @@
 
     private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
         val task = createSplitScreenTask(displayId)
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
         whenever(splitScreenController.isTaskInSplitScreen(task.taskId)).thenReturn(true)
         whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
         runningTasks.add(task)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index 1b347e0..5dd9d8a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -22,9 +22,11 @@
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
+import static android.content.ClipDescription.MIMETYPE_TEXT_INTENT;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -46,6 +48,8 @@
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.quality.Strictness.LENIENT;
 
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
@@ -61,6 +65,7 @@
 import android.graphics.Insets;
 import android.os.RemoteException;
 import android.view.DisplayInfo;
+import android.view.DragEvent;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -70,12 +75,15 @@
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.draganddrop.DragAndDropPolicy.Target;
 import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.startingsurface.TaskSnapshotWindow;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -107,6 +115,7 @@
     private DragAndDropPolicy mPolicy;
 
     private ClipData mActivityClipData;
+    private ClipData mLaunchableIntentClipData;
     private ClipData mNonResizeableActivityClipData;
     private ClipData mTaskClipData;
     private ClipData mShortcutClipData;
@@ -115,9 +124,16 @@
     private ActivityManager.RunningTaskInfo mFullscreenAppTask;
     private ActivityManager.RunningTaskInfo mNonResizeableFullscreenAppTask;
 
+    private MockitoSession mMockitoSession;
+
     @Before
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
+        mMockitoSession = mockitoSession()
+                .strictness(LENIENT)
+                .mockStatic(DragUtils.class)
+                .startMocking();
+        when(DragUtils.canHandleDrag(any())).thenReturn(true);
 
         Resources res = mock(Resources.class);
         Configuration config = new Configuration();
@@ -134,11 +150,12 @@
         mInsets = Insets.of(0, 0, 0, 0);
 
         mPolicy = spy(new DragAndDropPolicy(mContext, mSplitScreenStarter, mSplitScreenStarter));
-        mActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
-        mNonResizeableActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
+        mActivityClipData = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY);
+        mLaunchableIntentClipData = createIntentClipData();
+        mNonResizeableActivityClipData = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY);
         setClipDataResizeable(mNonResizeableActivityClipData, false);
-        mTaskClipData = createClipData(MIMETYPE_APPLICATION_TASK);
-        mShortcutClipData = createClipData(MIMETYPE_APPLICATION_SHORTCUT);
+        mTaskClipData = createAppClipData(MIMETYPE_APPLICATION_TASK);
+        mShortcutClipData = createAppClipData(MIMETYPE_APPLICATION_SHORTCUT);
 
         mHomeTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
         mFullscreenAppTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
@@ -149,10 +166,15 @@
         setRunningTask(mFullscreenAppTask);
     }
 
+    @After
+    public void tearDown() {
+        mMockitoSession.finishMocking();
+    }
+
     /**
-     * Creates a clip data that is by default resizeable.
+     * Creates an app-based clip data that is by default resizeable.
      */
-    private ClipData createClipData(String mimeType) {
+    private ClipData createAppClipData(String mimeType) {
         ClipDescription clipDescription = new ClipDescription(mimeType, new String[] { mimeType });
         Intent i = new Intent();
         switch (mimeType) {
@@ -164,7 +186,9 @@
                 i.putExtra(Intent.EXTRA_TASK_ID, 12345);
                 break;
             case MIMETYPE_APPLICATION_ACTIVITY:
-                i.putExtra(ClipDescription.EXTRA_PENDING_INTENT, mock(PendingIntent.class));
+                final PendingIntent pi = mock(PendingIntent.class);
+                doReturn(android.os.Process.myUserHandle()).when(pi).getCreatorUserHandle();
+                i.putExtra(ClipDescription.EXTRA_PENDING_INTENT, pi);
                 break;
         }
         i.putExtra(Intent.EXTRA_USER, android.os.Process.myUserHandle());
@@ -175,6 +199,22 @@
         return data;
     }
 
+    /**
+     * Creates an intent-based clip data that is by default resizeable.
+     */
+    private ClipData createIntentClipData() {
+        ClipDescription clipDescription = new ClipDescription("Intent",
+                new String[] { MIMETYPE_TEXT_INTENT });
+        PendingIntent intent = mock(PendingIntent.class);
+        when(intent.getCreatorUserHandle()).thenReturn(android.os.Process.myUserHandle());
+        ClipData.Item item = new ClipData.Item.Builder()
+                .setIntentSender(intent.getIntentSender())
+                .build();
+        ClipData data = new ClipData(clipDescription, item);
+        when(DragUtils.getLaunchIntent((ClipData) any())).thenReturn(intent);
+        return data;
+    }
+
     private ActivityManager.RunningTaskInfo createTaskInfo(int winMode, int actType) {
         ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
         info.configuration.windowConfiguration.setActivityType(actType);
@@ -204,58 +244,85 @@
 
     @Test
     public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() {
+        dragOverFullscreenHome_expectOnlyFullscreenTarget(mActivityClipData);
+    }
+
+    @Test
+    public void testDragAppOverFullscreenApp_expectSplitScreenTargets() {
+        dragOverFullscreenApp_expectSplitScreenTargets(mActivityClipData);
+    }
+
+    @Test
+    public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenTargets() {
+        dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(mActivityClipData);
+    }
+
+    @Test
+    public void testDragIntentOverFullscreenHome_expectOnlyFullscreenTarget() {
+        dragOverFullscreenHome_expectOnlyFullscreenTarget(mLaunchableIntentClipData);
+    }
+
+    @Test
+    public void testDragIntentOverFullscreenApp_expectSplitScreenTargets() {
+        dragOverFullscreenApp_expectSplitScreenTargets(mLaunchableIntentClipData);
+    }
+
+    @Test
+    public void testDragIntentOverFullscreenAppPhone_expectVerticalSplitScreenTargets() {
+        dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(mLaunchableIntentClipData);
+    }
+
+    private void dragOverFullscreenHome_expectOnlyFullscreenTarget(ClipData data) {
         doReturn(true).when(mSplitScreenStarter).isLeftRightSplit();
         setRunningTask(mHomeTask);
-        DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
-                mLandscapeDisplayLayout, mActivityClipData);
+        DragSession dragSession = new DragSession(mActivityTaskManager,
+                mLandscapeDisplayLayout, data);
         dragSession.update();
         mPolicy.start(dragSession, mLoggerSessionId);
         ArrayList<Target> targets = assertExactTargetTypes(
                 mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
 
-        mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
+        mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN));
         verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
                 eq(SPLIT_POSITION_UNDEFINED), any());
     }
 
-    @Test
-    public void testDragAppOverFullscreenApp_expectSplitScreenTargets() {
+    private void dragOverFullscreenApp_expectSplitScreenTargets(ClipData data) {
         doReturn(true).when(mSplitScreenStarter).isLeftRightSplit();
         setRunningTask(mFullscreenAppTask);
-        DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
-                mLandscapeDisplayLayout, mActivityClipData);
+        DragSession dragSession = new DragSession(mActivityTaskManager,
+                mLandscapeDisplayLayout, data);
         dragSession.update();
         mPolicy.start(dragSession, mLoggerSessionId);
         ArrayList<Target> targets = assertExactTargetTypes(
                 mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
 
-        mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT), mActivityClipData);
+        mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT));
         verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
                 eq(SPLIT_POSITION_TOP_OR_LEFT), any());
         reset(mSplitScreenStarter);
 
-        mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
+        mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT));
         verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
                 eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
     }
 
-    @Test
-    public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenTargets() {
+    private void dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(ClipData data) {
         doReturn(false).when(mSplitScreenStarter).isLeftRightSplit();
         setRunningTask(mFullscreenAppTask);
-        DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
-                mPortraitDisplayLayout, mActivityClipData);
+        DragSession dragSession = new DragSession(mActivityTaskManager,
+                mPortraitDisplayLayout, data);
         dragSession.update();
         mPolicy.start(dragSession, mLoggerSessionId);
         ArrayList<Target> targets = assertExactTargetTypes(
                 mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
 
-        mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP), mActivityClipData);
+        mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP));
         verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
                 eq(SPLIT_POSITION_TOP_OR_LEFT), any());
         reset(mSplitScreenStarter);
 
-        mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
+        mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM));
         verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
                 eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
     }
@@ -263,7 +330,7 @@
     @Test
     public void testTargetHitRects() {
         setRunningTask(mFullscreenAppTask);
-        DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
+        DragSession dragSession = new DragSession(mActivityTaskManager,
                 mLandscapeDisplayLayout, mActivityClipData);
         dragSession.update();
         mPolicy.start(dragSession, mLoggerSessionId);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index d4e9ac9..e74c804 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -36,7 +36,6 @@
 import android.content.pm.ActivityInfo;
 import android.graphics.Rect;
 import android.os.RemoteException;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Rational;
@@ -45,6 +44,8 @@
 import android.view.SurfaceControl;
 import android.window.WindowContainerToken;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.wm.shell.MockSurfaceControlHelper;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 5d968d3..d38fc6c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -42,10 +42,11 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.RemoteException;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.common.DisplayController;
@@ -128,7 +129,7 @@
         }).when(mMockExecutor).execute(any());
         mShellInit = spy(new ShellInit(mMockExecutor));
         mShellController = spy(new ShellController(mContext, mShellInit, mMockShellCommandHandler,
-                mMockExecutor));
+                mMockDisplayInsetsController, mMockExecutor));
         mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
                 mShellController, mMockDisplayController, mMockPipAnimationController,
                 mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
index 45f6c8c..72db6e0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
@@ -23,20 +23,21 @@
 import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_FULLSCREEN;
 import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
 
-import static java.util.Collections.EMPTY_LIST;
-
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import static java.util.Collections.EMPTY_LIST;
+
 import android.app.PendingIntent;
 import android.app.RemoteAction;
 import android.graphics.drawable.Icon;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.pip.PipMediaController;
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 10e9e11..41a4e8d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -58,6 +58,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
@@ -96,6 +97,8 @@
     private DesktopModeTaskRepository mDesktopModeTaskRepository;
     @Mock
     private ActivityTaskManager mActivityTaskManager;
+    @Mock
+    private DisplayInsetsController mDisplayInsetsController;
 
     private ShellTaskOrganizer mShellTaskOrganizer;
     private RecentTasksController mRecentTasksController;
@@ -110,7 +113,7 @@
         when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
         mShellInit = spy(new ShellInit(mMainExecutor));
         mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
-                mMainExecutor));
+                mDisplayInsetsController, mMainExecutor));
         mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
                 mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
                 Optional.of(mDesktopModeTaskRepository), mMainExecutor);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 315d97e..3c387f0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -123,7 +123,7 @@
         assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext));
         MockitoAnnotations.initMocks(this);
         mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
-                mMainExecutor));
+                mDisplayInsetsController, mMainExecutor));
         mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
                 mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
                 mRootTDAOrganizer, mDisplayController, mDisplayImeController,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
index 012c4081..ff76a2f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
@@ -40,6 +40,7 @@
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -65,6 +66,7 @@
 
     private @Mock Context mContext;
     private @Mock DisplayManager mDisplayManager;
+    private @Mock DisplayInsetsController mDisplayInsetsController;
     private @Mock ShellCommandHandler mShellCommandHandler;
     private @Mock ShellTaskOrganizer mTaskOrganizer;
     private @Mock ShellExecutor mMainExecutor;
@@ -83,7 +85,7 @@
         doReturn(super.mContext.getResources()).when(mContext).getResources();
         mShellInit = spy(new ShellInit(mMainExecutor));
         mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
-                mMainExecutor));
+                mDisplayInsetsController, mMainExecutor));
         mController = new StartingWindowController(mContext, mShellInit, mShellController,
                 mTaskOrganizer, mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool);
         mShellInit.init();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index 7c520c3..6292018 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
+import android.graphics.Rect;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -35,8 +36,8 @@
 
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.ExternalInterfaceBinder;
-import com.android.wm.shell.common.ShellExecutor;
 
 import org.junit.After;
 import org.junit.Before;
@@ -63,12 +64,15 @@
     private ShellCommandHandler mShellCommandHandler;
     @Mock
     private Context mTestUserContext;
+    @Mock
+    private DisplayInsetsController mDisplayInsetsController;
 
     private TestShellExecutor mExecutor;
     private ShellController mController;
     private TestConfigurationChangeListener mConfigChangeListener;
     private TestKeyguardChangeListener mKeyguardChangeListener;
     private TestUserChangeListener mUserChangeListener;
+    private TestDisplayImeChangeListener mDisplayImeChangeListener;
 
 
     @Before
@@ -77,8 +81,10 @@
         mKeyguardChangeListener = new TestKeyguardChangeListener();
         mConfigChangeListener = new TestConfigurationChangeListener();
         mUserChangeListener = new TestUserChangeListener();
+        mDisplayImeChangeListener = new TestDisplayImeChangeListener();
         mExecutor = new TestShellExecutor();
-        mController = new ShellController(mContext, mShellInit, mShellCommandHandler, mExecutor);
+        mController = new ShellController(mContext, mShellInit, mShellCommandHandler,
+                mDisplayInsetsController, mExecutor);
         mController.onConfigurationChanged(getConfigurationCopy());
     }
 
@@ -130,6 +136,45 @@
     }
 
     @Test
+    public void testAddDisplayImeChangeListener_ensureCallback() {
+        mController.asShell().addDisplayImeChangeListener(
+                mDisplayImeChangeListener, mExecutor);
+
+        final Rect bounds = new Rect(10, 20, 30, 40);
+        mController.onImeBoundsChanged(bounds);
+        mController.onImeVisibilityChanged(true);
+        mExecutor.flushAll();
+
+        assertTrue(mDisplayImeChangeListener.boundsChanged == 1);
+        assertTrue(bounds.equals(mDisplayImeChangeListener.lastBounds));
+        assertTrue(mDisplayImeChangeListener.visibilityChanged == 1);
+        assertTrue(mDisplayImeChangeListener.lastVisibility);
+    }
+
+    @Test
+    public void testDoubleAddDisplayImeChangeListener_ensureSingleCallback() {
+        mController.asShell().addDisplayImeChangeListener(
+                mDisplayImeChangeListener, mExecutor);
+        mController.asShell().addDisplayImeChangeListener(
+                mDisplayImeChangeListener, mExecutor);
+
+        mController.onImeVisibilityChanged(true);
+        mExecutor.flushAll();
+        assertTrue(mDisplayImeChangeListener.visibilityChanged == 1);
+    }
+
+    @Test
+    public void testAddRemoveDisplayImeChangeListener_ensureNoCallback() {
+        mController.asShell().addDisplayImeChangeListener(
+                mDisplayImeChangeListener, mExecutor);
+        mController.asShell().removeDisplayImeChangeListener(mDisplayImeChangeListener);
+
+        mController.onImeVisibilityChanged(true);
+        mExecutor.flushAll();
+        assertTrue(mDisplayImeChangeListener.visibilityChanged == 0);
+    }
+
+    @Test
     public void testAddUserChangeListener_ensureCallback() {
         mController.addUserChangeListener(mUserChangeListener);
 
@@ -457,4 +502,23 @@
             lastUserProfiles = profiles;
         }
     }
+
+    private static class TestDisplayImeChangeListener implements DisplayImeChangeListener {
+        public int boundsChanged = 0;
+        public Rect lastBounds;
+        public int visibilityChanged = 0;
+        public boolean lastVisibility = false;
+
+        @Override
+        public void onImeBoundsChanged(int displayId, Rect bounds) {
+            boundsChanged++;
+            lastBounds = bounds;
+        }
+
+        @Override
+        public void onImeVisibilityChanged(int displayId, boolean isShowing) {
+            visibilityChanged++;
+            lastVisibility = isShowing;
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
index 0504439..fbc0db9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
@@ -32,7 +32,6 @@
 import android.app.ActivityManager;
 import android.graphics.Rect;
 import android.os.IBinder;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.SurfaceControl;
@@ -40,6 +39,8 @@
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.transition.Transitions;
 
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 f84685a..6940739 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
@@ -27,10 +27,14 @@
 import android.hardware.display.DisplayManager
 import android.hardware.display.VirtualDisplay
 import android.os.Handler
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import android.util.SparseArray
 import android.view.Choreographer
 import android.view.Display.DEFAULT_DISPLAY
+import android.view.IWindowManager
 import android.view.InputChannel
 import android.view.InputMonitor
 import android.view.InsetsSource
@@ -40,6 +44,10 @@
 import android.view.WindowInsets.Type.navigationBars
 import android.view.WindowInsets.Type.statusBars
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.window.flags.Flags
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.ShellTestCase
@@ -49,6 +57,7 @@
 import com.android.wm.shell.common.DisplayLayout
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeStatus
 import com.android.wm.shell.desktopmode.DesktopTasksController
 import com.android.wm.shell.sysui.KeyguardChangeListener
 import com.android.wm.shell.sysui.ShellCommandHandler
@@ -57,6 +66,7 @@
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
@@ -69,8 +79,11 @@
 import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
 import java.util.Optional
 import java.util.function.Supplier
+import org.mockito.Mockito
+import org.mockito.kotlin.spy
 
 
 /** Tests of [DesktopModeWindowDecorViewModel]  */
@@ -78,6 +91,10 @@
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper
 class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
+    @JvmField
+    @Rule
+    val setFlagsRule = SetFlagsRule()
+
     @Mock private lateinit var mockDesktopModeWindowDecorFactory:
             DesktopModeWindowDecoration.Factory
     @Mock private lateinit var mockMainHandler: Handler
@@ -96,10 +113,12 @@
     @Mock private lateinit var mockShellExecutor: ShellExecutor
     @Mock private lateinit var mockRootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
     @Mock private lateinit var mockShellCommandHandler: ShellCommandHandler
+    @Mock private lateinit var mockWindowManager: IWindowManager
 
     private val transactionFactory = Supplier<SurfaceControl.Transaction> {
         SurfaceControl.Transaction()
     }
+    private val windowDecorByTaskIdSpy = spy(SparseArray<DesktopModeWindowDecoration>())
 
     private lateinit var shellInit: ShellInit
     private lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener
@@ -108,12 +127,15 @@
     @Before
     fun setUp() {
         shellInit = ShellInit(mockShellExecutor)
+        windowDecorByTaskIdSpy.clear()
         desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
                 mContext,
+                mockShellExecutor,
                 mockMainHandler,
                 mockMainChoreographer,
                 shellInit,
                 mockShellCommandHandler,
+                mockWindowManager,
                 mockTaskOrganizer,
                 mockDisplayController,
                 mockShellController,
@@ -124,7 +146,8 @@
                 mockDesktopModeWindowDecorFactory,
                 mockInputMonitorFactory,
                 transactionFactory,
-                mockRootTaskDisplayAreaOrganizer
+                mockRootTaskDisplayAreaOrganizer,
+            windowDecorByTaskIdSpy
         )
 
         whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
@@ -328,6 +351,89 @@
         verify(decoration, times(1)).relayout(task)
     }
 
+    @Test
+    fun testDestroyWindowDecoration_closesBeforeCleanup() {
+        val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+        val decoration = setUpMockDecorationForTask(task)
+        val inOrder = Mockito.inOrder(decoration, windowDecorByTaskIdSpy)
+
+        onTaskOpening(task)
+        desktopModeWindowDecorViewModel.destroyWindowDecoration(task)
+
+        inOrder.verify(decoration).close()
+        inOrder.verify(windowDecorByTaskIdSpy).remove(task.taskId)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    fun testWindowDecor_desktopModeUnsupportedOnDevice_decorNotCreated() {
+        val mockitoSession: StaticMockitoSession = mockitoSession()
+            .strictness(Strictness.LENIENT)
+            .spyStatic(DesktopModeStatus::class.java)
+            .startMocking()
+        try {
+            // Simulate default enforce device restrictions system property
+            whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
+
+            val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+            // Simulate device that doesn't support desktop mode
+            doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+
+            onTaskOpening(task)
+            verify(mockDesktopModeWindowDecorFactory, never())
+                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+        } finally {
+            mockitoSession.finishMocking()
+        }
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    fun testWindowDecor_desktopModeUnsupportedOnDevice_deviceRestrictionsOverridden_decorCreated() {
+        val mockitoSession: StaticMockitoSession = mockitoSession()
+            .strictness(Strictness.LENIENT)
+            .spyStatic(DesktopModeStatus::class.java)
+            .startMocking()
+        try {
+            // Simulate enforce device restrictions system property overridden to false
+            whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(false)
+            // Simulate device that doesn't support desktop mode
+            doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+
+            val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+            setUpMockDecorationsForTasks(task)
+
+            onTaskOpening(task)
+            verify(mockDesktopModeWindowDecorFactory)
+                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+        } finally {
+            mockitoSession.finishMocking()
+        }
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    fun testWindowDecor_deviceSupportsDesktopMode_decorCreated() {
+        val mockitoSession: StaticMockitoSession = mockitoSession()
+            .strictness(Strictness.LENIENT)
+            .spyStatic(DesktopModeStatus::class.java)
+            .startMocking()
+        try {
+            // Simulate default enforce device restrictions system property
+            whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
+
+            val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+            doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+            setUpMockDecorationsForTasks(task)
+
+            onTaskOpening(task)
+            verify(mockDesktopModeWindowDecorFactory)
+                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+        } finally {
+            mockitoSession.finishMocking()
+        }
+    }
+
     private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) {
         desktopModeWindowDecorViewModel.onTaskOpening(
                 task,
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 40e61dd..f9b5882 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
@@ -16,6 +16,10 @@
 
 package com.android.wm.shell.windowdecor;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.any;
@@ -168,6 +172,57 @@
         assertThat(relayoutParams.mCornerRadius).isGreaterThan(0);
     }
 
+    @Test
+    public void updateRelayoutParams_freeformAndTransparentAppearance_allowsInputFallthrough() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        taskInfo.taskDescription.setSystemBarsAppearance(
+                APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND);
+        final RelayoutParams relayoutParams = new RelayoutParams();
+
+        DesktopModeWindowDecoration.updateRelayoutParams(
+                relayoutParams,
+                mTestableContext,
+                taskInfo,
+                /* applyStartTransactionOnDraw= */ true,
+                /* shouldSetTaskPositionAndCrop */ false);
+
+        assertThat(relayoutParams.mAllowCaptionInputFallthrough).isTrue();
+    }
+
+    @Test
+    public void updateRelayoutParams_freeformButOpaqueAppearance_disallowsInputFallthrough() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        taskInfo.taskDescription.setSystemBarsAppearance(0);
+        final RelayoutParams relayoutParams = new RelayoutParams();
+
+        DesktopModeWindowDecoration.updateRelayoutParams(
+                relayoutParams,
+                mTestableContext,
+                taskInfo,
+                /* applyStartTransactionOnDraw= */ true,
+                /* shouldSetTaskPositionAndCrop */ false);
+
+        assertThat(relayoutParams.mAllowCaptionInputFallthrough).isFalse();
+    }
+
+    @Test
+    public void updateRelayoutParams_fullscreen_disallowsInputFallthrough() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        final RelayoutParams relayoutParams = new RelayoutParams();
+
+        DesktopModeWindowDecoration.updateRelayoutParams(
+                relayoutParams,
+                mTestableContext,
+                taskInfo,
+                /* applyStartTransactionOnDraw= */ true,
+                /* shouldSetTaskPositionAndCrop */ false);
+
+        assertThat(relayoutParams.mAllowCaptionInputFallthrough).isFalse();
+    }
+
     private void fillRoundedCornersResources(int fillValue) {
         when(mMockRoundedCornersRadiusArray.getDimensionPixelSize(anyInt(), anyInt()))
                 .thenReturn(fillValue);
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index f0c6395..49254d1 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -81,7 +81,7 @@
   std::string overlay_path(loaded_idmap->OverlayApkPath());
   auto fd = unique_fd(base::utf8::open(overlay_path.c_str(), O_RDONLY | O_CLOEXEC));
   std::unique_ptr<AssetsProvider> overlay_assets;
-  if (IsFabricatedOverlay(fd)) {
+  if (IsFabricatedOverlayName(overlay_path) && IsFabricatedOverlay(fd)) {
     // Fabricated overlays do not contain resource definitions. All of the overlay resource values
     // are defined inline in the idmap.
     overlay_assets = EmptyAssetsProvider::Create(std::move(overlay_path));
@@ -137,8 +137,7 @@
       return {};
     }
     loaded_arsc = LoadedArsc::Load(data, length, loaded_idmap.get(), property_flags);
-  } else if (loaded_idmap != nullptr &&
-      IsFabricatedOverlay(std::string(loaded_idmap->OverlayApkPath()))) {
+  } else if (loaded_idmap != nullptr && IsFabricatedOverlay(loaded_idmap->OverlayApkPath())) {
     loaded_arsc = LoadedArsc::Load(loaded_idmap.get());
   } else {
     loaded_arsc = LoadedArsc::CreateEmpty();
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index 5f98b8f..9824190 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -18,8 +18,10 @@
 
 #include "androidfw/Idmap.h"
 
+#include "android-base/file.h"
 #include "android-base/logging.h"
 #include "android-base/stringprintf.h"
+#include "android-base/utf8.h"
 #include "androidfw/misc.h"
 #include "androidfw/ResourceTypes.h"
 #include "androidfw/Util.h"
@@ -250,7 +252,12 @@
 }
 } // namespace
 
-LoadedIdmap::LoadedIdmap(std::string&& idmap_path, const Idmap_header* header,
+// O_PATH is a lightweight way of creating an FD, only exists on Linux
+#ifndef O_PATH
+#define O_PATH (0)
+#endif
+
+LoadedIdmap::LoadedIdmap(const std::string& idmap_path, const Idmap_header* header,
                          const Idmap_data_header* data_header,
                          const Idmap_target_entry* target_entries,
                          const Idmap_target_entry_inline* target_inline_entries,
@@ -267,10 +274,10 @@
       configurations_(configs),
       overlay_entries_(overlay_entries),
       string_pool_(std::move(string_pool)),
-      idmap_path_(std::move(idmap_path)),
+      idmap_fd_(android::base::utf8::open(idmap_path.c_str(), O_RDONLY|O_CLOEXEC|O_BINARY|O_PATH)),
       overlay_apk_path_(overlay_apk_path),
       target_apk_path_(target_apk_path),
-      idmap_last_mod_time_(getFileModDate(idmap_path_.data())) {}
+      idmap_last_mod_time_(getFileModDate(idmap_fd_.get())) {}
 
 std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPiece idmap_data) {
   ATRACE_CALL();
@@ -368,7 +375,7 @@
 }
 
 bool LoadedIdmap::IsUpToDate() const {
-  return idmap_last_mod_time_ == getFileModDate(idmap_path_.c_str());
+  return idmap_last_mod_time_ == getFileModDate(idmap_fd_.get());
 }
 
 }  // namespace android
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 2c99f1a..a3dd983 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -54,6 +54,8 @@
 #define INT32_MAX ((int32_t)(2147483647))
 #endif
 
+using namespace std::literals;
+
 namespace android {
 
 #if defined(_WIN32)
@@ -237,12 +239,24 @@
     fill9patchOffsets(reinterpret_cast<Res_png_9patch*>(outData));
 }
 
-bool IsFabricatedOverlay(const std::string& path) {
-  return IsFabricatedOverlay(path.c_str());
+bool IsFabricatedOverlayName(std::string_view path) {
+  static constexpr auto suffixFrro = ".frro"sv;
+  static constexpr auto suffixIdmap = ".frro@idmap"sv;
+
+  return (path.size() > suffixFrro.size() && path.ends_with(suffixFrro))
+        || (path.size() > suffixIdmap.size() && path.ends_with(suffixIdmap));
 }
 
-bool IsFabricatedOverlay(const char* path) {
-  auto fd = base::unique_fd(base::utf8::open(path, O_RDONLY|O_CLOEXEC));
+bool IsFabricatedOverlay(std::string_view path) {
+  if (!IsFabricatedOverlayName(path)) {
+    return false;
+  }
+  std::string path_copy;
+  if (path[path.size()] != '\0') {
+    path_copy.assign(path);
+    path = path_copy;
+  }
+  auto fd = base::unique_fd(base::utf8::open(path.data(), O_RDONLY|O_CLOEXEC|O_BINARY));
   if (fd < 0) {
     return false;
   }
@@ -7319,9 +7333,6 @@
 public:
     void add(uint32_t targetResId, uint32_t overlayResId) {
         uint8_t targetTypeId = Res_GETTYPE(targetResId);
-        if (mData.find(targetTypeId) == mData.end()) {
-            mData.emplace(targetTypeId, std::set<std::pair<uint32_t, uint32_t>>());
-        }
         auto& entries = mData[targetTypeId];
         entries.insert(std::make_pair(targetResId, overlayResId));
     }
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/Android.bp b/libs/androidfw/fuzz/resxmlparser_fuzzer/Android.bp
new file mode 100644
index 0000000..4b008a7
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/Android.bp
@@ -0,0 +1,51 @@
+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_libs_androidfw_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_libs_androidfw_license"],
+}
+
+cc_fuzz {
+    name: "resxmlparser_fuzzer",
+    srcs: [
+        "resxmlparser_fuzzer.cpp",
+    ],
+    host_supported: true,
+
+    static_libs: ["libgmock"],
+    target: {
+        android: {
+            shared_libs: [
+                "libandroidfw",
+                "libbase",
+                "libbinder",
+                "libcutils",
+                "liblog",
+                "libutils",
+            ],
+        },
+        host: {
+            static_libs: [
+                "libandroidfw",
+                "libbase",
+                "libbinder",
+                "libcutils",
+                "liblog",
+                "libutils",
+            ],
+        },
+        darwin: {
+            // libbinder is not supported on mac
+            enabled: false,
+        },
+    },
+
+    include_dirs: [
+        "system/incremental_delivery/incfs/util/include/",
+    ],
+
+    corpus: ["testdata/*"],
+    dictionary: "xmlparser_fuzzer.dict",
+}
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/resxmlparser_fuzzer.cpp b/libs/androidfw/fuzz/resxmlparser_fuzzer/resxmlparser_fuzzer.cpp
new file mode 100644
index 0000000..829a396
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/resxmlparser_fuzzer.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+#include <cstdint>
+#include <cstddef>
+#include <fuzzer/FuzzedDataProvider.h>
+#include "androidfw/ResourceTypes.h"
+
+static void populateDynamicRefTableWithFuzzedData(
+    android::DynamicRefTable& table,
+    FuzzedDataProvider& fuzzedDataProvider) {
+
+    const size_t numMappings = fuzzedDataProvider.ConsumeIntegralInRange<size_t>(1, 5);
+    for (size_t i = 0; i < numMappings; ++i) {
+        const uint8_t packageId = fuzzedDataProvider.ConsumeIntegralInRange<uint8_t>(0x02, 0x7F);
+
+        // Generate a package name
+        std::string packageName;
+        size_t packageNameLength = fuzzedDataProvider.ConsumeIntegralInRange<size_t>(1, 128);
+        for (size_t j = 0; j < packageNameLength; ++j) {
+            // Consume characters only in the ASCII range (0x20 to 0x7E) to ensure valid UTF-8
+            char ch = fuzzedDataProvider.ConsumeIntegralInRange<char>(0x20, 0x7E);
+            packageName.push_back(ch);
+        }
+
+        // Convert std::string to String16 for compatibility
+        android::String16 androidPackageName(packageName.c_str(), packageName.length());
+
+        // Add the mapping to the table
+        table.addMapping(androidPackageName, packageId);
+    }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    FuzzedDataProvider fuzzedDataProvider(data, size);
+
+    auto dynamic_ref_table = std::make_shared<android::DynamicRefTable>();
+
+    // Populate the DynamicRefTable with fuzzed data
+    populateDynamicRefTableWithFuzzedData(*dynamic_ref_table, fuzzedDataProvider);
+
+    auto tree = android::ResXMLTree(std::move(dynamic_ref_table));
+
+    std::vector<uint8_t> xmlData = fuzzedDataProvider.ConsumeRemainingBytes<uint8_t>();
+    if (tree.setTo(xmlData.data(), xmlData.size()) != android::NO_ERROR) {
+        return 0; // Exit early if unable to parse XML data
+    }
+
+    tree.restart();
+
+    size_t len = 0;
+    auto code = tree.next();
+    if (code == android::ResXMLParser::START_TAG) {
+        // Access element name
+        auto name = tree.getElementName(&len);
+
+        // Access attributes of the current element
+        for (size_t i = 0; i < tree.getAttributeCount(); i++) {
+            // Access attribute name
+            auto attrName = tree.getAttributeName(i, &len);
+        }
+    } else if (code == android::ResXMLParser::TEXT) {
+        const auto text = tree.getText(&len);
+    }
+    return 0; // Non-zero return values are reserved for future use.
+}
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/attributes.xml b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/attributes.xml
new file mode 100644
index 0000000..417fec7
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/attributes.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+    <child id="1">
+        <subchild type="A">Content A</subchild>
+        <subchild type="B">Content B</subchild>
+    </child>
+    <child id="2" extra="data">
+        <subchild type="C">Content C</subchild>
+    </child>
+</root>
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/basic.xml b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/basic.xml
new file mode 100644
index 0000000..7e13db5
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/basic.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+    <child1>Value 1</child1>
+    <child2>Value 2</child2>
+</root>
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/cdata.xml b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/cdata.xml
new file mode 100644
index 0000000..90cdf35
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/cdata.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+    <!-- Example with special characters and CDATA -->
+    <data><![CDATA[Some <encoded> data & other "special" characters]]></data>
+    <message>Hello &amp; Welcome!</message>
+</root>
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/xmlparser_fuzzer.dict b/libs/androidfw/fuzz/resxmlparser_fuzzer/xmlparser_fuzzer.dict
new file mode 100644
index 0000000..745ded4
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/xmlparser_fuzzer.dict
@@ -0,0 +1,11 @@
+root_tag=<root>
+child_tag=<child>
+end_child_tag=</child>
+id_attr=id="
+type_attr=type="
+cdata_start=<![CDATA[
+cdata_end=]]>
+ampersand_entity=&amp;
+xml_header=<?xml version="1.0" encoding="UTF-8"?>
+comment_start=<!--
+comment_end= -->
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index d9f7c2a..c32a38e 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -23,6 +23,7 @@
 #include <variant>
 
 #include "android-base/macros.h"
+#include "android-base/unique_fd.h"
 #include "androidfw/ConfigDescription.h"
 #include "androidfw/StringPiece.h"
 #include "androidfw/ResourceTypes.h"
@@ -159,11 +160,6 @@
   // Loads an IDMAP from a chunk of memory. Returns nullptr if the IDMAP data was malformed.
   static std::unique_ptr<LoadedIdmap> Load(StringPiece idmap_path, StringPiece idmap_data);
 
-  // Returns the path to the IDMAP.
-  std::string_view IdmapPath() const {
-    return idmap_path_;
-  }
-
   // Returns the path to the RRO (Runtime Resource Overlay) APK for which this IDMAP was generated.
   std::string_view OverlayApkPath() const {
     return overlay_apk_path_;
@@ -203,7 +199,7 @@
   const Idmap_overlay_entry* overlay_entries_;
   const std::unique_ptr<ResStringPool> string_pool_;
 
-  std::string idmap_path_;
+  android::base::unique_fd idmap_fd_;
   std::string_view overlay_apk_path_;
   std::string_view target_apk_path_;
   time_t idmap_last_mod_time_;
@@ -211,7 +207,7 @@
  private:
   DISALLOW_COPY_AND_ASSIGN(LoadedIdmap);
 
-  explicit LoadedIdmap(std::string&& idmap_path,
+  explicit LoadedIdmap(const std::string& idmap_path,
                        const Idmap_header* header,
                        const Idmap_data_header* data_header,
                        const Idmap_target_entry* target_entries,
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 3d1403d..c264890 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -59,8 +59,8 @@
 constexpr const uint32_t kFabricatedOverlayCurrentVersion = 3;
 
 // Returns whether or not the path represents a fabricated overlay.
-bool IsFabricatedOverlay(const std::string& path);
-bool IsFabricatedOverlay(const char* path);
+bool IsFabricatedOverlayName(std::string_view path);
+bool IsFabricatedOverlay(std::string_view path);
 bool IsFabricatedOverlay(android::base::borrowed_fd fd);
 
 /**
diff --git a/libs/androidfw/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h
index d40d24e..077609d 100644
--- a/libs/androidfw/include/androidfw/misc.h
+++ b/libs/androidfw/include/androidfw/misc.h
@@ -43,6 +43,8 @@
 FileType getFileType(const char* fileName);
 /* get the file's modification date; returns -1 w/errno set on failure */
 time_t getFileModDate(const char* fileName);
+/* same, but also returns -1 if the file has already been deleted */
+time_t getFileModDate(int fd);
 
 // Check if |path| or |fd| resides on a readonly filesystem.
 bool isReadonlyFilesystem(const char* path);
diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp
index d3949e9..93dcaf5 100644
--- a/libs/androidfw/misc.cpp
+++ b/libs/androidfw/misc.cpp
@@ -76,13 +76,23 @@
 /*
  * Get a file's modification date.
  */
-time_t getFileModDate(const char* fileName)
-{
+time_t getFileModDate(const char* fileName) {
     struct stat sb;
+    if (stat(fileName, &sb) < 0) {
+        return (time_t)-1;
+    }
+    return sb.st_mtime;
+}
 
-    if (stat(fileName, &sb) < 0)
-        return (time_t) -1;
-
+time_t getFileModDate(int fd) {
+    struct stat sb;
+    if (fstat(fd, &sb) < 0) {
+        return (time_t)-1;
+    }
+    if (sb.st_nlink <= 0) {
+        errno = ENOENT;
+        return (time_t)-1;
+    }
     return sb.st_mtime;
 }
 
diff --git a/libs/hostgraphics/ADisplay.cpp b/libs/hostgraphics/ADisplay.cpp
new file mode 100644
index 0000000..9cc1f40
--- /dev/null
+++ b/libs/hostgraphics/ADisplay.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <apex/display.h>
+#include <utils/Errors.h>
+
+namespace android::display::impl {
+
+/**
+ * Implementation of ADisplayConfig
+ */
+struct DisplayConfigImpl {
+    /**
+     * The width in pixels of the display configuration.
+     */
+    int32_t width{1080};
+
+    /**
+     * The height in pixels of the display configuration.
+     */
+
+    int32_t height{1920};
+
+    /**
+     * The refresh rate of the display configuration, in frames per second.
+     */
+    float fps{60.0};
+
+    /**
+     * The vsync offset at which surfaceflinger runs, in nanoseconds.
+     */
+    int64_t sfOffset{0};
+
+    /**
+     * The vsync offset at which applications run, in nanoseconds.
+     */
+    int64_t appOffset{0};
+};
+
+// DisplayConfigImpl allocation is not managed through C++ memory apis, so
+// preventing calling the destructor here.
+static_assert(std::is_trivially_destructible<DisplayConfigImpl>::value);
+
+/**
+ * Implementation of ADisplay
+ */
+struct DisplayImpl {
+    /**
+     * The type of the display, i.e. whether it is an internal or external
+     * display.
+     */
+    ADisplayType type;
+
+    /**
+     * The preferred WCG dataspace
+     */
+    ADataSpace wcgDataspace;
+
+    /**
+     * The preferred WCG pixel format
+     */
+    AHardwareBuffer_Format wcgPixelFormat;
+
+    /**
+     * The config for this display.
+     */
+    DisplayConfigImpl config;
+};
+
+// DisplayImpl allocation is not managed through C++ memory apis, so
+// preventing calling the destructor here.
+static_assert(std::is_trivially_destructible<DisplayImpl>::value);
+
+} // namespace android::display::impl
+
+using namespace android;
+using namespace android::display::impl;
+
+namespace android {
+
+int ADisplay_acquirePhysicalDisplays(ADisplay*** outDisplays) {
+    // This is running on host, so there are no physical displays available.
+    // Create 1 fake display instead.
+    DisplayImpl** const impls = reinterpret_cast<DisplayImpl**>(
+            malloc(sizeof(DisplayImpl*) + sizeof(DisplayImpl)));
+    DisplayImpl* const displayData = reinterpret_cast<DisplayImpl*>(impls + 1);
+
+    displayData[0] = DisplayImpl{ADisplayType::DISPLAY_TYPE_INTERNAL,
+                                 ADataSpace::ADATASPACE_UNKNOWN,
+                                 AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+                                 DisplayConfigImpl()};
+    impls[0] = displayData;
+    *outDisplays = reinterpret_cast<ADisplay**>(impls);
+    return 1;
+}
+
+void ADisplay_release(ADisplay** displays) {
+    if (displays == nullptr) {
+        return;
+    }
+    free(displays);
+}
+
+float ADisplay_getMaxSupportedFps(ADisplay* display) {
+    DisplayImpl* impl = reinterpret_cast<DisplayImpl*>(display);
+    return impl->config.fps;
+}
+
+ADisplayType ADisplay_getDisplayType(ADisplay* display) {
+    return reinterpret_cast<DisplayImpl*>(display)->type;
+}
+
+void ADisplay_getPreferredWideColorFormat(ADisplay* display, ADataSpace* outDataspace,
+                                          AHardwareBuffer_Format* outPixelFormat) {
+    DisplayImpl* impl = reinterpret_cast<DisplayImpl*>(display);
+    *outDataspace = impl->wcgDataspace;
+    *outPixelFormat = impl->wcgPixelFormat;
+}
+
+int ADisplay_getCurrentConfig(ADisplay* display, ADisplayConfig** outConfig) {
+    DisplayImpl* impl = reinterpret_cast<DisplayImpl*>(display);
+    *outConfig = reinterpret_cast<ADisplayConfig*>(&impl->config);
+    return OK;
+}
+
+int32_t ADisplayConfig_getWidth(ADisplayConfig* config) {
+    return reinterpret_cast<DisplayConfigImpl*>(config)->width;
+}
+
+int32_t ADisplayConfig_getHeight(ADisplayConfig* config) {
+    return reinterpret_cast<DisplayConfigImpl*>(config)->height;
+}
+
+float ADisplayConfig_getFps(ADisplayConfig* config) {
+    return reinterpret_cast<DisplayConfigImpl*>(config)->fps;
+}
+
+int64_t ADisplayConfig_getCompositorOffsetNanos(ADisplayConfig* config) {
+    return reinterpret_cast<DisplayConfigImpl*>(config)->sfOffset;
+}
+
+int64_t ADisplayConfig_getAppVsyncOffsetNanos(ADisplayConfig* config) {
+    return reinterpret_cast<DisplayConfigImpl*>(config)->appOffset;
+}
+
+} // namespace android
diff --git a/libs/hostgraphics/Android.bp b/libs/hostgraphics/Android.bp
index f166fde..4407af6 100644
--- a/libs/hostgraphics/Android.bp
+++ b/libs/hostgraphics/Android.bp
@@ -22,6 +22,7 @@
 
     srcs: [
         ":libui_host_common",
+        "ADisplay.cpp",
         "Fence.cpp",
         "HostBufferQueue.cpp",
         "PublicFormat.cpp",
@@ -32,16 +33,21 @@
         // When frameworks/native/include will be removed from the list of automatic includes.
         // We will have to copy necessary headers with a pre-build step (generated headers).
         ".",
-        "frameworks/native/libs/nativebase/include",
-        "frameworks/native/libs/nativewindow/include",
         "frameworks/native/libs/arect/include",
         "frameworks/native/libs/ui/include_private",
     ],
+
+    header_libs: [
+        "libnativebase_headers",
+        "libnativedisplay_headers",
+        "libnativewindow_headers",
+    ],
+
     export_include_dirs: ["."],
 
     target: {
         windows: {
             enabled: true,
-        }
+        },
     },
 }
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 40239b8..4486f55 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -346,6 +346,7 @@
         "jni/android_util_PathParser.cpp",
 
         "jni/Bitmap.cpp",
+        "jni/BitmapRegionDecoder.cpp",
         "jni/BufferUtils.cpp",
         "jni/HardwareBufferHelpers.cpp",
         "jni/BitmapFactory.cpp",
@@ -421,14 +422,12 @@
                 "jni/android_graphics_TextureLayer.cpp",
                 "jni/android_graphics_HardwareRenderer.cpp",
                 "jni/android_graphics_HardwareBufferRenderer.cpp",
-                "jni/BitmapRegionDecoder.cpp",
                 "jni/GIFMovie.cpp",
                 "jni/GraphicsStatsService.cpp",
                 "jni/Movie.cpp",
                 "jni/MovieImpl.cpp",
                 "jni/pdf/PdfDocument.cpp",
                 "jni/pdf/PdfEditor.cpp",
-                "jni/pdf/PdfRenderer.cpp",
                 "jni/pdf/PdfUtils.cpp",
             ],
             shared_libs: [
@@ -559,8 +558,13 @@
         "AnimatorManager.cpp",
         "CanvasTransform.cpp",
         "DamageAccumulator.cpp",
+        "DeviceInfo.cpp",
+        "FrameInfo.cpp",
+        "FrameInfoVisualizer.cpp",
+        "FrameMetricsReporter.cpp",
         "Gainmap.cpp",
         "Interpolator.cpp",
+        "JankTracker.cpp",
         "LightingInfo.cpp",
         "Matrix.cpp",
         "Mesh.cpp",
@@ -623,13 +627,8 @@
                 "utils/NdkUtils.cpp",
                 "AutoBackendTextureRelease.cpp",
                 "DeferredLayerUpdater.cpp",
-                "DeviceInfo.cpp",
-                "FrameInfo.cpp",
-                "FrameInfoVisualizer.cpp",
                 "HardwareBitmapUploader.cpp",
                 "HWUIProperties.sysprop",
-                "JankTracker.cpp",
-                "FrameMetricsReporter.cpp",
                 "Layer.cpp",
                 "LayerUpdateQueue.cpp",
                 "ProfileDataContainer.cpp",
@@ -643,7 +642,10 @@
             cflags: ["-Wno-implicit-fallthrough"],
         },
         host: {
-            header_libs: ["libnativebase_headers"],
+            header_libs: [
+                "libnativebase_headers",
+                "libnativedisplay_headers",
+            ],
 
             local_include_dirs: ["platform/host"],
 
@@ -655,7 +657,11 @@
                 "platform/host/WebViewFunctorManager.cpp",
             ],
 
-            cflags: ["-Wno-unused-private-field"],
+            cflags: [
+                "-DHWUI_NULL_GPU",
+                "-DNULL_GPU_MAX_TEXTURE_SIZE=4096",
+                "-Wno-unused-private-field",
+            ],
         },
     },
 }
diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp
index 32bc122..af7a496 100644
--- a/libs/hwui/DeviceInfo.cpp
+++ b/libs/hwui/DeviceInfo.cpp
@@ -108,6 +108,10 @@
     get()->mSupportFp16ForHdr = supportFp16ForHdr;
 }
 
+void DeviceInfo::setSupportRgba10101010ForHdr(bool supportRgba10101010ForHdr) {
+    get()->mSupportRgba10101010ForHdr = supportRgba10101010ForHdr;
+}
+
 void DeviceInfo::setSupportMixedColorSpaces(bool supportMixedColorSpaces) {
     get()->mSupportMixedColorSpaces = supportMixedColorSpaces;
 }
diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h
index a5a841e..fb58a69 100644
--- a/libs/hwui/DeviceInfo.h
+++ b/libs/hwui/DeviceInfo.h
@@ -69,6 +69,15 @@
         return get()->mSupportFp16ForHdr;
     };
 
+    static void setSupportRgba10101010ForHdr(bool supportRgba10101010ForHdr);
+    static bool isSupportRgba10101010ForHdr() {
+        if (!Properties::hdr10bitPlus) {
+            return false;
+        }
+
+        return get()->mSupportRgba10101010ForHdr;
+    };
+
     static void setSupportMixedColorSpaces(bool supportMixedColorSpaces);
     static bool isSupportMixedColorSpaces() { return get()->mSupportMixedColorSpaces; };
 
@@ -102,6 +111,7 @@
     int mMaxTextureSize;
     sk_sp<SkColorSpace> mWideColorSpace = SkColorSpace::MakeSRGB();
     bool mSupportFp16ForHdr = false;
+    bool mSupportRgba10101010ForHdr = false;
     bool mSupportMixedColorSpaces = false;
     SkColorType mWideColorType = SkColorType::kN32_SkColorType;
     int mDisplaysSize = 0;
diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in
index d21f07ef..b12486e 100644
--- a/libs/hwui/DisplayListOps.in
+++ b/libs/hwui/DisplayListOps.in
@@ -26,6 +26,7 @@
 X(ClipRect)
 X(ClipRRect)
 X(ClipRegion)
+X(ClipShader)
 X(ResetClip)
 X(DrawPaint)
 X(DrawBehind)
diff --git a/libs/hwui/FrameInfoVisualizer.cpp b/libs/hwui/FrameInfoVisualizer.cpp
index 59f2169..1e53fc2 100644
--- a/libs/hwui/FrameInfoVisualizer.cpp
+++ b/libs/hwui/FrameInfoVisualizer.cpp
@@ -249,6 +249,7 @@
 }
 
 void FrameInfoVisualizer::dumpData(int fd) {
+#ifdef __ANDROID__
     RETURN_IF_PROFILING_DISABLED();
 
     // This method logs the last N frames (where N is <= mDataSize) since the
@@ -268,6 +269,7 @@
                 durationMS(i, FrameInfoIndex::IssueDrawCommandsStart, FrameInfoIndex::SwapBuffers),
                 durationMS(i, FrameInfoIndex::SwapBuffers, FrameInfoIndex::FrameCompleted));
     }
+#endif
 }
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 4b0ddd2..638a060 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -17,10 +17,10 @@
 #include "JankTracker.h"
 
 #include <cutils/ashmem.h>
+#include <cutils/trace.h>
 #include <errno.h>
 #include <inttypes.h>
 #include <log/log.h>
-#include <sys/mman.h>
 
 #include <algorithm>
 #include <cmath>
@@ -278,7 +278,7 @@
 
 void JankTracker::dumpData(int fd, const ProfileDataDescription* description,
                            const ProfileData* data) {
-
+#ifdef __ANDROID__
     if (description) {
         switch (description->type) {
             case JankTrackerType::Generic:
@@ -296,9 +296,11 @@
     }
     data->dump(fd);
     dprintf(fd, "\n");
+#endif
 }
 
 void JankTracker::dumpFrames(int fd) {
+#ifdef __ANDROID__
     dprintf(fd, "\n\n---PROFILEDATA---\n");
     for (size_t i = 0; i < static_cast<size_t>(FrameInfoIndex::NumIndexes); i++) {
         dprintf(fd, "%s", FrameInfoNames[i]);
@@ -315,6 +317,7 @@
         }
     }
     dprintf(fd, "\n---PROFILEDATA---\n\n");
+#endif
 }
 
 void JankTracker::reset() REQUIRES(mDataMutex) {
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 3b694c5..54aef55 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -207,6 +207,13 @@
     SkClipOp op;
     void draw(SkCanvas* c, const SkMatrix&) const { c->clipRegion(region, op); }
 };
+struct ClipShader final : Op {
+    static const auto kType = Type::ClipShader;
+    ClipShader(const sk_sp<SkShader>& shader, SkClipOp op) : shader(shader), op(op) {}
+    sk_sp<SkShader> shader;
+    SkClipOp op;
+    void draw(SkCanvas* c, const SkMatrix&) const { c->clipShader(shader, op); }
+};
 struct ResetClip final : Op {
     static const auto kType = Type::ResetClip;
     ResetClip() {}
@@ -822,6 +829,9 @@
 void DisplayListData::clipRegion(const SkRegion& region, SkClipOp op) {
     this->push<ClipRegion>(0, region, op);
 }
+void DisplayListData::clipShader(const sk_sp<SkShader>& shader, SkClipOp op) {
+    this->push<ClipShader>(0, shader, op);
+}
 void DisplayListData::resetClip() {
     this->push<ResetClip>(0);
 }
@@ -1134,6 +1144,11 @@
     fDL->clipRegion(region, op);
     this->INHERITED::onClipRegion(region, op);
 }
+void RecordingCanvas::onClipShader(sk_sp<SkShader> shader, SkClipOp op) {
+    setClipMayBeComplex();
+    fDL->clipShader(shader, op);
+    this->INHERITED::onClipShader(shader, op);
+}
 void RecordingCanvas::onResetClip() {
     // This is part of "replace op" emulation, but rely on the following intersection
     // clip to potentially mark the clip as complex. If we are already complex, we do
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index afadbfd..965264f 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -140,6 +140,7 @@
     void translateZ(SkScalar);
 
     void clipPath(const SkPath&, SkClipOp, bool aa);
+    void clipShader(const sk_sp<SkShader>& shader, SkClipOp);
     void clipRect(const SkRect&, SkClipOp, bool aa);
     void clipRRect(const SkRRect&, SkClipOp, bool aa);
     void clipRegion(const SkRegion&, SkClipOp);
@@ -216,6 +217,7 @@
     void onClipRect(const SkRect&, SkClipOp, ClipEdgeStyle) override;
     void onClipRRect(const SkRRect&, SkClipOp, ClipEdgeStyle) override;
     void onClipPath(const SkPath&, SkClipOp, ClipEdgeStyle) override;
+    void onClipShader(sk_sp<SkShader>, SkClipOp) override;
     void onClipRegion(const SkRegion&, SkClipOp) override;
     void onResetClip() override;
 
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index e358b57..b1ad8b2 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -16,10 +16,22 @@
 
 #pragma once
 
-#ifdef __ANDROID__ // Layoutlib does not support device info
-#include "DeviceInfo.h"
-#endif // __ANDROID__
+#include <SkBlendMode.h>
+#include <SkCamera.h>
+#include <SkColor.h>
+#include <SkImageFilter.h>
+#include <SkMatrix.h>
+#include <SkRegion.h>
+#include <androidfw/ResourceTypes.h>
+#include <cutils/compiler.h>
+#include <stddef.h>
+#include <utils/Log.h>
 
+#include <algorithm>
+#include <ostream>
+#include <vector>
+
+#include "DeviceInfo.h"
 #include "Outline.h"
 #include "Rect.h"
 #include "RevealClip.h"
@@ -27,21 +39,6 @@
 #include "utils/MathUtils.h"
 #include "utils/PaintUtils.h"
 
-#include <SkBlendMode.h>
-#include <SkImageFilter.h>
-#include <SkCamera.h>
-#include <SkColor.h>
-#include <SkMatrix.h>
-#include <SkRegion.h>
-
-#include <androidfw/ResourceTypes.h>
-#include <cutils/compiler.h>
-#include <stddef.h>
-#include <utils/Log.h>
-#include <algorithm>
-#include <ostream>
-#include <vector>
-
 class SkBitmap;
 class SkColorFilter;
 class SkPaint;
@@ -546,13 +543,9 @@
     }
 
     bool fitsOnLayer() const {
-#ifdef __ANDROID__ // Layoutlib does not support device info
         const DeviceInfo* deviceInfo = DeviceInfo::get();
         return mPrimitiveFields.mWidth <= deviceInfo->maxTextureSize() &&
                mPrimitiveFields.mHeight <= deviceInfo->maxTextureSize();
-#else
-        return mPrimitiveFields.mWidth <= 4096 && mPrimitiveFields.mHeight <= 4096;
-#endif
     }
 
     bool promotedToLayer() const {
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 536ff78..af169f4 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -16,6 +16,7 @@
 
 #include "VectorDrawable.h"
 
+#include <gui/TraceUtils.h>
 #include <math.h>
 #include <string.h>
 #include <utils/Log.h>
@@ -26,12 +27,7 @@
 #include "SkSamplingOptions.h"
 #include "SkScalar.h"
 #include "hwui/Paint.h"
-
-#ifdef __ANDROID__
 #include "renderthread/RenderThread.h"
-#endif
-
-#include <gui/TraceUtils.h>
 #include "utils/Macros.h"
 #include "utils/VectorDrawableUtils.h"
 
@@ -544,7 +540,7 @@
 }
 
 bool Tree::canReuseBitmap(Bitmap* bitmap, int width, int height) {
-    return bitmap && width <= bitmap->width() && height <= bitmap->height();
+    return bitmap && width == bitmap->width() && height == bitmap->height();
 }
 
 void Tree::onPropertyChanged(TreeProperties* prop) {
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index 3d7e559..76a0a64 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -76,3 +76,10 @@
   description: "Automatically animate all changes in HDR headroom"
   bug: "314810174"
 }
+
+flag {
+  name: "draw_region"
+  namespace: "core_graphics"
+  description: "Add canvas#drawRegion API"
+  bug: "318612129"
+}
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index 883f273..fb0cdb0 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -70,7 +70,6 @@
 extern int register_android_graphics_fonts_FontFamily(JNIEnv* env);
 extern int register_android_graphics_pdf_PdfDocument(JNIEnv* env);
 extern int register_android_graphics_pdf_PdfEditor(JNIEnv* env);
-extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env);
 extern int register_android_graphics_text_MeasuredText(JNIEnv* env);
 extern int register_android_graphics_text_LineBreaker(JNIEnv *env);
 extern int register_android_graphics_text_TextShaper(JNIEnv *env);
@@ -142,7 +141,6 @@
             REG_JNI(register_android_graphics_fonts_FontFamily),
             REG_JNI(register_android_graphics_pdf_PdfDocument),
             REG_JNI(register_android_graphics_pdf_PdfEditor),
-            REG_JNI(register_android_graphics_pdf_PdfRenderer),
             REG_JNI(register_android_graphics_text_MeasuredText),
             REG_JNI(register_android_graphics_text_LineBreaker),
             REG_JNI(register_android_graphics_text_TextShaper),
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index d15b1680..d9e2c8c 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -860,7 +860,8 @@
 static void android_view_ThreadedRenderer_initDisplayInfo(
         JNIEnv* env, jclass, jint physicalWidth, jint physicalHeight, jfloat refreshRate,
         jint wideColorDataspace, jlong appVsyncOffsetNanos, jlong presentationDeadlineNanos,
-        jboolean supportFp16ForHdr, jboolean supportMixedColorSpaces) {
+        jboolean supportFp16ForHdr, jboolean supportRgba10101010ForHdr,
+        jboolean supportMixedColorSpaces) {
     DeviceInfo::setWidth(physicalWidth);
     DeviceInfo::setHeight(physicalHeight);
     DeviceInfo::setRefreshRate(refreshRate);
@@ -868,6 +869,7 @@
     DeviceInfo::setAppVsyncOffsetNanos(appVsyncOffsetNanos);
     DeviceInfo::setPresentationDeadlineNanos(presentationDeadlineNanos);
     DeviceInfo::setSupportFp16ForHdr(supportFp16ForHdr);
+    DeviceInfo::setSupportRgba10101010ForHdr(supportRgba10101010ForHdr);
     DeviceInfo::setSupportMixedColorSpaces(supportMixedColorSpaces);
 }
 
@@ -1020,7 +1022,7 @@
         {"nSetForceDark", "(JI)V", (void*)android_view_ThreadedRenderer_setForceDark},
         {"nSetDisplayDensityDpi", "(I)V",
          (void*)android_view_ThreadedRenderer_setDisplayDensityDpi},
-        {"nInitDisplayInfo", "(IIFIJJZZ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo},
+        {"nInitDisplayInfo", "(IIFIJJZZZ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo},
         {"preload", "()V", (void*)android_view_ThreadedRenderer_preload},
         {"isWebViewOverlaysEnabled", "()Z",
          (void*)android_view_ThreadedRenderer_isWebViewOverlaysEnabled},
diff --git a/libs/hwui/jni/pdf/PdfRenderer.cpp b/libs/hwui/jni/pdf/PdfRenderer.cpp
deleted file mode 100644
index cc1f961..0000000
--- a/libs/hwui/jni/pdf/PdfRenderer.cpp
+++ /dev/null
@@ -1,134 +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.
- */
-
-#include "PdfUtils.h"
-
-#include "GraphicsJNI.h"
-#include "SkBitmap.h"
-#include "SkMatrix.h"
-#include "fpdfview.h"
-
-#include <vector>
-#include <utils/Log.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-namespace android {
-
-static const int RENDER_MODE_FOR_DISPLAY = 1;
-static const int RENDER_MODE_FOR_PRINT = 2;
-
-static struct {
-    jfieldID x;
-    jfieldID y;
-} gPointClassInfo;
-
-static jlong nativeOpenPageAndGetSize(JNIEnv* env, jclass thiz, jlong documentPtr,
-        jint pageIndex, jobject outSize) {
-    FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
-
-    FPDF_PAGE page = FPDF_LoadPage(document, pageIndex);
-    if (!page) {
-        jniThrowException(env, "java/lang/IllegalStateException",
-                "cannot load page");
-        return -1;
-    }
-
-    double width = 0;
-    double height = 0;
-
-    int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height);
-    if (!result) {
-        jniThrowException(env, "java/lang/IllegalStateException",
-                    "cannot get page size");
-        return -1;
-    }
-
-    env->SetIntField(outSize, gPointClassInfo.x, width);
-    env->SetIntField(outSize, gPointClassInfo.y, height);
-
-    return reinterpret_cast<jlong>(page);
-}
-
-static void nativeClosePage(JNIEnv* env, jclass thiz, jlong pagePtr) {
-    FPDF_PAGE page = reinterpret_cast<FPDF_PAGE>(pagePtr);
-    FPDF_ClosePage(page);
-}
-
-static void nativeRenderPage(JNIEnv* env, jclass thiz, jlong documentPtr, jlong pagePtr,
-        jlong bitmapPtr, jint clipLeft, jint clipTop, jint clipRight, jint clipBottom,
-        jlong transformPtr, jint renderMode) {
-    FPDF_PAGE page = reinterpret_cast<FPDF_PAGE>(pagePtr);
-
-    SkBitmap skBitmap;
-    bitmap::toBitmap(bitmapPtr).getSkBitmap(&skBitmap);
-
-    const int stride = skBitmap.width() * 4;
-
-    FPDF_BITMAP bitmap = FPDFBitmap_CreateEx(skBitmap.width(), skBitmap.height(),
-            FPDFBitmap_BGRA, skBitmap.getPixels(), stride);
-
-    int renderFlags = FPDF_REVERSE_BYTE_ORDER;
-    if (renderMode == RENDER_MODE_FOR_DISPLAY) {
-        renderFlags |= FPDF_LCD_TEXT;
-    } else if (renderMode == RENDER_MODE_FOR_PRINT) {
-        renderFlags |= FPDF_PRINTING;
-    }
-
-    SkMatrix matrix = *reinterpret_cast<SkMatrix*>(transformPtr);
-    SkScalar transformValues[6];
-    if (!matrix.asAffine(transformValues)) {
-        jniThrowException(env, "java/lang/IllegalArgumentException",
-                "transform matrix has perspective. Only affine matrices are allowed.");
-        return;
-    }
-
-    FS_MATRIX transform = {transformValues[SkMatrix::kAScaleX], transformValues[SkMatrix::kASkewY],
-                           transformValues[SkMatrix::kASkewX], transformValues[SkMatrix::kAScaleY],
-                           transformValues[SkMatrix::kATransX],
-                           transformValues[SkMatrix::kATransY]};
-
-    FS_RECTF clip = {(float) clipLeft, (float) clipTop, (float) clipRight, (float) clipBottom};
-
-    FPDF_RenderPageBitmapWithMatrix(bitmap, page, &transform, &clip, renderFlags);
-
-    skBitmap.notifyPixelsChanged();
-}
-
-static const JNINativeMethod gPdfRenderer_Methods[] = {
-    {"nativeCreate", "(IJ)J", (void*) nativeOpen},
-    {"nativeClose", "(J)V", (void*) nativeClose},
-    {"nativeGetPageCount", "(J)I", (void*) nativeGetPageCount},
-    {"nativeScaleForPrinting", "(J)Z", (void*) nativeScaleForPrinting},
-    {"nativeRenderPage", "(JJJIIIIJI)V", (void*) nativeRenderPage},
-    {"nativeOpenPageAndGetSize", "(JILandroid/graphics/Point;)J", (void*) nativeOpenPageAndGetSize},
-    {"nativeClosePage", "(J)V", (void*) nativeClosePage}
-};
-
-int register_android_graphics_pdf_PdfRenderer(JNIEnv* env) {
-    int result = RegisterMethodsOrDie(
-            env, "android/graphics/pdf/PdfRenderer", gPdfRenderer_Methods,
-            NELEM(gPdfRenderer_Methods));
-
-    jclass clazz = FindClassOrDie(env, "android/graphics/Point");
-    gPointClassInfo.x = GetFieldIDOrDie(env, clazz, "x", "I");
-    gPointClassInfo.y = GetFieldIDOrDie(env, clazz, "y", "I");
-
-    return result;
-};
-
-};
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 326b6ed..99469d1 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -650,7 +650,11 @@
             mSurfaceColorSpace = DeviceInfo::get()->getWideColorSpace();
             break;
         case ColorMode::Hdr:
-            if (DeviceInfo::get()->isSupportFp16ForHdr()) {
+            if (DeviceInfo::get()->isSupportRgba10101010ForHdr()) {
+                mSurfaceColorType = SkColorType::kRGBA_10x6_SkColorType;
+                mSurfaceColorSpace = SkColorSpace::MakeRGB(
+                        GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3);
+            } else if (DeviceInfo::get()->isSupportFp16ForHdr()) {
                 mSurfaceColorType = SkColorType::kRGBA_F16_SkColorType;
                 mSurfaceColorSpace = SkColorSpace::MakeSRGB();
             } else {
@@ -675,7 +679,8 @@
     if (mColorMode == ColorMode::Hdr || mColorMode == ColorMode::Hdr10) {
         mTargetSdrHdrRatio = ratio;
 
-        if (mColorMode == ColorMode::Hdr && DeviceInfo::get()->isSupportFp16ForHdr()) {
+        if (mColorMode == ColorMode::Hdr && DeviceInfo::get()->isSupportFp16ForHdr() &&
+            !DeviceInfo::get()->isSupportRgba10101010ForHdr()) {
             mSurfaceColorSpace = SkColorSpace::MakeSRGB();
         } else {
             mSurfaceColorSpace = SkColorSpace::MakeRGB(
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 1d03301..abf64d0 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -411,7 +411,8 @@
 
     // If the previous frame was dropped we don't need to hold onto it, so
     // just keep using the previous frame's structure instead
-    if (const auto reason = wasSkipped(mCurrentFrameInfo)) {
+    const auto reason = wasSkipped(mCurrentFrameInfo);
+    if (reason.has_value()) {
         // Use the oldest skipped frame in case we skip more than a single frame
         if (!mSkippedFrameInfo) {
             switch (*reason) {
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index b5f7caa..0d0af11 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -25,6 +25,7 @@
 #include <android/sync.h>
 #include <gui/TraceUtils.h>
 #include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/vk/GrVkBackendSemaphore.h>
 #include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
 #include <include/gpu/ganesh/vk/GrVkDirectContext.h>
 #include <ui/FatVector.h>
@@ -597,15 +598,14 @@
                         close(fence_clone);
                         sync_wait(bufferInfo->dequeue_fence, -1 /* forever */);
                     } else {
-                        GrBackendSemaphore backendSemaphore;
-                        backendSemaphore.initVulkan(semaphore);
+                        GrBackendSemaphore beSemaphore = GrBackendSemaphores::MakeVk(semaphore);
                         // Skia will take ownership of the VkSemaphore and delete it once the wait
                         // has finished. The VkSemaphore also owns the imported fd, so it will
                         // close the fd when it is deleted.
-                        bufferInfo->skSurface->wait(1, &backendSemaphore);
+                        bufferInfo->skSurface->wait(1, &beSemaphore);
                         // The following flush blocks the GPU immediately instead of waiting for
                         // other drawing ops. It seems dequeue_fence is not respected otherwise.
-                        // TODO: remove the flush after finding why backendSemaphore is not working.
+                        // TODO: remove the flush after finding why beSemaphore is not working.
                         skgpu::ganesh::FlushAndSubmit(bufferInfo->skSurface.get());
                     }
                 }
@@ -626,7 +626,7 @@
     SharedSemaphoreInfo(PFN_vkDestroySemaphore destroyFunction, VkDevice device,
                         VkSemaphore semaphore)
             : mDestroyFunction(destroyFunction), mDevice(device), mSemaphore(semaphore) {
-        mGrBackendSemaphore.initVulkan(semaphore);
+        mGrBackendSemaphore = GrBackendSemaphores::MakeVk(mSemaphore);
     }
 
     ~SharedSemaphoreInfo() { mDestroyFunction(mDevice, mSemaphore, nullptr); }
@@ -798,8 +798,7 @@
         return UNKNOWN_ERROR;
     }
 
-    GrBackendSemaphore beSemaphore;
-    beSemaphore.initVulkan(semaphore);
+    GrBackendSemaphore beSemaphore = GrBackendSemaphores::MakeVk(semaphore);
 
     // Skia will take ownership of the VkSemaphore and delete it once the wait has finished. The
     // VkSemaphore also owns the imported fd, so it will close the fd when it is deleted.
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index f84107e..f9dc5fa 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -41,6 +41,8 @@
 
 namespace {
 
+static const bool ENABLE_POINTER_CHOREOGRAPHER = input_flags::enable_pointer_choreographer();
+
 const ui::Transform kIdentityTransform;
 
 } // namespace
@@ -224,7 +226,7 @@
 
     mLocked.presentation = presentation;
 
-    if (input_flags::enable_pointer_choreographer()) {
+    if (ENABLE_POINTER_CHOREOGRAPHER) {
         // When pointer choreographer is enabled, the presentation mode is only set once when the
         // PointerController is constructed, before the display viewport is provided.
         // TODO(b/293587049): Clean up the PointerController interface after pointer choreographer
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index 6dc45a6..6a32c5a 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -202,11 +202,13 @@
                 && update.state.surfaceDrawn;
         bool becomingVisible = wantSurfaceVisibleAndDrawn && !update.state.surfaceVisible;
         bool becomingHidden = !wantSurfaceVisibleAndDrawn && update.state.surfaceVisible;
-        if (update.state.surfaceControl != NULL && (becomingVisible || becomingHidden
-                || (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA
-                        | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER
-                        | DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID
-                        | DIRTY_ICON_STYLE))))) {
+        if (update.state.surfaceControl != NULL &&
+            (becomingVisible || becomingHidden ||
+             (wantSurfaceVisibleAndDrawn &&
+              (update.state.dirty &
+               (DIRTY_ALPHA | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER |
+                DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID | DIRTY_ICON_STYLE |
+                DIRTY_DRAW_DROP_SHADOW))))) {
             needApplyTransaction = true;
 
             if (wantSurfaceVisibleAndDrawn
@@ -235,13 +237,15 @@
                         update.state.transformationMatrix.dtdy);
             }
 
-            if (wantSurfaceVisibleAndDrawn
-                    && (becomingVisible
-                            || (update.state.dirty & (DIRTY_HOTSPOT | DIRTY_ICON_STYLE)))) {
+            if (wantSurfaceVisibleAndDrawn &&
+                (becomingVisible ||
+                 (update.state.dirty &
+                  (DIRTY_HOTSPOT | DIRTY_ICON_STYLE | DIRTY_DRAW_DROP_SHADOW)))) {
                 Parcel p;
                 p.writeInt32(static_cast<int32_t>(update.state.icon.style));
                 p.writeFloat(update.state.icon.hotSpotX);
                 p.writeFloat(update.state.icon.hotSpotY);
+                p.writeBool(update.state.icon.drawNativeDropShadow);
 
                 // Pass cursor metadata in the sprite surface so that when Android is running as a
                 // client OS (e.g. ARC++) the host OS can get the requested cursor metadata and
@@ -388,12 +392,13 @@
     uint32_t dirty;
     if (icon.isValid()) {
         mLocked.state.icon.bitmap = icon.bitmap.copy(ANDROID_BITMAP_FORMAT_RGBA_8888);
-        if (!mLocked.state.icon.isValid()
-                || mLocked.state.icon.hotSpotX != icon.hotSpotX
-                || mLocked.state.icon.hotSpotY != icon.hotSpotY) {
+        if (!mLocked.state.icon.isValid() || mLocked.state.icon.hotSpotX != icon.hotSpotX ||
+            mLocked.state.icon.hotSpotY != icon.hotSpotY ||
+            mLocked.state.icon.drawNativeDropShadow != icon.drawNativeDropShadow) {
             mLocked.state.icon.hotSpotX = icon.hotSpotX;
             mLocked.state.icon.hotSpotY = icon.hotSpotY;
-            dirty = DIRTY_BITMAP | DIRTY_HOTSPOT;
+            mLocked.state.icon.drawNativeDropShadow = icon.drawNativeDropShadow;
+            dirty = DIRTY_BITMAP | DIRTY_HOTSPOT | DIRTY_DRAW_DROP_SHADOW;
         } else {
             dirty = DIRTY_BITMAP;
         }
@@ -404,7 +409,7 @@
         }
     } else if (mLocked.state.icon.isValid()) {
         mLocked.state.icon.bitmap.reset();
-        dirty = DIRTY_BITMAP | DIRTY_HOTSPOT | DIRTY_ICON_STYLE;
+        dirty = DIRTY_BITMAP | DIRTY_HOTSPOT | DIRTY_ICON_STYLE | DIRTY_DRAW_DROP_SHADOW;
     } else {
         return; // setting to invalid icon and already invalid so nothing to do
     }
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index 04ecb38..35776e9 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -151,6 +151,7 @@
         DIRTY_HOTSPOT = 1 << 6,
         DIRTY_DISPLAY_ID = 1 << 7,
         DIRTY_ICON_STYLE = 1 << 8,
+        DIRTY_DRAW_DROP_SHADOW = 1 << 9,
     };
 
     /* Describes the state of a sprite.
diff --git a/libs/input/SpriteIcon.h b/libs/input/SpriteIcon.h
index 0939af4..7d45d02 100644
--- a/libs/input/SpriteIcon.h
+++ b/libs/input/SpriteIcon.h
@@ -40,7 +40,7 @@
     PointerIconStyle style{PointerIconStyle::TYPE_NULL};
     float hotSpotX{};
     float hotSpotY{};
-    bool drawNativeDropShadow{false};
+    bool drawNativeDropShadow{};
 
     inline bool isValid() const { return bitmap.isValid() && !bitmap.isEmpty(); }
 
diff --git a/location/api/system-current.txt b/location/api/system-current.txt
index 2e7a541..254d74a 100644
--- a/location/api/system-current.txt
+++ b/location/api/system-current.txt
@@ -616,8 +616,8 @@
   @FlaggedApi(Flags.FLAG_NEW_GEOCODER) public abstract class GeocodeProviderBase {
     ctor public GeocodeProviderBase(@NonNull android.content.Context, @NonNull String);
     method @NonNull public final android.os.IBinder getBinder();
-    method public abstract void onForwardGeocode(@NonNull android.location.provider.ForwardGeocodeRequest, @NonNull android.os.OutcomeReceiver<java.util.List<android.location.Address>,java.lang.Exception>);
-    method public abstract void onReverseGeocode(@NonNull android.location.provider.ReverseGeocodeRequest, @NonNull android.os.OutcomeReceiver<java.util.List<android.location.Address>,java.lang.Exception>);
+    method public abstract void onForwardGeocode(@NonNull android.location.provider.ForwardGeocodeRequest, @NonNull android.os.OutcomeReceiver<java.util.List<android.location.Address>,java.lang.Throwable>);
+    method public abstract void onReverseGeocode(@NonNull android.location.provider.ReverseGeocodeRequest, @NonNull android.os.OutcomeReceiver<java.util.List<android.location.Address>,java.lang.Throwable>);
     field public static final String ACTION_GEOCODE_PROVIDER = "com.android.location.service.GeocodeProvider";
   }
 
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 156be38..f33bcb7 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -11,7 +11,7 @@
     name: "location_bypass"
     namespace: "location"
     description: "Enable location bypass appops behavior"
-    bug: "301150056"
+    bug: "329151785"
 }
 
 flag {
diff --git a/location/java/android/location/provider/ForwardGeocodeRequest.java b/location/java/android/location/provider/ForwardGeocodeRequest.java
index 8f227b1..89d14fb 100644
--- a/location/java/android/location/provider/ForwardGeocodeRequest.java
+++ b/location/java/android/location/provider/ForwardGeocodeRequest.java
@@ -260,7 +260,11 @@
             mCallingAttributionTag = null;
         }
 
-        /** Sets the attribution tag. */
+        /**
+         * Sets the attribution tag.
+         *
+         * @param attributionTag The attribution tag to associate with the request.
+         */
         @NonNull
         public Builder setCallingAttributionTag(@NonNull String attributionTag) {
             mCallingAttributionTag = attributionTag;
diff --git a/location/java/android/location/provider/GeocodeProviderBase.java b/location/java/android/location/provider/GeocodeProviderBase.java
index e2c48b9..71644d07 100644
--- a/location/java/android/location/provider/GeocodeProviderBase.java
+++ b/location/java/android/location/provider/GeocodeProviderBase.java
@@ -104,14 +104,14 @@
      */
     public abstract void onForwardGeocode(
             @NonNull ForwardGeocodeRequest request,
-            @NonNull OutcomeReceiver<List<Address>, Exception> callback);
+            @NonNull OutcomeReceiver<List<Address>, Throwable> callback);
 
     /**
      * Requests reverse geocoding of the given arguments. The given callback must be invoked once.
      */
     public abstract void onReverseGeocode(
             @NonNull ReverseGeocodeRequest request,
-            @NonNull OutcomeReceiver<List<Address>, Exception> callback);
+            @NonNull OutcomeReceiver<List<Address>, Throwable> callback);
 
     private class Service extends IGeocodeProvider.Stub {
         @Override
@@ -145,7 +145,7 @@
         }
     }
 
-    private static class SingleUseCallback implements OutcomeReceiver<List<Address>, Exception> {
+    private static class SingleUseCallback implements OutcomeReceiver<List<Address>, Throwable> {
 
         private final AtomicReference<IGeocodeCallback> mCallback;
 
@@ -154,7 +154,7 @@
         }
 
         @Override
-        public void onError(Exception e) {
+        public void onError(Throwable e) {
             try {
                 Objects.requireNonNull(mCallback.getAndSet(null)).onError(e.toString());
             } catch (RemoteException r) {
diff --git a/location/java/android/location/provider/ReverseGeocodeRequest.java b/location/java/android/location/provider/ReverseGeocodeRequest.java
index 57c9047..2107707 100644
--- a/location/java/android/location/provider/ReverseGeocodeRequest.java
+++ b/location/java/android/location/provider/ReverseGeocodeRequest.java
@@ -207,7 +207,11 @@
             mCallingAttributionTag = null;
         }
 
-        /** Sets the attribution tag. */
+        /**
+         * Sets the attribution tag.
+         *
+         * @param attributionTag The attribution tag to associate with the request.
+         */
         @NonNull
         public Builder setCallingAttributionTag(@NonNull String attributionTag) {
             mCallingAttributionTag = attributionTag;
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index ea136ed..c90c441 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -1079,7 +1079,7 @@
      * @return one of the values that can be set in {@link Builder#setEncoding(int)} or
      * {@link AudioFormat#ENCODING_INVALID} if not set.
      */
-    public @Encoding int getEncoding() {
+    public @EncodingCanBeInvalid int getEncoding() {
         return mEncoding;
     }
 
@@ -1486,6 +1486,44 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface Encoding {}
 
+    /** @hide same as @Encoding, but adding ENCODING_INVALID */
+    @IntDef(flag = false, prefix = "ENCODING", value = {
+            ENCODING_INVALID,
+            ENCODING_DEFAULT,
+            ENCODING_PCM_16BIT,
+            ENCODING_PCM_8BIT,
+            ENCODING_PCM_FLOAT,
+            ENCODING_AC3,
+            ENCODING_E_AC3,
+            ENCODING_DTS,
+            ENCODING_DTS_HD,
+            ENCODING_MP3,
+            ENCODING_AAC_LC,
+            ENCODING_AAC_HE_V1,
+            ENCODING_AAC_HE_V2,
+            ENCODING_IEC61937,
+            ENCODING_DOLBY_TRUEHD,
+            ENCODING_AAC_ELD,
+            ENCODING_AAC_XHE,
+            ENCODING_AC4,
+            ENCODING_E_AC3_JOC,
+            ENCODING_DOLBY_MAT,
+            ENCODING_OPUS,
+            ENCODING_PCM_24BIT_PACKED,
+            ENCODING_PCM_32BIT,
+            ENCODING_MPEGH_BL_L3,
+            ENCODING_MPEGH_BL_L4,
+            ENCODING_MPEGH_LC_L3,
+            ENCODING_MPEGH_LC_L4,
+            ENCODING_DTS_UHD_P1,
+            ENCODING_DRA,
+            ENCODING_DTS_HD_MA,
+            ENCODING_DTS_UHD_P2,
+            ENCODING_DSD }
+    )
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EncodingCanBeInvalid {}
+
     /** @hide */
     public static final int[] SURROUND_SOUND_ENCODING = {
             ENCODING_AC3,
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 8f3f82e..6f7024a 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -7878,9 +7878,9 @@
      */
     @FlaggedApi(FLAG_SUPPORTED_DEVICE_TYPES_API)
     public @NonNull Set<Integer>
-            getSupportedDeviceTypes(int direction) {
+            getSupportedDeviceTypes(@AudioDeviceRole int direction) {
         if (direction != GET_DEVICES_OUTPUTS && direction != GET_DEVICES_INPUTS) {
-            throw new IllegalArgumentException("AudioManager.getSupportedDeviceTypes("
+            throw new IllegalArgumentException("AudioManager.getSupportedDeviceTypes(0x"
                     + Integer.toHexString(direction) + ") - Invalid.");
         }
 
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index 72aaa35..55c8ed5 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -37,6 +37,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.ParcelFileDescriptor;
+import android.os.Trace;
 import android.view.Surface;
 
 import dalvik.system.VMRuntime;
@@ -925,6 +926,7 @@
         if (ir == null) {
             return;
         }
+        Trace.beginSection("android.media.ImageReader#postEventFromNative");
 
         final Executor executor;
         final OnImageAvailableListener listener;
@@ -948,6 +950,7 @@
                 }
             });
         }
+        Trace.endSection();
     }
 
     /**
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 9548525..f5dc6ea 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -2345,6 +2345,15 @@
             throw new IllegalArgumentException("Can't use crypto and descrambler together!");
         }
 
+        // at the moment no codecs support detachable surface
+        if (android.media.codec.Flags.nullOutputSurface()) {
+            // Detached surface flag is only meaningful if surface is null. Otherwise, it is
+            // ignored.
+            if (surface == null && (flags & CONFIGURE_FLAG_DETACHED_SURFACE) != 0) {
+                throw new IllegalArgumentException("Codec does not support detached surface");
+            }
+        }
+
         String[] keys = null;
         Object[] values = null;
 
@@ -2419,7 +2428,8 @@
      *  output.
      *
      *  @throws IllegalStateException if the codec was not
-     *                                configured in surface mode.
+     *            configured in surface mode or if the codec does not support
+     *            detaching the output surface.
      *  @see CONFIGURE_FLAG_DETACHED_SURFACE
      */
     @FlaggedApi(FLAG_NULL_OUTPUT_SURFACE)
@@ -2429,6 +2439,7 @@
         }
         // note: we still have a surface in detached mode, so keep mHasSurface
         // we also technically allow calling detachOutputSurface multiple times in a row
+        throw new IllegalStateException("codec does not support detaching output surface");
         // native_detachSurface();
     }
 
@@ -4750,6 +4761,9 @@
         }
 
         void setBufferInfo(MediaCodec.BufferInfo info) {
+            // since any of setBufferInfo(s) should translate to getBufferInfos,
+            // mBufferInfos needs to be reset for every setBufferInfo(s)
+            mBufferInfos.clear();
             mPresentationTimeUs = info.presentationTimeUs;
             mFlags = info.flags;
         }
diff --git a/media/java/android/media/RingtoneSelection.java b/media/java/android/media/RingtoneSelection.java
deleted file mode 100644
index b7c3721..0000000
--- a/media/java/android/media/RingtoneSelection.java
+++ /dev/null
@@ -1,742 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.FlaggedApi;
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.TestApi;
-import android.content.ContentProvider;
-import android.content.ContentResolver;
-import android.net.Uri;
-import android.os.UserHandle;
-import android.os.vibrator.Flags;
-import android.provider.MediaStore;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
-
-/**
- * Immutable representation a desired ringtone, usually originating from a user preference.
- * Unlike sound-only Uris, a "silent" setting is an explicit selection value, rather than null.
- *
- * <p>This representation can be converted into (or from) a URI form for storing within a string
- * preference or when using the ringtone picker via {@link RingtoneManager#ACTION_RINGTONE_PICKER}.
- * It does not carry any actual media data - it only references the components that make
- * up the preference. Initial selections can be built using {@link RingtoneSelection.Builder}.
- *
- * <p>A RingtoneSelection is typically played by passing into a {@link Ringtone.Builder}, and
- * supplementing with contextual defaults from the application. Bad Uris are handled by the
- * {@link Ringtone} class - the RingtoneSelection doesn't validate the target of the Uri.
- *
- * <p>When a RingtoneSelection is created/loaded, the values of its properties are modified
- * to be internally consistent and reflect effective values - with the exception of not verifying
- * the actual URI content. For example, loading a selection Uri that sets a sound source to
- * {@link #SOUND_SOURCE_URI}, but doesn't also have a sound Uri set, will result in this class
- * instead returning {@link #SOUND_SOURCE_UNSPECIFIED} from {@link #getSoundSource}.
- *
- * <h2>Storing preferences</h2>
- *
- * <p>A ringtone preference can have several states: either unset, set to a ringtone selection Uri,
- * or, from prior to the introduction of {@code RingtoneSelection}, set to a sound-only Uri or
- * explicitly set to null to indicate silent.
- *
- * @hide
- */
-@TestApi
-@FlaggedApi(Flags.FLAG_HAPTICS_CUSTOMIZATION_ENABLED)
-public final class RingtoneSelection {
-
-    /**
-     * The sound source was specified but its value was not recognized. This value is used
-     * internally for not stripping unrecognised (possibly future) values during processing.
-     * @hide
-     */
-    public static final int SOUND_SOURCE_UNKNOWN = -1;
-
-    /**
-     * The sound source is not explicitly specified, so it can follow default behavior for its
-     * context.
-     */
-    public static final int SOUND_SOURCE_UNSPECIFIED = 0;
-
-    /**
-     * Sound is explicitly disabled, such as the user having selected "Silent" in the sound picker.
-     */
-    public static final int SOUND_SOURCE_OFF = 1;
-
-    /**
-     * The sound Uri should be used as the source of sound.
-     */
-    public static final int SOUND_SOURCE_URI = 2;
-
-    /**
-     * The sound should explicitly use the system default.
-     *
-     * <p>This value isn't valid within the system default itself.
-     */
-    public static final int SOUND_SOURCE_SYSTEM_DEFAULT = 3;
-
-    // Note: Value 4 reserved for possibility of SOURCE_SOURCE_APPLICATION_DEFAULT.
-
-    /**
-     * Directive for how to make sound.
-     * @hide
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = "SOUND_SOURCE_", value = {
-            SOUND_SOURCE_UNKNOWN,
-            SOUND_SOURCE_UNSPECIFIED,
-            SOUND_SOURCE_OFF,
-            SOUND_SOURCE_URI,
-            SOUND_SOURCE_SYSTEM_DEFAULT,
-    })
-    public @interface SoundSource {}
-
-    /**
-     * The vibration source was specified but its value was not recognized.
-     * This value is used internally for not stripping unrecognised (possibly
-     * future) values during processing.
-     * @hide
-     */
-    public static final int VIBRATION_SOURCE_UNKNOWN = -1;
-
-    /**
-     * Vibration source is not explicitly specified. If vibration is enabled, this will use the
-     * first available of {@link #VIBRATION_SOURCE_AUDIO_CHANNEL},
-     * {@link #VIBRATION_SOURCE_APPLICATION_DEFAULT}, or {@link #VIBRATION_SOURCE_SYSTEM_DEFAULT}.
-     */
-    public static final int VIBRATION_SOURCE_UNSPECIFIED = 0;
-
-    /** Specifies that vibration is explicitly disabled for this ringtone. */
-    public static final int VIBRATION_SOURCE_OFF = 1;
-
-    /** The vibration Uri should be used as the source of vibration. */
-    public static final int VIBRATION_SOURCE_URI = 2;
-
-    /**
-     * The vibration should explicitly use the system default.
-     *
-     * <p>This value isn't valid within the system default itself.
-     */
-    public static final int VIBRATION_SOURCE_SYSTEM_DEFAULT = 3;
-
-    /**
-     * Specifies that vibration should use the vibration provided by the application. This is
-     * typically the application's own default for the use-case, provided via
-     * {@link Ringtone.Builder#setVibrationEffect}. For notification channels, this is the vibration
-     * effect saved on the notification channel.
-     *
-     * <p>If no vibration is specified by the application, this value behaves if the source was
-     * {@link #VIBRATION_SOURCE_UNSPECIFIED}.
-     *
-     * <p>This value isn't valid within the system default.
-     */
-    public static final int VIBRATION_SOURCE_APPLICATION_DEFAULT = 4;
-
-    /**
-     * Specifies that vibration should use haptic audio channels from the
-     * sound Uri. If the sound URI doesn't have haptic channels, then reverts to the order specified
-     * by {@link #VIBRATION_SOURCE_UNSPECIFIED}.
-     */
-    // Numeric gap from VIBRATION_SOURCE_APPLICATION_DEFAULT in case we want other common elements.
-    public static final int VIBRATION_SOURCE_AUDIO_CHANNEL = 10;
-
-    /**
-     * Specifies that vibration should generate haptic audio channels from the
-     * audio tracks of the sound Uri.
-     *
-     * If the sound Uri already has haptic channels, then behaves as though
-     * {@link #VIBRATION_SOURCE_AUDIO_CHANNEL} was specified instead.
-     */
-    public static final int VIBRATION_SOURCE_HAPTIC_GENERATOR = 11;
-
-    /**
-     * Directive for how to vibrate.
-     * @hide
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = "VIBRATION_SOURCE_", value = {
-            VIBRATION_SOURCE_UNKNOWN,
-            VIBRATION_SOURCE_UNSPECIFIED,
-            VIBRATION_SOURCE_OFF,
-            VIBRATION_SOURCE_URI,
-            VIBRATION_SOURCE_APPLICATION_DEFAULT,
-            VIBRATION_SOURCE_AUDIO_CHANNEL,
-            VIBRATION_SOURCE_HAPTIC_GENERATOR,
-    })
-    public @interface VibrationSource {}
-
-    /**
-     * Configures {@link #RingtoneSelection#fromUri} to treat an unrecognized Uri as the sound Uri
-     * for the returned {@link RingtoneSelection}, with null meaning {@link #SOUND_SOURCE_OFF},
-     * and symbolic default URIs ({@link RingtoneManager#getDefaultUri}) meaning
-     * {@link #SOUND_SOURCE_SYSTEM_DEFAULT}.
-     *
-     * <p>This behavior is particularly suited to loading values from older settings that may
-     * contain a raw sound Uri or null for silent.
-     *
-     * <p>An unrecognized Uri is one for which {@link #isRingtoneSelectionUri(Uri)} returns false.
-     */
-    public static final int FROM_URI_RINGTONE_SELECTION_OR_SOUND = 1;
-
-    /**
-     * Configures {@link #RingtoneSelection#fromUri} to treat an unrecognized Uri as the vibration
-     * Uri for the returned {@link RingtoneSelection}, with null meaning
-     * {@link #VIBRATION_SOURCE_OFF} and symbolic default URIs
-     * ({@link RingtoneManager#getDefaultUri}) meaning {@link #VIBRATION_SOURCE_SYSTEM_DEFAULT}.
-     *
-     * <p>An unrecognized Uri is one for which {@link #isRingtoneSelectionUri(Uri)} returns false.
-     */
-    public static final int FROM_URI_RINGTONE_SELECTION_OR_VIBRATION = 2;
-
-    /**
-     * Configures {@link #RingtoneSelection#fromUri} to treat an unrecognized Uri as an invalid
-     * value. Null or an invalid values will revert to default behavior correspnoding to
-     * {@link #DEFAULT_SELECTION_URI_STRING}. Symbolic default URIs
-     * ({@link RingtoneManager#getDefaultUri}) will set both
-     * {@link #SOUND_SOURCE_SYSTEM_DEFAULT} and {@link #VIBRATION_SOURCE_SYSTEM_DEFAULT}.
-     *
-     * <p>An unrecognized Uri is one for which {@link #isRingtoneSelectionUri(Uri)} returns false,
-     * which include {@code null}.
-     */
-    public static final int FROM_URI_RINGTONE_SELECTION_ONLY = 3;
-
-    /**
-     * How to treat values in {@link #fromUri}.
-     * @hide
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = "FROM_URI_", value = {
-            FROM_URI_RINGTONE_SELECTION_OR_SOUND,
-            FROM_URI_RINGTONE_SELECTION_OR_VIBRATION,
-            FROM_URI_RINGTONE_SELECTION_ONLY
-    })
-    public @interface FromUriBehavior {}
-
-    private static final String BASE_RINGTONE_URI = "content://media/ringtone";
-    /**
-     * String representation of a RingtoneSelection Uri that says to use defaults (equivalent
-     * to {@code new RingtoneSelection.Builder().build()}).
-     */
-    public static final String DEFAULT_SELECTION_URI_STRING = BASE_RINGTONE_URI;
-
-    private static final String MEDIA_URI_RINGTONE_PATH = "/ringtone";
-
-    /* Query param keys. */
-    private static final String URI_PARAM_SOUND_URI = "su";
-    private static final String URI_PARAM_SOUND_SOURCE = "ss";
-    private static final String URI_PARAM_VIBRATION_URI = "vu";
-    private static final String URI_PARAM_VIBRATION_SOURCE = "vs";
-
-    /* Common param values */
-    private static final String SOURCE_OFF_STRING = "off";
-    private static final String SOURCE_SYSTEM_DEFAULT_STRING = "sys";
-
-    /* Vibration source param values. */
-    private static final String VIBRATION_SOURCE_AUDIO_CHANNEL_STRING = "ac";
-    private static final String VIBRATION_SOURCE_APPLICATION_DEFAULT_STRING = "app";
-    private static final String VIBRATION_SOURCE_HAPTIC_GENERATOR_STRING = "hg";
-
-    @Nullable
-    private final Uri mSoundUri;
-    @SoundSource
-    private final int mSoundSource;
-
-    @Nullable
-    private final Uri mVibrationUri;
-    @VibrationSource
-    private final int mVibrationSource;
-
-    private RingtoneSelection(@Nullable Uri soundUri, @SoundSource int soundSource,
-            @Nullable Uri vibrationUri, @VibrationSource int vibrationSource) {
-        // Enforce guarantees on the source values: revert to unspecified if they depend on
-        // something that's not set.
-        //
-        // The non-public "unknown" value can't appear in a getter result, it's just a reserved
-        // "null" value and should be treated the same as an unrecognized value. This can be seen
-        // in Uri parsing. For this and other unrecognized values, we either revert them to the URI
-        // source, if a Uri was included, or the "unspecified" source otherwise. This can be
-        // seen in action in the Uri parsing.
-        //
-        // The "unspecified" source is a public value meaning that there is no specific
-        // behavior indicated, and the defaults and fallbacks should be applied. For example, an
-        // vibration source value of "system default" means to explicitly use the system default
-        // vibration. However, an "unspecified" vibration source will first see if audio coupled
-        // or application-default vibrations are available.
-        mSoundSource = switch (soundSource) {
-            // Supported explicit values that don't have a Uri.
-            case SOUND_SOURCE_OFF, SOUND_SOURCE_UNSPECIFIED, SOUND_SOURCE_SYSTEM_DEFAULT ->
-                    soundSource;
-            // Uri and unknown/unrecognized values: use a Uri if one is present, else revert to
-            // unspecified.
-            default ->
-                soundUri != null ? SOUND_SOURCE_URI : SOUND_SOURCE_UNSPECIFIED;
-        };
-        mVibrationSource = switch (vibrationSource) {
-            // Enforce vibration sources that require a sound Uri.
-            case VIBRATION_SOURCE_AUDIO_CHANNEL, VIBRATION_SOURCE_HAPTIC_GENERATOR ->
-                    soundUri != null ? vibrationSource : VIBRATION_SOURCE_UNSPECIFIED;
-            // Supported explicit values that don't rely on any Uri.
-            case VIBRATION_SOURCE_OFF, VIBRATION_SOURCE_UNSPECIFIED,
-                    VIBRATION_SOURCE_SYSTEM_DEFAULT, VIBRATION_SOURCE_APPLICATION_DEFAULT ->
-                    vibrationSource;
-            // Uri and unknown/unrecognized values: use a Uri if one is present, else revert to
-            // unspecified.
-            default ->
-                    vibrationUri != null ? VIBRATION_SOURCE_URI : VIBRATION_SOURCE_UNSPECIFIED;
-        };
-        // Clear Uri values if they're un-used by the source.
-        mSoundUri = mSoundSource == SOUND_SOURCE_URI ? soundUri : null;
-        mVibrationUri = mVibrationSource == VIBRATION_SOURCE_URI ? vibrationUri : null;
-    }
-
-    /**
-     * Returns the stored sound behavior.
-     */
-    @SoundSource
-    public int getSoundSource() {
-        return mSoundSource;
-    }
-
-    /**
-     * Returns the sound Uri for this selection. This is guaranteed to be non-null if
-     * {@link #getSoundSource} returns {@link #SOUND_SOURCE_URI}.
-     */
-    @Nullable
-    public Uri getSoundUri() {
-        return mSoundUri;
-    }
-
-    /**
-     * Returns the selected vibration behavior.
-     */
-    @VibrationSource
-    public int getVibrationSource() {
-        return mVibrationSource;
-    }
-
-    /**
-     * Returns the vibration Uri for this selection. This is guaranteed to be non-null if
-     * {@link #getVibrationSource} returns {@link #SOUND_SOURCE_URI}.
-     */
-    @Nullable
-    public Uri getVibrationUri() {
-        return mVibrationUri;
-    }
-
-    /**
-     * Converts the ringtone selection into a Uri-form, suitable for storing as a user preference
-     * or returning as a result.
-     */
-    @NonNull
-    public Uri toUri() {
-        Uri.Builder builder = new Uri.Builder()
-                .scheme(ContentResolver.SCHEME_CONTENT)
-                .authority(MediaStore.AUTHORITY)
-                .path(MEDIA_URI_RINGTONE_PATH);
-        if (mSoundUri != null) {
-            builder.appendQueryParameter(URI_PARAM_SOUND_URI, mSoundUri.toString());
-        }
-        // Only off is explicit for sound sources
-        String soundSourceStr = soundSourceToString(mSoundSource);
-        if (soundSourceStr != null) {
-            builder.appendQueryParameter(URI_PARAM_SOUND_SOURCE, soundSourceStr);
-        }
-        if (mVibrationUri != null) {
-            builder.appendQueryParameter(URI_PARAM_VIBRATION_URI, mVibrationUri.toString());
-        }
-        String vibrationSourceStr = vibrationSourceToString(mVibrationSource);
-        if (vibrationSourceStr != null) {
-            builder.appendQueryParameter(URI_PARAM_VIBRATION_SOURCE, vibrationSourceStr);
-        }
-        return builder.build();
-    }
-
-    /**
-     * Returns true if the Uri is an encoded {@link RingtoneSelection}. This method doesn't
-     * validate the parameters of the selection.
-     *
-     * @see #fromUri
-     * @see #toUri
-     */
-    public static boolean isRingtoneSelectionUri(@Nullable Uri uri) {
-        if (uri == null) {
-            return false;
-        }
-        // Any URI content://media/ringtone
-        return ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
-                && MediaStore.AUTHORITY.equals(
-                        ContentProvider.getAuthorityWithoutUserId(uri.getAuthority()))
-                && MEDIA_URI_RINGTONE_PATH.equals(uri.getPath());
-    }
-
-    /**
-     * Strip the specified userId from internal Uris. Non-stripped userIds will typically be
-     * for work profiles referencing system ringtones from the host user.
-     *
-     * This is only for use in RingtoneManager.
-     * @hide
-     */
-    @VisibleForTesting
-    public RingtoneSelection getWithoutUserId(int userIdToStrip) {
-        if (mSoundSource != SOUND_SOURCE_URI && mVibrationSource != VIBRATION_SOURCE_URI) {
-            return this;
-        }
-
-        // Ok if uri is null. We only replace explicit references to the specified (current) userId.
-        int soundUserId = ContentProvider.getUserIdFromUri(mSoundUri, UserHandle.USER_NULL);
-        int vibrationUserId = ContentProvider.getUserIdFromUri(mVibrationUri, UserHandle.USER_NULL);
-        boolean needToChangeSound =
-                soundUserId != UserHandle.USER_NULL && soundUserId == userIdToStrip;
-        boolean needToChangeVibration =
-                vibrationUserId != UserHandle.USER_NULL && vibrationUserId == userIdToStrip;
-
-        // Anything to do?
-        if (!needToChangeSound && !needToChangeVibration) {
-            return this;
-        }
-
-        RingtoneSelection.Builder updated = new Builder(this);
-        // The relevant uris can't be null, because they contain userIdToStrip.
-        if (needToChangeSound) {
-            // mSoundUri is not null, so the result of getUriWithoutUserId won't be null.
-            updated.setSoundSource(ContentProvider.getUriWithoutUserId(mSoundUri));
-        }
-        if (needToChangeVibration) {
-            updated.setVibrationSource(ContentProvider.getUriWithoutUserId(mVibrationUri));
-        }
-        return updated.build();
-    }
-
-    @Override
-    public String toString() {
-        return toUri().toString();
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (o == this) {
-            return true;
-        }
-        if (!(o instanceof RingtoneSelection other)) {
-            return false;
-        }
-        return this.mSoundSource == other.mSoundSource
-                && this.mVibrationSource == other.mVibrationSource
-                && Objects.equals(this.mSoundUri, other.mSoundUri)
-                && Objects.equals(this.mVibrationUri, other.mVibrationUri);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mSoundSource, mVibrationSource, mSoundUri, mVibrationUri);
-    }
-
-    /**
-     * Converts a Uri into a RingtoneSelection.
-     *
-     * <p>Null values, Uris that {@link #isRingtoneSelectionUri(Uri)} returns false (except for
-     * old-style symbolic default Uris {@link RingtoneManager#getDefaultUri}) will be treated
-     * according to the behaviour specified by the {@code unrecognizedValueBehavior} parameter.
-     *
-     * <p>Symbolic default Uris (where {@link RingtoneManager#getDefaultType(Uri)} returns -1,
-     * will map sources to {@link #SOUND_SOURCE_SYSTEM_DEFAULT} and
-     * {@link #VIBRATION_SOURCE_SYSTEM_DEFAULT}.
-     *
-     * @param uri The Uri to convert, potentially null.
-     * @param unrecognizedValueBehavior indicates how to treat values for which
-     *   {@link #isRingtoneSelectionUri(Uri)} returns false (including null).
-     * @return the RingtoneSelection represented by the given uri.
-     */
-    @NonNull
-    public static RingtoneSelection fromUri(@Nullable Uri uri,
-            @FromUriBehavior int unrecognizedValueBehavior) {
-        if (isRingtoneSelectionUri(uri)) {
-            return parseRingtoneSelectionUri(uri);
-        }
-        // Old symbolic default URIs map to the sources suggested by the unrecognized behavior.
-        // It doesn't always map to both sources because the app may have its own default behavior
-        // to apply, so non-primary sources are left as unspecified, which will revert to the
-        // system default in the absence of an app default.
-        boolean isDefaultUri = RingtoneManager.getDefaultType(uri) > 0;
-        RingtoneSelection.Builder builder = new RingtoneSelection.Builder();
-        switch (unrecognizedValueBehavior) {
-            case FROM_URI_RINGTONE_SELECTION_ONLY:
-                if (isDefaultUri) {
-                    builder.setSoundSource(SOUND_SOURCE_SYSTEM_DEFAULT);
-                    builder.setVibrationSource(VIBRATION_SOURCE_SYSTEM_DEFAULT);
-                }
-                // Always return unspecified (defaults) for unrecognized ringtone selection Uris.
-                return builder.build();
-            case FROM_URI_RINGTONE_SELECTION_OR_SOUND:
-                if (uri == null) {
-                    return builder.setSoundSource(SOUND_SOURCE_OFF).build();
-                } else if (isDefaultUri) {
-                    return builder.setSoundSource(SOUND_SOURCE_SYSTEM_DEFAULT).build();
-                } else {
-                    return builder.setSoundSource(uri).build();
-                }
-            case FROM_URI_RINGTONE_SELECTION_OR_VIBRATION:
-                if (uri == null) {
-                    return builder.setVibrationSource(VIBRATION_SOURCE_OFF).build();
-                } else if (isDefaultUri) {
-                    return builder.setVibrationSource(VIBRATION_SOURCE_SYSTEM_DEFAULT).build();
-                } else {
-                    // Unlike sound, there's no legacy settings alias uri for the default.
-                    return builder.setVibrationSource(uri).build();
-                }
-            default:
-                throw new IllegalArgumentException("Unknown behavior parameter: "
-                        + unrecognizedValueBehavior);
-        }
-    }
-
-    /**
-     * Parses the Uri, which has already been checked for {@link #isRingtoneSelectionUri(Uri)}.
-     * As a special case to be more compatible, if the RingtoneSelection has a userId specified in
-     * the authority, then this is pushed down into the sound and vibration Uri, as the selection
-     * itself is agnostic.
-     */
-    @NonNull
-    private static RingtoneSelection parseRingtoneSelectionUri(@NonNull Uri uri) {
-        RingtoneSelection.Builder builder = new RingtoneSelection.Builder();
-        int soundSource = stringToSoundSource(uri.getQueryParameter(URI_PARAM_SOUND_SOURCE));
-        int vibrationSource = stringToVibrationSource(
-                uri.getQueryParameter(URI_PARAM_VIBRATION_SOURCE));
-        Uri soundUri = getParamAsUri(uri, URI_PARAM_SOUND_URI);
-        Uri vibrationUri = getParamAsUri(uri, URI_PARAM_VIBRATION_URI);
-
-        // Add userId if necessary. This won't override an existing one in the sound/vib Uris.
-        int userIdFromAuthority = ContentProvider.getUserIdFromAuthority(
-                uri.getAuthority(), UserHandle.USER_NULL);
-        if (userIdFromAuthority != UserHandle.USER_NULL) {
-            // Won't override existing user id.
-            if (soundUri != null) {
-                soundUri = ContentProvider.maybeAddUserId(soundUri, userIdFromAuthority);
-            }
-            if (vibrationUri != null) {
-                vibrationUri = ContentProvider.maybeAddUserId(vibrationUri, userIdFromAuthority);
-            }
-        }
-
-        // Set sound uri if present, but map system default Uris to the system default source.
-        if (soundUri != null) {
-            if (RingtoneManager.getDefaultType(uri) >= 0) {
-                builder.setSoundSource(SOUND_SOURCE_SYSTEM_DEFAULT);
-            } else {
-                builder.setSoundSource(soundUri);
-            }
-        }
-        if (vibrationUri != null) {
-            builder.setVibrationSource(vibrationUri);
-        }
-
-        // Don't set the source if there's a URI and the source is default, because that will
-        // override the Uri source set above. In effect, we prioritise "explicit" sources over
-        // an implicit Uri source - except for "default", which isn't really explicit.
-        if (soundSource != SOUND_SOURCE_UNSPECIFIED || soundUri == null) {
-            builder.setSoundSource(soundSource);
-        }
-        if (vibrationSource != VIBRATION_SOURCE_UNSPECIFIED || vibrationUri == null) {
-            builder.setVibrationSource(vibrationSource);
-        }
-        return builder.build();
-    }
-
-    @Nullable
-    private static Uri getParamAsUri(@NonNull Uri uri, String param) {
-        // This returns the uri-decoded value, no need to further decode.
-        String value = uri.getQueryParameter(param);
-        if (value == null) {
-            return null;
-        }
-        return Uri.parse(value);
-    }
-
-    /**
-     * Converts the {@link SoundSource} to the uri query param value for it, or null
-     * if the sound source is default, unknown, or implicit (uri).
-     */
-    @Nullable
-    private static String soundSourceToString(@SoundSource int soundSource) {
-        return switch (soundSource) {
-            case SOUND_SOURCE_OFF -> SOURCE_OFF_STRING;
-            case SOUND_SOURCE_SYSTEM_DEFAULT -> SOURCE_SYSTEM_DEFAULT_STRING;
-            default -> null;
-        };
-    }
-
-    /**
-     * Returns the sound source int corresponding to the query string value. Returns
-     * {@link #SOUND_SOURCE_UNKNOWN} if the value isn't recognised, and
-     * {@link #SOUND_SOURCE_UNSPECIFIED} if the value is {@code null} (not in the Uri).
-     */
-    @SoundSource
-    private static int stringToSoundSource(@Nullable String soundSource) {
-        if (soundSource == null) {
-            return SOUND_SOURCE_UNSPECIFIED;
-        }
-        return switch (soundSource) {
-            case SOURCE_OFF_STRING -> SOUND_SOURCE_OFF;
-            case SOURCE_SYSTEM_DEFAULT_STRING -> SOUND_SOURCE_SYSTEM_DEFAULT;
-            default -> SOUND_SOURCE_UNKNOWN;
-        };
-    }
-
-    /**
-     * Converts the {@code vibrationSource} to the uri query param value for it, or null
-     * if the vibration source is default, unknown, or implicit (uri).
-     */
-    @Nullable
-    private static String vibrationSourceToString(@VibrationSource int vibrationSource) {
-        return switch (vibrationSource) {
-            case VIBRATION_SOURCE_OFF -> SOURCE_OFF_STRING;
-            case VIBRATION_SOURCE_AUDIO_CHANNEL -> VIBRATION_SOURCE_AUDIO_CHANNEL_STRING;
-            case VIBRATION_SOURCE_HAPTIC_GENERATOR -> VIBRATION_SOURCE_HAPTIC_GENERATOR_STRING;
-            case VIBRATION_SOURCE_APPLICATION_DEFAULT ->
-                    VIBRATION_SOURCE_APPLICATION_DEFAULT_STRING;
-            case VIBRATION_SOURCE_SYSTEM_DEFAULT -> SOURCE_SYSTEM_DEFAULT_STRING;
-            default -> null;
-        };
-    }
-
-    @VibrationSource
-    private static int stringToVibrationSource(@Nullable String vibrationSource) {
-        if (vibrationSource == null) {
-            return VIBRATION_SOURCE_UNSPECIFIED;
-        }
-        return switch (vibrationSource) {
-            case SOURCE_OFF_STRING -> VIBRATION_SOURCE_OFF;
-            case SOURCE_SYSTEM_DEFAULT_STRING -> VIBRATION_SOURCE_SYSTEM_DEFAULT;
-            case VIBRATION_SOURCE_AUDIO_CHANNEL_STRING -> VIBRATION_SOURCE_AUDIO_CHANNEL;
-            case VIBRATION_SOURCE_HAPTIC_GENERATOR_STRING -> VIBRATION_SOURCE_HAPTIC_GENERATOR;
-            case VIBRATION_SOURCE_APPLICATION_DEFAULT_STRING ->
-                    VIBRATION_SOURCE_APPLICATION_DEFAULT;
-            default -> VIBRATION_SOURCE_UNKNOWN;
-        };
-    }
-
-    /**
-     * Builder for {@link RingtoneSelection}. In general, this builder will be used by interfaces
-     * allowing the user to configure their selection. Once a selection is stored as a Uri, then
-     * the RingtoneSelection can be loaded directly using {@link RingtoneSelection#fromUri}.
-     */
-    @FlaggedApi(Flags.FLAG_HAPTICS_CUSTOMIZATION_ENABLED)
-    public static final class Builder {
-        private Uri mSoundUri;
-        private Uri mVibrationUri;
-        @SoundSource private int mSoundSource = SOUND_SOURCE_UNSPECIFIED;
-        @VibrationSource private int mVibrationSource = VIBRATION_SOURCE_UNSPECIFIED;
-
-        /**
-         * Creates a new {@link RingtoneSelection} builder. A default ringtone selection has its
-         * sound and vibration source unspecified, which means they would fall back to app/system
-         * defaults.
-         */
-        public Builder() {}
-
-        /**
-         * Creates a builder initialized with the given ringtone selection.
-         */
-        public Builder(@NonNull RingtoneSelection selection) {
-            requireNonNull(selection);
-            mSoundSource = selection.getSoundSource();
-            mSoundUri = selection.getSoundUri();
-            mVibrationSource = selection.getVibrationSource();
-            mVibrationUri = selection.getVibrationUri();
-        }
-
-        /**
-         * Sets the desired sound source.
-         *
-         * <p>Values other than {@link #SOUND_SOURCE_URI} will clear any previous sound Uri.
-         * For {@link #SOUND_SOURCE_URI}, the {@link #setSoundSource(Uri)} method should be
-         * used instead, as setting it here will have no effect unless the Uri is also set.
-         */
-        @NonNull
-        public Builder setSoundSource(@SoundSource int soundSource) {
-            mSoundSource = soundSource;
-            if (soundSource != SOUND_SOURCE_URI && soundSource != SOUND_SOURCE_UNKNOWN) {
-                // Note that this means the configuration of "silent sound, but use haptic
-                // generator" is currently not supported. Future support could be added by either
-                // using the vibration uri in that case, or by having a special
-                // "setSoundUriForVibrationOnly(Uri)" method that sets sound source to off but
-                // also retains the Uri.
-                mSoundUri = null;
-            }
-            return this;
-        }
-
-        /**
-         * Sets the sound source to {@link #SOUND_SOURCE_URI}, and the sound Uri to the
-         * specified {@link Uri}.
-         */
-        @NonNull
-        public Builder setSoundSource(@NonNull Uri soundUri) {
-            // getCanonicalUri shouldn't return null. If it somehow did, then the
-            // RingtoneSelection constructor will revert this to unspecified.
-            mSoundUri = requireNonNull(soundUri).getCanonicalUri();
-            mSoundSource = SOUND_SOURCE_URI;
-            return this;
-        }
-
-        /**
-         * Sets the vibration source to the specified value.
-         *
-         * <p>Values other than {@link #VIBRATION_SOURCE_URI} will clear any previous vibration Uri.
-         * For {@link #VIBRATION_SOURCE_URI}, the {@link #setVibrationSource(Uri)} method should be
-         * used instead, as setting it here will have no effect unless the Uri is also set.
-         */
-        @NonNull
-        public Builder setVibrationSource(@VibrationSource int vibrationSource) {
-            mVibrationSource = vibrationSource;
-            if (vibrationSource != VIBRATION_SOURCE_URI
-                    && vibrationSource != VIBRATION_SOURCE_UNKNOWN) {
-                mVibrationUri = null;
-            }
-            return this;
-        }
-
-        /**
-         * Sets the vibration source to {@link #VIBRATION_SOURCE_URI}, and the vibration Uri to the
-         * specified {@link Uri}.
-         */
-        @NonNull
-        public Builder setVibrationSource(@NonNull Uri vibrationUri) {
-            // getCanonicalUri shouldn't return null. If it somehow did, then the
-            // RingtoneSelection constructor will revert this to unspecified.
-            mVibrationUri = requireNonNull(vibrationUri).getCanonicalUri();
-            mVibrationSource = VIBRATION_SOURCE_URI;
-            return this;
-        }
-
-        /**
-         * Returns the ringtone Uri that was configured.
-         */
-        @NonNull
-        public RingtoneSelection build() {
-            return new RingtoneSelection(mSoundUri, mSoundSource, mVibrationUri, mVibrationSource);
-        }
-    }
-}
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index 48b4766..a53a8ce 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -27,7 +27,9 @@
 import android.media.AudioDeviceInfo;
 import android.media.AudioFormat;
 import android.media.AudioSystem;
+import android.os.Binder;
 import android.os.Build;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -52,6 +54,8 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private int mMixType = MIX_TYPE_INVALID;
 
+    private final IBinder mToken;
+
     // written by AudioPolicy
     int mMixState = MIX_STATE_DISABLED;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -68,7 +72,7 @@
      */
     private AudioMix(@NonNull AudioMixingRule rule, @NonNull AudioFormat format,
             int routeFlags, int callbackFlags,
-            int deviceType, @Nullable String deviceAddress) {
+            int deviceType, @Nullable String deviceAddress, IBinder token) {
         mRule = Objects.requireNonNull(rule);
         mFormat = Objects.requireNonNull(format);
         mRouteFlags = routeFlags;
@@ -76,6 +80,7 @@
         mCallbackFlags = callbackFlags;
         mDeviceSystemType = deviceType;
         mDeviceAddress = (deviceAddress == null) ? new String("") : deviceAddress;
+        mToken = token;
     }
 
     // CALLBACK_FLAG_* values: keep in sync with AudioMix::kCbFlag* values defined
@@ -270,15 +275,22 @@
         if (o == null || getClass() != o.getClass()) return false;
 
         final AudioMix that = (AudioMix) o;
+        boolean tokenMatch = android.media.audiopolicy.Flags.audioMixOwnership()
+                ? Objects.equals(this.mToken, that.mToken)
+                : true;
         return Objects.equals(this.mRouteFlags, that.mRouteFlags)
             && Objects.equals(this.mRule, that.mRule)
             && Objects.equals(this.mMixType, that.mMixType)
-            && Objects.equals(this.mFormat, that.mFormat);
+            && Objects.equals(this.mFormat, that.mFormat)
+            && tokenMatch;
     }
 
     /** @hide */
     @Override
     public int hashCode() {
+        if (android.media.audiopolicy.Flags.audioMixOwnership()) {
+            return Objects.hash(mRouteFlags, mRule, mMixType, mFormat, mToken);
+        }
         return Objects.hash(mRouteFlags, mRule, mMixType, mFormat);
     }
 
@@ -298,6 +310,7 @@
         dest.writeString8(mDeviceAddress);
         mFormat.writeToParcel(dest, flags);
         mRule.writeToParcel(dest, flags);
+        dest.writeStrongBinder(mToken);
     }
 
     public static final @NonNull Parcelable.Creator<AudioMix> CREATOR = new Parcelable.Creator<>() {
@@ -317,6 +330,7 @@
             mixBuilder.setDevice(p.readInt(), p.readString8());
             mixBuilder.setFormat(AudioFormat.CREATOR.createFromParcel(p));
             mixBuilder.setMixingRule(AudioMixingRule.CREATOR.createFromParcel(p));
+            mixBuilder.setToken(p.readStrongBinder());
             return mixBuilder.build();
         }
 
@@ -339,6 +353,7 @@
         private AudioFormat mFormat = null;
         private int mRouteFlags = 0;
         private int mCallbackFlags = 0;
+        private IBinder mToken = null;
         // an AudioSystem.DEVICE_* value, not AudioDeviceInfo.TYPE_*
         private int mDeviceSystemType = AudioSystem.DEVICE_NONE;
         private String mDeviceAddress = null;
@@ -380,6 +395,15 @@
 
         /**
          * @hide
+         * Only used by AudioMix internally.
+         */
+        Builder setToken(IBinder token) {
+            mToken = token;
+            return this;
+        }
+
+        /**
+         * @hide
          * Only used by AudioPolicyConfig, not a public API.
          * @param callbackFlags which callbacks are called from native
          * @return the same Builder instance.
@@ -540,8 +564,13 @@
                     throw new IllegalArgumentException(error);
                 }
             }
+
+            if (mToken == null) {
+                mToken = new Binder();
+            }
+
             return new AudioMix(mRule, mFormat, mRouteFlags, mCallbackFlags, mDeviceSystemType,
-                    mDeviceAddress);
+                    mDeviceAddress, mToken);
         }
 
         private int getLoopbackDeviceSystemTypeForAudioMixingRule(AudioMixingRule rule) {
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index bbe461c..508c0a2b 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -337,7 +337,9 @@
 
     /**
      * Update the current configuration of the set of audio mixes by adding new ones, while
-     * keeping the policy registered.
+     * keeping the policy registered. If any of the provided audio mixes is invalid then none of
+     * the passed mixes will be registered.
+     *
      * This method can only be called on a registered policy.
      * @param mixes the list of {@link AudioMix} to add
      * @return {@link AudioManager#SUCCESS} if the change was successful, {@link AudioManager#ERROR}
@@ -375,12 +377,15 @@
     }
 
     /**
-     * Update the current configuration of the set of audio mixes by removing some, while
-     * keeping the policy registered.
-     * This method can only be called on a registered policy.
+     * Update the current configuration of the set of audio mixes for this audio policy by
+     * removing some, while keeping the policy registered. Will unregister all provided audio
+     * mixes, if possible.
+     *
+     * This method can only be called on a registered policy and only affects this current policy.
      * @param mixes the list of {@link AudioMix} to remove
      * @return {@link AudioManager#SUCCESS} if the change was successful, {@link AudioManager#ERROR}
-     *    otherwise.
+     *    otherwise. If only some of the provided audio mixes were detached but any one mix
+     *    failed to be detached, this method returns {@link AudioManager#ERROR}.
      */
     public int detachMixes(@NonNull List<AudioMix> mixes) {
         if (mixes == null) {
@@ -394,7 +399,6 @@
             for (AudioMix mix : mixes) {
                 if (mix == null) {
                     throw new IllegalArgumentException("Illegal null AudioMix in detachMixes");
-                    // TODO also check mix is currently contained in list of mixes
                 } else {
                     zeMixes.add(mix);
                 }
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index d277c7d..5e7c7c4 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -228,7 +228,7 @@
         }
     }
 
-    private void setMixRegistration(@NonNull final AudioMix mix) {
+    protected void setMixRegistration(@NonNull final AudioMix mix) {
         if (!mRegistrationId.isEmpty()) {
             if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) ==
                     AudioMix.ROUTE_FLAG_LOOP_BACK) {
@@ -246,7 +246,9 @@
     @GuardedBy("mMixes")
     protected void add(@NonNull ArrayList<AudioMix> mixes) {
         for (AudioMix mix : mixes) {
-            setMixRegistration(mix);
+            if (mix.getRegistration() == null || mix.getRegistration().isEmpty()) {
+                setMixRegistration(mix);
+            }
             mMixes.add(mix);
         }
     }
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 6cf9c6f..bf39425 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -109,5 +109,5 @@
     name: "enable_null_session_in_media_browser_service"
     namespace: "media_solutions"
     description: "Enables apps owning a MediaBrowserService to disconnect all connected browsers."
-    bug: "263520343"
+    bug: "185136506"
 }
diff --git a/media/java/android/media/metrics/PlaybackSession.java b/media/java/android/media/metrics/PlaybackSession.java
index f8dd756..6223acf 100644
--- a/media/java/android/media/metrics/PlaybackSession.java
+++ b/media/java/android/media/metrics/PlaybackSession.java
@@ -24,7 +24,10 @@
 import java.util.Objects;
 
 /**
- * An instances of this class represents a session of media playback.
+ * An instance of this class represents a session of media playback used to report playback
+ * metrics and events.
+ *
+ * Create a new instance using {@link MediaMetricsManager#createPlaybackSession}.
  */
 public final class PlaybackSession implements AutoCloseable {
     private final @NonNull String mId;
@@ -80,6 +83,21 @@
         mManager.reportTrackChangeEvent(mId, event);
     }
 
+    /**
+     * A session ID is used to identify a unique playback and to tie together lower-level
+     * playback components.
+     *
+     * Associate this session with a {@link MediaCodec} by passing the ID into
+     * {@link MediaFormat} through {@link MediaFormat#LOG_SESSION_ID} when
+     * creating the {@link MediaCodec}.
+     *
+     * Associate this session with an {@link AudioTrack} by calling
+     * {@link AudioTrack#setLogSessionId}.
+     *
+     * Associate this session with {@link MediaDrm} and {@link MediaCrypto} by calling
+     * {@link MediaDrm#getPlaybackComponent} and then calling
+     * {@link PlaybackComponent#setLogSessionId}.
+     */
     public @NonNull LogSessionId getSessionId() {
         return mLogSessionId;
     }
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index 76664a6..ddcb25b 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -61,7 +61,6 @@
 @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
 @SystemService(Context.TV_AD_SERVICE)
 public final class TvAdManager {
-    // TODO: implement more methods and unhide APIs.
     private static final String TAG = "TvAdManager";
 
     /**
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index 2fac8ce..dd2a534 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -393,16 +393,12 @@
     }
 
     /**
-     * Sets a listener to be invoked when an input event is not handled
-     * by the TV AD service.
+     * Sets a listener to be invoked when an input event is not handled by the TV AD service.
      *
      * @param listener The callback to be invoked when the unhandled input event is received.
      */
-    public void setOnUnhandledInputEventListener(
-            @NonNull @CallbackExecutor Executor executor,
-            @NonNull OnUnhandledInputEventListener listener) {
+    public void setOnUnhandledInputEventListener(@NonNull OnUnhandledInputEventListener listener) {
         mOnUnhandledInputEventListener = listener;
-        // TODO: handle CallbackExecutor
     }
 
     /**
@@ -441,6 +437,9 @@
     /**
      * Prepares the AD service of corresponding {@link TvAdService}.
      *
+     * <p>This should be called before calling {@link #startAdService()}. Otherwise,
+     * {@link #startAdService()} is a no-op.
+     *
      * @param serviceId the AD service ID, which can be found in TvAdServiceInfo#getId().
      */
     public void prepareAdService(@NonNull String serviceId, @NonNull String type) {
@@ -455,6 +454,9 @@
 
     /**
      * Starts the AD service.
+     *
+     * <p>This should be called after calling {@link #prepareAdService(String, String)}. Otherwise,
+     * it's a no-op.
      */
     public void startAdService() {
         if (DEBUG) {
@@ -467,6 +469,8 @@
 
     /**
      * Stops the AD service.
+     *
+     * <p>It's a no-op if the service is not started.
      */
     public void stopAdService() {
         if (DEBUG) {
@@ -659,6 +663,11 @@
         }
     }
 
+    /** @hide */
+    public TvAdManager.Session getAdSession() {
+        return mSession;
+    }
+
     private class MySessionCallback extends TvAdManager.SessionCallback {
         final String mServiceId;
 
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 382e65d..371e3d2 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -16,11 +16,13 @@
 
 //#define LOG_NDEBUG 0
 #define LOG_TAG "ImageReader_JNI"
+#define ATRACE_TAG ATRACE_TAG_CAMERA
 #include "android_media_Utils.h"
 #include <cutils/atomic.h>
 #include <utils/Log.h>
 #include <utils/misc.h>
 #include <utils/List.h>
+#include <utils/Trace.h>
 #include <utils/String8.h>
 
 #include <cstdio>
@@ -223,6 +225,7 @@
 
 void JNIImageReaderContext::onFrameAvailable(const BufferItem& /*item*/)
 {
+    ATRACE_CALL();
     ALOGV("%s: frame available", __FUNCTION__);
     bool needsDetach = false;
     JNIEnv* env = getJNIEnv(&needsDetach);
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 8396005..0fc80dd 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -2895,6 +2895,10 @@
         jint offset,
         jint size,
         std::shared_ptr<C2Buffer> *buffer) {
+    if ((offset + size) > context->capacity()) {
+        ALOGW("extractBufferFromContext: offset + size provided exceed capacity");
+        return;
+    }
     *buffer = context->toC2Buffer(offset, size);
     if (*buffer == nullptr) {
         if (!context->mMemory) {
@@ -2995,18 +2999,15 @@
                     "MediaCodec.LinearBlock#obtain method to obtain a compatible buffer.");
             return;
         }
-        sp<CryptoInfosWrapper> cryptoInfos = new CryptoInfosWrapper{decltype(cryptoInfos->value)()};
-        jint sampleSize = 0;
+        sp<CryptoInfosWrapper> cryptoInfos = nullptr;
+        jint sampleSize = totalSize;
         if (cryptoInfoArray != nullptr) {
+            cryptoInfos = new CryptoInfosWrapper{decltype(cryptoInfos->value)()};
             extractCryptoInfosFromObjectArray(env,
                     &sampleSize,
                     &cryptoInfos->value,
                     cryptoInfoArray,
                     &errorDetailMsg);
-        } else {
-            sampleSize = totalSize;
-            std::unique_ptr<CodecCryptoInfo> cryptoInfo{new MediaCodecCryptoInfo(totalSize)};
-            cryptoInfos->value.push_back(std::move(cryptoInfo));
         }
         if (env->ExceptionCheck()) {
             // Creation of cryptoInfo failed. Let the exception bubble up.
diff --git a/media/lib/tvremote/tests/Android.bp b/media/lib/tvremote/tests/Android.bp
index f02cfc3..280c515 100644
--- a/media/lib/tvremote/tests/Android.bp
+++ b/media/lib/tvremote/tests/Android.bp
@@ -17,6 +17,7 @@
     ],
     static_libs: [
         "mockito-target-minus-junit4",
+        "androidx.test.rules",
     ],
     platform_apis: true,
     certificate: "platform",
diff --git a/media/lib/tvremote/tests/src/com/android/media/tv/remoteprovider/TvRemoteProviderTest.java b/media/lib/tvremote/tests/src/com/android/media/tv/remoteprovider/TvRemoteProviderTest.java
index e6e3939..3b38a462 100644
--- a/media/lib/tvremote/tests/src/com/android/media/tv/remoteprovider/TvRemoteProviderTest.java
+++ b/media/lib/tvremote/tests/src/com/android/media/tv/remoteprovider/TvRemoteProviderTest.java
@@ -29,7 +29,8 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import java.util.ArrayList;
 
diff --git a/media/mca/tests/Android.bp b/media/mca/tests/Android.bp
index f02b4c0..04f083d 100644
--- a/media/mca/tests/Android.bp
+++ b/media/mca/tests/Android.bp
@@ -13,7 +13,10 @@
         "android.test.runner",
         "android.test.base",
     ],
-    static_libs: ["junit"],
+    static_libs: [
+        "junit",
+        "androidx.test.rules",
+    ],
     // Include all test java files.
     srcs: ["src/**/*.java"],
     platform_apis: true,
diff --git a/media/mca/tests/src/android/camera/mediaeffects/tests/functional/EffectsVideoCapture.java b/media/mca/tests/src/android/camera/mediaeffects/tests/functional/EffectsVideoCapture.java
index 474b00f..44f98050 100644
--- a/media/mca/tests/src/android/camera/mediaeffects/tests/functional/EffectsVideoCapture.java
+++ b/media/mca/tests/src/android/camera/mediaeffects/tests/functional/EffectsVideoCapture.java
@@ -16,18 +16,19 @@
 
 package android.camera.mediaeffects.tests.functional;
 
-import android.media.filterfw.samples.CameraEffectsRecordingSample;
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.content.Intent;
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.view.KeyEvent;
-import android.util.Log;
-import android.content.Intent;
-import android.os.Environment;
 import android.media.MediaMetadataRetriever;
+import android.media.filterfw.samples.CameraEffectsRecordingSample;
 import android.net.Uri;
+import android.os.Environment;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import androidx.test.filters.LargeTest;
+
 import java.io.File;
 
 public class EffectsVideoCapture extends ActivityInstrumentationTestCase2
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java
index 9b643ad..022f1da 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java
@@ -16,9 +16,6 @@
 
 package com.android.mediaframeworktest.functional;
 
-import com.android.mediaframeworktest.MediaFrameworkTest;
-import com.android.mediaframeworktest.MediaNames;
-
 import android.content.Context;
 import android.hardware.Camera;
 import android.hardware.Camera.PictureCallback;
@@ -27,10 +24,14 @@
 import android.os.ConditionVariable;
 import android.os.Looper;
 import android.test.ActivityInstrumentationTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 import android.view.SurfaceHolder;
 
+import androidx.test.filters.LargeTest;
+
+import com.android.mediaframeworktest.MediaFrameworkTest;
+import com.android.mediaframeworktest.MediaNames;
+
 import java.io.*;
 
 /**
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaMetadataTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaMetadataTest.java
index da106be..8f32e91 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaMetadataTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaMetadataTest.java
@@ -18,9 +18,10 @@
 
 import android.media.MediaMetadataRetriever;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
 
+import androidx.test.filters.MediumTest;
+
 import com.android.mediaframeworktest.MediaNames;
 import com.android.mediaframeworktest.MediaProfileReader;
 
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaMimeTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaMimeTest.java
index 728e68d..83793ee 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaMimeTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaMimeTest.java
@@ -16,21 +16,20 @@
 
 package com.android.mediaframeworktest.functional;
 
-import java.io.File;
-
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
 import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.Suppress;
-import android.util.Log;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.Suppress;
+
 import com.android.mediaframeworktest.MediaFrameworkTest;
 
+import java.io.File;
+
 /*
  * System tests for the handling of mime type in the media framework.
  *
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPlayerInvokeTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPlayerInvokeTest.java
index 55a1545..d7d1875 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPlayerInvokeTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPlayerInvokeTest.java
@@ -16,17 +16,14 @@
 
 package com.android.mediaframeworktest.functional;
 
-import com.android.mediaframeworktest.MediaFrameworkTest;
-import com.android.mediaframeworktest.MediaNames;
-
-import android.test.ActivityInstrumentationTestCase2;
-import android.util.Log;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.Suppress;
-
 import android.media.MediaPlayer;
 import android.os.Parcel;
+import android.test.ActivityInstrumentationTestCase2;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.Suppress;
+
+import com.android.mediaframeworktest.MediaFrameworkTest;
 
 import java.util.Calendar;
 import java.util.Random;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaAudioEffectTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaAudioEffectTest.java
index ab78714..6b8cbe9 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaAudioEffectTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaAudioEffectTest.java
@@ -16,28 +16,26 @@
 
 package com.android.mediaframeworktest.functional.audio;
 
-import com.android.mediaframeworktest.MediaFrameworkTest;
-import com.android.mediaframeworktest.MediaNames;
-import android.content.res.AssetFileDescriptor;
-import android.media.audiofx.AudioEffect;
 import android.media.AudioFormat;
 import android.media.AudioManager;
-import android.media.AudioTrack;
 import android.media.AudioRecord;
-import android.media.audiofx.EnvironmentalReverb;
-import android.media.audiofx.Equalizer;
+import android.media.AudioTrack;
 import android.media.MediaPlayer;
 import android.media.MediaRecorder;
-
+import android.media.audiofx.AudioEffect;
+import android.media.audiofx.EnvironmentalReverb;
+import android.media.audiofx.Equalizer;
 import android.os.Looper;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.Suppress;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
 
-import java.nio.ByteOrder;
+import androidx.test.filters.LargeTest;
+
+import com.android.mediaframeworktest.MediaFrameworkTest;
+import com.android.mediaframeworktest.MediaNames;
+
 import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.util.UUID;
 
 /**
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaAudioManagerTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaAudioManagerTest.java
index 3a332c6..25288e1 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaAudioManagerTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaAudioManagerTest.java
@@ -16,16 +16,16 @@
 
 package com.android.mediaframeworktest.functional.audio;
 
-import com.android.mediaframeworktest.MediaFrameworkTest;
 import android.content.Context;
 import android.media.AudioManager;
-import android.media.MediaPlayer;
 import android.media.AudioManager.OnAudioFocusChangeListener;
 import android.os.Looper;
 import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.util.Log;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.MediumTest;
+
+import com.android.mediaframeworktest.MediaFrameworkTest;
 
 /**
  * Junit / Instrumentation test case for the media AudioManager api
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaAudioTrackTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaAudioTrackTest.java
index eac5c28..107b51d 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaAudioTrackTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaAudioTrackTest.java
@@ -16,17 +16,15 @@
 
 package com.android.mediaframeworktest.functional.audio;
 
-import com.android.mediaframeworktest.MediaFrameworkTest;
-import com.android.mediaframeworktest.MediaNames;
-
 import android.media.AudioFormat;
 import android.media.AudioManager;
 import android.media.AudioTrack;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.Suppress;
+
+import androidx.test.filters.LargeTest;
+
+import com.android.mediaframeworktest.MediaFrameworkTest;
 
 /**
  * Junit / Instrumentation test case for the media AudioTrack api
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaBassBoostTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaBassBoostTest.java
index 1fa5c0d..c2dd246 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaBassBoostTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaBassBoostTest.java
@@ -16,27 +16,13 @@
 
 package com.android.mediaframeworktest.functional.audio;
 
-import com.android.mediaframeworktest.MediaFrameworkTest;
-import com.android.mediaframeworktest.MediaNames;
-import com.android.mediaframeworktest.functional.EnergyProbe;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.media.audiofx.AudioEffect;
-import android.media.AudioManager;
 import android.media.audiofx.BassBoost;
-import android.media.audiofx.Visualizer;
-import android.media.MediaPlayer;
-
-import android.os.Looper;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.Suppress;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
 
-import java.nio.ByteOrder;
-import java.nio.ByteBuffer;
-import java.util.UUID;
+import androidx.test.filters.LargeTest;
+
+import com.android.mediaframeworktest.MediaFrameworkTest;
 
 /**
  * Junit / Instrumentation test case for the media AudioTrack api
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaEnvReverbTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaEnvReverbTest.java
index e788c17..4bbd913 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaEnvReverbTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaEnvReverbTest.java
@@ -16,26 +16,20 @@
 
 package com.android.mediaframeworktest.functional.audio;
 
-import com.android.mediaframeworktest.MediaFrameworkTest;
-import com.android.mediaframeworktest.MediaNames;
-import com.android.mediaframeworktest.functional.EnergyProbe;
 import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.media.audiofx.AudioEffect;
 import android.media.AudioManager;
-import android.media.audiofx.EnvironmentalReverb;
-import android.media.audiofx.Visualizer;
 import android.media.MediaPlayer;
-
-import android.os.Looper;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.Suppress;
+import android.media.audiofx.AudioEffect;
+import android.media.audiofx.EnvironmentalReverb;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
 
-import java.nio.ByteOrder;
-import java.nio.ByteBuffer;
+import androidx.test.filters.LargeTest;
+
+import com.android.mediaframeworktest.MediaFrameworkTest;
+import com.android.mediaframeworktest.MediaNames;
+import com.android.mediaframeworktest.functional.EnergyProbe;
+
 import java.util.UUID;
 
 /**
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaEqualizerTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaEqualizerTest.java
index da9089d..a43f761 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaEqualizerTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaEqualizerTest.java
@@ -16,27 +16,13 @@
 
 package com.android.mediaframeworktest.functional.audio;
 
-import com.android.mediaframeworktest.MediaFrameworkTest;
-import com.android.mediaframeworktest.MediaNames;
-import com.android.mediaframeworktest.functional.EnergyProbe;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.media.audiofx.AudioEffect;
-import android.media.AudioManager;
 import android.media.audiofx.Equalizer;
-import android.media.audiofx.Visualizer;
-import android.media.MediaPlayer;
-
-import android.os.Looper;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.Suppress;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
 
-import java.nio.ByteOrder;
-import java.nio.ByteBuffer;
-import java.util.UUID;
+import androidx.test.filters.LargeTest;
+
+import com.android.mediaframeworktest.MediaFrameworkTest;
 
 /**
  * Junit / Instrumentation test case for the media AudioTrack api
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaPresetReverbTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaPresetReverbTest.java
index bc9c48d..9d3cf79 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaPresetReverbTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaPresetReverbTest.java
@@ -16,26 +16,20 @@
 
 package com.android.mediaframeworktest.functional.audio;
 
-import com.android.mediaframeworktest.MediaFrameworkTest;
-import com.android.mediaframeworktest.MediaNames;
-import com.android.mediaframeworktest.functional.EnergyProbe;
 import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.media.audiofx.AudioEffect;
 import android.media.AudioManager;
-import android.media.audiofx.PresetReverb;
-import android.media.audiofx.Visualizer;
 import android.media.MediaPlayer;
-
-import android.os.Looper;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.Suppress;
+import android.media.audiofx.AudioEffect;
+import android.media.audiofx.PresetReverb;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
 
-import java.nio.ByteOrder;
-import java.nio.ByteBuffer;
+import androidx.test.filters.LargeTest;
+
+import com.android.mediaframeworktest.MediaFrameworkTest;
+import com.android.mediaframeworktest.MediaNames;
+import com.android.mediaframeworktest.functional.EnergyProbe;
+
 import java.util.UUID;
 
 /**
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaVirtualizerTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaVirtualizerTest.java
index 122545f..144656c 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaVirtualizerTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaVirtualizerTest.java
@@ -16,27 +16,13 @@
 
 package com.android.mediaframeworktest.functional.audio;
 
-import com.android.mediaframeworktest.MediaFrameworkTest;
-import com.android.mediaframeworktest.MediaNames;
-import com.android.mediaframeworktest.functional.EnergyProbe;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.media.audiofx.AudioEffect;
-import android.media.AudioManager;
 import android.media.audiofx.Virtualizer;
-import android.media.audiofx.Visualizer;
-import android.media.MediaPlayer;
-
-import android.os.Looper;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.Suppress;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
 
-import java.nio.ByteOrder;
-import java.nio.ByteBuffer;
-import java.util.UUID;
+import androidx.test.filters.LargeTest;
+
+import com.android.mediaframeworktest.MediaFrameworkTest;
 
 /**
  * Junit / Instrumentation test case for the media AudioTrack api
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaVisualizerTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaVisualizerTest.java
index abf85d7..7644954 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaVisualizerTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaVisualizerTest.java
@@ -16,24 +16,20 @@
 
 package com.android.mediaframeworktest.functional.audio;
 
-import com.android.mediaframeworktest.MediaFrameworkTest;
-import com.android.mediaframeworktest.MediaNames;
 import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.media.audiofx.AudioEffect;
 import android.media.AudioManager;
-import android.media.audiofx.Visualizer;
 import android.media.MediaPlayer;
-
+import android.media.audiofx.AudioEffect;
+import android.media.audiofx.Visualizer;
 import android.os.Looper;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.Suppress;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
 
-import java.nio.ByteOrder;
-import java.nio.ByteBuffer;
+import androidx.test.filters.LargeTest;
+
+import com.android.mediaframeworktest.MediaFrameworkTest;
+import com.android.mediaframeworktest.MediaNames;
+
 import java.util.UUID;
 
 /**
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/SimTonesTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/SimTonesTest.java
index aaf992c..ae42074 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/SimTonesTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/SimTonesTest.java
@@ -17,12 +17,13 @@
 package com.android.mediaframeworktest.functional.audio;
 
 // import android.content.Resources;
-import com.android.mediaframeworktest.MediaFrameworkTest;
-import com.android.mediaframeworktest.functional.TonesAutoTest;
-
 import android.content.Context;
 import android.test.ActivityInstrumentationTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
+
+import com.android.mediaframeworktest.MediaFrameworkTest;
+import com.android.mediaframeworktest.functional.TonesAutoTest;
 
 /**
  * Junit / Instrumentation test case for the SIM tone generator
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/camera/CameraFunctionalTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/camera/CameraFunctionalTest.java
index 9c08d48..bf5e816 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/camera/CameraFunctionalTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/camera/CameraFunctionalTest.java
@@ -16,22 +16,23 @@
 
 package com.android.mediaframeworktest.functional.camera;
 
-import com.android.mediaframeworktest.MediaFrameworkTest;
-import com.android.mediaframeworktest.helpers.CameraTestHelper;
-
-import java.io.Writer;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-import java.util.List;
-
 import android.hardware.Camera.Parameters;
 import android.os.Handler;
 import android.os.Looper;
 import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 import android.view.SurfaceHolder;
 
+import androidx.test.filters.LargeTest;
+
+import com.android.mediaframeworktest.MediaFrameworkTest;
+import com.android.mediaframeworktest.helpers.CameraTestHelper;
+
+import java.io.Writer;
+import java.util.List;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
 /**
  * Junit / Instrumentation test case for the following camera APIs:
  * - flash
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/camera/CameraPairwiseTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/camera/CameraPairwiseTest.java
index f9d4964..45c95e0 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/camera/CameraPairwiseTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/camera/CameraPairwiseTest.java
@@ -20,17 +20,18 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 import android.view.SurfaceHolder;
 
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-import java.util.List;
+import androidx.test.filters.LargeTest;
 
 import com.android.mediaframeworktest.MediaFrameworkTest;
 import com.android.mediaframeworktest.helpers.CameraTestHelper;
 
+import java.util.List;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
 /**
  * Junit / Instrumentation test case for camera API pairwise testing
  * Settings tested against: flash mode, exposure compensation, white balance,
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediaplayback/MediaPlayerApiTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediaplayback/MediaPlayerApiTest.java
index 7be2707..8739820 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediaplayback/MediaPlayerApiTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediaplayback/MediaPlayerApiTest.java
@@ -16,20 +16,18 @@
 
 package com.android.mediaframeworktest.functional.mediaplayback;
 
+import android.content.Context;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.MediumTest;
+
 import com.android.mediaframeworktest.MediaFrameworkTest;
 import com.android.mediaframeworktest.MediaNames;
 import com.android.mediaframeworktest.MediaProfileReader;
 import com.android.mediaframeworktest.functional.CodecTest;
 
-import android.content.Context;
-import android.test.ActivityInstrumentationTestCase2;
-import android.util.Log;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.Suppress;
-
-import java.io.File;
-
 /**
  * Junit / Instrumentation test case for the media player api
  */
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediarecorder/MediaRecorderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediarecorder/MediaRecorderTest.java
index 35540e3..3c456fb 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediarecorder/MediaRecorderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediarecorder/MediaRecorderTest.java
@@ -16,37 +16,32 @@
 
 package com.android.mediaframeworktest.functional.mediarecorder;
 
-import com.android.mediaframeworktest.MediaFrameworkTest;
-import com.android.mediaframeworktest.MediaNames;
-
-import java.io.*;
-
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
-import android.graphics.Typeface;
 import android.hardware.Camera;
+import android.media.EncoderCapabilities.AudioEncoderCap;
+import android.media.EncoderCapabilities.VideoEncoderCap;
 import android.media.MediaCodec;
 import android.media.MediaMetadataRetriever;
 import android.media.MediaPlayer;
 import android.media.MediaRecorder;
-import android.media.EncoderCapabilities;
-import android.media.EncoderCapabilities.VideoEncoderCap;
-import android.media.EncoderCapabilities.AudioEncoderCap;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
 import android.view.Surface;
 import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import com.android.mediaframeworktest.MediaProfileReader;
+
+import androidx.test.filters.LargeTest;
+
+import com.android.mediaframeworktest.MediaFrameworkTest;
 import com.android.mediaframeworktest.MediaFrameworkTestRunner;
+import com.android.mediaframeworktest.MediaNames;
+import com.android.mediaframeworktest.MediaProfileReader;
 
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.Suppress;
+import java.io.*;
 import java.util.List;
 
-
 /**
  * Junit / Instrumentation test case for the media recorder api
  */
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index cc7a7d5..e89becd 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -31,9 +31,10 @@
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
+import androidx.test.filters.SmallTest;
+
 /**
  * <p>
  * Junit / Instrumentation test case for the camera2 api
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index 9d09dcc..eaa5a85 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -46,10 +46,11 @@
 import android.os.ServiceSpecificException;
 import android.os.SystemClock;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 import android.view.Surface;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.mediaframeworktest.MediaFrameworkIntegrationTestRunner;
 
 import org.mockito.ArgumentCaptor;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
index 8c05725..0154d6a 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
@@ -16,36 +16,33 @@
 
 package com.android.mediaframeworktest.performance;
 
-import com.android.mediaframeworktest.MediaFrameworkTest;
-import com.android.mediaframeworktest.MediaFrameworkPerfTestRunner;
-import com.android.mediaframeworktest.MediaNames;
-import com.android.mediaframeworktest.MediaTestUtil;
-
-import android.database.sqlite.SQLiteDatabase;
 import android.hardware.Camera;
 import android.hardware.Camera.PreviewCallback;
 import android.media.CamcorderProfile;
 import android.media.MediaPlayer;
 import android.media.MediaRecorder;
-import android.media.EncoderCapabilities.VideoEncoderCap;
 import android.os.ConditionVariable;
 import android.os.Looper;
 import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 import android.view.SurfaceHolder;
 
-import java.util.List;
+import androidx.test.filters.LargeTest;
+
+import com.android.mediaframeworktest.MediaFrameworkPerfTestRunner;
+import com.android.mediaframeworktest.MediaFrameworkTest;
+import com.android.mediaframeworktest.MediaNames;
+import com.android.mediaframeworktest.MediaProfileReader;
+import com.android.mediaframeworktest.MediaTestUtil;
+
 import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.Writer;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.BufferedWriter;
-
-import com.android.mediaframeworktest.MediaProfileReader;
 
 /**
  * Junit / Instrumentation - performance measurement for media player and
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2CaptureRequestTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2CaptureRequestTest.java
index 47caf0a..ac12884 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2CaptureRequestTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2CaptureRequestTest.java
@@ -60,7 +60,7 @@
     private static final int DEFAULT_SENSITIVITY = 100;
     private static final long EXPOSURE_TIME_ERROR_MARGIN_NS = 100000L; // 100us, Approximation.
     private static final float EXPOSURE_TIME_ERROR_MARGIN_RATE = 0.03f; // 3%, Approximation.
-    private static final float SENSITIVITY_ERROR_MARGIN_RATE = 0.03f; // 3%, Approximation.
+    private static final float SENSITIVITY_ERROR_MARGIN_RATE = 0.06f; // 6%, Approximation.
     private static final int DEFAULT_NUM_EXPOSURE_TIME_STEPS = 3;
     private static final int DEFAULT_NUM_SENSITIVITY_STEPS = 16;
     private static final int DEFAULT_SENSITIVITY_STEP_SIZE = 100;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java
index dc8da48..a47d8bc 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java
@@ -43,13 +43,13 @@
 import android.media.MediaFormat;
 import android.media.MediaRecorder;
 import android.os.SystemClock;
-import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 import android.util.Range;
 import android.util.Size;
 import android.view.Surface;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
 
 import com.android.ex.camera2.blocking.BlockingSessionCallback;
 import com.android.mediaframeworktest.Camera2SurfaceViewTestCase;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2SwitchPreviewTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2SwitchPreviewTest.java
index a26ee2d..002a143 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2SwitchPreviewTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2SwitchPreviewTest.java
@@ -16,71 +16,34 @@
 
 package com.android.mediaframeworktest.stress;
 
-import com.android.ex.camera2.blocking.BlockingSessionCallback;
-import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
-import com.android.mediaframeworktest.Camera2SurfaceViewTestCase;
-import com.android.mediaframeworktest.helpers.Camera2Focuser;
-import com.android.mediaframeworktest.helpers.CameraTestUtils;
-import com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleCaptureCallback;
-
-import android.graphics.ImageFormat;
-import android.graphics.Point;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
-import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession;
-import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraAccessException;
-import android.hardware.camera2.CameraCaptureSession;
-import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.CaptureResult;
-import android.hardware.camera2.DngCreator;
-import android.hardware.camera2.params.MeteringRectangle;
-import android.media.Image;
-import android.media.ImageReader;
-import android.media.CamcorderProfile;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.media.MediaRecorder;
-import android.os.ConditionVariable;
-import android.os.Environment;
-import android.util.Log;
-import android.util.Pair;
-import android.util.Rational;
-import android.util.Size;
-import android.view.Surface;
-import android.hardware.camera2.params.StreamConfigurationMap;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.util.Log;
-import android.util.Range;
-
-import java.io.ByteArrayOutputStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.io.File;
-import java.util.Arrays;
-import java.util.HashMap;
-
-import static com.android.mediaframeworktest.helpers.CameraTestUtils.CAPTURE_IMAGE_TIMEOUT_MS;
 import static com.android.mediaframeworktest.helpers.CameraTestUtils.MAX_READER_IMAGES;
-import static com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleImageReaderListener;
-import static com.android.mediaframeworktest.helpers.CameraTestUtils.basicValidateJpegImage;
-import static com.android.mediaframeworktest.helpers.CameraTestUtils.configureCameraSession;
-import static com.android.mediaframeworktest.helpers.CameraTestUtils.dumpFile;
-import static com.android.mediaframeworktest.helpers.CameraTestUtils.getDataFromImage;
-import static com.android.mediaframeworktest.helpers.CameraTestUtils.getValueNotNull;
-import static com.android.mediaframeworktest.helpers.CameraTestUtils.makeImageReader;
-import static com.android.ex.camera2.blocking.BlockingSessionCallback.SESSION_CLOSED;
-import static com.android.mediaframeworktest.helpers.CameraTestUtils.CAPTURE_IMAGE_TIMEOUT_MS;
-import static com.android.mediaframeworktest.helpers.CameraTestUtils.SESSION_CLOSE_TIMEOUT_MS;
 import static com.android.mediaframeworktest.helpers.CameraTestUtils.SIZE_BOUND_1080P;
 import static com.android.mediaframeworktest.helpers.CameraTestUtils.SIZE_BOUND_2160P;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleImageReaderListener;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.configureCameraSession;
 import static com.android.mediaframeworktest.helpers.CameraTestUtils.getSupportedVideoSizes;
 
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.media.CamcorderProfile;
+import android.media.ImageReader;
+import android.media.MediaRecorder;
+import android.util.Log;
+import android.util.Range;
+import android.util.Size;
+import android.view.Surface;
+
 import com.android.ex.camera2.blocking.BlockingSessionCallback;
 import com.android.mediaframeworktest.Camera2SurfaceViewTestCase;
-import com.android.mediaframeworktest.helpers.CameraTestUtils;
+import com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleCaptureCallback;
 
-import junit.framework.AssertionFailedError;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
 
 /**
  * <p>Tests Back/Front camera switching and Camera/Video modes witching.</p>
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/CameraStressTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/CameraStressTest.java
index 74244b9..9883df2e3 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/CameraStressTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/CameraStressTest.java
@@ -16,6 +16,16 @@
 
 package com.android.mediaframeworktest.stress;
 
+import android.hardware.Camera.Parameters;
+import android.os.Handler;
+import android.os.Looper;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+
 import com.android.mediaframeworktest.MediaFrameworkTest;
 import com.android.mediaframeworktest.helpers.CameraTestHelper;
 
@@ -23,19 +33,9 @@
 import java.io.File;
 import java.io.FileWriter;
 import java.io.Writer;
+import java.util.List;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
-import java.util.List;
-
-import android.hardware.Camera.Parameters;
-import android.os.Handler;
-import android.os.Looper;
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.util.Log;
-import android.view.SurfaceHolder;
-
-import androidx.test.InstrumentationRegistry;
 
 /**
  * Junit / Instrumentation test case for the following camera APIs:
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStreamingStressTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStreamingStressTest.java
index 6a820ec..cb69e1b 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStreamingStressTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStreamingStressTest.java
@@ -16,16 +16,16 @@
 
 package com.android.mediaframeworktest.stress;
 
-import com.android.mediaframeworktest.MediaFrameworkTest;
-import com.android.mediaframeworktest.MediaPlayerStressTestRunner;
-
 import android.os.Bundle;
 import android.os.Environment;
 import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 
+import androidx.test.filters.LargeTest;
+
+import com.android.mediaframeworktest.MediaFrameworkTest;
 import com.android.mediaframeworktest.MediaNames;
+import com.android.mediaframeworktest.MediaPlayerStressTestRunner;
 import com.android.mediaframeworktest.functional.CodecTest;
 
 import java.io.BufferedReader;
@@ -34,7 +34,6 @@
 import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.Writer;
-
 import java.util.ArrayList;
 import java.util.List;
 
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStressTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStressTest.java
index 4221f1b..221881a 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStressTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStressTest.java
@@ -16,16 +16,16 @@
 
 package com.android.mediaframeworktest.stress;
 
-import com.android.mediaframeworktest.MediaFrameworkTest;
-import com.android.mediaframeworktest.MediaPlayerStressTestRunner;
-
 import android.os.Bundle;
 import android.os.Environment;
 import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 
+import androidx.test.filters.LargeTest;
+
+import com.android.mediaframeworktest.MediaFrameworkTest;
 import com.android.mediaframeworktest.MediaNames;
+import com.android.mediaframeworktest.MediaPlayerStressTestRunner;
 import com.android.mediaframeworktest.functional.CodecTest;
 
 import java.io.BufferedWriter;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java
index 199f179..47bd633 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java
@@ -17,16 +17,6 @@
 package com.android.mediaframeworktest.stress;
 
 
-import com.android.mediaframeworktest.MediaFrameworkTest;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.Writer;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-
 import android.hardware.Camera;
 import android.media.CamcorderProfile;
 import android.media.MediaPlayer;
@@ -35,11 +25,22 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 import android.view.SurfaceHolder;
+
+import androidx.test.filters.LargeTest;
+
+import com.android.mediaframeworktest.MediaFrameworkTest;
 import com.android.mediaframeworktest.MediaRecorderStressTestRunner;
 
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
 /**
  * Junit / Instrumentation test case for the media player api
  */
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/template/AudioTestHarnessTemplateAndroidTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/template/AudioTestHarnessTemplateAndroidTest.java
index 6c2a3f7..fec108f 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/template/AudioTestHarnessTemplateAndroidTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/template/AudioTestHarnessTemplateAndroidTest.java
@@ -20,9 +20,10 @@
 import android.media.MediaPlayer;
 import android.os.Bundle;
 import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 
+import androidx.test.filters.LargeTest;
+
 import com.android.mediaframeworktest.AudioTestHarnessTemplateRunner;
 import com.android.mediaframeworktest.MediaFrameworkTest;
 
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
index c814eba..964da45 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
@@ -16,12 +16,10 @@
 
 package com.android.mediaframeworktest.unit;
 
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
-import android.util.Pair;
-import android.util.Range;
-import android.util.Rational;
-import android.util.SizeF;
+import static android.hardware.camera2.impl.CameraMetadataNative.*;
+
+import static com.android.mediaframeworktest.unit.ByteArrayHelpers.*;
+
 import android.graphics.ImageFormat;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -31,7 +29,6 @@
 import android.hardware.camera2.CameraMetadata;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
-import android.util.Size;
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.marshal.impl.MarshalQueryableEnum;
 import android.hardware.camera2.params.ColorSpaceTransform;
@@ -45,9 +42,14 @@
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.hardware.camera2.params.TonemapCurve;
 import android.hardware.camera2.utils.TypeReference;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Rational;
+import android.util.Size;
+import android.util.SizeF;
 
-import static android.hardware.camera2.impl.CameraMetadataNative.*;
-import static com.android.mediaframeworktest.unit.ByteArrayHelpers.*;
+import androidx.test.filters.SmallTest;
 
 import java.lang.reflect.Array;
 import java.nio.ByteBuffer;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsTypeReferenceTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsTypeReferenceTest.java
index 1b765ea..924ad47 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsTypeReferenceTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsTypeReferenceTest.java
@@ -20,8 +20,7 @@
 
 import android.hardware.camera2.utils.TypeReference;
 
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
+import androidx.test.filters.SmallTest;
 
 import java.lang.reflect.Type;
 import java.util.List;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsUncheckedThrowTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsUncheckedThrowTest.java
index b648763..6cb8b54 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsUncheckedThrowTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsUncheckedThrowTest.java
@@ -18,7 +18,8 @@
 
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.utils.UncheckedThrow;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import junit.framework.Assert;
 
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ImageReaderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ImageReaderTest.java
index 2cb5704..d9fc88d 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ImageReaderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ImageReaderTest.java
@@ -24,7 +24,8 @@
 import android.media.ImageReader;
 import android.media.ImageReader.OnImageAvailableListener;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 public class ImageReaderTest extends AndroidTestCase {
 
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java
index ea0494f..65264d3 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java
@@ -36,7 +36,8 @@
 import android.provider.MediaStore.Images;
 import android.provider.MediaStore.Video;
 import android.test.InstrumentationTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
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 e3d3897..80edf8a 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
@@ -26,10 +26,10 @@
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
 
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.media.playback.flags.Flags;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetCurrentPositionStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetCurrentPositionStateUnitTest.java
index 37dd4b5..06c527d 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetCurrentPositionStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetCurrentPositionStateUnitTest.java
@@ -18,7 +18,8 @@
 
 import android.media.MediaPlayer;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 /**
  * Unit test class to test the set of valid and invalid states that
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetDurationStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetDurationStateUnitTest.java
index 48fd16c..5a36044 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetDurationStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetDurationStateUnitTest.java
@@ -18,7 +18,8 @@
 
 import android.media.MediaPlayer;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 /**
  * Unit test class to test the set of valid and invalid states that
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetVideoHeightStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetVideoHeightStateUnitTest.java
index 6d3c083..f86f77e 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetVideoHeightStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetVideoHeightStateUnitTest.java
@@ -18,7 +18,8 @@
 
 import android.media.MediaPlayer;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 /**
  * Unit test class to test the set of valid and invalid states that
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetVideoWidthStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetVideoWidthStateUnitTest.java
index 198439c..fb31edb3 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetVideoWidthStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetVideoWidthStateUnitTest.java
@@ -18,7 +18,8 @@
 
 import android.media.MediaPlayer;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 /**
  * Unit test class to test the set of valid and invalid states that
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerIsPlayingStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerIsPlayingStateUnitTest.java
index b9c63fd..e1c992e 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerIsPlayingStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerIsPlayingStateUnitTest.java
@@ -18,7 +18,8 @@
 
 import android.media.MediaPlayer;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 /**
  * Unit test class to test the set of valid and invalid states that
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerMetadataParserTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerMetadataParserTest.java
index bfa3976..7aed3b7 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerMetadataParserTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerMetadataParserTest.java
@@ -18,10 +18,9 @@
 import android.media.Metadata;
 import android.os.Parcel;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
 
-import java.util.Calendar;
+import androidx.test.filters.SmallTest;
+
 import java.util.Date;
 
 /*
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerPauseStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerPauseStateUnitTest.java
index 7a96792..aa4bfe6 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerPauseStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerPauseStateUnitTest.java
@@ -18,7 +18,8 @@
 
 import android.media.MediaPlayer;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 /**
  * Unit test class to test the set of valid and invalid states that
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerResetStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerResetStateUnitTest.java
index 2497cd7..d8651f5 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerResetStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerResetStateUnitTest.java
@@ -18,7 +18,8 @@
 
 import android.media.MediaPlayer;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 /**
  * Unit test class to test the set of valid and invalid states that
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerSeekToStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerSeekToStateUnitTest.java
index 991fe9c..3bcde7f 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerSeekToStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerSeekToStateUnitTest.java
@@ -18,7 +18,8 @@
 
 import android.media.MediaPlayer;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 /**
  * Unit test class to test the set of valid and invalid states that
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerSetAudioStreamTypeStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerSetAudioStreamTypeStateUnitTest.java
index b984514..36e93c0 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerSetAudioStreamTypeStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerSetAudioStreamTypeStateUnitTest.java
@@ -16,10 +16,11 @@
 
 package com.android.mediaframeworktest.unit;
 
-import android.media.MediaPlayer;
 import android.media.AudioManager;
+import android.media.MediaPlayer;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 /**
  * Unit test class to test the set of valid and invalid states that
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerSetLoopingStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerSetLoopingStateUnitTest.java
index 17c9d8c..adb56d0f 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerSetLoopingStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerSetLoopingStateUnitTest.java
@@ -18,7 +18,8 @@
 
 import android.media.MediaPlayer;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 /**
  * Unit test class to test the set of valid and invalid states that
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerSetVolumeStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerSetVolumeStateUnitTest.java
index a149565..84691da 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerSetVolumeStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerSetVolumeStateUnitTest.java
@@ -18,7 +18,8 @@
 
 import android.media.MediaPlayer;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 /**
  * Unit test class to test the set of valid and invalid states that
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerStartStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerStartStateUnitTest.java
index 68c8e42..a0999d8 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerStartStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerStartStateUnitTest.java
@@ -18,7 +18,8 @@
 
 import android.media.MediaPlayer;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 /**
  * Unit test class to test the set of valid and invalid states that
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerStopStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerStopStateUnitTest.java
index ab8519a..b0673a8 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerStopStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerStopStateUnitTest.java
@@ -18,7 +18,8 @@
 
 import android.media.MediaPlayer;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 /**
  * Unit test class to test the set of valid and invalid states that
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderPrepareStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderPrepareStateUnitTest.java
index 134144d..8299a7e 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderPrepareStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderPrepareStateUnitTest.java
@@ -18,8 +18,9 @@
 
 import android.media.MediaRecorder;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.Suppress;
+
+import androidx.test.filters.MediumTest;
+
 import java.io.IOException;
 
 /**
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderResetStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderResetStateUnitTest.java
index cae9e31..1e4c060 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderResetStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderResetStateUnitTest.java
@@ -18,8 +18,8 @@
 
 import android.media.MediaRecorder;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.Suppress;
+
+import androidx.test.filters.MediumTest;
 
 /**
  * Unit test class to test the set of valid and invalid states that
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderSetAudioEncoderStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderSetAudioEncoderStateUnitTest.java
index 4b5a818..b3e9009d 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderSetAudioEncoderStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderSetAudioEncoderStateUnitTest.java
@@ -18,8 +18,8 @@
 
 import android.media.MediaRecorder;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.Suppress;
+
+import androidx.test.filters.MediumTest;
 
 /**
  * Unit test class to test the set of valid and invalid states that
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderSetAudioSourceStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderSetAudioSourceStateUnitTest.java
index f8ab48cf..3b56e6f 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderSetAudioSourceStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderSetAudioSourceStateUnitTest.java
@@ -18,8 +18,8 @@
 
 import android.media.MediaRecorder;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.Suppress;
+
+import androidx.test.filters.MediumTest;
 
 /**
  * Unit test class to test the set of valid and invalid states that
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderSetOutputFileStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderSetOutputFileStateUnitTest.java
index 712a758..0980802 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderSetOutputFileStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderSetOutputFileStateUnitTest.java
@@ -18,8 +18,8 @@
 
 import android.media.MediaRecorder;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.Suppress;
+
+import androidx.test.filters.MediumTest;
 
 /**
  * Unit test class to test the set of valid and invalid states that
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderSetOutputFormatStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderSetOutputFormatStateUnitTest.java
index cacdd87..3a4a7c7 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderSetOutputFormatStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderSetOutputFormatStateUnitTest.java
@@ -18,8 +18,8 @@
 
 import android.media.MediaRecorder;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.Suppress;
+
+import androidx.test.filters.MediumTest;
 
 /**
  * Unit test class to test the set of valid and invalid states that
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderStartStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderStartStateUnitTest.java
index d1232fc..192c896 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderStartStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderStartStateUnitTest.java
@@ -18,8 +18,8 @@
 
 import android.media.MediaRecorder;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.Suppress;
+
+import androidx.test.filters.MediumTest;
 
 /**
  * Unit test class to test the set of valid and invalid states that
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderStopStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderStopStateUnitTest.java
index 91100ae..093ed88 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderStopStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaRecorderStopStateUnitTest.java
@@ -18,10 +18,10 @@
 
 import android.media.MediaRecorder;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.Suppress;
 import android.util.Log;
 
+import androidx.test.filters.MediumTest;
+
 /**
  * Unit test class to test the set of valid and invalid states that
  * MediaRecorder.stop() method can be called.
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RangeTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RangeTest.java
index 9dd2732..f54f06d 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RangeTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RangeTest.java
@@ -16,10 +16,11 @@
 
 package com.android.mediaframeworktest.unit;
 
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Range;
 import android.util.Rational;
 
+import androidx.test.filters.SmallTest;
+
 /**
  * <pre>
  * adb shell am instrument \
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RationalTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RationalTest.java
index 1bb7db9..a94b5af 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RationalTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RationalTest.java
@@ -16,9 +16,12 @@
 
 package com.android.mediaframeworktest.unit;
 
-import android.test.suitebuilder.annotation.SmallTest;
+import static android.util.Rational.*;
+
 import android.util.Rational;
 
+import androidx.test.filters.SmallTest;
+
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -28,8 +31,6 @@
 import java.io.Serializable;
 import java.lang.reflect.Field;
 
-import static android.util.Rational.*;
-
 /**
  * <pre>
  * adb shell am instrument \
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/SurfaceUtilsTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/SurfaceUtilsTest.java
index f578e46..cd564b2 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/SurfaceUtilsTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/SurfaceUtilsTest.java
@@ -19,9 +19,10 @@
 import android.graphics.ImageFormat;
 import android.hardware.camera2.utils.SurfaceUtils;
 import android.media.ImageReader;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.view.Surface;
 
+import androidx.test.filters.SmallTest;
+
 import junit.framework.Assert;
 
 public class SurfaceUtilsTest extends junit.framework.TestCase {
diff --git a/media/tests/projection/Android.bp b/media/tests/projection/Android.bp
index c9a8864..fd5f195 100644
--- a/media/tests/projection/Android.bp
+++ b/media/tests/projection/Android.bp
@@ -3,6 +3,7 @@
 //########################################################################
 
 package {
+    default_team: "trendy_team_lse_desktop_os_experience",
     // 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"
diff --git a/mime/java-res/android.mime.types b/mime/java-res/android.mime.types
index b795560..5cf807d 100644
--- a/mime/java-res/android.mime.types
+++ b/mime/java-res/android.mime.types
@@ -80,6 +80,7 @@
 ?audio/aac-adts aac
 ?audio/ac3 ac3 a52
 ?audio/amr amr
+?audio/x-gsm gsm
 ?audio/imelody imy
 ?audio/midi rtttl xmf
 ?audio/mobile-xmf mxmf
@@ -103,6 +104,7 @@
 ?image/x-adobe-dng dng
 ?image/x-fuji-raf raf
 ?image/x-icon ico
+?image/x-jg art
 ?image/x-nikon-nrw nrw
 ?image/x-panasonic-rw2 rw2
 ?image/x-pentax-pef pef
@@ -117,7 +119,7 @@
 ?text/x-vcard vcf
 
 ?video/3gpp2 3gpp2 3gp2 3g2
-?video/3gpp 3gpp
+?video/3gpp 3gpp 3gp
 ?video/avi avi
 ?video/m4v m4v
 ?video/mp4 m4v f4v mp4v mpeg4
@@ -153,6 +155,7 @@
 audio/x-mpegurl m3u m3u8
 image/jpeg jpg
 image/x-ms-bmp bmp
+image/x-photoshop psd
 text/plain txt
 text/x-c++hdr hpp
 text/x-c++src cpp
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 7f3792d..752ebdf 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -98,6 +98,7 @@
         "libpowermanager",
         "android.hardware.configstore@1.0",
         "android.hardware.configstore-utils",
+        "android.os.flags-aconfig-cc",
         "libnativedisplay",
     ],
 
diff --git a/native/android/input.cpp b/native/android/input.cpp
index 53699bc..0a22314 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -124,11 +124,11 @@
 }
 
 float AMotionEvent_getXOffset(const AInputEvent* motion_event) {
-    return static_cast<const MotionEvent*>(motion_event)->getXOffset();
+    return static_cast<const MotionEvent*>(motion_event)->getRawXOffset();
 }
 
 float AMotionEvent_getYOffset(const AInputEvent* motion_event) {
-    return static_cast<const MotionEvent*>(motion_event)->getYOffset();
+    return static_cast<const MotionEvent*>(motion_event)->getRawYOffset();
 }
 
 float AMotionEvent_getXPrecision(const AInputEvent* motion_event) {
diff --git a/nfc/Android.bp b/nfc/Android.bp
index b6bc40d..7698e2b 100644
--- a/nfc/Android.bp
+++ b/nfc/Android.bp
@@ -38,6 +38,7 @@
     name: "framework-nfc",
     libs: [
         "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage
+        "framework-permission-s",
     ],
     static_libs: [
         "android.nfc.flags-aconfig-java",
@@ -75,6 +76,9 @@
         "//apex_available:platform",
         "com.android.nfcservices",
     ],
+    aconfig_declarations: [
+        "android.nfc.flags-aconfig",
+    ],
 }
 
 filegroup {
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 0fb7c95..9e0bb86 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -194,21 +194,21 @@
 package android.nfc.cardemulation {
 
   public final class CardEmulation {
-    method public boolean categoryAllowsForegroundPreference(String);
+    method @Deprecated public boolean categoryAllowsForegroundPreference(String);
     method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public java.util.List<java.lang.String> getAidsForPreferredPaymentService();
     method public java.util.List<java.lang.String> getAidsForService(android.content.ComponentName, String);
-    method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService();
+    method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService();
     method public static android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter);
-    method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService();
-    method public int getSelectionModeForCategory(String);
+    method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService();
+    method @Deprecated public int getSelectionModeForCategory(String);
     method public boolean isDefaultServiceForAid(android.content.ComponentName, String);
     method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
     method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
     method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean);
     method public boolean removeAidsForService(android.content.ComponentName, String);
-    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setDefaultToObserveModeForService(@NonNull android.content.ComponentName, boolean);
     method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean setOffHostForService(@NonNull android.content.ComponentName, @NonNull String);
     method public boolean setPreferredService(android.app.Activity, android.content.ComponentName);
+    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setShouldDefaultToObserveModeForService(@NonNull android.content.ComponentName, boolean);
     method public boolean supportsAidPrefixRegistration();
     method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName);
     method public boolean unsetPreferredService(android.app.Activity);
@@ -265,12 +265,13 @@
   }
 
   @FlaggedApi("android.nfc.nfc_read_polling_loop") public final class PollingFrame implements android.os.Parcelable {
-    ctor public PollingFrame(int, @Nullable byte[], int, int);
+    ctor public PollingFrame(int, @Nullable byte[], int, int, boolean);
     method public int describeContents();
     method @NonNull public byte[] getData();
-    method public int getGain();
     method public int getTimestamp();
+    method public boolean getTriggeredAutoTransact();
     method public int getType();
+    method public int getVendorSpecificGain();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.PollingFrame> CREATOR;
     field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final int POLLING_LOOP_TYPE_A = 65; // 0x41
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
index 64f7fa4..85a07b7 100644
--- a/nfc/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -30,7 +30,7 @@
     boolean isDefaultServiceForAid(int userHandle, in ComponentName service, String aid);
     boolean setDefaultServiceForCategory(int userHandle, in ComponentName service, String category);
     boolean setDefaultForNextTap(int userHandle, in ComponentName service);
-    boolean setDefaultToObserveModeForService(int userId, in android.content.ComponentName service, boolean enable);
+    boolean setShouldDefaultToObserveModeForService(int userId, in android.content.ComponentName service, boolean enable);
     boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup);
     boolean registerPollingLoopFilterForService(int userHandle, in ComponentName service, in String pollingLoopFilter, boolean autoTransact);
     boolean setOffHostForService(int userHandle, in ComponentName service, in String offHostSecureElement);
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
index e62e37b..2c7d61e 100644
--- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -141,7 +141,7 @@
     /**
      * Whether the NFC stack should default to Observe Mode when this preferred service.
      */
-    private boolean mDefaultToObserveMode;
+    private boolean mShouldDefaultToObserveMode;
 
     /**
      * @hide
@@ -275,8 +275,8 @@
                         com.android.internal.R.styleable.HostApduService_settingsActivity);
                 mOffHostName = null;
                 mStaticOffHostName = mOffHostName;
-                mDefaultToObserveMode = sa.getBoolean(
-                        R.styleable.HostApduService_defaultToObserveMode,
+                mShouldDefaultToObserveMode = sa.getBoolean(
+                        R.styleable.HostApduService_shouldDefaultToObserveMode,
                         false);
                 sa.recycle();
             } else {
@@ -297,8 +297,8 @@
                         com.android.internal.R.styleable.HostApduService_settingsActivity);
                 mOffHostName = sa.getString(
                         com.android.internal.R.styleable.OffHostApduService_secureElementName);
-                mDefaultToObserveMode = sa.getBoolean(
-                        R.styleable.HostApduService_defaultToObserveMode,
+                mShouldDefaultToObserveMode = sa.getBoolean(
+                        R.styleable.HostApduService_shouldDefaultToObserveMode,
                         false);
                 if (mOffHostName != null) {
                     if (mOffHostName.equals("eSE")) {
@@ -633,22 +633,22 @@
     }
 
     /**
-     * Returns whether the NFC stack should default to observe mode when this servise is preferred.
-     * @return whether the NFC stack should default to observe mode when this servise is preferred
+     * Returns whether the NFC stack should default to observe mode when this service is preferred.
+     * @return whether the NFC stack should default to observe mode when this service is preferred
      */
     @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
-    public boolean defaultToObserveMode() {
-        return mDefaultToObserveMode;
+    public boolean shouldDefaultToObserveMode() {
+        return mShouldDefaultToObserveMode;
     }
 
     /**
-     * Sets whether the NFC stack should default to observe mode when this servise is preferred.
-     * @param defaultToObserveMode whether the NFC stack should default to observe mode when this
-     *                             servise is preferred
+     * Sets whether the NFC stack should default to observe mode when this service is preferred.
+     * @param shouldDefaultToObserveMode whether the NFC stack should default to observe mode when
+     *                                  this service is preferred
      */
     @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
-    public void setDefaultToObserveMode(boolean defaultToObserveMode) {
-        mDefaultToObserveMode = defaultToObserveMode;
+    public void setShouldDefaultToObserveMode(boolean shouldDefaultToObserveMode) {
+        mShouldDefaultToObserveMode = shouldDefaultToObserveMode;
     }
 
     /**
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index 47ddd9d..e55f540 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -27,6 +27,7 @@
 import android.annotation.UserHandleAware;
 import android.annotation.UserIdInt;
 import android.app.Activity;
+import android.app.role.RoleManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -278,13 +279,22 @@
      * @param category The category, e.g. {@link #CATEGORY_PAYMENT}
      * @return whether AIDs in the category can be handled by a service
      *         specified by the foreground app.
+     *
+     * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
+     * Preferred Payment service is no longer valid. All routings will be done in a AID
+     * category agnostic manner.
      */
     @SuppressWarnings("NonUserGetterCalled")
+    @Deprecated
     public boolean categoryAllowsForegroundPreference(String category) {
+        Context contextAsUser = mContext.createContextAsUser(
+                UserHandle.of(UserHandle.myUserId()), 0);
+        RoleManager roleManager = contextAsUser.getSystemService(RoleManager.class);
+        if (roleManager.isRoleAvailable(RoleManager.ROLE_WALLET)) {
+            return true;
+        }
         if (CATEGORY_PAYMENT.equals(category)) {
             boolean preferForeground = false;
-            Context contextAsUser = mContext.createContextAsUser(
-                    UserHandle.of(UserHandle.myUserId()), 0);
             try {
                 preferForeground = Settings.Secure.getInt(
                         contextAsUser.getContentResolver(),
@@ -309,7 +319,12 @@
      *    to pick a service if there is a conflict.
      * @param category The category, for example {@link #CATEGORY_PAYMENT}
      * @return the selection mode for the passed in category
+     *
+     * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
+     * Preferred Payment service is no longer valid. All routings will be done in a AID
+     * category agnostic manner.
      */
+    @Deprecated
     public int getSelectionModeForCategory(String category) {
         if (CATEGORY_PAYMENT.equals(category)) {
             boolean paymentRegistered = false;
@@ -348,11 +363,11 @@
      * @return whether the change was successful.
      */
     @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
-    public boolean setDefaultToObserveModeForService(@NonNull ComponentName service,
+    public boolean setShouldDefaultToObserveModeForService(@NonNull ComponentName service,
             boolean enable) {
         try {
-            return sService.setDefaultToObserveModeForService(mContext.getUser().getIdentifier(),
-                    service, enable);
+            return sService.setShouldDefaultToObserveModeForService(
+                    mContext.getUser().getIdentifier(), service, enable);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to reach CardEmulationService.");
         }
@@ -792,8 +807,15 @@
      *                                               (e.g. eSE/eSE1, eSE2, etc.).
      *                          2. "OffHost" if the payment service does not specify secure element
      *                             name.
+     *
+     * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
+     * Preferred Payment service is no longer valid. All routings will go to the Wallet Holder app.
+     * A payment service will be selected automatically based on registered AIDs. In the case of
+     * multiple services that register for the same payment AID, the selection will be done on
+     * an alphabetical order based on the component names.
      */
     @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+    @Deprecated
     @Nullable
     public String getRouteDestinationForPreferredPaymentService() {
         try {
@@ -836,8 +858,15 @@
      * Returns a user-visible description of the preferred payment service.
      *
      * @return the preferred payment service description
+     *
+     * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
+     * Preferred Payment service is no longer valid. All routings will go to the Wallet Holder app.
+     * A payment service will be selected automatically based on registered AIDs. In the case of
+     * multiple services that register for the same payment AID, the selection will be done on
+     * an alphabetical order based on the component names.
      */
     @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+    @Deprecated
     @Nullable
     public CharSequence getDescriptionForPreferredPaymentService() {
         try {
diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.java b/nfc/java/android/nfc/cardemulation/PollingFrame.java
index 3383f3b..7028c8f 100644
--- a/nfc/java/android/nfc/cardemulation/PollingFrame.java
+++ b/nfc/java/android/nfc/cardemulation/PollingFrame.java
@@ -133,12 +133,23 @@
     @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
     public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
 
+    /**
+     * KEY_POLLING_LOOP_TIMESTAMP is the Bundle key for whether this polling frame triggered
+     * autoTransact in the Bundle included in MSG_POLLING_LOOP.
+     *
+     * @hide
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static final String KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT =
+            "android.nfc.cardemulation.TRIGGERED_AUTOTRANSACT";
+
 
     @PollingFrameType
     private final int mType;
     private final byte[] mData;
     private final int mGain;
     private final int mTimestamp;
+    private final boolean mTriggeredAutoTransact;
 
     public static final @NonNull Parcelable.Creator<PollingFrame> CREATOR =
             new Parcelable.Creator<>() {
@@ -157,16 +168,19 @@
         mType = frame.getInt(KEY_POLLING_LOOP_TYPE);
         byte[] data = frame.getByteArray(KEY_POLLING_LOOP_DATA);
         mData = (data == null) ? new byte[0] : data;
-        mGain = frame.getByte(KEY_POLLING_LOOP_GAIN);
+        mGain = frame.getInt(KEY_POLLING_LOOP_GAIN, -1);
         mTimestamp = frame.getInt(KEY_POLLING_LOOP_TIMESTAMP);
+        mTriggeredAutoTransact = frame.containsKey(KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT)
+                && frame.getBoolean(KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT);
     }
 
     public PollingFrame(@PollingFrameType int type, @Nullable byte[] data,
-            int gain, int timestamp) {
+            int gain, int timestamp, boolean triggeredAutoTransact) {
         mType = type;
         mData = data == null ? new byte[0] : data;
         mGain = gain;
         mTimestamp = timestamp;
+        mTriggeredAutoTransact = triggeredAutoTransact;
     }
 
     /**
@@ -194,14 +208,15 @@
     /**
      * Returns the gain representing the field strength of the NFC field when this polling loop
      * frame was observed.
+     * @return the gain or -1 if there is no gain measurement associated with this frame.
      */
-    public int getGain() {
+    public int getVendorSpecificGain() {
         return mGain;
     }
 
     /**
      * Returns the timestamp of when the polling loop frame was observed in milliseconds. These
-     * timestamps are relative and not absolute and should only be used fro comparing the timing of
+     * timestamps are relative and not absolute and should only be used for comparing the timing of
      * frames relative to each other.
      * @return the timestamp in milliseconds
      */
@@ -209,6 +224,14 @@
         return mTimestamp;
     }
 
+    /**
+     * Returns whether this frame triggered the device to automatically disable observe mode and
+     * allow one transaction.
+     */
+    public boolean getTriggeredAutoTransact() {
+        return mTriggeredAutoTransact;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -227,16 +250,19 @@
     public Bundle toBundle() {
         Bundle frame = new Bundle();
         frame.putInt(KEY_POLLING_LOOP_TYPE, getType());
-        frame.putByte(KEY_POLLING_LOOP_GAIN, (byte) getGain());
+        if (getVendorSpecificGain() != -1) {
+            frame.putInt(KEY_POLLING_LOOP_GAIN, (byte) getVendorSpecificGain());
+        }
         frame.putByteArray(KEY_POLLING_LOOP_DATA, getData());
         frame.putInt(KEY_POLLING_LOOP_TIMESTAMP, getTimestamp());
+        frame.putBoolean(KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT, getTriggeredAutoTransact());
         return frame;
     }
 
     @Override
     public String toString() {
         return "PollingFrame { Type: " + (char) getType()
-                + ", gain: " + getGain()
+                + ", gain: " + getVendorSpecificGain()
                 + ", timestamp: " + Integer.toUnsignedString(getTimestamp())
                 + ", data: [" + HexFormat.ofDelimiter(" ").formatHex(getData()) + "] }";
     }
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index 0a2619c..ba084c0 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -2,6 +2,7 @@
 
 flag {
     name: "enable_nfc_mainline"
+    is_exported: true
     namespace: "nfc"
     description: "Flag for NFC mainline changes"
     bug: "292140387"
@@ -9,6 +10,7 @@
 
 flag {
     name: "enable_nfc_reader_option"
+    is_exported: true
     namespace: "nfc"
     description: "Flag for NFC reader option API changes"
     bug: "291187960"
@@ -16,6 +18,7 @@
 
 flag {
     name: "enable_nfc_user_restriction"
+    is_exported: true
     namespace: "nfc"
     description: "Flag for NFC user restriction"
     bug: "291187960"
@@ -23,6 +26,7 @@
 
 flag {
     name: "nfc_observe_mode"
+    is_exported: true
     namespace: "nfc"
     description: "Enable NFC Observe Mode"
     bug: "294217286"
@@ -30,6 +34,7 @@
 
 flag {
     name: "nfc_read_polling_loop"
+    is_exported: true
     namespace: "nfc"
     description: "Enable NFC Polling Loop Notifications"
     bug: "294217286"
@@ -65,6 +70,7 @@
 
 flag {
     name: "enable_nfc_set_discovery_tech"
+    is_exported: true
     namespace: "nfc"
     description: "Flag for NFC set discovery tech API"
     bug: "300351519"
@@ -72,6 +78,7 @@
 
 flag {
     name: "nfc_vendor_cmd"
+    is_exported: true
     namespace: "nfc"
     description: "Enable NFC vendor command support"
     bug: "289879306"
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
index 4500a22..5dcfd3b 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
@@ -40,7 +40,7 @@
      *
      * This can be called using the JavaScript below:
      * <script type="text/javascript">
-     *     function getRequestedCapability(duration) {
+     *     function getRequestedCapability() {
      *         DataBoostWebServiceFlow.getRequestedCapability();
      *     }
      * </script>
@@ -57,6 +57,25 @@
      *
      * This can be called using the JavaScript below:
      * <script type="text/javascript">
+     *     function notifyPurchaseSuccessful(duration_ms_long = 0) {
+     *         DataBoostWebServiceFlow.notifyPurchaseSuccessful(duration_ms_long);
+     *     }
+     * </script>
+     *
+     * @param duration The duration for which the premium capability is purchased in milliseconds.
+     *                 NOTE: The duration parameter is not used.
+     */
+    @JavascriptInterface
+    public void notifyPurchaseSuccessful(long duration) {
+        mActivity.onPurchaseSuccessful();
+    }
+
+    /**
+     * Interface method allowing the carrier website to notify the slice purchase application of
+     * a successful premium capability purchase.
+     *
+     * This can be called using the JavaScript below:
+     * <script type="text/javascript">
      *     function notifyPurchaseSuccessful() {
      *         DataBoostWebServiceFlow.notifyPurchaseSuccessful();
      *     }
diff --git a/packages/CompanionDeviceManager/res/values-nb/strings.xml b/packages/CompanionDeviceManager/res/values-nb/strings.xml
index bc3ad06..d910f2f 100644
--- a/packages/CompanionDeviceManager/res/values-nb/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-nb/strings.xml
@@ -72,6 +72,6 @@
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Strøm apper og andre systemfunksjoner fra telefonen"</string>
     <string name="permission_media_routing_control_summary" msgid="2714631092321412250">"Se en liste over tilgjengelige enheter, og kontroller hvilken enhet som strømmer eller caster lyd eller video fra andre apper"</string>
-    <string name="device_type" product="default" msgid="8268703872070046263">"telefonen"</string>
-    <string name="device_type" product="tablet" msgid="5038791954983067774">"nettbrettet"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telefon"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"nettbrett"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 6019aa8..42d0cc4 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -52,12 +52,21 @@
     <!-- Confirmation for associating an application with a companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
     <string name="title_app_streaming">Allow &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone</string>
 
+    <!-- Confirmation for associating an application with a companion device of APP_STREAMING profile (type) with mirroring enabled [CHAR LIMIT=NONE] -->
+    <string name="title_app_streaming_with_mirroring">Allow &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to stream your phone\u2019s apps?</string>
+
+    <!-- Summary for associating an application with a companion device of APP_STREAMING profile [CHAR LIMIT=NONE] -->
+    <string name="summary_app_streaming">%1$s will have access to anything that’s visible or played on the phone, including audio, photos, passwords, and messages.&lt;br/>&lt;br/>%1$s will be able to stream apps until you remove access to this permission.</string>
+
     <!-- Title of the helper dialog for APP_STREAMING profile [CHAR LIMIT=30]. -->
     <string name="helper_title_app_streaming">Cross-device services</string>
 
     <!-- Description of the helper dialog for APP_STREAMING profile. [CHAR LIMIT=NONE] -->
     <string name="helper_summary_app_streaming"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="display_name" example="Chromebook">%2$s</xliff:g> to stream apps between your devices</string>
 
+    <!-- Description of the helper dialog for APP_STREAMING profile with mirroring enabled. [CHAR LIMIT=NONE] -->
+    <string name="helper_summary_app_streaming_with_mirroring"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="display_name" example="Chromebook">%2$s</xliff:g> to display and stream apps between your devices</string>
+
     <!-- ================= DEVICE_PROFILE_AUTOMOTIVE_PROJECTION ================= -->
 
     <!-- Confirmation for associating an application with a companion device of AUTOMOTIVE_PROJECTION profile (type) [CHAR LIMIT=NONE] -->
@@ -85,6 +94,12 @@
     <!-- Confirmation for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile (type) [CHAR LIMIT=NONE] -->
     <string name="title_nearby_device_streaming">Allow &lt;strong&gt;<xliff:g id="device_name" example="NearbyStreamer">%1$s</xliff:g>&lt;/strong&gt; to take this action?</string>
 
+    <!-- Confirmation for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile (type) with mirroring enabled [CHAR LIMIT=NONE] -->
+    <string name="title_nearby_device_streaming_with_mirroring">Allow &lt;strong&gt;<xliff:g id="device_name" example="NearbyStreamer">%1$s</xliff:g>&lt;/strong&gt; to stream your phone\u2019s apps and system features?</string>
+
+    <!-- Summary for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile [CHAR LIMIT=NONE] -->
+    <string name="summary_nearby_device_streaming">%1$s will have access to anything that’s visible or played on your phone, including audio, photos, payment info, passwords, and messages.&lt;br/>&lt;br/>%1$s will be able to stream apps and system features until you remove access to this permission.</string>
+
     <!-- Description of the helper dialog for NEARBY_DEVICE_STREAMING profile. [CHAR LIMIT=NONE] -->
     <string name="helper_summary_nearby_device_streaming"><xliff:g id="app_name" example="NearbyStreamerApp">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_name" example="NearbyDevice">%2$s</xliff:g> to stream apps and other system features to nearby devices</string>
 
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 66282dc..1231b63 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -27,13 +27,13 @@
 
 import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState;
 import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState.FINISHED_TIMEOUT;
-import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_PERMISSIONS;
-import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_NAMES;
 import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_ICONS;
+import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_NAMES;
+import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_PERMISSIONS;
 import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_SUMMARIES;
+import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_TITLES;
 import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_PROFILES;
 import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_SELF_MANAGED_PROFILES;
-import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_TITLES;
 import static com.android.companiondevicemanager.Utils.getApplicationLabel;
 import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
 import static com.android.companiondevicemanager.Utils.getIcon;
@@ -68,6 +68,7 @@
 import android.util.Log;
 import android.view.View;
 import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.Button;
 import android.widget.ImageButton;
 import android.widget.ImageView;
@@ -178,7 +179,7 @@
     // onActivityResult() after the association is created.
     private @Nullable DeviceFilterPair<?> mSelectedDevice;
 
-    private LinearLayoutManager mPermissionsLayoutManager = new LinearLayoutManager(this);
+    private final LinearLayoutManager mPermissionsLayoutManager = new LinearLayoutManager(this);
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -483,10 +484,18 @@
         }
 
         title = getHtmlFromResources(this, PROFILE_TITLES.get(deviceProfile), deviceName);
+
+        if (PROFILE_SUMMARIES.containsKey(deviceProfile)) {
+            final int summaryResourceId = PROFILE_SUMMARIES.get(deviceProfile);
+            final Spanned summary = getHtmlFromResources(this, summaryResourceId,
+                    deviceName);
+            mSummary.setText(summary);
+        } else {
+            mSummary.setVisibility(View.GONE);
+        }
+
         setupPermissionList(deviceProfile);
 
-        // Summary is not needed for selfManaged dialog.
-        mSummary.setVisibility(View.GONE);
         mTitle.setText(title);
         mVendorHeaderName.setText(vendorName);
         mVendorHeader.setVisibility(View.VISIBLE);
@@ -691,20 +700,26 @@
     private void setupPermissionList(String deviceProfile) {
         final List<Integer> permissionTypes = new ArrayList<>(
                 PROFILE_PERMISSIONS.get(deviceProfile));
+        if (permissionTypes.isEmpty()) {
+            // Nothing to do if there are no permission types.
+            return;
+        }
+
         mPermissionListAdapter.setPermissionType(permissionTypes);
         mPermissionListRecyclerView.setAdapter(mPermissionListAdapter);
         mPermissionListRecyclerView.setLayoutManager(mPermissionsLayoutManager);
 
         disableButtons();
 
+        LinearLayoutManager permissionListLayoutManager =
+                (LinearLayoutManager) mPermissionListRecyclerView
+                        .getLayoutManager();
+
         // Enable buttons once users scroll down to the bottom of the permission list.
         mPermissionListRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
             @Override
-            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
-                super.onScrollStateChanged(recyclerView, newState);
-                if (!recyclerView.canScrollVertically(1)) {
-                    enableButtons();
-                }
+            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+                enableAllowButtonIfNeeded(permissionListLayoutManager);
             }
         });
         // Enable buttons if last item in the permission list is visible to the users when
@@ -713,26 +728,36 @@
                 new ViewTreeObserver.OnGlobalLayoutListener() {
                     @Override
                     public void onGlobalLayout() {
-                        LinearLayoutManager layoutManager =
-                                (LinearLayoutManager) mPermissionListRecyclerView
-                                        .getLayoutManager();
-                        int lastVisibleItemPosition =
-                                layoutManager.findLastCompletelyVisibleItemPosition();
-                        int numItems = mPermissionListRecyclerView.getAdapter().getItemCount();
-
-                        if (lastVisibleItemPosition >= numItems - 1) {
-                            enableButtons();
-                        }
-
+                        enableAllowButtonIfNeeded(permissionListLayoutManager);
                         mPermissionListRecyclerView.getViewTreeObserver()
                                 .removeOnGlobalLayoutListener(this);
                     }
                 });
 
+        // Set accessibility for the recyclerView that to be able scroll up/down for voice access.
+        mPermissionListRecyclerView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+                super.onInitializeAccessibilityNodeInfo(host, info);
+                info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_DOWN);
+                info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP);
+            }
+        });
+
         mConstraintList.setVisibility(View.VISIBLE);
         mPermissionListRecyclerView.setVisibility(View.VISIBLE);
     }
 
+    // Enable the Allow button if the last element in the PermissionListRecyclerView is reached.
+    private void enableAllowButtonIfNeeded(LinearLayoutManager layoutManager) {
+        int lastVisibleItemPosition =
+                layoutManager.findLastCompletelyVisibleItemPosition();
+        int numItems = mPermissionListRecyclerView.getAdapter().getItemCount();
+
+        if (lastVisibleItemPosition >= numItems - 1) {
+            enableButtons();
+        }
+    }
+
     // Disable and grey out the Allow and Don't allow buttons if the last permission in the
     // permission list is not visible to the users.
     private void disableButtons() {
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
index 23a11d6..dc68bcc 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
@@ -27,11 +27,13 @@
 import static java.util.Collections.unmodifiableMap;
 import static java.util.Collections.unmodifiableSet;
 
+import android.companion.virtual.flags.Flags;
 import android.os.Build;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -122,10 +124,19 @@
     static final Map<String, Integer> PROFILE_TITLES;
     static {
         final Map<String, Integer> map = new ArrayMap<>();
-        map.put(DEVICE_PROFILE_APP_STREAMING, R.string.title_app_streaming);
+        if (Flags.interactiveScreenMirror()) {
+            map.put(DEVICE_PROFILE_APP_STREAMING, R.string.title_app_streaming_with_mirroring);
+        } else {
+            map.put(DEVICE_PROFILE_APP_STREAMING, R.string.title_app_streaming);
+        }
         map.put(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, R.string.title_automotive_projection);
         map.put(DEVICE_PROFILE_COMPUTER, R.string.title_computer);
-        map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, R.string.title_nearby_device_streaming);
+        if (Flags.interactiveScreenMirror()) {
+            map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
+                    R.string.title_nearby_device_streaming_with_mirroring);
+        } else {
+            map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, R.string.title_nearby_device_streaming);
+        }
         map.put(DEVICE_PROFILE_WATCH, R.string.confirmation_title);
         map.put(DEVICE_PROFILE_GLASSES, R.string.confirmation_title_glasses);
         map.put(null, R.string.confirmation_title);
@@ -138,6 +149,11 @@
         final Map<String, Integer> map = new ArrayMap<>();
         map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch);
         map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses);
+        if (Flags.interactiveScreenMirror()) {
+            map.put(DEVICE_PROFILE_APP_STREAMING, R.string.summary_app_streaming);
+            map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
+                    R.string.summary_nearby_device_streaming);
+        }
         map.put(null, R.string.summary_generic);
 
         PROFILE_SUMMARIES = unmodifiableMap(map);
@@ -146,11 +162,16 @@
     static final Map<String, List<Integer>> PROFILE_PERMISSIONS;
     static {
         final Map<String, List<Integer>> map = new ArrayMap<>();
-        map.put(DEVICE_PROFILE_APP_STREAMING, Arrays.asList(PERMISSION_APP_STREAMING));
         map.put(DEVICE_PROFILE_COMPUTER, Arrays.asList(
                 PERMISSION_NOTIFICATION_LISTENER_ACCESS, PERMISSION_STORAGE));
-        map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
-                Arrays.asList(PERMISSION_NEARBY_DEVICE_STREAMING));
+        if (Flags.interactiveScreenMirror()) {
+            map.put(DEVICE_PROFILE_APP_STREAMING, Collections.emptyList());
+            map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, Collections.emptyList());
+        } else {
+            map.put(DEVICE_PROFILE_APP_STREAMING, Arrays.asList(PERMISSION_APP_STREAMING));
+            map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
+                    Arrays.asList(PERMISSION_NEARBY_DEVICE_STREAMING));
+        }
         if (Build.VERSION.SDK_INT > UPSIDE_DOWN_CAKE) {
             map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATIONS, PERMISSION_PHONE,
                     PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_CALENDAR,
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
index 8f32dbb..fe0e021 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
@@ -26,6 +26,7 @@
 
 import android.annotation.Nullable;
 import android.companion.AssociationRequest;
+import android.companion.virtual.flags.Flags;
 import android.content.DialogInterface;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
@@ -129,7 +130,9 @@
             case DEVICE_PROFILE_APP_STREAMING:
                 title = getHtmlFromResources(getContext(), R.string.helper_title_app_streaming);
                 summary = getHtmlFromResources(
-                        getContext(), R.string.helper_summary_app_streaming, title, displayName);
+                        getContext(), Flags.interactiveScreenMirror()
+                                ? R.string.helper_summary_app_streaming_with_mirroring
+                                : R.string.helper_summary_app_streaming, title, displayName);
                 break;
 
             case DEVICE_PROFILE_COMPUTER:
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityAnnotations.java b/packages/Connectivity/framework/src/android/net/ConnectivityAnnotations.java
deleted file mode 100644
index eb1faa0..0000000
--- a/packages/Connectivity/framework/src/android/net/ConnectivityAnnotations.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-import android.annotation.IntDef;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Type annotations for constants used in the connectivity API surface.
- *
- * The annotations are maintained in a separate class so that it can be built as
- * a separate library that other modules can build against, as Typedef should not
- * be exposed as SystemApi.
- *
- * @hide
- */
-public final class ConnectivityAnnotations {
-    private ConnectivityAnnotations() {}
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(flag = true, value = {
-            ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER,
-            ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY,
-            ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE,
-    })
-    public @interface MultipathPreference {}
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(flag = false, value = {
-            ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED,
-            ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED,
-            ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED,
-    })
-    public @interface RestrictBackgroundStatus {}
-}
diff --git a/packages/CrashRecovery/framework/Android.bp b/packages/CrashRecovery/framework/Android.bp
index c0d93531..9480327 100644
--- a/packages/CrashRecovery/framework/Android.bp
+++ b/packages/CrashRecovery/framework/Android.bp
@@ -3,7 +3,7 @@
     module_type: "filegroup",
     config_namespace: "ANDROID",
     bool_variables: [
-        "move_crashrecovery_files",
+        "crashrecovery_files_in_platform",
     ],
     properties: [
         "srcs",
@@ -12,14 +12,13 @@
 
 platform_filegroup {
     name: "framework-crashrecovery-sources",
-    srcs: [
-        "java/**/*.java",
-        "java/**/*.aidl",
-    ],
     soong_config_variables: {
-        // if the flag is enabled, then files would be moved to module
-        move_crashrecovery_files: {
-            srcs: [],
+        // if this flag is enabled, then files are part of platform
+        crashrecovery_files_in_platform: {
+            srcs: [
+                "java/**/*.java",
+                "java/**/*.aidl",
+            ],
         },
     },
     path: "java",
@@ -31,7 +30,7 @@
     module_type: "filegroup",
     config_namespace: "ANDROID",
     bool_variables: [
-        "move_crashrecovery_files",
+        "crashrecovery_files_in_module",
     ],
     properties: [
         "srcs",
@@ -40,10 +39,9 @@
 
 module_filegroup {
     name: "framework-crashrecovery-module-sources",
-    srcs: [],
     soong_config_variables: {
-        // if the flag is enabled, then files would be moved to module
-        move_crashrecovery_files: {
+        // if this flag is enabled, then files are part of module
+        crashrecovery_files_in_module: {
             srcs: [
                 "java/**/*.java",
                 "java/**/*.aidl",
diff --git a/packages/CrashRecovery/services/Android.bp b/packages/CrashRecovery/services/Android.bp
index ab10b5a..961b41f 100644
--- a/packages/CrashRecovery/services/Android.bp
+++ b/packages/CrashRecovery/services/Android.bp
@@ -3,7 +3,7 @@
     module_type: "filegroup",
     config_namespace: "ANDROID",
     bool_variables: [
-        "move_crashrecovery_files",
+        "crashrecovery_files_in_platform",
     ],
     properties: [
         "srcs",
@@ -12,15 +12,14 @@
 
 platform_filegroup {
     name: "services-crashrecovery-sources",
-    srcs: [
-        "java/**/*.java",
-        "java/**/*.aidl",
-        ":statslog-crashrecovery-java-gen",
-    ],
     soong_config_variables: {
-        // if the flag is enabled, then files would be moved to module
-        move_crashrecovery_files: {
-            srcs: [],
+        // if this flag is enabled, then files are part of platform
+        crashrecovery_files_in_platform: {
+            srcs: [
+                "java/**/*.java",
+                "java/**/*.aidl",
+                ":statslog-crashrecovery-java-gen",
+            ],
         },
     },
     visibility: ["//frameworks/base:__subpackages__"],
@@ -31,7 +30,7 @@
     module_type: "filegroup",
     config_namespace: "ANDROID",
     bool_variables: [
-        "move_crashrecovery_files",
+        "crashrecovery_files_in_module",
     ],
     properties: [
         "srcs",
@@ -40,10 +39,9 @@
 
 module_filegroup {
     name: "services-crashrecovery-module-sources",
-    srcs: [],
     soong_config_variables: {
-        // if the flag is enabled, then files would be moved to module
-        move_crashrecovery_files: {
+        // if this flag is enabled, then files are part of module
+        crashrecovery_files_in_module: {
             srcs: [
                 "java/**/*.java",
                 "java/**/*.aidl",
diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
index ce8fb65..37b5d40 100644
--- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
@@ -38,15 +38,15 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
-import android.util.LongArrayQueue;
 import android.util.Slog;
 import android.util.Xml;
+import android.utils.BackgroundThread;
+import android.utils.LongArrayQueue;
+import android.utils.XmlUtils;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
index 9217e70..0fcec26 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
@@ -29,7 +29,6 @@
 import android.content.pm.VersionedPackage;
 import android.os.Build;
 import android.os.Environment;
-import android.os.FileUtils;
 import android.os.PowerManager;
 import android.os.RecoverySystem;
 import android.os.SystemClock;
@@ -42,10 +41,11 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
+import android.utils.ArrayUtils;
+import android.utils.FileUtils;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
 import com.android.server.PackageWatchdog.FailureReasons;
 import com.android.server.PackageWatchdog.PackageHealthObserver;
 import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 5fb47dd..0fb9327 100644
--- a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -75,6 +75,9 @@
     private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
             | ApplicationInfo.FLAG_SYSTEM;
 
+    private static final String PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG =
+            "persist.device_config.configuration.disable_high_impact_rollback";
+
     private final Context mContext;
     private final Handler mHandler;
     private final ApexManager mApexManager;
@@ -605,6 +608,10 @@
             // Apply all available low impact rollbacks.
             mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
         } else if (minRollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_HIGH) {
+            // Check disable_high_impact_rollback device config before performing rollback
+            if (SystemProperties.getBoolean(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, false)) {
+                return;
+            }
             // Rollback one package at a time. If that doesn't resolve the issue, rollback
             // next with same impact level.
             mHandler.post(() -> rollbackHighImpact(availableRollbacks, rollbackReason));
@@ -718,7 +725,9 @@
                 impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
                 break;
             case PackageManager.ROLLBACK_USER_IMPACT_HIGH:
-                impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_90;
+                if (!SystemProperties.getBoolean(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, false)) {
+                    impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_90;
+                }
                 break;
             default:
                 impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
diff --git a/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
new file mode 100644
index 0000000..fa4d6af
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.File;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Copied over from frameworks/base/core/java/com/android/internal/util/ArrayUtils.java
+ *
+ * @hide
+ */
+public class ArrayUtils {
+    private ArrayUtils() { /* cannot be instantiated */ }
+    public static final File[] EMPTY_FILE = new File[0];
+
+
+    /**
+     * Return first index of {@code value} in {@code array}, or {@code -1} if
+     * not found.
+     */
+    public static <T> int indexOf(@Nullable T[] array, T value) {
+        if (array == null) return -1;
+        for (int i = 0; i < array.length; i++) {
+            if (Objects.equals(array[i], value)) return i;
+        }
+        return -1;
+    }
+
+    /** @hide */
+    public static @NonNull File[] defeatNullable(@Nullable File[] val) {
+        return (val != null) ? val : EMPTY_FILE;
+    }
+
+    /**
+     * Checks if given array is null or has zero elements.
+     */
+    public static boolean isEmpty(@Nullable int[] array) {
+        return array == null || array.length == 0;
+    }
+
+    /**
+     * True if the byte array is null or has length 0.
+     */
+    public static boolean isEmpty(@Nullable byte[] array) {
+        return array == null || array.length == 0;
+    }
+
+    /**
+     * Converts from List of bytes to byte array
+     * @param list
+     * @return byte[]
+     */
+    public static byte[] toPrimitive(List<byte[]> list) {
+        if (list.size() == 0) {
+            return new byte[0];
+        }
+        int byteLen = list.get(0).length;
+        byte[] array = new byte[list.size() * byteLen];
+        for (int i = 0; i < list.size(); i++) {
+            for (int j = 0; j < list.get(i).length; j++) {
+                array[i * byteLen + j] = list.get(i)[j];
+            }
+        }
+        return array;
+    }
+
+    /**
+     * Adds value to given array if not already present, providing set-like
+     * behavior.
+     */
+    public static @NonNull int[] appendInt(@Nullable int[] cur, int val) {
+        return appendInt(cur, val, false);
+    }
+
+    /**
+     * Adds value to given array.
+     */
+    public static @NonNull int[] appendInt(@Nullable int[] cur, int val,
+            boolean allowDuplicates) {
+        if (cur == null) {
+            return new int[] { val };
+        }
+        final int n = cur.length;
+        if (!allowDuplicates) {
+            for (int i = 0; i < n; i++) {
+                if (cur[i] == val) {
+                    return cur;
+                }
+            }
+        }
+        int[] ret = new int[n + 1];
+        System.arraycopy(cur, 0, ret, 0, n);
+        ret[n] = val;
+        return ret;
+    }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
new file mode 100644
index 0000000..afcf689
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
@@ -0,0 +1,103 @@
+/*
+ *  * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.utils;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Thread for asynchronous event processing. This thread is configured as
+ * {@link android.os.Process#THREAD_PRIORITY_BACKGROUND}, which means fewer CPU
+ * resources will be dedicated to it, and it will "have less chance of impacting
+ * the responsiveness of the user interface."
+ * <p>
+ * This thread is best suited for tasks that the user is not actively waiting
+ * for, or for tasks that the user expects to be executed eventually.
+ *
+ * @see com.android.internal.os.BackgroundThread
+ *
+ * TODO: b/326916057 depend on modules-utils-backgroundthread instead
+ * @hide
+ */
+public final class BackgroundThread extends HandlerThread {
+    private static final Object sLock = new Object();
+
+    @GuardedBy("sLock")
+    private static BackgroundThread sInstance;
+    @GuardedBy("sLock")
+    private static Handler sHandler;
+    @GuardedBy("sLock")
+    private static HandlerExecutor sHandlerExecutor;
+
+    private BackgroundThread() {
+        super(BackgroundThread.class.getName(), android.os.Process.THREAD_PRIORITY_BACKGROUND);
+    }
+
+    @GuardedBy("sLock")
+    private static void ensureThreadLocked() {
+        if (sInstance == null) {
+            sInstance = new BackgroundThread();
+            sInstance.start();
+            sHandler = new Handler(sInstance.getLooper());
+            sHandlerExecutor = new HandlerExecutor(sHandler);
+        }
+    }
+
+    /**
+     * Get the singleton instance of this class.
+     *
+     * @return the singleton instance of this class
+     */
+    @NonNull
+    public static BackgroundThread get() {
+        synchronized (sLock) {
+            ensureThreadLocked();
+            return sInstance;
+        }
+    }
+
+    /**
+     * Get the singleton {@link Handler} for this class.
+     *
+     * @return the singleton {@link Handler} for this class.
+     */
+    @NonNull
+    public static Handler getHandler() {
+        synchronized (sLock) {
+            ensureThreadLocked();
+            return sHandler;
+        }
+    }
+
+    /**
+     * Get the singleton {@link Executor} for this class.
+     *
+     * @return the singleton {@link Executor} for this class.
+     */
+    @NonNull
+    public static Executor getExecutor() {
+        synchronized (sLock) {
+            ensureThreadLocked();
+            return sHandlerExecutor;
+        }
+    }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
new file mode 100644
index 0000000..e4923bf
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Bits and pieces copied from hidden API of android.os.FileUtils.
+ *
+ * @hide
+ */
+public class FileUtils {
+    /**
+     * Read a text file into a String, optionally limiting the length.
+     *
+     * @param file     to read (will not seek, so things like /proc files are OK)
+     * @param max      length (positive for head, negative of tail, 0 for no limit)
+     * @param ellipsis to add of the file was truncated (can be null)
+     * @return the contents of the file, possibly truncated
+     * @throws IOException if something goes wrong reading the file
+     * @hide
+     */
+    public static @Nullable String readTextFile(@Nullable File file, @Nullable int max,
+            @Nullable String ellipsis) throws IOException {
+        InputStream input = new FileInputStream(file);
+        // wrapping a BufferedInputStream around it because when reading /proc with unbuffered
+        // input stream, bytes read not equal to buffer size is not necessarily the correct
+        // indication for EOF; but it is true for BufferedInputStream due to its implementation.
+        BufferedInputStream bis = new BufferedInputStream(input);
+        try {
+            long size = file.length();
+            if (max > 0 || (size > 0 && max == 0)) {  // "head" mode: read the first N bytes
+                if (size > 0 && (max == 0 || size < max)) max = (int) size;
+                byte[] data = new byte[max + 1];
+                int length = bis.read(data);
+                if (length <= 0) return "";
+                if (length <= max) return new String(data, 0, length);
+                if (ellipsis == null) return new String(data, 0, max);
+                return new String(data, 0, max) + ellipsis;
+            } else if (max < 0) {  // "tail" mode: keep the last N
+                int len;
+                boolean rolled = false;
+                byte[] last = null;
+                byte[] data = null;
+                do {
+                    if (last != null) rolled = true;
+                    byte[] tmp = last;
+                    last = data;
+                    data = tmp;
+                    if (data == null) data = new byte[-max];
+                    len = bis.read(data);
+                } while (len == data.length);
+
+                if (last == null && len <= 0) return "";
+                if (last == null) return new String(data, 0, len);
+                if (len > 0) {
+                    rolled = true;
+                    System.arraycopy(last, len, last, 0, last.length - len);
+                    System.arraycopy(data, 0, last, last.length - len, len);
+                }
+                if (ellipsis == null || !rolled) return new String(last);
+                return ellipsis + new String(last);
+            } else {  // "cat" mode: size unknown, read it all in streaming fashion
+                ByteArrayOutputStream contents = new ByteArrayOutputStream();
+                int len;
+                byte[] data = new byte[1024];
+                do {
+                    len = bis.read(data);
+                    if (len > 0) contents.write(data, 0, len);
+                } while (len == data.length);
+                return contents.toString();
+            }
+        } finally {
+            bis.close();
+            input.close();
+        }
+    }
+
+    /**
+     * Perform an fsync on the given FileOutputStream. The stream at this
+     * point must be flushed but not yet closed.
+     *
+     * @hide
+     */
+    public static boolean sync(FileOutputStream stream) {
+        try {
+            if (stream != null) {
+                stream.getFD().sync();
+            }
+            return true;
+        } catch (IOException e) {
+        }
+        return false;
+    }
+
+    /**
+     * List the files in the directory or return empty file.
+     *
+     * @hide
+     */
+    public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
+        return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles())
+            : ArrayUtils.EMPTY_FILE;
+    }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
new file mode 100644
index 0000000..fdb15e2
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.utils;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+
+/**
+ * An adapter {@link Executor} that posts all executed tasks onto the given
+ * {@link Handler}.
+ *
+ * TODO: b/326916057 depend on modules-utils-backgroundthread instead
+ * @hide
+ */
+public class HandlerExecutor implements Executor {
+    private final Handler mHandler;
+
+    public HandlerExecutor(@NonNull Handler handler) {
+        mHandler = Objects.requireNonNull(handler);
+    }
+
+    @Override
+    public void execute(Runnable command) {
+        if (!mHandler.post(command)) {
+            throw new RejectedExecutionException(mHandler + " is shutting down");
+        }
+    }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
new file mode 100644
index 0000000..5cdc253
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.utils;
+
+import libcore.util.EmptyArray;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Copied from frameworks/base/core/java/android/util/LongArrayQueue.java
+ *
+ * @hide
+ */
+public class LongArrayQueue {
+
+    private long[] mValues;
+    private int mSize;
+    private int mHead;
+    private int mTail;
+
+    private long[] newUnpaddedLongArray(int num) {
+        return new long[num];
+    }
+    /**
+     * Initializes a queue with the given starting capacity.
+     *
+     * @param initialCapacity the capacity.
+     */
+    public LongArrayQueue(int initialCapacity) {
+        if (initialCapacity == 0) {
+            mValues = EmptyArray.LONG;
+        } else {
+            mValues = newUnpaddedLongArray(initialCapacity);
+        }
+        mSize = 0;
+        mHead = mTail = 0;
+    }
+
+    /**
+     * Initializes a queue with default starting capacity.
+     */
+    public LongArrayQueue() {
+        this(16);
+    }
+
+    /** @hide */
+    public static int growSize(int currentSize) {
+        return currentSize <= 4 ? 8 : currentSize * 2;
+    }
+
+    private void grow() {
+        if (mSize < mValues.length) {
+            throw new IllegalStateException("Queue not full yet!");
+        }
+        final int newSize = growSize(mSize);
+        final long[] newArray = newUnpaddedLongArray(newSize);
+        final int r = mValues.length - mHead; // Number of elements on and to the right of head.
+        System.arraycopy(mValues, mHead, newArray, 0, r);
+        System.arraycopy(mValues, 0, newArray, r, mHead);
+        mValues = newArray;
+        mHead = 0;
+        mTail = mSize;
+    }
+
+    /**
+     * Returns the number of elements in the queue.
+     */
+    public int size() {
+        return mSize;
+    }
+
+    /**
+     * Removes all elements from this queue.
+     */
+    public void clear() {
+        mSize = 0;
+        mHead = mTail = 0;
+    }
+
+    /**
+     * Adds a value to the tail of the queue.
+     *
+     * @param value the value to be added.
+     */
+    public void addLast(long value) {
+        if (mSize == mValues.length) {
+            grow();
+        }
+        mValues[mTail] = value;
+        mTail = (mTail + 1) % mValues.length;
+        mSize++;
+    }
+
+    /**
+     * Removes an element from the head of the queue.
+     *
+     * @return the element at the head of the queue.
+     * @throws NoSuchElementException if the queue is empty.
+     */
+    public long removeFirst() {
+        if (mSize == 0) {
+            throw new NoSuchElementException("Queue is empty!");
+        }
+        final long ret = mValues[mHead];
+        mHead = (mHead + 1) % mValues.length;
+        mSize--;
+        return ret;
+    }
+
+    /**
+     * Returns the element at the given position from the head of the queue, where 0 represents the
+     * head of the queue.
+     *
+     * @param position the position from the head of the queue.
+     * @return the element found at the given position.
+     * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or
+     *                                   {@code position} >= {@link #size()}
+     */
+    public long get(int position) {
+        if (position < 0 || position >= mSize) {
+            throw new IndexOutOfBoundsException("Index " + position
+                + " not valid for a queue of size " + mSize);
+        }
+        final int index = (mHead + position) % mValues.length;
+        return mValues[index];
+    }
+
+    /**
+     * Returns the element at the head of the queue, without removing it.
+     *
+     * @return the element at the head of the queue.
+     * @throws NoSuchElementException if the queue is empty
+     */
+    public long peekFirst() {
+        if (mSize == 0) {
+            throw new NoSuchElementException("Queue is empty!");
+        }
+        return mValues[mHead];
+    }
+
+    /**
+     * Returns the element at the tail of the queue.
+     *
+     * @return the element at the tail of the queue.
+     * @throws NoSuchElementException if the queue is empty.
+     */
+    public long peekLast() {
+        if (mSize == 0) {
+            throw new NoSuchElementException("Queue is empty!");
+        }
+        final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1;
+        return mValues[index];
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+        if (mSize <= 0) {
+            return "{}";
+        }
+
+        final StringBuilder buffer = new StringBuilder(mSize * 64);
+        buffer.append('{');
+        buffer.append(get(0));
+        for (int i = 1; i < mSize; i++) {
+            buffer.append(", ");
+            buffer.append(get(i));
+        }
+        buffer.append('}');
+        return buffer.toString();
+    }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
new file mode 100644
index 0000000..dbbef61
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.utils;
+
+import android.annotation.NonNull;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import com.android.modules.utils.TypedXmlPullParser;
+
+import libcore.util.XmlObjectFactory;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Copied over partly from frameworks/base/core/java/com/android/internal/util/XmlUtils.java
+ *
+ * @hide
+ */
+public class XmlUtils {
+
+    private static final String STRING_ARRAY_SEPARATOR = ":";
+
+    /** @hide */
+    public static final void beginDocument(XmlPullParser parser, String firstElementName)
+            throws XmlPullParserException, IOException {
+        int type;
+        while ((type = parser.next()) != parser.START_TAG
+            && type != parser.END_DOCUMENT) {
+            // Do nothing
+        }
+
+        if (type != parser.START_TAG) {
+            throw new XmlPullParserException("No start tag found");
+        }
+
+        if (!parser.getName().equals(firstElementName)) {
+            throw new XmlPullParserException("Unexpected start tag: found " + parser.getName()
+                + ", expected " + firstElementName);
+        }
+    }
+
+    /** @hide */
+    public static boolean nextElementWithin(XmlPullParser parser, int outerDepth)
+            throws IOException, XmlPullParserException {
+        for (;;) {
+            int type = parser.next();
+            if (type == XmlPullParser.END_DOCUMENT
+                    || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) {
+                return false;
+            }
+            if (type == XmlPullParser.START_TAG
+                    && parser.getDepth() == outerDepth + 1) {
+                return true;
+            }
+        }
+    }
+
+    private static XmlPullParser newPullParser() {
+        try {
+            XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
+            parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
+            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+            return parser;
+        } catch (XmlPullParserException e) {
+            throw new AssertionError();
+        }
+    }
+
+    /** @hide */
+    public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in)
+            throws IOException {
+        final byte[] magic = new byte[4];
+        if (in instanceof FileInputStream) {
+            try {
+                Os.pread(((FileInputStream) in).getFD(), magic, 0, magic.length, 0);
+            } catch (ErrnoException e) {
+                throw e.rethrowAsIOException();
+            }
+        } else {
+            if (!in.markSupported()) {
+                in = new BufferedInputStream(in);
+            }
+            in.mark(8);
+            in.read(magic);
+            in.reset();
+        }
+
+        final TypedXmlPullParser xml;
+        xml = (TypedXmlPullParser) newPullParser();
+        try {
+            xml.setInput(in, "UTF_8");
+        } catch (XmlPullParserException e) {
+            throw new IOException(e);
+        }
+        return xml;
+    }
+}
diff --git a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one_dark.xml b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_middle.xml
similarity index 60%
rename from packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one_dark.xml
rename to packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_middle.xml
index 39f49ca..781373d 100644
--- a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one_dark.xml
+++ b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_middle.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2023 The Android Open Source Project
+  ~ Copyright (C) 2024 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -15,16 +15,10 @@
   ~ limitations under the License.
   -->
 
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi"
-        android:color="@android:color/transparent">
-    <item
-        android:bottom="1dp"
-        android:shape="rectangle"
-        android:top="1dp">
-        <shape>
-            <corners android:radius="28dp" />
-            <solid android:color="@android:color/system_surface_container_high_dark" />
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+        xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi">
+        <shape android:shape="rectangle">
+            <solid android:color="?androidprv:attr/materialColorSurfaceContainer" />
         </shape>
-    </item>
-</ripple>
\ No newline at end of file
+</inset>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
index f13402c..f28c354 100644
--- a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
+++ b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
@@ -15,16 +15,12 @@
   ~ limitations under the License.
   -->
 
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi"
-        android:color="@android:color/transparent">
-    <item
-        android:bottom="1dp"
-        android:shape="rectangle"
-        android:top="1dp">
-        <shape>
-            <corners android:radius="4dp" />
-            <solid android:color="@color/dropdown_container" />
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+        xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi">
+        <shape android:shape="rectangle">
+            <corners android:topLeftRadius="4dp" android:topRightRadius="4dp"
+            android:bottomLeftRadius="0dp" android:bottomRightRadius="0dp"/>
+            <solid android:color="?androidprv:attr/materialColorSurfaceContainer" />
         </shape>
-    </item>
-</ripple>
\ No newline at end of file
+</inset>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_passkey_24.xml b/packages/CredentialManager/res/drawable/ic_passkey_24.xml
index a2c4f37..5298f9e 100644
--- a/packages/CredentialManager/res/drawable/ic_passkey_24.xml
+++ b/packages/CredentialManager/res/drawable/ic_passkey_24.xml
@@ -13,16 +13,14 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-<vector
-    android:alpha="0.8"
-    android:height="24dp"
-    android:viewportHeight="24"
-    android:viewportWidth="24"
-    android:width="24dp"
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools">
-  <path android:fillColor="#4C463C" android:fillType="evenOdd" android:pathData="M22.18,14.09C22.18,15.364 21.408,16.459 20.306,16.931L21.247,17.872L20.219,18.9L21.247,19.928L19.068,22.107L18.099,21.138L18.099,17.017C16.878,16.604 16,15.45 16,14.09C16,12.383 17.383,11 19.09,11C20.796,11 22.18,12.383 22.18,14.09ZM20.692,14.091C20.692,14.976 19.975,15.693 19.09,15.693C18.205,15.693 17.488,14.976 17.488,14.091C17.488,13.206 18.205,12.488 19.09,12.488C19.975,12.488 20.692,13.206 20.692,14.091Z"/>
-  <path android:fillColor="#4C463C" android:pathData="M14.978,8.476C14.978,10.865 13.041,12.802 10.652,12.802C8.263,12.802 6.326,10.865 6.326,8.476C6.326,6.087 8.263,4.15 10.652,4.15C13.041,4.15 14.978,6.087 14.978,8.476Z"/>
-  <path android:fillColor="#4C463C" android:pathData="M2,19.263C2,16.39 7.762,14.937 10.652,14.937C11.782,14.937 13.353,15.16 14.845,15.602C15.177,16.491 15.804,17.236 16.607,17.717V21.454H2V19.263Z"/>
+<!--LINT.IfChange-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="960"
+        android:viewportHeight="960">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M120,800L120,688Q120,654 137.5,625.5Q155,597 184,582Q246,551 310,535.5Q374,520 440,520Q460,520 480,521.5Q500,523 520,526Q516,584 541,635.5Q566,687 614,720L614,800L120,800ZM760,920L700,860L700,674Q656,661 628,624.5Q600,588 600,540Q600,482 641,441Q682,400 740,400Q798,400 839,441Q880,482 880,540Q880,585 854.5,620Q829,655 790,670L840,720L780,780L840,840L760,920ZM440,480Q374,480 327,433Q280,386 280,320Q280,254 327,207Q374,160 440,160Q506,160 553,207Q600,254 600,320Q600,386 553,433Q506,480 440,480ZM740,560Q757,560 768.5,548.5Q780,537 780,520Q780,503 768.5,491.5Q757,480 740,480Q723,480 711.5,491.5Q700,503 700,520Q700,537 711.5,548.5Q723,560 740,560Z"/>
 </vector>
+<!--LINT.ThenChange(/packages/CredentialManager/shared/res/drawable/ic_passkey_24.xml)-->
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/more_options_list_item.xml b/packages/CredentialManager/res/drawable/more_options_list_item.xml
index d7b509e..3f9d815 100644
--- a/packages/CredentialManager/res/drawable/more_options_list_item.xml
+++ b/packages/CredentialManager/res/drawable/more_options_list_item.xml
@@ -15,17 +15,16 @@
   ~ limitations under the License.
   -->
 
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi"
-        android:color="@android:color/transparent">
-    <item
-        android:bottom="1dp"
-        android:shape="rectangle"
-        android:top="1dp">
-        <shape>
-            <corners android:bottomLeftRadius="4dp"
-                     android:bottomRightRadius="4dp"/>
-            <solid android:color="@color/sign_in_options_container" />
-        </shape>
-    </item>
-</ripple>
\ No newline at end of file
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi"
+    android:insetLeft="-2dp"
+    android:insetRight="-2dp"
+    android:insetBottom="-2dp">
+    <shape>
+        <corners android:topLeftRadius="0dp" android:topRightRadius="0dp"
+            android:bottomLeftRadius="4dp" android:bottomRightRadius="4dp"/>
+        <solid android:color="?androidprv:attr/materialColorSurfaceContainer"/>
+        <stroke android:color="?androidprv:attr/materialColorOutlineVariant" android:width="1dp"/>
+    </shape>
+</inset>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
index 910ff96..7f09dd5 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
@@ -14,30 +14,52 @@
   ~ limitations under the License.
   -->
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
                 android:id="@android:id/content"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:minHeight="@dimen/dropdown_touch_target_min_height"
                 android:orientation="horizontal"
-                android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
                 android:elevation="3dp">
 
-    <ImageView
-        android:id="@android:id/icon1"
+    <LinearLayout
+        android:id="@+id/credential_card"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_centerVertical="true"
-        android:layout_alignParentStart="true"
-        android:contentDescription="@string/more_options_content_description"
-        android:background="@null"/>
-    <TextView
-        android:id="@android:id/text1"
-        android:layout_width="wrap_content"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@android:id/icon1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:paddingLeft="@dimen/autofill_view_left_padding"
+            android:src="@drawable/more_horiz_24px"
+            android:tint="?androidprv:attr/materialColorOnSurface"
+            android:layout_alignParentStart="true"
+            android:contentDescription="@string/more_options_content_description"
+            android:background="@null"/>
+
+    <LinearLayout
+        android:id="@+id/text_container"
+        android:layout_width="@dimen/autofill_dropdown_textview_max_width"
         android:layout_height="wrap_content"
-        android:layout_alignParentTop="true"
-        android:layout_toEndOf="@android:id/icon1"
-        android:minWidth="@dimen/autofill_dropdown_textview_min_width"
-        android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
-        style="@style/autofill.TextTitle"/>
+        android:paddingLeft="@dimen/autofill_view_left_padding"
+        android:paddingRight="@dimen/autofill_view_right_padding"
+        android:paddingTop="@dimen/more_options_item_vertical_padding"
+        android:paddingBottom="@dimen/more_options_item_vertical_padding"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@android:id/text1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true"
+            android:textColor="?androidprv:attr/materialColorOnSurface"
+            android:layout_toEndOf="@android:id/icon1"
+            style="@style/autofill.TextTitle"/>
+    </LinearLayout>
+
+    </LinearLayout>
 
 </RelativeLayout>
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index 4bf0e99..08948d7 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -14,37 +14,60 @@
   ~ limitations under the License.
   -->
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
                 android:id="@android:id/content"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:minHeight="@dimen/dropdown_touch_target_min_height"
-                android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
                 android:elevation="3dp">
 
-        <ImageView
-            android:id="@android:id/icon1"
+        <LinearLayout
+            android:id="@+id/credential_card"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_centerVertical="true"
-            android:layout_alignParentStart="true"
-            android:background="@null"/>
-        <TextView
-            android:id="@android:id/text1"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_alignParentTop="true"
-            android:layout_toEndOf="@android:id/icon1"
-            android:minWidth="@dimen/autofill_dropdown_textview_min_width"
-            android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
-            style="@style/autofill.TextTitle"/>
-        <TextView
-            android:id="@android:id/text2"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_below="@android:id/text1"
-            android:layout_toEndOf="@android:id/icon1"
-            android:minWidth="@dimen/autofill_dropdown_textview_min_width"
-            android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
-            style="@style/autofill.TextSubtitle"/>
+            android:orientation="horizontal">
+
+                <ImageView
+                    android:id="@android:id/icon1"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:layout_alignParentStart="true"
+                    android:paddingLeft="@dimen/autofill_view_left_padding"
+                    app:tint="?androidprv:attr/materialColorOnSurface"
+                    android:background="@null"/>
+
+                <LinearLayout
+                    android:id="@+id/text_container"
+                    android:layout_width="@dimen/autofill_dropdown_textview_max_width"
+                    android:layout_height="wrap_content"
+                    android:paddingLeft="@dimen/autofill_view_left_padding"
+                    android:paddingRight="@dimen/autofill_view_right_padding"
+                    android:paddingTop="@dimen/autofill_view_top_padding"
+                    android:paddingBottom="@dimen/autofill_view_bottom_padding"
+                    android:orientation="vertical">
+
+                        <TextView
+                            android:id="@android:id/text1"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_alignParentTop="true"
+                            android:layout_toEndOf="@android:id/icon1"
+                            android:textColor="?androidprv:attr/materialColorOnSurface"
+                            style="@style/autofill.TextTitle"/>
+
+                        <TextView
+                            android:id="@android:id/text2"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_below="@android:id/text1"
+                            android:layout_toEndOf="@android:id/icon1"
+                            android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
+                            style="@style/autofill.TextSubtitle"/>
+
+                </LinearLayout>
+
+        </LinearLayout>
 
 </RelativeLayout>
diff --git a/packages/CredentialManager/res/values-af/strings.xml b/packages/CredentialManager/res/values-af/strings.xml
index 6c46ffc..5d9295b 100644
--- a/packages/CredentialManager/res/values-af/strings.xml
+++ b/packages/CredentialManager/res/values-af/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Wagwoorde sal steeds saam met toegangsleutels beskikbaar wees terwyl ons na ’n wagwoordlose toekoms beweeg."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Kies waar om jou <xliff:g id="CREATETYPES">%1$s</xliff:g> te stoor"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Kies ’n wagwoordbestuurder om jou inligting te stoor en volgende keer vinniger aan te meld"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Skep toegangsleutel vir <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Stoor wagwoord vir <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Wil jy toegangsleutel skep om by <xliff:g id="APPNAME">%1$s</xliff:g> aan te meld?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Wil jy wagwoord stoor om by <xliff:g id="APPNAME">%1$s</xliff:g> aan te meld?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Stoor aanmeldinligting vir <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"toegangsleutel"</string>
     <string name="password" msgid="6738570945182936667">"wagwoord"</string>
diff --git a/packages/CredentialManager/res/values-am/strings.xml b/packages/CredentialManager/res/values-am/strings.xml
index 6d65fe1..2e8021d 100644
--- a/packages/CredentialManager/res/values-am/strings.xml
+++ b/packages/CredentialManager/res/values-am/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"ወደ የይለፍ ቃል የሌለው ወደፊት ስንሄድ የይለፍ ቃላት ከይለፍ ቁልፎች ጎን ለጎን ይገኛሉ።"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"የእርስዎን <xliff:g id="CREATETYPES">%1$s</xliff:g> የት እንደሚያስቀምጡ ይምረጡ"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"መረጃዎን ለማስቀመጥ እና በቀጣይ ጊዜ በፍጥነት በመለያ ለመግባት የሚስጥር ቁልፍ አስተዳዳሪን ይምረጡ"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"ለ<xliff:g id="APPNAME">%1$s</xliff:g> የይለፍ ቁልፍ ይፈጠር?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"ለ<xliff:g id="APPNAME">%1$s</xliff:g> የይለፍ ቃል ይቀመጥ?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"ወደ <xliff:g id="APPNAME">%1$s</xliff:g> ለመግባት የይለፍ ቁልፍ ይፈጠር?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"ወደ <xliff:g id="APPNAME">%1$s</xliff:g> ለመግባት የይለፍ ቃል ይቀመጥ?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"ለ<xliff:g id="APPNAME">%1$s</xliff:g> የመግቢያ መረጃ ይቀመጥ?"</string>
     <string name="passkey" msgid="632353688396759522">"የይለፍ ቁልፍ"</string>
     <string name="password" msgid="6738570945182936667">"የይለፍ ቃል"</string>
diff --git a/packages/CredentialManager/res/values-ar/strings.xml b/packages/CredentialManager/res/values-ar/strings.xml
index 654d5b6..a2d328c 100644
--- a/packages/CredentialManager/res/values-ar/strings.xml
+++ b/packages/CredentialManager/res/values-ar/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"بينما ننطلق نحو مستقبل بدون كلمات مرور، ستظل كلمات المرور متوفّرة إلى جانب مفاتيح المرور."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"اختيار المكان الذي تريد حفظ <xliff:g id="CREATETYPES">%1$s</xliff:g> فيه"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"اختَر مدير كلمات مرور لحفظ معلوماتك وتسجيل الدخول بشكل أسرع في المرة القادمة."</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"هل تريد إنشاء مفتاح مرور لتطبيق \"<xliff:g id="APPNAME">%1$s</xliff:g>\"؟"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"هل تريد حفظ كلمة المرور لتطبيق \"<xliff:g id="APPNAME">%1$s</xliff:g>\"؟"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"هل تريد إنشاء مفتاح مرور لتسجيل الدخول إلى تطبيق <xliff:g id="APPNAME">%1$s</xliff:g>؟"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"هل تريد حفظ كلمة المرور لتسجيل الدخول إلى تطبيق <xliff:g id="APPNAME">%1$s</xliff:g>؟"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"هل تريد حفظ معلومات تسجيل الدخول لتطبيق \"<xliff:g id="APPNAME">%1$s</xliff:g>\"؟"</string>
     <string name="passkey" msgid="632353688396759522">"مفتاح المرور"</string>
     <string name="password" msgid="6738570945182936667">"كلمة المرور"</string>
diff --git a/packages/CredentialManager/res/values-as/strings.xml b/packages/CredentialManager/res/values-as/strings.xml
index 9ab41dd..3efcea8 100644
--- a/packages/CredentialManager/res/values-as/strings.xml
+++ b/packages/CredentialManager/res/values-as/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"আমি পাছৱৰ্ডবিহীন ভৱিষ্যতৰ দিশে আগবঢ়াৰ লগে লগে পাছকীৰ লগতে পাছৱৰ্ডসমূহো উপলব্ধ হ’ব।"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"আপোনাৰ <xliff:g id="CREATETYPES">%1$s</xliff:g> ক’ত ছেভ কৰিব লাগে সেয়া বাছনি কৰক"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"আপোনাৰ তথ্য ছেভ কৰি পৰৱৰ্তী সময়ত দ্ৰুতভাৱে ছাইন ইন কৰিবলৈ এটা পাছৱৰ্ড পৰিচালক বাছনি কৰক"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g>ৰ বাবে পাছকী সৃষ্টি কৰিবনে?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g>ৰ বাবে পাছৱৰ্ড ছেভ কৰিবনে?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g>ত ছাইন ইন কৰিবলৈ পাছকী সৃষ্টি কৰিবনে?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g>ত ছাইন ইন কৰিবলৈ পাছৱৰ্ড ছেভ কৰিবনে?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g>ৰ বাবে ছাইন ইনৰ তথ্য ছেভ কৰিবনে?"</string>
     <string name="passkey" msgid="632353688396759522">"পাছকী"</string>
     <string name="password" msgid="6738570945182936667">"পাছৱৰ্ড"</string>
diff --git a/packages/CredentialManager/res/values-az/strings.xml b/packages/CredentialManager/res/values-az/strings.xml
index 67efc8b..627e2c0 100644
--- a/packages/CredentialManager/res/values-az/strings.xml
+++ b/packages/CredentialManager/res/values-az/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Parolsuz gələcəyə doğru irəlilədikcə parollar hələ də açarlar ilə yanaşı əlçatan olacaq."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g> elementinin saxlanacağı yeri seçin"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Məlumatlarınızı yadda saxlamaq və növbəti dəfə daha sürətli daxil olmaq üçün parol meneceri seçin"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> üçün giriş açarı yaradılsın?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> üçün parol yadda saxlanılsın?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> tətbiqinə daxil olmaq üçün giriş açarı yaradılsın?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> tətbiqinə daxil olmaq üçün parol yadda saxlansın?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> üçün giriş məlumatları yadda saxlansın?"</string>
     <string name="passkey" msgid="632353688396759522">"açar"</string>
     <string name="password" msgid="6738570945182936667">"parol"</string>
diff --git a/packages/CredentialManager/res/values-b+sr+Latn/strings.xml b/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
index e01e8a3..c4111e4 100644
--- a/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
+++ b/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Kako se krećemo ka budućnosti bez lozinki, lozinke će i dalje biti dostupne uz pristupne kodove."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Odaberite gde ćete sačuvati: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Izaberite menadžera lozinki da biste sačuvali podatke i brže se prijavili sledeći put"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Želite da napravite pristupni kôd za: <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Želite da sačuvate lozinku za: <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Želite da napravite pristupni ključ da biste se prijavili u <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Želite da sačuvate lozinku da biste se prijavili u <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Želite da sačuvate podatke za prijavljivanje za: <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"pristupni kôd"</string>
     <string name="password" msgid="6738570945182936667">"lozinka"</string>
diff --git a/packages/CredentialManager/res/values-be/strings.xml b/packages/CredentialManager/res/values-be/strings.xml
index e7fe622..f970d16 100644
--- a/packages/CredentialManager/res/values-be/strings.xml
+++ b/packages/CredentialManager/res/values-be/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Хоць мы ўжо рухаемся ў бок будучыні без выкарыстання пароляў, яны па-ранейшаму застануцца даступнымі нароўні з ключамі доступу."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Выберыце, куды захаваць <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Выберыце менеджар пароляў, каб захаваць свае даныя і забяспечыць хуткі ўваход у наступныя разы"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Стварыце ключ доступу да праграмы \"<xliff:g id="APPNAME">%1$s</xliff:g>\""</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Захаваць пароль для праграмы \"<xliff:g id="APPNAME">%1$s</xliff:g>\"?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Стварыць ключ доступу для ўваходу ў праграму \"<xliff:g id="APPNAME">%1$s</xliff:g>\"?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Захаваць пароль для ўваходу ў праграму \"<xliff:g id="APPNAME">%1$s</xliff:g>\"?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Захаваць інфармацыю пра спосаб уваходу ў праграму \"<xliff:g id="APPNAME">%1$s</xliff:g>\"?"</string>
     <string name="passkey" msgid="632353688396759522">"ключ доступу"</string>
     <string name="password" msgid="6738570945182936667">"пароль"</string>
diff --git a/packages/CredentialManager/res/values-bg/strings.xml b/packages/CredentialManager/res/values-bg/strings.xml
index 9b5255ef..e3758ea3 100644
--- a/packages/CredentialManager/res/values-bg/strings.xml
+++ b/packages/CredentialManager/res/values-bg/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Паролите ще продължат да са налице заедно с ключовете за достъп по пътя ни към бъдеще без пароли."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Изберете къде да запазите своите <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Изберете мениджър на пароли, в който да се запазят данните ви, така че следващия път да влезете по-бързо в профила си"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Да се създаде ли код за достъп за <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Да се запази ли паролата за <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Искате ли да създадете ключ за достъп, с който да влизате в(ъв) <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Искате ли да запазите паролата, за да влизате в(ъв) <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Да се запазят ли данните за вход за <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"код за достъп"</string>
     <string name="password" msgid="6738570945182936667">"парола"</string>
diff --git a/packages/CredentialManager/res/values-bn/strings.xml b/packages/CredentialManager/res/values-bn/strings.xml
index 981431c..b6f9a88 100644
--- a/packages/CredentialManager/res/values-bn/strings.xml
+++ b/packages/CredentialManager/res/values-bn/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"আমরা পাসওয়ার্ডবিহীন ভবিষ্যতের দিকে এগিয়ে গেলেও, এখনও \'পাসকী\'-এর পাশাপাশি পাসওয়ার্ড ব্যবহার করা যাবে।"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"আপনার <xliff:g id="CREATETYPES">%1$s</xliff:g> কোথায় সেভ করবেন তা বেছে নিন"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"আপনার তথ্য সেভ করতে একটি Password Manager বেছে নিন এবং পরের বার আরও দ্রুত সাইন-ইন করুন"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g>-এর জন্য \'পাসকী\' তৈরি করবেন?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g>-এর জন্য পাসওয়ার্ড সেভ করবেন?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> অ্যাপে সাইন-ইন করার জন্য পাসকী তৈরি করবেন?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> অ্যাপে সাইন-ইন করার জন্য পাসওয়ার্ড সেভ করবেন?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g>-এর জন্য সাইন-ইন সংক্রান্ত তথ্য সেভ করবেন?"</string>
     <string name="passkey" msgid="632353688396759522">"পাসকী"</string>
     <string name="password" msgid="6738570945182936667">"পাসওয়ার্ড"</string>
diff --git a/packages/CredentialManager/res/values-bs/strings.xml b/packages/CredentialManager/res/values-bs/strings.xml
index 2e51625..6c00ac0 100644
--- a/packages/CredentialManager/res/values-bs/strings.xml
+++ b/packages/CredentialManager/res/values-bs/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Kako se krećemo prema budućnosti bez lozinki, lozinke će i dalje biti dostupne uz pristupne ključeve."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Odaberite gdje će se pohranjivati <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Odaberite upravitelja lozinki da sačuvate svoje informacije i brže se prijavite sljedeći put"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Kreirati pristupni ključ za aplikaciju <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Sačuvati lozinku za aplikaciju <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Kreirati pristupni ključ da se prijavite u aplikaciju <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Sačuvati lozinku da se prijavite u aplikaciju <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Sačuvati informacije o prijavi za aplikaciju <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"pristupni ključ"</string>
     <string name="password" msgid="6738570945182936667">"lozinka"</string>
diff --git a/packages/CredentialManager/res/values-ca/strings.xml b/packages/CredentialManager/res/values-ca/strings.xml
index 97d758c..0d0850f 100644
--- a/packages/CredentialManager/res/values-ca/strings.xml
+++ b/packages/CredentialManager/res/values-ca/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Tot i que avancem cap a un futur sense contrasenyes, continuaran estant disponibles juntament amb les claus d\'accés."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Tria on vols desar les <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Selecciona un gestor de contrasenyes per desar la teva informació i iniciar la sessió més ràpidament la pròxima vegada"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Vols crear la clau d\'accés per a <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Vols desar la contrasenya per a <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Vols crear una clau d\'accés per iniciar la sessió a <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Vols desar la contrasenya per iniciar la sessió a <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Vols desar la informació d\'inici de sessió per a <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"clau d\'accés"</string>
     <string name="password" msgid="6738570945182936667">"contrasenya"</string>
diff --git a/packages/CredentialManager/res/values-cs/strings.xml b/packages/CredentialManager/res/values-cs/strings.xml
index c317c92..d21afe7 100644
--- a/packages/CredentialManager/res/values-cs/strings.xml
+++ b/packages/CredentialManager/res/values-cs/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Ačkoliv směřujeme k budoucnosti bez hesel, vedle přístupových klíčů budou stále k dispozici i hesla."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Určete, kam ukládat <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Vyberte správce hesel k uložení svých údajů, abyste se příště mohli přihlásit rychleji"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Vytvořit přístupový klíč pro aplikaci <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Uložit heslo pro aplikaci <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Vytvořit přístupový klíč k přihlašování do aplikace <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Uložit heslo k přihlašování do aplikace <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Uložit přihlašovací údaje pro aplikaci <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"přístupový klíč"</string>
     <string name="password" msgid="6738570945182936667">"heslo"</string>
diff --git a/packages/CredentialManager/res/values-da/strings.xml b/packages/CredentialManager/res/values-da/strings.xml
index cc6b12a..c868a4b 100644
--- a/packages/CredentialManager/res/values-da/strings.xml
+++ b/packages/CredentialManager/res/values-da/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Selvom vi nærmer os en fremtid, hvor adgangskoder er mindre fremtrædende, kan de stadig bruges i samspil med adgangsnøgler."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Vælg, hvor du vil gemme dine <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Vælg en adgangskodeadministrator for at gemme dine oplysninger, så du kan logge ind hurtigere næste gang"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Vil du oprette en adgangsnøgle til <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Vil du gemme adgangskoden til <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Vil du oprette en adgangsnøgle for at logge ind på <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Vil du gemme adgangskoden for at logge ind på <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Vil du gemme loginoplysningerne til <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"adgangsnøgle"</string>
     <string name="password" msgid="6738570945182936667">"adgangskode"</string>
diff --git a/packages/CredentialManager/res/values-de/strings.xml b/packages/CredentialManager/res/values-de/strings.xml
index 0dbde65..4fba522 100644
--- a/packages/CredentialManager/res/values-de/strings.xml
+++ b/packages/CredentialManager/res/values-de/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Auch wenn wir uns auf eine passwortlose Zukunft zubewegen, werden neben Passkeys weiter Passwörter verfügbar sein."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Wähle aus, wo deine <xliff:g id="CREATETYPES">%1$s</xliff:g> gespeichert werden sollen"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Du kannst einen Passwortmanager auswählen, um deine Anmeldedaten zu speichern, damit du dich nächstes Mal schneller anmelden kannst"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Passkey für <xliff:g id="APPNAME">%1$s</xliff:g> erstellen?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Passwort für <xliff:g id="APPNAME">%1$s</xliff:g> speichern?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Passkey zur Anmeldung in <xliff:g id="APPNAME">%1$s</xliff:g> erstellen?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Passwort zur Anmeldung in <xliff:g id="APPNAME">%1$s</xliff:g> speichern?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Anmeldedaten für <xliff:g id="APPNAME">%1$s</xliff:g> speichern?"</string>
     <string name="passkey" msgid="632353688396759522">"Passkey"</string>
     <string name="password" msgid="6738570945182936667">"Passwort"</string>
diff --git a/packages/CredentialManager/res/values-el/strings.xml b/packages/CredentialManager/res/values-el/strings.xml
index c2a2863..ad6a424 100644
--- a/packages/CredentialManager/res/values-el/strings.xml
+++ b/packages/CredentialManager/res/values-el/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Καθώς κινούμαστε προς ένα μέλλον χωρίς κωδικούς πρόσβασης, οι κωδικοί πρόσβασης θα εξακολουθούν να είναι διαθέσιμοι μαζί με τα κλειδιά πρόσβασης."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Επιλέξτε πού θα αποθηκεύονται τα <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Επιλέξτε ένα πρόγραμμα διαχείρισης κωδικών πρόσβασης για να αποθηκεύσετε τα στοιχεία σας και να συνδεθείτε πιο γρήγορα την επόμενη φορά."</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Δημιουργία κλειδιού πρόσβασης για <xliff:g id="APPNAME">%1$s</xliff:g>;"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Αποθήκευση κωδικού πρόσβασης για <xliff:g id="APPNAME">%1$s</xliff:g>;"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Δημιουργία κλειδιού πρόσβασης για σύνδεση στην εφαρμογή <xliff:g id="APPNAME">%1$s</xliff:g>;"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Αποθήκευση κωδικού πρόσβασης για σύνδεση στην εφαρμογή <xliff:g id="APPNAME">%1$s</xliff:g>;"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Αποθήκευση στοιχείων σύνδεσης για <xliff:g id="APPNAME">%1$s</xliff:g>;"</string>
     <string name="passkey" msgid="632353688396759522">"κλειδί πρόσβασης"</string>
     <string name="password" msgid="6738570945182936667">"κωδικός πρόσβασης"</string>
diff --git a/packages/CredentialManager/res/values-en-rAU/strings.xml b/packages/CredentialManager/res/values-en-rAU/strings.xml
index 7ae0583..6aa1b5e 100644
--- a/packages/CredentialManager/res/values-en-rAU/strings.xml
+++ b/packages/CredentialManager/res/values-en-rAU/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"As we move towards a passwordless future, passwords will still be available alongside passkeys."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Choose where to save your <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Select a password manager to save your info and sign in faster next time"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Create passkey for <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Save password for <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Create passkey to sign in to <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Save password to sign in to <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Save sign-in info for <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"passkey"</string>
     <string name="password" msgid="6738570945182936667">"password"</string>
diff --git a/packages/CredentialManager/res/values-en-rCA/strings.xml b/packages/CredentialManager/res/values-en-rCA/strings.xml
index 195f12f..10d95993 100644
--- a/packages/CredentialManager/res/values-en-rCA/strings.xml
+++ b/packages/CredentialManager/res/values-en-rCA/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"As we move towards a passwordless future, passwords will still be available alongside passkeys."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Choose where to save your <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Select a password manager to save your info and sign in faster next time"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Create passkey for <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Save password for <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Create passkey to sign in to <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Save password to sign in to <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Save sign-in info for <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"passkey"</string>
     <string name="password" msgid="6738570945182936667">"password"</string>
diff --git a/packages/CredentialManager/res/values-en-rGB/strings.xml b/packages/CredentialManager/res/values-en-rGB/strings.xml
index 7ae0583..6aa1b5e 100644
--- a/packages/CredentialManager/res/values-en-rGB/strings.xml
+++ b/packages/CredentialManager/res/values-en-rGB/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"As we move towards a passwordless future, passwords will still be available alongside passkeys."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Choose where to save your <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Select a password manager to save your info and sign in faster next time"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Create passkey for <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Save password for <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Create passkey to sign in to <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Save password to sign in to <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Save sign-in info for <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"passkey"</string>
     <string name="password" msgid="6738570945182936667">"password"</string>
diff --git a/packages/CredentialManager/res/values-en-rIN/strings.xml b/packages/CredentialManager/res/values-en-rIN/strings.xml
index 7ae0583..6aa1b5e 100644
--- a/packages/CredentialManager/res/values-en-rIN/strings.xml
+++ b/packages/CredentialManager/res/values-en-rIN/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"As we move towards a passwordless future, passwords will still be available alongside passkeys."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Choose where to save your <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Select a password manager to save your info and sign in faster next time"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Create passkey for <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Save password for <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Create passkey to sign in to <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Save password to sign in to <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Save sign-in info for <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"passkey"</string>
     <string name="password" msgid="6738570945182936667">"password"</string>
diff --git a/packages/CredentialManager/res/values-en-rXC/strings.xml b/packages/CredentialManager/res/values-en-rXC/strings.xml
index 5949607..16c0d1d 100644
--- a/packages/CredentialManager/res/values-en-rXC/strings.xml
+++ b/packages/CredentialManager/res/values-en-rXC/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‎‏‏‏‎‎‎‎‏‏‎‎‏‏‏‏‏‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‎‏‏‎‎‏‏‏‎‏‏‏‎‎‏‏‎‎‎‎‏‏‎As we move towards a passwordless future, passwords will still be available alongside passkeys.‎‏‎‎‏‎"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‏‎‏‏‎‎‏‎‏‏‎‏‎‏‎‏‏‎‎‏‏‎‏‎‎‎‎‏‎‎‎‏‏‎‏‎‏‎‏‏‏‎‎‎‎‏‏‎‎‎Choose where to save your ‎‏‎‎‏‏‎<xliff:g id="CREATETYPES">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‏‎‏‏‏‎‏‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‎‏‏‏‎‎‎‎‏‎‏‎‎‎‎‏‎‏‏‎‎‏‎‏‎‏‎‎Select a password manager to save your info and sign in faster next time‎‏‎‎‏‎"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‏‏‏‎‏‎‎‏‎‏‎‎‏‎‎‏‏‏‎‏‎‎‎‎‏‎‏‏‎‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‎‏‎‎‎‏‏‏‎‎Create passkey for ‎‏‎‎‏‏‎<xliff:g id="APPNAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‏‎‎‎‎‎‏‎‏‏‎‏‏‎‏‎‎‎‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‏‎‎‏‏‏‏‏‏‎Save password for ‎‏‎‎‏‏‎<xliff:g id="APPNAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‎‎‎‎‎‏‎‏‏‏‏‎‏‏‎‎‏‎‏‎‎‎‎‏‏‏‎‎‏‎‎‏‏‎‎‏‎‎‏‏‏‎‎‏‎‎‏‏‏‏‏‎Create passkey to sign in to ‎‏‎‎‏‏‎<xliff:g id="APPNAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‎‎‏‏‎‏‏‎‏‎‏‎‎‎‎‏‏‎‏‎‏‎‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‏‎‏‏‎‎‏‏‎‎‏‏‎‎‎Save password to sign in to ‎‏‎‎‏‏‎<xliff:g id="APPNAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‏‏‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‏‎‏‏‎‎‏‏‎‎‏‏‎‎‎‎‏‎‏‏‎‎‏‎‏‏‎‎‎‎‎‏‎Save sign-in info for ‎‏‎‎‏‏‎<xliff:g id="APPNAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
     <string name="passkey" msgid="632353688396759522">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‏‎‎‎‏‏‎‏‎‎‏‎‎‏‎‎‏‎‏‎‏‎‎‎‏‎‎‏‎‎‏‎‏‎‎‏‏‎‎‎‎‎‎‎‎‎‏‏‏‏‎‎‎‏‎‎passkey‎‏‎‎‏‎"</string>
     <string name="password" msgid="6738570945182936667">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‎‏‎‎‎‎‏‏‎‏‏‎‏‎‎‏‎‎‏‎‎‏‏‏‏‎‎‏‎‏‏‎‎‏‎‏‏‎‏‎‏‏‏‎‎‏‎‏‏‎‏‏‎password‎‏‎‎‏‎"</string>
diff --git a/packages/CredentialManager/res/values-es-rUS/strings.xml b/packages/CredentialManager/res/values-es-rUS/strings.xml
index f42693e..6bab1ae 100644
--- a/packages/CredentialManager/res/values-es-rUS/strings.xml
+++ b/packages/CredentialManager/res/values-es-rUS/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"A medida que avanzamos hacia un futuro sin contraseñas, estas seguirán estando disponibles junto a las llaves de acceso."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Elige dónde guardar tus <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Selecciona un administrador de contraseñas para guardar tu información y acceder más rápido la próxima vez"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"¿Quieres crear una llave de acceso para <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"¿Quieres guardar la contraseña para <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"¿Quieres crear una llave de acceso para acceder a <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"¿Quieres guardar la contraseña para acceder a <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"¿Quieres guardar la información de acceso para <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"llave de acceso"</string>
     <string name="password" msgid="6738570945182936667">"contraseña"</string>
diff --git a/packages/CredentialManager/res/values-es/strings.xml b/packages/CredentialManager/res/values-es/strings.xml
index 7e16e20..a727f458 100644
--- a/packages/CredentialManager/res/values-es/strings.xml
+++ b/packages/CredentialManager/res/values-es/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Aunque nos dirigimos hacia un mundo sin contraseñas, estas seguirán estando disponibles junto con las llaves de acceso."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Elige dónde guardar tus <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Selecciona un gestor de contraseñas para guardar tu información e iniciar sesión más rápido la próxima vez"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"¿Crear llave de acceso para <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"¿Guardar la contraseña de <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"¿Crear llave de acceso para iniciar sesión en <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"¿Guardar contraseña para iniciar sesión en <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"¿Guardar la información de inicio de sesión de <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"llave de acceso"</string>
     <string name="password" msgid="6738570945182936667">"contraseña"</string>
diff --git a/packages/CredentialManager/res/values-et/strings.xml b/packages/CredentialManager/res/values-et/strings.xml
index 0ae48b4..2727612 100644
--- a/packages/CredentialManager/res/values-et/strings.xml
+++ b/packages/CredentialManager/res/values-et/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Liikudes paroolivaba tuleviku poole, jäävad paroolid pääsuvõtmete kõrval siiski kättesaadavaks."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Valige, kuhu soovite oma <xliff:g id="CREATETYPES">%1$s</xliff:g> salvestada"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Valige paroolihaldur, et salvestada oma teave ja järgmisel korral kiiremini sisse logida"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Kas luua rakenduse <xliff:g id="APPNAME">%1$s</xliff:g> jaoks pääsuvõti?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Kas salvestada rakenduse <xliff:g id="APPNAME">%1$s</xliff:g> jaoks parool?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Kas luua rakendusse <xliff:g id="APPNAME">%1$s</xliff:g> sisselogimiseks pääsuvõti?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Kas salvestada rakendusse <xliff:g id="APPNAME">%1$s</xliff:g> sisselogimiseks parool?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Kas salvestada rakenduse <xliff:g id="APPNAME">%1$s</xliff:g> jaoks sisselogimisandmed?"</string>
     <string name="passkey" msgid="632353688396759522">"pääsuvõti"</string>
     <string name="password" msgid="6738570945182936667">"parool"</string>
diff --git a/packages/CredentialManager/res/values-eu/strings.xml b/packages/CredentialManager/res/values-eu/strings.xml
index 8775c87..f0debcc 100644
--- a/packages/CredentialManager/res/values-eu/strings.xml
+++ b/packages/CredentialManager/res/values-eu/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Pasahitzik gabeko etorkizun baterantz goazen arren, pasahitzek sarbide-gakoen bizikide izaten jarraituko dute."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Aukeratu non gorde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Hautatu informazioa gordetzeko pasahitz-kudeatzaile bat eta hasi saioa bizkorrago hurrengoan"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> atzitzeko sarbide-gako bat sortu nahi duzu?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> aplikazioko pasahitza gorde nahi duzu?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> aplikazioan saioa hasteko sarbide-gako bat sortu nahi duzu?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> aplikazioan saioa hasteko pasahitza gorde nahi duzu?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> aplikazioko saioa hasteko informazioa gorde nahi duzu?"</string>
     <string name="passkey" msgid="632353688396759522">"sarbide-gakoa"</string>
     <string name="password" msgid="6738570945182936667">"pasahitza"</string>
diff --git a/packages/CredentialManager/res/values-fa/strings.xml b/packages/CredentialManager/res/values-fa/strings.xml
index 381c61e..a88b353 100644
--- a/packages/CredentialManager/res/values-fa/strings.xml
+++ b/packages/CredentialManager/res/values-fa/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"درحالی‌که به‌سوی آینده‌ای بی‌گذرواژه حرکت می‌کنیم، گذرواژه‌ها همچنان در کنار گذرکلیدها دردسترس خواهند بود"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"جایی را برای ذخیره کردن <xliff:g id="CREATETYPES">%1$s</xliff:g> انتخاب کنید"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"مدیر گذرواژه‌ای انتخاب کنید تا اطلاعاتتان را ذخیره کنید و دفعه بعد سریع‌تر به سیستم وارد شوید"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"برای <xliff:g id="APPNAME">%1$s</xliff:g> گذرکلید ایجاد شود؟"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"گذرواژه <xliff:g id="APPNAME">%1$s</xliff:g> ذخیره شود؟"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"برای ورود به سیستم <xliff:g id="APPNAME">%1$s</xliff:g>، گذرکلید ایجاد شود؟"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"برای ورود به سیستم <xliff:g id="APPNAME">%1$s</xliff:g>، گذرواژه ذخیره شود؟"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"اطلاعات ورود به سیستم <xliff:g id="APPNAME">%1$s</xliff:g> ذخیره شود؟"</string>
     <string name="passkey" msgid="632353688396759522">"گذرکلید"</string>
     <string name="password" msgid="6738570945182936667">"گذرواژه"</string>
diff --git a/packages/CredentialManager/res/values-fi/strings.xml b/packages/CredentialManager/res/values-fi/strings.xml
index 3c1c596..82af70f 100644
--- a/packages/CredentialManager/res/values-fi/strings.xml
+++ b/packages/CredentialManager/res/values-fi/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Kehitys kulkee kohti salasanatonta tulevaisuutta, mutta salasanat ovat edelleen käytettävissä avainkoodien ohella."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Valitse, minne <xliff:g id="CREATETYPES">%1$s</xliff:g> tallennetaan"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Valitse salasanojen ylläpitotyökalu, niin voit tallentaa tietosi ja kirjautua ensi kerralla nopeammin sisään"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Luodaanko avainkoodi (<xliff:g id="APPNAME">%1$s</xliff:g>)?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Tallennetaanko salasana (<xliff:g id="APPNAME">%1$s</xliff:g>)?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Luodaanko avainkoodi sisäänkirjautumista (<xliff:g id="APPNAME">%1$s</xliff:g>) varten?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Tallennetaanko salasana sisäänkirjautumista (<xliff:g id="APPNAME">%1$s</xliff:g>) varten?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Tallennetaanko kirjautumistiedot (<xliff:g id="APPNAME">%1$s</xliff:g>)?"</string>
     <string name="passkey" msgid="632353688396759522">"avainkoodi"</string>
     <string name="password" msgid="6738570945182936667">"salasana"</string>
diff --git a/packages/CredentialManager/res/values-fr-rCA/strings.xml b/packages/CredentialManager/res/values-fr-rCA/strings.xml
index ff95af2..61f2204 100644
--- a/packages/CredentialManager/res/values-fr-rCA/strings.xml
+++ b/packages/CredentialManager/res/values-fr-rCA/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"À mesure que nous nous dirigeons vers un avenir sans mot de passe, ils resteront toujours utilisés parallèlement aux clés d\'accès."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Choisir où enregistrer vos <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Sélectionnez un gestionnaire de mots de passe pour enregistrer vos renseignements et vous connecter plus rapidement la prochaine fois"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Créer une clé d\'accès pour <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Enregistrer le mot de passe pour <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Créer une clé d\'accès pour se connecter à <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Enregistrer un mot de passe pour se connecter à <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Enregistrer les renseignements de connexion pour <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"clé d\'accès"</string>
     <string name="password" msgid="6738570945182936667">"mot de passe"</string>
diff --git a/packages/CredentialManager/res/values-fr/strings.xml b/packages/CredentialManager/res/values-fr/strings.xml
index d393912..15715f3 100644
--- a/packages/CredentialManager/res/values-fr/strings.xml
+++ b/packages/CredentialManager/res/values-fr/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Nous nous dirigeons vers un futur sans mots de passe, mais ceux-ci resteront disponibles en plus des clés d\'accès."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Choisissez où enregistrer vos <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Sélectionnez un gestionnaire de mots de passe pour enregistrer vos informations et vous connecter plus rapidement la prochaine fois"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Créer une clé d\'accès pour <xliff:g id="APPNAME">%1$s</xliff:g> ?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Enregistrer le mot de passe pour <xliff:g id="APPNAME">%1$s</xliff:g> ?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Créer une clé d\'accès pour se connecter à <xliff:g id="APPNAME">%1$s</xliff:g> ?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Enregistrer un mot de passe pour se connecter à <xliff:g id="APPNAME">%1$s</xliff:g> ?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Enregistrer les informations de connexion pour <xliff:g id="APPNAME">%1$s</xliff:g> ?"</string>
     <string name="passkey" msgid="632353688396759522">"clé d\'accès"</string>
     <string name="password" msgid="6738570945182936667">"mot de passe"</string>
diff --git a/packages/CredentialManager/res/values-gl/strings.xml b/packages/CredentialManager/res/values-gl/strings.xml
index d3c8222..1ca0774 100644
--- a/packages/CredentialManager/res/values-gl/strings.xml
+++ b/packages/CredentialManager/res/values-gl/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">"Xestor de credenciais"</string>
+    <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
     <string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
     <string name="string_more_options" msgid="2763852250269945472">"Gardar doutro xeito"</string>
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Durante este percorrido cara a un futuro sen contrasinais, estes seguirán estando dispoñibles a canda as claves de acceso."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Escolle onde queres gardar: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Selecciona un xestor de contrasinais para gardar a túa información e iniciar sesión máis rápido a próxima vez"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Queres crear unha clave de acceso para <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Queres gardar o contrasinal de <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Queres crear unha clave de acceso para iniciar sesión en <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Queres gardar o contrasinal para iniciar sesión en <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Queres gardar a información de inicio de sesión de <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"clave de acceso"</string>
     <string name="password" msgid="6738570945182936667">"contrasinal"</string>
diff --git a/packages/CredentialManager/res/values-gu/strings.xml b/packages/CredentialManager/res/values-gu/strings.xml
index 3d4da84..7d8df9a 100644
--- a/packages/CredentialManager/res/values-gu/strings.xml
+++ b/packages/CredentialManager/res/values-gu/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"આપણે પાસવર્ડ રહિત ભવિષ્ય તરફ આગળ વધી રહ્યાં છીએ, છતાં પાસકીની સાથોસાથ હજી પણ પાસવર્ડ ઉપલબ્ધ રહેશે."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"તમારી <xliff:g id="CREATETYPES">%1$s</xliff:g> ક્યાં સાચવવી તે પસંદ કરો"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"તમારી માહિતી સાચવવા માટે પાસવર્ડ મેનેજર પસંદ કરો અને આગલી વખતે વધુ ઝડપથી સાઇન ઇન કરો"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> માટે પાસકી બનાવીએ?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> માટે પાસવર્ડ સાચવીએ?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g>માં સાઇન ઇન કરવા માટે પાસકી બનાવીએ?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g>માં પાસવર્ડ સાચવવા માટે પાસકી બનાવીએ?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> માટે સાઇન-ઇન કરવાની માહિતી સાચવીએ?"</string>
     <string name="passkey" msgid="632353688396759522">"પાસકી"</string>
     <string name="password" msgid="6738570945182936667">"પાસવર્ડ"</string>
diff --git a/packages/CredentialManager/res/values-hi/strings.xml b/packages/CredentialManager/res/values-hi/strings.xml
index 672a852..b644864 100644
--- a/packages/CredentialManager/res/values-hi/strings.xml
+++ b/packages/CredentialManager/res/values-hi/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"आने वाले समय में बिना पासवर्ड वाली टेक्नोलॉजी यानी पासकी का इस्तेमाल बढ़ेगा, हालांकि इसके साथ-साथ पासवर्ड भी इस्तेमाल किए जा सकेंगे."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"चुनें कि अपनी <xliff:g id="CREATETYPES">%1$s</xliff:g> कहां सेव करनी हैं"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"अपनी जानकारी सेव करने के लिए, पासवर्ड मैनेजर चुनें और अगली बार ज़्यादा तेज़ी से साइन इन करें"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"क्या आपको <xliff:g id="APPNAME">%1$s</xliff:g> के लिए पासकी बनानी है?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"क्या आपको <xliff:g id="APPNAME">%1$s</xliff:g> के लिए पासवर्ड सेव करना है?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"क्या आपको <xliff:g id="APPNAME">%1$s</xliff:g> में साइन इन करने के लिए पासकी बनानी है?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"क्या आपको <xliff:g id="APPNAME">%1$s</xliff:g> में साइन इन करने के लिए पासवर्ड सेव करना है?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"क्या आपको <xliff:g id="APPNAME">%1$s</xliff:g> के लिए साइन-इन की जानकारी सेव करनी है?"</string>
     <string name="passkey" msgid="632353688396759522">"पासकी"</string>
     <string name="password" msgid="6738570945182936667">"पासवर्ड"</string>
diff --git a/packages/CredentialManager/res/values-hr/strings.xml b/packages/CredentialManager/res/values-hr/strings.xml
index 525ade3..86d52d5 100644
--- a/packages/CredentialManager/res/values-hr/strings.xml
+++ b/packages/CredentialManager/res/values-hr/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Kako idemo u smjeru budućnosti bez zaporki, one će i dalje biti dostupne uz pristupne ključeve."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Odaberite gdje će se spremati <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Odaberite upravitelja zaporki kako biste spremili svoje informacije i drugi se put brže prijavili"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Želite li izraditi pristupni ključ za <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Spremiti zaporku za <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Želite li izraditi pristupni ključ za prijavu u aplikaciju <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Želite li spremiti zaporku za prijavu u aplikaciju <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Spremiti informacije o prijavi za <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"pristupni ključ"</string>
     <string name="password" msgid="6738570945182936667">"zaporka"</string>
diff --git a/packages/CredentialManager/res/values-hu/strings.xml b/packages/CredentialManager/res/values-hu/strings.xml
index 1a67ecb..539feb4 100644
--- a/packages/CredentialManager/res/values-hu/strings.xml
+++ b/packages/CredentialManager/res/values-hu/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Miközben a jelszó nélküli jövő felé haladunk, a jelszavak továbbra is rendelkezésre állnak majd az azonosítókulcsok mellett."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Válassza ki, hogy hova szeretné menteni <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Válasszon jelszókezelőt, hogy menthesse az adatait, és gyorsabban jelentkezhessen be a következő alkalommal."</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Létrehoz azonosítókulcsot a következőhöz: <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Szeretné elmenteni a(z) <xliff:g id="APPNAME">%1$s</xliff:g> jelszavát?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Létrehoz azonosítókulcsot a következőbe való bejelentkezéshez: <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Menti a jelszót a következőbe való bejelentkezéshez: <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Menti a bejelentkezési adatokat a következőhöz: <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"azonosítókulcs"</string>
     <string name="password" msgid="6738570945182936667">"jelszó"</string>
diff --git a/packages/CredentialManager/res/values-hy/strings.xml b/packages/CredentialManager/res/values-hy/strings.xml
index aeb3990..5f06a7a 100644
--- a/packages/CredentialManager/res/values-hy/strings.xml
+++ b/packages/CredentialManager/res/values-hy/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Թեև մենք առանց գաղտնաբառերի ապագայի ճանապարհին ենք, դրանք դեռ հասանելի կլինեն անցաբառերի հետ մեկտեղ։"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Նշեք, թե որտեղ եք ուզում պահել ձեր <xliff:g id="CREATETYPES">%1$s</xliff:g>ը"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Ընտրեք գաղտնաբառերի կառավարիչ՝ ձեր տեղեկությունները պահելու և հաջորդ անգամ ավելի արագ մուտք գործելու համար"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Ստեղծե՞լ անցաբառ <xliff:g id="APPNAME">%1$s</xliff:g> հավելվածի համար"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Պահե՞լ «<xliff:g id="APPNAME">%1$s</xliff:g>» հավելվածի գաղտնաբառը"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Ստեղծե՞լ անցաբառ՝ <xliff:g id="APPNAME">%1$s</xliff:g> հավելված մուտք գործելու համար"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Պահե՞լ գաղտնաբառը՝ <xliff:g id="APPNAME">%1$s</xliff:g> հավելված մուտք գործելու համար"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Պահե՞լ «<xliff:g id="APPNAME">%1$s</xliff:g>» հավելվածի մուտքի տվյալները"</string>
     <string name="passkey" msgid="632353688396759522">"անցաբառ"</string>
     <string name="password" msgid="6738570945182936667">"գաղտնաբառ"</string>
diff --git a/packages/CredentialManager/res/values-in/strings.xml b/packages/CredentialManager/res/values-in/strings.xml
index 8bc8686..ad8aeec 100644
--- a/packages/CredentialManager/res/values-in/strings.xml
+++ b/packages/CredentialManager/res/values-in/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Sandi akan tetap tersedia bersama kunci sandi seiring perjalanan menuju era di mana sandi tidak diperlukan lagi."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Pilih tempat penyimpanan <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Pilih pengelola sandi untuk menyimpan info Anda dan login lebih cepat lain kali"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Buat kunci sandi untuk <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Simpan sandi untuk <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Buat kunci sandi untuk login ke <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Simpan sandi untuk login ke <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Simpan info login untuk <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"kunci sandi"</string>
     <string name="password" msgid="6738570945182936667">"sandi"</string>
diff --git a/packages/CredentialManager/res/values-is/strings.xml b/packages/CredentialManager/res/values-is/strings.xml
index e757463..bc6bdfc 100644
--- a/packages/CredentialManager/res/values-is/strings.xml
+++ b/packages/CredentialManager/res/values-is/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Við stefnum að framtíð án aðgangsorða en aðgangsorð verða áfram í boði samhliða aðgangslyklum."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Veldu hvar þú vilt vista <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Veldu aðgangsorðastjórnun til að vista upplýsingarnar og vera fljótari að skrá þig inn næst"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Viltu búa til aðgangslykil fyrir <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Viltu vista aðgangsorð fyrir <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Búa til aðgangslykil til að skrá þig inn á <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Vista aðgangsorð til að skrá þig inn á <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Viltu vista innskráningarupplýsingar fyrir <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"aðgangslykill"</string>
     <string name="password" msgid="6738570945182936667">"aðgangsorð"</string>
diff --git a/packages/CredentialManager/res/values-it/strings.xml b/packages/CredentialManager/res/values-it/strings.xml
index 724137b..2b0d83c 100644
--- a/packages/CredentialManager/res/values-it/strings.xml
+++ b/packages/CredentialManager/res/values-it/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Il futuro sarà senza password, ma per ora saranno ancora disponibili insieme alle passkey."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Scegli dove salvare le <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Seleziona un gestore delle password per salvare i tuoi dati e accedere più velocemente la prossima volta"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Vuoi creare una passkey per <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Vuoi salvare la password di <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Creare passkey per accedere all\'app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Salvare password per accedere all\'app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Vuoi salvare i dati di accesso di <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"passkey"</string>
     <string name="password" msgid="6738570945182936667">"password"</string>
diff --git a/packages/CredentialManager/res/values-iw/strings.xml b/packages/CredentialManager/res/values-iw/strings.xml
index a3adedf..ee9dd5d 100644
--- a/packages/CredentialManager/res/values-iw/strings.xml
+++ b/packages/CredentialManager/res/values-iw/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"אנחנו מתקדמים לעבר עתיד ללא סיסמאות, אבל עדיין אפשר יהיה להשתמש בסיסמאות וגם במפתחות גישה."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"בחירת המקום לשמירה של <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"אפשר לבחור באחד משירותי ניהול הסיסמאות כדי לשמור את הפרטים ולהיכנס לחשבון מהר יותר בפעם הבאה"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"ליצור מפתח גישה ל-<xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"לשמור את הסיסמה של <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"ליצור מפתח גישה כדי להיכנס לחשבון ב-<xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"לשמור את הסיסמה כדי להיכנס לחשבון ב-<xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"לשמור את פרטי הכניסה של <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"מפתח גישה"</string>
     <string name="password" msgid="6738570945182936667">"סיסמה"</string>
diff --git a/packages/CredentialManager/res/values-ja/strings.xml b/packages/CredentialManager/res/values-ja/strings.xml
index 2269f7e..b60638b 100644
--- a/packages/CredentialManager/res/values-ja/strings.xml
+++ b/packages/CredentialManager/res/values-ja/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"将来的にパスワードレスに移行するにあたり、パスワードもパスキーと並行して引き続きご利用いただけます。"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g>の保存先を選択"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"パスワード マネージャーを選択して情報を保存しておくと、次回からすばやくログインできます"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> のパスキーを作成しますか?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> のパスワードを保存しますか?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> にログインするためにパスキーを作成しますか?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> にログインするためにパスワードを保存しますか?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> のログイン情報を保存しますか?"</string>
     <string name="passkey" msgid="632353688396759522">"パスキー"</string>
     <string name="password" msgid="6738570945182936667">"パスワード"</string>
diff --git a/packages/CredentialManager/res/values-ka/strings.xml b/packages/CredentialManager/res/values-ka/strings.xml
index f58e433..c089f4a 100644
--- a/packages/CredentialManager/res/values-ka/strings.xml
+++ b/packages/CredentialManager/res/values-ka/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"უპაროლო მომავალში პაროლები კვლავ ხელმისაწვდომი იქნება, წვდომის გასაღებებთან ერთად."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"აირჩიეთ სად შეინახოთ თქვენი <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"აირჩიეთ პაროლების მმართველი თქვენი ინფორმაციის შესანახად, რომ მომავალში უფრო სწრაფად შეხვიდეთ."</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"შექმნით წვდომის გასაღებს <xliff:g id="APPNAME">%1$s</xliff:g> აპისთვის?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"შეინახავთ <xliff:g id="APPNAME">%1$s</xliff:g> აპის პაროლს?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"შესასვლელად წვდომის გასაღების შექმნა: <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"შესასვლელი პაროლის შენახვა: <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"შეინახავთ <xliff:g id="APPNAME">%1$s</xliff:g> აპში შესვლის ინფორმაციას?"</string>
     <string name="passkey" msgid="632353688396759522">"წვდომის გასაღები"</string>
     <string name="password" msgid="6738570945182936667">"პაროლი"</string>
diff --git a/packages/CredentialManager/res/values-kk/strings.xml b/packages/CredentialManager/res/values-kk/strings.xml
index 79fe5ce..2200b18 100644
--- a/packages/CredentialManager/res/values-kk/strings.xml
+++ b/packages/CredentialManager/res/values-kk/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Құпия сөзсіз болашақ жақын болғанына қарамастан, келешекте құпия сөздерді кіру кілттерімен қатар қолдана беруге болады."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g> қайда сақталатынын таңдаңыз"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Мәліметіңізді сақтап, келесіде жылдам кіру үшін құпия сөз менеджерін таңдаңыз."</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> үшін кіру кілтін жасау керек пе?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> үшін құпия сөзді сақтау керек пе?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> қолданбасына кіру үшін кіру кілті жасалсын ба?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> қолданбасына кіру үшін құпия сөз сақталсын ба?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> үшін кіру мәліметін сақтау керек пе?"</string>
     <string name="passkey" msgid="632353688396759522">"Кіру кілті"</string>
     <string name="password" msgid="6738570945182936667">"құпия сөз"</string>
diff --git a/packages/CredentialManager/res/values-km/strings.xml b/packages/CredentialManager/res/values-km/strings.xml
index feba45e..d0faaec 100644
--- a/packages/CredentialManager/res/values-km/strings.xml
+++ b/packages/CredentialManager/res/values-km/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"នៅពេលដែលយើងឈានទៅរកអនាគតដែលគ្មានពាក្យសម្ងាត់ ពាក្យសម្ងាត់នៅតែអាចប្រើបានរួមជាមួយកូដសម្ងាត់។"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"ជ្រើសរើសកន្លែង​ដែលត្រូវរក្សាទុក<xliff:g id="CREATETYPES">%1$s</xliff:g>របស់អ្នក"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"ជ្រើសរើស​កម្មវិធីគ្រប់គ្រងពាក្យសម្ងាត់ ដើម្បីរក្សាទុក​ព័ត៌មានរបស់អ្នក និងចូលគណនី​បានកាន់តែរហ័ស​នៅពេលលើកក្រោយ"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"បង្កើត​កូដសម្ងាត់​សម្រាប់ <xliff:g id="APPNAME">%1$s</xliff:g> ឬ?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"រក្សាទុក​ពាក្យសម្ងាត់​សម្រាប់ <xliff:g id="APPNAME">%1$s</xliff:g> ឬ?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"បង្កើតកូដសម្ងាត់ ដើម្បីចូលគណនី <xliff:g id="APPNAME">%1$s</xliff:g> ឬ?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"រក្សាទុកពាក្យសម្ងាត់ ដើម្បីចូលគណនី <xliff:g id="APPNAME">%1$s</xliff:g> ឬ?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"រក្សាទុក​ព័ត៌មានអំពី​ការចូលគណនីសម្រាប់ <xliff:g id="APPNAME">%1$s</xliff:g> ឬ?"</string>
     <string name="passkey" msgid="632353688396759522">"កូដសម្ងាត់"</string>
     <string name="password" msgid="6738570945182936667">"ពាក្យសម្ងាត់"</string>
diff --git a/packages/CredentialManager/res/values-kn/strings.xml b/packages/CredentialManager/res/values-kn/strings.xml
index 17d4950..59e3b5c 100644
--- a/packages/CredentialManager/res/values-kn/strings.xml
+++ b/packages/CredentialManager/res/values-kn/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"ನಾವು ಪಾಸ್‌ವರ್ಡ್ ರಹಿತ ತಂತ್ರಜ್ಞಾನದ ಕಡೆಗೆ ಸಾಗುತ್ತಿರುವಾಗ, ಪಾಸ್‌ಕೀಗಳ ಜೊತೆಗೆ ಪಾಸ್‌ವರ್ಡ್‌ಗಳು ಇನ್ನೂ ಲಭ್ಯವಿರುತ್ತವೆ."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"ನಿಮ್ಮ <xliff:g id="CREATETYPES">%1$s</xliff:g> ಎಲ್ಲಿ ಸೇವ್‌ ಆಗಬೇಕು ಎಂಬುದನ್ನು ಆರಿಸಿ"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"ನಿಮ್ಮ ಮಾಹಿತಿಯನ್ನು ಉಳಿಸಲು ಪಾಸ್‌ವರ್ಡ್ ನಿರ್ವಾಹಕವನ್ನು ಆಯ್ಕೆಮಾಡಿ ಹಾಗೂ ಮುಂದಿನ ಬಾರಿ ವೇಗವಾಗಿ ಸೈನ್ ಇನ್ ಮಾಡಿ"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> ಗಾಗಿ ಪಾಸ್‌ಕೀ ಅನ್ನು ರಚಿಸುವುದೇ?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> ಗಾಗಿ ಪಾಸ್‌ವರ್ಡ್‌ ಉಳಿಸುವುದೇ?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> ಗೆ ಸೈನ್ ಇನ್ ಮಾಡಲು ಪಾಸ್‌ಕೀ ರಚಿಸುವುದೇ?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> ಗೆ ಸೈನ್ ಇನ್ ಮಾಡಲು ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ಸೇವ್ ಮಾಡುವುದೇ?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> ಗಾಗಿ ಸೈನ್-ಇನ್ ಮಾಹಿತಿಯನ್ನು ಉಳಿಸುವುದೇ?"</string>
     <string name="passkey" msgid="632353688396759522">"ಪಾಸ್‌ಕೀ"</string>
     <string name="password" msgid="6738570945182936667">"ಪಾಸ್‌ವರ್ಡ್"</string>
diff --git a/packages/CredentialManager/res/values-ko/strings.xml b/packages/CredentialManager/res/values-ko/strings.xml
index c6bec2e..fd48d18 100644
--- a/packages/CredentialManager/res/values-ko/strings.xml
+++ b/packages/CredentialManager/res/values-ko/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"비밀번호 없는 미래로 나아가는 과정에서 비밀번호는 여전히 패스키와 함께 사용될 것입니다."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g> 저장 위치 선택"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"정보를 저장해서 다음에 더 빠르게 로그인하려면 비밀번호 관리자를 선택하세요."</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g>의 패스키를 만드시겠습니까?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g>의 비밀번호를 저장하시겠습니까?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"패스키를 생성하여 <xliff:g id="APPNAME">%1$s</xliff:g>에 로그인하시겠습니까?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"비밀번호를 저장하여 <xliff:g id="APPNAME">%1$s</xliff:g>에 로그인하시겠습니까?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g>의 로그인 정보를 저장하시겠습니까?"</string>
     <string name="passkey" msgid="632353688396759522">"패스키"</string>
     <string name="password" msgid="6738570945182936667">"비밀번호"</string>
diff --git a/packages/CredentialManager/res/values-ky/strings.xml b/packages/CredentialManager/res/values-ky/strings.xml
index effa375..6a01462 100644
--- a/packages/CredentialManager/res/values-ky/strings.xml
+++ b/packages/CredentialManager/res/values-ky/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Сырсөзсүз келечекти көздөй баратсак да, аларды киргизүүчү ачкычтар менен бирге колдоно берүүгө болот."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g> кайда сакталарын тандаңыз"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Маалыматыңызды сактоо жана кийинки жолу тезирээк кирүү үчүн сырсөздөрдү башкаргычты тандаңыз"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> колдонмосуна киргизүүчү ачкыч түзөсүзбү?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> үчүн сырсөз сакталсынбы?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> колдонмосуна кирүү үчүн киргизүүчү ачкычты түзөсүзбү?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> колдонмосуна кирүү үчүн сырсөздү сактайсызбы?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> үчүн кирүү маалыматы сакталсынбы?"</string>
     <string name="passkey" msgid="632353688396759522">"киргизүүчү ачкыч"</string>
     <string name="password" msgid="6738570945182936667">"сырсөз"</string>
diff --git a/packages/CredentialManager/res/values-lo/strings.xml b/packages/CredentialManager/res/values-lo/strings.xml
index d921f56..e71c60f 100644
--- a/packages/CredentialManager/res/values-lo/strings.xml
+++ b/packages/CredentialManager/res/values-lo/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"ໃນຂະນະທີ່ພວກເຮົາກ້າວໄປສູ່ອະນາຄົດທີ່ບໍ່ຕ້ອງໃຊ້ລະຫັດຜ່ານ, ລະຫັດຜ່ານຈະຍັງຄົງໃຊ້ໄດ້ຄວບຄູ່ໄປກັບກະແຈຜ່ານ."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"ເລືອກບ່ອນທີ່ຈະບັນທຶກ <xliff:g id="CREATETYPES">%1$s</xliff:g> ຂອງທ່ານ"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"ເລືອກຕົວຈັດການລະຫັດຜ່ານເພື່ອບັນທຶກຂໍ້ມູນຂອງທ່ານ ແລະ ເຂົ້າສູ່ລະບົບໄວຂຶ້ນໃນເທື່ອຕໍ່ໄປ"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"ສ້າງກະແຈຜ່ານສຳລັບ <xliff:g id="APPNAME">%1$s</xliff:g> ບໍ?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"ບັນທຶກລະຫັດຜ່ານສຳລັບ <xliff:g id="APPNAME">%1$s</xliff:g> ບໍ?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"ສ້າງກະແຈຜ່ານເພື່ອເຂົ້າສູ່ລະບົບ <xliff:g id="APPNAME">%1$s</xliff:g> ບໍ?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"ບັນທຶກລະຫັດຜ່ານເພື່ອເຂົ້າສູ່ລະບົບ <xliff:g id="APPNAME">%1$s</xliff:g> ບໍ?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"ບັນທຶກຂໍ້ມູນການເຂົ້າສູ່ລະບົບສຳລັບ <xliff:g id="APPNAME">%1$s</xliff:g> ບໍ?"</string>
     <string name="passkey" msgid="632353688396759522">"ກະແຈຜ່ານ"</string>
     <string name="password" msgid="6738570945182936667">"ລະຫັດຜ່ານ"</string>
diff --git a/packages/CredentialManager/res/values-lt/strings.xml b/packages/CredentialManager/res/values-lt/strings.xml
index f4fc6d45..55c5cd2 100644
--- a/packages/CredentialManager/res/values-lt/strings.xml
+++ b/packages/CredentialManager/res/values-lt/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Kol stengiamės padaryti, kad ateityje nereikėtų naudoti slaptažodžių, jie vis dar bus pasiekiami kartu su prieigos raktais."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Pasirinkite, kur išsaugoti „<xliff:g id="CREATETYPES">%1$s</xliff:g>“"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Pasirinkite slaptažodžių tvarkyklę, kurią naudodami galėsite išsaugoti informaciją ir kitą kartą prisijungti greičiau"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Sukurti „passkey“, skirtą „<xliff:g id="APPNAME">%1$s</xliff:g>“?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Išsaugoti „<xliff:g id="APPNAME">%1$s</xliff:g>“ slaptažodį?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Sukurti prieigos raktą, skirtą prisijungti prie „<xliff:g id="APPNAME">%1$s</xliff:g>“?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Sukurti slaptažodį, skirtą prisijungti prie „<xliff:g id="APPNAME">%1$s</xliff:g>“?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Išsaugoti prisijungimo prie „<xliff:g id="APPNAME">%1$s</xliff:g>“ informaciją?"</string>
     <string name="passkey" msgid="632353688396759522">"„passkey“"</string>
     <string name="password" msgid="6738570945182936667">"slaptažodis"</string>
diff --git a/packages/CredentialManager/res/values-lv/strings.xml b/packages/CredentialManager/res/values-lv/strings.xml
index e43b5a0..2c0f8e1 100644
--- a/packages/CredentialManager/res/values-lv/strings.xml
+++ b/packages/CredentialManager/res/values-lv/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Lai arī pamazām notiek pāreja uz darbu bez parolēm, tās joprojām būs pieejamas līdzās piekļuves atslēgām."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Izvēlieties, kur saglabāt savas <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Lai saglabātu informāciju un nākamreiz varētu pierakstīties ātrāk, atlasiet paroļu pārvaldnieku."</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Vai izveidot piekļuves atslēgu lietotnei <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Vai saglabāt paroli lietotnei <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Vai izveidot piekļuves atslēgu, lai pierakstītos lietotnē <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Vai saglabāt paroli, lai pierakstītos lietotnē <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Vai saglabāt pierakstīšanās informāciju lietotnei <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"piekļuves atslēga"</string>
     <string name="password" msgid="6738570945182936667">"parole"</string>
diff --git a/packages/CredentialManager/res/values-mk/strings.xml b/packages/CredentialManager/res/values-mk/strings.xml
index 3206640..6f8f76b 100644
--- a/packages/CredentialManager/res/values-mk/strings.xml
+++ b/packages/CredentialManager/res/values-mk/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Како што се движиме кон иднина без лозинки, лозинките сепак ќе бидат достапни покрај криптографските клучеви."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Изберете каде да ги зачувате вашите <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Изберете управник со лозинки за да ги зачувате вашите податоци и да се најавите побрзо следниот пат"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Да се создаде криптографски клуч за <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Дали да се зачува лозинката за <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Да се создаде криптографски клуч за најавување на <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Да се зачува лозинката за најавување на <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Да се зачуваат податоците за најавување за <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"криптографски клуч"</string>
     <string name="password" msgid="6738570945182936667">"лозинка"</string>
diff --git a/packages/CredentialManager/res/values-ml/strings.xml b/packages/CredentialManager/res/values-ml/strings.xml
index 60d0af2..ab32bc9 100644
--- a/packages/CredentialManager/res/values-ml/strings.xml
+++ b/packages/CredentialManager/res/values-ml/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"നമ്മൾ പാസ്‍വേഡ് രഹിത ഭാവിയിലേക്ക് ചുവടുവെച്ചുകൊണ്ടിരിക്കുകയാണ് എങ്കിലും, പാസ്‌കീകൾക്കൊപ്പം പാസ്‍വേഡുകൾ തുടർന്നും ലഭ്യമായിരിക്കും."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"നിങ്ങളുടെ <xliff:g id="CREATETYPES">%1$s</xliff:g> എവിടെയാണ് സംരക്ഷിക്കേണ്ടതെന്ന് തിരഞ്ഞെടുക്കുക"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"നിങ്ങളുടെ വിവരങ്ങൾ സംരക്ഷിക്കാനും അടുത്ത തവണ വേഗത്തിൽ സൈൻ ഇൻ ചെയ്യാനും ഒരു പാസ്‌വേഡ് മാനേജർ തിരഞ്ഞെടുക്കുക"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> എന്നതിനായി പാസ്‌കീ സൃഷ്ടിക്കണോ?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> എന്നതിനായി പാസ്‌വേഡ് സംരക്ഷിക്കണോ?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> എന്നതിലേക്ക് സൈൻ ഇൻ ചെയ്യാൻ പാസ്‌കീ സൃഷ്ടിക്കണോ?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> എന്നതിലേക്ക് സൈൻ ഇൻ ചെയ്യാൻ പാസ്‌വേഡ് സംരക്ഷിക്കണോ?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> എന്നതിനായി സൈൻ ഇൻ വിവരങ്ങൾ സംരക്ഷിക്കണോ?"</string>
     <string name="passkey" msgid="632353688396759522">"പാസ്‌കീ"</string>
     <string name="password" msgid="6738570945182936667">"പാസ്‌വേഡ്"</string>
diff --git a/packages/CredentialManager/res/values-mn/strings.xml b/packages/CredentialManager/res/values-mn/strings.xml
index 1050fc7..a704ea0 100644
--- a/packages/CredentialManager/res/values-mn/strings.xml
+++ b/packages/CredentialManager/res/values-mn/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Бид нууц үггүй ирээдүй рүү урагшлахын хэрээр нууц үг нь нэвтрэх түлхүүрийн хамтаар боломжтой хэвээр байх болно."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g>-г хаана хадгалахаа сонгоно уу"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Мэдээллээ хадгалж, дараагийн удаа илүү хурдан нэвтрэхийн тулд нууц үгний менежерийг сонгоно уу"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g>-д passkey үүсгэх үү?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g>-н нууц үгийг хадгалах уу?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g>-д нэвтрэхийн тулд нэвтрэх түлхүүр үүсгэх үү?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g>-д нэвтрэхийн тулд нууц үгийг хадгалах уу?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g>-н нэвтрэх мэдээллийг хадгалах уу?"</string>
     <string name="passkey" msgid="632353688396759522">"passkey"</string>
     <string name="password" msgid="6738570945182936667">"нууц үг"</string>
diff --git a/packages/CredentialManager/res/values-mr/strings.xml b/packages/CredentialManager/res/values-mr/strings.xml
index 11fee3b..d3e14dd 100644
--- a/packages/CredentialManager/res/values-mr/strings.xml
+++ b/packages/CredentialManager/res/values-mr/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"पासवर्ड न वापरणाऱ्या भविष्यात पुढे जाताना, पासवर्ड तरीही पासकीच्या बरोबरीने उपलब्ध असतील."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"तुमची <xliff:g id="CREATETYPES">%1$s</xliff:g> कुठे सेव्ह करायची ते निवडा"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"तुमची माहिती सेव्ह करण्यासाठी आणि पुढच्या वेळी जलद साइन इन करण्याकरिता Password Manager निवडा"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> साठी पासकी तयार करायची का?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> साठी पासवर्ड सेव्ह करायचा का?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> मध्ये साइन इन करण्यासाठी पासकी तयार करायची आहे का?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> मध्ये साइन इन करण्यासाठी पासवर्ड सेव्ह करायचा आहे का?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> साठी साइन-इन माहिती सेव्ह करायची का?"</string>
     <string name="passkey" msgid="632353688396759522">"पासकी"</string>
     <string name="password" msgid="6738570945182936667">"पासवर्ड"</string>
diff --git a/packages/CredentialManager/res/values-ms/strings.xml b/packages/CredentialManager/res/values-ms/strings.xml
index 8198707..a491177 100644
--- a/packages/CredentialManager/res/values-ms/strings.xml
+++ b/packages/CredentialManager/res/values-ms/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Meskipun masa depan kita nanti tidak memerlukan kata laluan, kata laluan masih akan tersedia bersama dengan kunci laluan."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Pilih tempat untuk menyimpan <xliff:g id="CREATETYPES">%1$s</xliff:g> anda"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Pilih Password Manager untuk menyimpan maklumat anda dan log masuk lebih pantas pada kali seterusnya"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Cipta kunci laluan untuk <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Simpan kata laluan untuk <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Buat kunci laluan untuk log masuk ke <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Simpan kata laluan untuk log masuk ke <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Simpan maklumat log masuk untuk <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"kunci laluan"</string>
     <string name="password" msgid="6738570945182936667">"kata laluan"</string>
diff --git a/packages/CredentialManager/res/values-my/strings.xml b/packages/CredentialManager/res/values-my/strings.xml
index 02b80cf..b427a58 100644
--- a/packages/CredentialManager/res/values-my/strings.xml
+++ b/packages/CredentialManager/res/values-my/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"စကားဝှက်မသုံးခြင်း အနာဂတ်ဆီသို့ ရှေ့ဆက်ရာတွင် လျှို့ဝှက်ကီးများနှင့်အတူ စကားဝှက်များကို ဆက်လက်အသုံးပြုနိုင်ပါမည်။"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"သင်၏ <xliff:g id="CREATETYPES">%1$s</xliff:g> သိမ်းရန်နေရာ ရွေးခြင်း"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"သင့်အချက်အလက်သိမ်းပြီး နောက်တစ်ကြိမ်၌ ပိုမိုမြန်ဆန်စွာ လက်မှတ်ထိုးဝင်ရန် စကားဝှက်မန်နေဂျာကို ရွေးပါ"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> အတွက် လျှို့ဝှက်ကီးပြုလုပ်မလား။"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> အတွက် စကားဝှက်ကို သိမ်းမလား။"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> သို့ လက်မှတ်ထိုးဝင်ရန် လျှို့ဝှက်ကီး ပြုလုပ်မလား။"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> သို့ လက်မှတ်ထိုးဝင်ရန် စကားဝှက်ကို သိမ်းမလား။"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> အတွက် လက်မှတ်ထိုးဝင်သည့်အချက်အလက်ကို သိမ်းမလား။"</string>
     <string name="passkey" msgid="632353688396759522">"လျှို့ဝှက်ကီး"</string>
     <string name="password" msgid="6738570945182936667">"စကားဝှက်"</string>
diff --git a/packages/CredentialManager/res/values-nb/strings.xml b/packages/CredentialManager/res/values-nb/strings.xml
index 4ec8524..1e780ee5 100644
--- a/packages/CredentialManager/res/values-nb/strings.xml
+++ b/packages/CredentialManager/res/values-nb/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Vi går mot en fremtid uten passord, men passord fortsetter å være tilgjengelige ved siden av passnøkler."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Velg hvor du vil lagre <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Velg et verktøy for passordlagring for å lagre informasjonen din og logge på raskere neste gang"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Vil du opprette en passnøkkel for <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Vil du lagre passord for <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Vil du opprette en passnøkkel for å logge på <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Vil du lagre passordet for å logge på <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Vil du lagre påloggingsinformasjon for <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"passnøkkel"</string>
     <string name="password" msgid="6738570945182936667">"passord"</string>
diff --git a/packages/CredentialManager/res/values-ne/strings.xml b/packages/CredentialManager/res/values-ne/strings.xml
index 28223dd..55d4e87 100644
--- a/packages/CredentialManager/res/values-ne/strings.xml
+++ b/packages/CredentialManager/res/values-ne/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"हामी पासवर्डरहित भविष्यतर्फ बढ्दै गर्दा पासकीका साथसाथै पासवर्ड पनि उपलब्ध हुने छ।"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"तपाईं आफ्ना <xliff:g id="CREATETYPES">%1$s</xliff:g> कहाँ सेभ गर्न चाहनुहुन्छ भन्ने कुरा छनौट गर्नुहोस्"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"कुनै पासवर्ड म्यानेजरमा आफ्नो जानकारी सेभ गरी अर्को पटक अझ छिटो साइन इन गर्नुहोस्"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> को पासकी बनाउने हो?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> को पासवर्ड सेभ गर्ने हो?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> मा साइन इन गर्न पासकी बनाउने हो?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> मा साइन इन गर्न पासवर्ड सेभ गर्ने हो?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> मा साइन गर्न प्रयोग गरिनु पर्ने जानकारी सेभ गर्ने हो?"</string>
     <string name="passkey" msgid="632353688396759522">"पासकी"</string>
     <string name="password" msgid="6738570945182936667">"पासवर्ड"</string>
diff --git a/packages/CredentialManager/res/values-nl/strings.xml b/packages/CredentialManager/res/values-nl/strings.xml
index b82382c..53b3d70 100644
--- a/packages/CredentialManager/res/values-nl/strings.xml
+++ b/packages/CredentialManager/res/values-nl/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"We zijn op weg naar een wachtwoordloze toekomst, maar naast toegangssleutels kun je nog steeds gebruikmaken van wachtwoorden."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Kiezen waar je je <xliff:g id="CREATETYPES">%1$s</xliff:g> wilt opslaan"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Selecteer een wachtwoordmanager om je informatie op te slaan en de volgende keer sneller in te loggen"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Toegangssleutel maken voor <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Wachtwoord opslaan voor <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Toegangssleutel maken om in te loggen bij <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Wachtwoord opslaan om in te loggen bij <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Inloggegevens opslaan voor <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"Toegangssleutel"</string>
     <string name="password" msgid="6738570945182936667">"wachtwoord"</string>
diff --git a/packages/CredentialManager/res/values-or/strings.xml b/packages/CredentialManager/res/values-or/strings.xml
index 57853f0..ad268c9 100644
--- a/packages/CredentialManager/res/values-or/strings.xml
+++ b/packages/CredentialManager/res/values-or/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"ଆମେ ଏକ ପାସୱାର୍ଡବିହୀନ ଭବିଷ୍ୟତ ଆଡ଼କୁ ମୁଭ କରୁଥିବା ଯୋଗୁଁ ଏବେ ବି ପାସକୀଗୁଡ଼ିକ ସହିତ ପାସୱାର୍ଡ ଉପଲବ୍ଧ ହେବ।"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"ଆପଣଙ୍କ <xliff:g id="CREATETYPES">%1$s</xliff:g> କେଉଁଠାରେ ସେଭ କରିବେ ତାହା ବାଛନ୍ତୁ"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"ଆପଣଙ୍କ ସୂଚନା ସେଭ କରି ପରବର୍ତ୍ତୀ ସମୟରେ ଶୀଘ୍ର ସାଇନ ଇନ କରିବା ପାଇଁ ଏକ Password Manager ଚୟନ କରନ୍ତୁ"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> ପାଇଁ ପାସକୀ ତିଆରି କରିବେ?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> ପାଇଁ ପାସୱାର୍ଡ ସେଭ କରିବେ?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g>ରେ ସାଇନ ଇନ କରିବାକୁ ପାସକୀ ତିଆରି କରିବେ?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g>ରେ ସାଇନ ଇନ କରିବାକୁ ପାସୱାର୍ଡ ସେଭ କରିବେ?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> ପାଇଁ ସାଇନ-ଇନର ସୂଚନା ସେଭ କରିବେ?"</string>
     <string name="passkey" msgid="632353688396759522">"ପାସକୀ"</string>
     <string name="password" msgid="6738570945182936667">"ପାସୱାର୍ଡ"</string>
diff --git a/packages/CredentialManager/res/values-pa/strings.xml b/packages/CredentialManager/res/values-pa/strings.xml
index 25e1f0a..8328d47 100644
--- a/packages/CredentialManager/res/values-pa/strings.xml
+++ b/packages/CredentialManager/res/values-pa/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"ਹਾਲਾਂਕਿ, ਅਸੀਂ ਪਾਸਵਰਡ ਰਹਿਤ ਭਵਿੱਖ ਵੱਲ ਵਧ ਰਹੇ ਹਾਂ, ਪਰ ਪਾਸਕੀਆਂ ਦੇ ਨਾਲ ਪਾਸਵਰਡ ਹਾਲੇ ਵੀ ਉਪਲਬਧ ਹੋਣਗੇ।"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"ਚੁਣੋ ਕਿ ਆਪਣੀਆਂ <xliff:g id="CREATETYPES">%1$s</xliff:g> ਨੂੰ ਕਿੱਥੇ ਰੱਖਿਅਤ ਕਰਨਾ ਹੈ"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"ਆਪਣੀ ਜਾਣਕਾਰੀ ਨੂੰ ਰੱਖਿਅਤ ਕਰਨ ਅਤੇ ਅਗਲੀ ਵਾਰ ਤੇਜ਼ੀ ਨਾਲ ਸਾਈਨ-ਇਨ ਕਰਨ ਲਈ ਪਾਸਵਰਡ ਪ੍ਰਬੰਧਕ ਚੁਣੋ"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"ਕੀ <xliff:g id="APPNAME">%1$s</xliff:g> ਲਈ ਪਾਸਕੀ ਬਣਾਉਣੀ ਹੈ?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"ਕੀ <xliff:g id="APPNAME">%1$s</xliff:g> ਲਈ ਪਾਸਵਰਡ ਰੱਖਿਅਤ ਕਰਨਾ ਹੈ?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"ਕੀ <xliff:g id="APPNAME">%1$s</xliff:g> ਵਿੱਚ ਸਾਈਨ-ਇਨ ਕਰਨ ਲਈ ਪਾਸਕੀ ਬਣਾਉਣੀ ਹੈ?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"ਕੀ <xliff:g id="APPNAME">%1$s</xliff:g> ਵਿੱਚ ਸਾਈਨ-ਇਨ ਕਰਨ ਲਈ ਪਾਸਵਰਡ ਰੱਖਿਅਤ ਕਰਨਾ ਹੈ?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"ਕੀ <xliff:g id="APPNAME">%1$s</xliff:g> ਲਈ ਸਾਈਨ-ਇਨ ਜਾਣਕਾਰੀ ਰੱਖਿਅਤ ਕਰਨੀ ਹੈ?"</string>
     <string name="passkey" msgid="632353688396759522">"ਪਾਸਕੀ"</string>
     <string name="password" msgid="6738570945182936667">"ਪਾਸਵਰਡ"</string>
diff --git a/packages/CredentialManager/res/values-pl/strings.xml b/packages/CredentialManager/res/values-pl/strings.xml
index e581ff2..7114c3a 100644
--- a/packages/CredentialManager/res/values-pl/strings.xml
+++ b/packages/CredentialManager/res/values-pl/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"W czasie przechodzenia na technologie niewymagające haseł możliwość stosowania haseł – niezależnie od kluczy dostępu – wciąż będzie dostępna."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Wybierz, gdzie zapisywać <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Wybierz menedżera haseł, aby zapisywać informacje i logować się szybciej"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Utworzyć klucz dla aplikacji <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Zapisać hasło do aplikacji <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Utworzyć klucz dostępu do logowania w aplikacji <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Zapisać hasło do logowania w aplikacji <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Zapisać dane logowania do aplikacji <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"klucz"</string>
     <string name="password" msgid="6738570945182936667">"hasło"</string>
diff --git a/packages/CredentialManager/res/values-pt-rBR/strings.xml b/packages/CredentialManager/res/values-pt-rBR/strings.xml
index f6d03df..0b03f72 100644
--- a/packages/CredentialManager/res/values-pt-rBR/strings.xml
+++ b/packages/CredentialManager/res/values-pt-rBR/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Estamos avançando em direção a um futuro sem senhas, mas elas ainda vão estar disponíveis junto às chaves de acesso."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Escolha onde salvar suas <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Selecione um gerenciador de senhas para salvar suas informações e fazer login mais rapidamente na próxima vez"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Criar uma chave de acesso para o app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Salvar senha do app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Criar chave de acesso para fazer login no app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Salvar senha para fazer login no app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Salvar informações de login do app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"chave de acesso"</string>
     <string name="password" msgid="6738570945182936667">"senha"</string>
diff --git a/packages/CredentialManager/res/values-pt-rPT/strings.xml b/packages/CredentialManager/res/values-pt-rPT/strings.xml
index 0007fad..b4cf374 100644
--- a/packages/CredentialManager/res/values-pt-rPT/strings.xml
+++ b/packages/CredentialManager/res/values-pt-rPT/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"À medida que avançamos para um futuro sem palavras-passe, as palavras-passe continuam disponíveis juntamente com as chaves de acesso."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Escolha onde guardar as suas <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Selecione um gestor de palavras-passe para guardar as suas informações e iniciar sessão mais rapidamente da próxima vez"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Criar uma chave de acesso para a app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Guardar a palavra-passe da app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Criar a chave de acesso para iniciar sessão na app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Guardar a palavra-passe para iniciar sessão na app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Guardar as informações de início de sessão da app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"chave de acesso"</string>
     <string name="password" msgid="6738570945182936667">"palavra-passe"</string>
diff --git a/packages/CredentialManager/res/values-pt/strings.xml b/packages/CredentialManager/res/values-pt/strings.xml
index f6d03df..0b03f72 100644
--- a/packages/CredentialManager/res/values-pt/strings.xml
+++ b/packages/CredentialManager/res/values-pt/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Estamos avançando em direção a um futuro sem senhas, mas elas ainda vão estar disponíveis junto às chaves de acesso."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Escolha onde salvar suas <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Selecione um gerenciador de senhas para salvar suas informações e fazer login mais rapidamente na próxima vez"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Criar uma chave de acesso para o app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Salvar senha do app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Criar chave de acesso para fazer login no app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Salvar senha para fazer login no app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Salvar informações de login do app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"chave de acesso"</string>
     <string name="password" msgid="6738570945182936667">"senha"</string>
diff --git a/packages/CredentialManager/res/values-ro/strings.xml b/packages/CredentialManager/res/values-ro/strings.xml
index 32d0056..8c865c4 100644
--- a/packages/CredentialManager/res/values-ro/strings.xml
+++ b/packages/CredentialManager/res/values-ro/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Ne îndreptăm spre un viitor fără parole, însă acestea sunt încă disponibile, alături de cheile de acces."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Alege unde dorești să salvezi <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Selectează un manager de parole pentru a salva informațiile și a te conecta mai rapid data viitoare"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Creezi o cheie de acces pentru <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Salvezi parola pentru <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Creezi o cheie de acces pentru a te conecta la <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Salvezi parola pentru a te conecta la <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Salvezi informațiile de conectare pentru <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"cheia de acces"</string>
     <string name="password" msgid="6738570945182936667">"parolă"</string>
diff --git a/packages/CredentialManager/res/values-ru/strings.xml b/packages/CredentialManager/res/values-ru/strings.xml
index a48b0f2c..92eea32 100644
--- a/packages/CredentialManager/res/values-ru/strings.xml
+++ b/packages/CredentialManager/res/values-ru/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Хотя движение к будущему без паролей уже началось, их по-прежнему можно будет использовать наряду с ключами доступа."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Укажите, куда нужно сохранить <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Выберите менеджер паролей, чтобы сохранять учетные данные и быстро выполнять вход."</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Создать ключ доступа для приложения \"<xliff:g id="APPNAME">%1$s</xliff:g>\"?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Сохранить пароль для приложения \"<xliff:g id="APPNAME">%1$s</xliff:g>\"?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Создать ключ доступа для входа в приложение \"<xliff:g id="APPNAME">%1$s</xliff:g>\"?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Сохранить ключ доступа для входа в приложение \"<xliff:g id="APPNAME">%1$s</xliff:g>\"?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Сохранить учетные данные для приложения \"<xliff:g id="APPNAME">%1$s</xliff:g>\"?"</string>
     <string name="passkey" msgid="632353688396759522">"ключ доступа"</string>
     <string name="password" msgid="6738570945182936667">"пароль"</string>
diff --git a/packages/CredentialManager/res/values-si/strings.xml b/packages/CredentialManager/res/values-si/strings.xml
index d6113b3..ec47018 100644
--- a/packages/CredentialManager/res/values-si/strings.xml
+++ b/packages/CredentialManager/res/values-si/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"අපි මුරපද රහිත අනාගතයක් කරා ගමන් කරන විට, මුරයතුරු සමග මුරපද තවමත් පවතී."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"ඔබේ <xliff:g id="CREATETYPES">%1$s</xliff:g> සුරැකිය යුතු ස්ථානය තෝරා ගන්න"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"ඔබේ තතු සුරැකීමට සහ මීළඟ වතාවේ වේගයෙන් පුරනය වීමට මුරපද කළමනාකරුවෙකු තෝරන්න"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> සඳහා මුරයතුර තනන්න ද?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> සඳහා මුරපදය සුරකින්න ද?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> වෙත පුරනය වීමට මුරයතුරක් තනන්න ද?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> වෙත පුරනය වීමට මුරපදය සුරකින්න ද?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> සඳහා පුරනය වීමේ තතු සුරකින්න ද?"</string>
     <string name="passkey" msgid="632353688396759522">"මුරයතුර"</string>
     <string name="password" msgid="6738570945182936667">"මුරපදය"</string>
diff --git a/packages/CredentialManager/res/values-sk/strings.xml b/packages/CredentialManager/res/values-sk/strings.xml
index 62d8f19..7426c97 100644
--- a/packages/CredentialManager/res/values-sk/strings.xml
+++ b/packages/CredentialManager/res/values-sk/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Blížime sa k budúcnosti bez hesiel, ale heslá budú popri prístupových kľúčoch stále k dispozícii."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Vyberte, kam sa majú ukladať <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Vyberte správcu hesiel, do ktorého sa budú ukladať vaše údaje, aby ste sa nabudúce mohli rýchlejšie prihlásiť"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Chcete vytvoriť prístupový kľúč pre aplikáciu <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Chcete uložiť heslo pre aplikáciu <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Chcete vytvoriť prístupový kľúč na prihlasovanie do aplikácie <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Chcete uložiť heslo na prihlasovanie do aplikácie <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Chcete uložiť prihlasovacie údaje pre aplikáciu <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"prístupový kľúč"</string>
     <string name="password" msgid="6738570945182936667">"heslo"</string>
diff --git a/packages/CredentialManager/res/values-sl/strings.xml b/packages/CredentialManager/res/values-sl/strings.xml
index 846d27d..8e08512 100644
--- a/packages/CredentialManager/res/values-sl/strings.xml
+++ b/packages/CredentialManager/res/values-sl/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Na poti v prihodnost brez gesel bodo poleg ključev za dostop še vedno v uporabi tudi gesla."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Izbira mesta za shranjevanje <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Izberite upravitelja gesel za shranjevanje podatkov za prijavo, da se boste naslednjič lahko hitreje prijavili."</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Želite ustvariti ključ za dostop do aplikacije <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Želite shraniti geslo za aplikacijo <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Želite ustvariti ključ za dostop za prijavo v aplikacijo <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Želite shraniti geslo za prijavo v aplikacijo <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Želite shraniti podatke za prijavo za aplikacijo <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"ključ za dostop"</string>
     <string name="password" msgid="6738570945182936667">"geslo"</string>
diff --git a/packages/CredentialManager/res/values-sq/strings.xml b/packages/CredentialManager/res/values-sq/strings.xml
index 7678125..96f1ff0 100644
--- a/packages/CredentialManager/res/values-sq/strings.xml
+++ b/packages/CredentialManager/res/values-sq/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Teksa shkojmë drejt një të ardhmeje pa fjalëkalime, këto të fundit do të ofrohen ende së bashku me çelësat e kalimit."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Zgjidh se ku t\'i ruash <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Zgjidh një menaxher fjalëkalimesh për të ruajtur informacionet e tua dhe për t\'u identifikuar më shpejt herën tjetër"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Të krijohet çelësi i kalimit për <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Të ruhet fjalëkalimi për <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Të krijohet një çelës kalimit për t\'u identifikuar në <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Të ruhet fjalëkalimi për t\'u identifikuar në <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Të ruhen informacionet e identifikimit për <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"çelësin e kalimit"</string>
     <string name="password" msgid="6738570945182936667">"fjalëkalimi"</string>
diff --git a/packages/CredentialManager/res/values-sr/strings.xml b/packages/CredentialManager/res/values-sr/strings.xml
index 8c71e71..dae62bc 100644
--- a/packages/CredentialManager/res/values-sr/strings.xml
+++ b/packages/CredentialManager/res/values-sr/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Како се крећемо ка будућности без лозинки, лозинке ће и даље бити доступне уз приступне кодове."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Одаберите где ћете сачувати: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Изаберите менаџера лозинки да бисте сачували податке и брже се пријавили следећи пут"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Желите да направите приступни кôд за: <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Желите да сачувате лозинку за: <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Желите да направите приступни кључ да бисте се пријавили у <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Желите да сачувате лозинку да бисте се пријавили у <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Желите да сачувате податке за пријављивање за: <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"приступни кôд"</string>
     <string name="password" msgid="6738570945182936667">"лозинка"</string>
diff --git a/packages/CredentialManager/res/values-sv/strings.xml b/packages/CredentialManager/res/values-sv/strings.xml
index 9aa1165..2326497 100644
--- a/packages/CredentialManager/res/values-sv/strings.xml
+++ b/packages/CredentialManager/res/values-sv/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Medan vi beger oss mot en lösenordslös framtid kommer lösenord fortfarande att vara tillgängliga utöver nycklar."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Välj var du vill spara <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Välj en lösenordshanterare för att spara dina uppgifter och logga in snabbare nästa gång"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Vill du skapa en nyckel för <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Vill du spara lösenordet för <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Vill du skapa en nyckel för att logga in i <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Vill du spara lösenordet för att logga in i <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Vill du spara inloggningsuppgifterna för <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"nyckel"</string>
     <string name="password" msgid="6738570945182936667">"lösenord"</string>
diff --git a/packages/CredentialManager/res/values-sw/strings.xml b/packages/CredentialManager/res/values-sw/strings.xml
index adf659e..6c39509 100644
--- a/packages/CredentialManager/res/values-sw/strings.xml
+++ b/packages/CredentialManager/res/values-sw/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Tunavyoelekea katika enzi isiyo ya manenosiri, manenosiri yataendelea kupatikana pamoja na funguo za siri."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Chagua ambako unahifadhi <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Chagua kidhibiti cha manenosiri ili uhifadhi taarifa zako na uingie kwenye akaunti kwa urahisi wakati mwingine"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Ungependa kuunda ufunguo wa siri kwa ajili ya <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Ungependa kuhifadhi nenosiri kwa ajili ya <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Ungependa kuunda ufunguo wa siri ili uingie katika <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Ungependa kuhifadhi nenosiri ili uingie katika <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Ungependa kuhifadhi maelezo ya kuingia katika akaunti kwa ajili ya <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"ufunguo wa siri"</string>
     <string name="password" msgid="6738570945182936667">"nenosiri"</string>
diff --git a/packages/CredentialManager/res/values-ta/strings.xml b/packages/CredentialManager/res/values-ta/strings.xml
index c43776e..6feb66f 100644
--- a/packages/CredentialManager/res/values-ta/strings.xml
+++ b/packages/CredentialManager/res/values-ta/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"கடவுச்சொல்லற்ற எதிர்காலத்தை நோக்கி நாம் பயணிக்கிறோம். கடவுச்சாவிகளைப் பயன்படுத்தும் இதே வேளையில் கடவுச்சொற்களையும் பயன்படுத்த முடியும்."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"உங்கள் <xliff:g id="CREATETYPES">%1$s</xliff:g> எங்கே சேமிக்கப்பட வேண்டும் என்பதைத் தேர்வுசெய்யுங்கள்"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"உங்கள் தகவல்களைச் சேமித்து அடுத்த முறை விரைவாக உள்நுழைய ஒரு கடவுச்சொல் நிர்வாகியைத் தேர்வுசெய்யுங்கள்"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> ஆப்ஸுக்கான கடவுச்சாவியை உருவாக்கவா?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> ஆப்ஸுக்கான கடவுச்சொல்லைச் சேமிக்கவா?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> ஆப்ஸில் உள்நுழைய கடவுச்சாவியை உருவாக்கவா?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> ஆப்ஸில் உள்நுழைய கடவுச்சொல்லைச் சேமிக்கவா?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> ஆப்ஸுக்கான உள்நுழைவு விவரங்களைச் சேமிக்கவா?"</string>
     <string name="passkey" msgid="632353688396759522">"கடவுச்சாவி"</string>
     <string name="password" msgid="6738570945182936667">"கடவுச்சொல்"</string>
diff --git a/packages/CredentialManager/res/values-te/strings.xml b/packages/CredentialManager/res/values-te/strings.xml
index 980fb152..bf3c1e0 100644
--- a/packages/CredentialManager/res/values-te/strings.xml
+++ b/packages/CredentialManager/res/values-te/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"మనం భవిష్యత్తులో పాస్‌వర్డ్ రహిత టెక్నాలజీని ఉపయోగించినా, పాస్‌కీలతో పాటు పాస్‌వర్డ్‌లు కూడా అందుబాటులో ఉంటాయి."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"మీ <xliff:g id="CREATETYPES">%1$s</xliff:g> ఎక్కడ సేవ్ చేయాలో ఎంచుకోండి"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"తర్వాతిసారి మరింత వేగంగా సైన్ ఇన్ చేసేందుకు వీలుగా మీ సమాచారాన్ని సేవ్ చేయడం కోసం ఒక పాస్‌వర్డ్ మేనేజర్‌ను ఎంచుకోండి"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> కోసం పాస్‌-కీని క్రియేట్ చేయాలా?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> కోసం పాస్‌వర్డ్‌ను సేవ్ చేయాలా?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g>‌కు సైన్ ఇన్ చేయడానికి పాస్-కీని క్రియేట్ చేయాలా?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g>‌కు సైన్ ఇన్ చేయడానికి పాస్‌వర్డ్‌ను సేవ్ చేయాలా?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> కోసం సైన్ ఇన్ సమాచారాన్ని సేవ్ చేయాలా?"</string>
     <string name="passkey" msgid="632353688396759522">"పాస్-కీ"</string>
     <string name="password" msgid="6738570945182936667">"పాస్‌వర్డ్"</string>
diff --git a/packages/CredentialManager/res/values-th/strings.xml b/packages/CredentialManager/res/values-th/strings.xml
index e222d6b..4fc8ba0e 100644
--- a/packages/CredentialManager/res/values-th/strings.xml
+++ b/packages/CredentialManager/res/values-th/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"ในขณะที่เราก้าวไปสู่อนาคตที่ไม่ต้องใช้รหัสผ่านนั้น รหัสผ่านจะยังคงใช้ได้อยู่ควบคู่ไปกับการเปลี่ยนไปใช้พาสคีย์"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"เลือกว่าต้องการบันทึก<xliff:g id="CREATETYPES">%1$s</xliff:g>ไว้ที่ใด"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"เลือกเครื่องมือจัดการรหัสผ่านเพื่อบันทึกข้อมูลและลงชื่อเข้าใช้เร็วขึ้นในครั้งถัดไป"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"สร้างพาสคีย์สำหรับ <xliff:g id="APPNAME">%1$s</xliff:g> ไหม"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"บันทึกรหัสผ่านสำหรับ <xliff:g id="APPNAME">%1$s</xliff:g> ไหม"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"สร้างพาสคีย์เพื่อลงชื่อเข้าใช้ <xliff:g id="APPNAME">%1$s</xliff:g> ไหม"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"บันทึกรหัสผ่านเพื่อลงชื่อเข้าใช้ <xliff:g id="APPNAME">%1$s</xliff:g> ไหม"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"บันทึกข้อมูลการลงชื่อเข้าใช้สำหรับ <xliff:g id="APPNAME">%1$s</xliff:g> ไหม"</string>
     <string name="passkey" msgid="632353688396759522">"พาสคีย์"</string>
     <string name="password" msgid="6738570945182936667">"รหัสผ่าน"</string>
diff --git a/packages/CredentialManager/res/values-tl/strings.xml b/packages/CredentialManager/res/values-tl/strings.xml
index 5487df1..e6bcadd 100644
--- a/packages/CredentialManager/res/values-tl/strings.xml
+++ b/packages/CredentialManager/res/values-tl/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Habang lumalayo tayo sa mga password, magiging available pa rin ang mga password kasama ng mga passkey."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Piliin kung saan mo ise-save ang iyong <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Pumili ng password manager para ma-save ang iyong impormasyon at makapag-sign in nang mas mabilis sa susunod na pagkakataon"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Gumawa ng passkey para sa <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"I-save ang password para sa <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Gumawa ng passkey para mag-sign in sa <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"I-save ang password para mag-sign in sa <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"I-save ang impormasyon sa pag-sign in para sa <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"passkey"</string>
     <string name="password" msgid="6738570945182936667">"password"</string>
diff --git a/packages/CredentialManager/res/values-tr/strings.xml b/packages/CredentialManager/res/values-tr/strings.xml
index 5dda8aa..f9455e7 100644
--- a/packages/CredentialManager/res/values-tr/strings.xml
+++ b/packages/CredentialManager/res/values-tr/strings.xml
@@ -39,8 +39,8 @@
     <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 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_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> uygulamasında oturum açmak için geçiş anahtarı oluşturulsun mu?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> uygulamasında oturum açmak 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">"Geçiş anahtarı"</string>
     <string name="password" msgid="6738570945182936667">"Şifre"</string>
diff --git a/packages/CredentialManager/res/values-uk/strings.xml b/packages/CredentialManager/res/values-uk/strings.xml
index 1cda5d4..e804777 100644
--- a/packages/CredentialManager/res/values-uk/strings.xml
+++ b/packages/CredentialManager/res/values-uk/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"На шляху до безпарольного майбутнього паролі й надалі будуть використовуватися паралельно з ключами доступу."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Виберіть, де зберігати <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Виберіть менеджер паролів, щоб зберігати свої дані й надалі входити в облікові записи швидше"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Створити ключ доступу для додатка <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Зберегти пароль для додатка <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Створити ключ доступу для входу в додаток <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Зберегти пароль для входу в додаток <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Зберегти дані для входу для додатка <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"ключ доступу"</string>
     <string name="password" msgid="6738570945182936667">"пароль"</string>
diff --git a/packages/CredentialManager/res/values-ur/strings.xml b/packages/CredentialManager/res/values-ur/strings.xml
index 105045c..9562fdf 100644
--- a/packages/CredentialManager/res/values-ur/strings.xml
+++ b/packages/CredentialManager/res/values-ur/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"چونکہ ہم بغیر پاس ورڈ والے مستقبل کی طرف جا رہے ہیں اس کے باوجود پاس ورڈز پاس کیز کے ساتھ ہی دستیاب ہوں گے۔"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"منتخب کریں کہ آپ کی <xliff:g id="CREATETYPES">%1$s</xliff:g> کو کہاں محفوظ کرنا ہے"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"اپنی معلومات کو محفوظ کرنے اور اگلی بار تیزی سے سائن ان کرنے کے لیے پاس ورڈ مینیجر منتخب کریں"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> کے لیے پاس کی تخلیق کریں؟"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> کے لیے پاس ورڈ کو محفوظ کریں؟"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> میں سائن ان کرنے کیلئے پاس کی تخلیق کریں؟"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> میں سائن ان کرنے کیلئے پاس ورڈ محفوظ کریں؟"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> کے لیے سائن ان کی معلومات محفوظ کریں؟"</string>
     <string name="passkey" msgid="632353688396759522">"پاس کی"</string>
     <string name="password" msgid="6738570945182936667">"پاس ورڈ"</string>
diff --git a/packages/CredentialManager/res/values-uz/strings.xml b/packages/CredentialManager/res/values-uz/strings.xml
index ec42f07..4e27d3e7 100644
--- a/packages/CredentialManager/res/values-uz/strings.xml
+++ b/packages/CredentialManager/res/values-uz/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Parolsiz kelajak sari harakatlanar ekanmiz, parollar kalitlar bilan birga ishlatilishda davom etadi."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Bu <xliff:g id="CREATETYPES">%1$s</xliff:g> qayerga saqlanishini tanlang"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Maʼlumotlaringizni saqlash va keyingi safar tez kirish uchun parollar menejerini tanlang"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> uchun kod yaratilsinmi?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> uchun parol saqlansinmi?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> ilovasiga kirish uchun kirish kaliti yaratilsinmi?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> ilovasiga kirish uchun parol saqlansinmi?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> uchun kirish maʼlumoti saqlansinmi?"</string>
     <string name="passkey" msgid="632353688396759522">"kalit"</string>
     <string name="password" msgid="6738570945182936667">"parol"</string>
diff --git a/packages/CredentialManager/res/values-vi/strings.xml b/packages/CredentialManager/res/values-vi/strings.xml
index dbee658..c774b93 100644
--- a/packages/CredentialManager/res/values-vi/strings.xml
+++ b/packages/CredentialManager/res/values-vi/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Trong quá trình chúng tôi hướng đến tương lai không dùng mật khẩu, bạn vẫn sẽ dùng được mật khẩu cùng với khoá truy cập."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Chọn vị trí lưu <xliff:g id="CREATETYPES">%1$s</xliff:g> của bạn"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Hãy chọn một trình quản lý mật khẩu để lưu thông tin của bạn và đăng nhập nhanh hơn vào lần tới"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Tạo khoá đăng nhập cho <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Lưu mật khẩu cho <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Tạo khoá truy cập để đăng nhập vào <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Lưu mật khẩu để đăng nhập vào <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Lưu thông tin đăng nhập cho <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"khoá đăng nhập"</string>
     <string name="password" msgid="6738570945182936667">"mật khẩu"</string>
diff --git a/packages/CredentialManager/res/values-zh-rCN/strings.xml b/packages/CredentialManager/res/values-zh-rCN/strings.xml
index c82f2f8..11448e9 100644
--- a/packages/CredentialManager/res/values-zh-rCN/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rCN/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"在我们向无密码未来迈进的过程中,密码仍会与通行密钥并行使用。"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"选择保存<xliff:g id="CREATETYPES">%1$s</xliff:g>的位置"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"请选择一款密码管理工具来保存您的信息,以便下次更快地登录"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"要为“<xliff:g id="APPNAME">%1$s</xliff:g>”创建通行密钥吗?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"要保存“<xliff:g id="APPNAME">%1$s</xliff:g>”的密码吗?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"要创建通行密钥以便登录 <xliff:g id="APPNAME">%1$s</xliff:g> 吗?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"要保存密码以便登录 <xliff:g id="APPNAME">%1$s</xliff:g> 吗?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"要保存“<xliff:g id="APPNAME">%1$s</xliff:g>”的登录信息吗?"</string>
     <string name="passkey" msgid="632353688396759522">"通行密钥"</string>
     <string name="password" msgid="6738570945182936667">"密码"</string>
diff --git a/packages/CredentialManager/res/values-zh-rHK/strings.xml b/packages/CredentialManager/res/values-zh-rHK/strings.xml
index 1d3e5aa..8f69643 100644
--- a/packages/CredentialManager/res/values-zh-rHK/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rHK/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"我們將會改用無密碼技術,而密碼仍可與密鑰並行使用。"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"選擇儲存<xliff:g id="CREATETYPES">%1$s</xliff:g>的位置"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"選取密碼管理工具即可儲存自己的資料,縮短下次登入的時間"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"要為「<xliff:g id="APPNAME">%1$s</xliff:g>」建立密鑰嗎?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"要儲存「<xliff:g id="APPNAME">%1$s</xliff:g>」的密碼嗎?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"要建立密鑰以登入「<xliff:g id="APPNAME">%1$s</xliff:g>」嗎?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"要儲存密碼以登入「<xliff:g id="APPNAME">%1$s</xliff:g>」嗎?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"要儲存「<xliff:g id="APPNAME">%1$s</xliff:g>」的登入資料嗎?"</string>
     <string name="passkey" msgid="632353688396759522">"密鑰"</string>
     <string name="password" msgid="6738570945182936667">"密碼"</string>
diff --git a/packages/CredentialManager/res/values-zh-rTW/strings.xml b/packages/CredentialManager/res/values-zh-rTW/strings.xml
index 184505a..b5fa62c 100644
--- a/packages/CredentialManager/res/values-zh-rTW/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rTW/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"我們日後將改採無密碼技術,密碼仍可與密碼金鑰並行使用。"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"選擇要將<xliff:g id="CREATETYPES">%1$s</xliff:g>存在哪裡"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"選取密碼管理工具並儲存資訊,下次就能更快登入"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"要為「<xliff:g id="APPNAME">%1$s</xliff:g>」建立密碼金鑰嗎?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"要儲存「<xliff:g id="APPNAME">%1$s</xliff:g>」的密碼嗎?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"要建立密碼金鑰以登入「<xliff:g id="APPNAME">%1$s</xliff:g>」嗎?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"要儲存密碼以登入「<xliff:g id="APPNAME">%1$s</xliff:g>」嗎?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"要儲存「<xliff:g id="APPNAME">%1$s</xliff:g>」的登入資訊嗎?"</string>
     <string name="passkey" msgid="632353688396759522">"密碼金鑰"</string>
     <string name="password" msgid="6738570945182936667">"密碼"</string>
diff --git a/packages/CredentialManager/res/values-zu/strings.xml b/packages/CredentialManager/res/values-zu/strings.xml
index 8feeb17..5915a73 100644
--- a/packages/CredentialManager/res/values-zu/strings.xml
+++ b/packages/CredentialManager/res/values-zu/strings.xml
@@ -39,8 +39,8 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Njengoba sibhekela kwikusasa elingenaphasiwedi, amagama ayimfihlo asazotholakala eceleni kokhiye bokudlula."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Khetha lapho ongagcina khona i-<xliff:g id="CREATETYPES">%1$s</xliff:g> yakho"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Khetha isiphathi sephasiwedi ukuze ulondoloze ulwazi lwakho futhi ungene ngemvume ngokushesha ngesikhathi esizayo."</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Sungula ukhiye wokudlula we-<xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="choose_create_option_password_title" msgid="7097275038523578687">"Londolozela amaphasiwedi ye-<xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Sungula ukhiye wokudlula ukuze ungene ngemvume ku-<xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="6238446571944651980">"Londoloza iphasiwedi ukuze ungene ngemvume ku-<xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Londoloza ulwazi lokungena lwe-<xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"ukhiye wokudlula"</string>
     <string name="password" msgid="6738570945182936667">"iphasiwedi"</string>
diff --git a/packages/CredentialManager/res/values/colors.xml b/packages/CredentialManager/res/values/colors.xml
index b4d2eeb..9d31b35 100644
--- a/packages/CredentialManager/res/values/colors.xml
+++ b/packages/CredentialManager/res/values/colors.xml
@@ -15,14 +15,12 @@
   -->
 
 <!-- Color palette -->
-<resources>
-    <!-- These colors are used for Remote Views. -->
-    <color name="text_primary">#1A1B20</color>
-    <color name="text_secondary">#44474F</color>
-    <color name="dropdown_container">#F3F3FA</color>
-    <color name="sign_in_options_container">#DADADA</color>
-    <color name="sign_in_options_icon_color">#1B1B1B</color>
+<resources
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
 
-    <!-- These colors are used for Inline Suggestions. -->
-    <color name="inline_background">#FFFFFF</color>
+    <!-- These colors are used for Remote Views. -->
+    <color name="onSurface">?androidprv:attr/materialColorOnSurface</color>
+    <color name="onSurfaceVariant">?androidprv:attr/materialColorOnSurfaceVariant</color>
+    <color name="surfaceDim">?androidprv:attr/materialColorSurfaceDim</color>
+    <color name="surfaceContainer">?androidprv:attr/materialColorSurfaceContainer</color>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/dimens.xml b/packages/CredentialManager/res/values/dimens.xml
index 350920b2..314437e 100644
--- a/packages/CredentialManager/res/values/dimens.xml
+++ b/packages/CredentialManager/res/values/dimens.xml
@@ -18,16 +18,18 @@
 
 <resources>
     <dimen name="autofill_view_top_padding">12dp</dimen>
-    <dimen name="autofill_view_right_padding">12dp</dimen>
+    <dimen name="autofill_view_right_padding">16dp</dimen>
     <dimen name="autofill_view_bottom_padding">12dp</dimen>
     <dimen name="autofill_view_left_padding">16dp</dimen>
-    <dimen name="autofill_view_icon_to_text_padding">10dp</dimen>
+    <dimen name="autofill_view_icon_to_text_padding">16dp</dimen>
+    <dimen name="autofill_view_end_items_padding">8dp</dimen>
+    <dimen name="more_options_item_vertical_padding">14dp</dimen>
     <dimen name="autofill_icon_size">24dp</dimen>
     <dimen name="autofill_dropdown_textview_min_width">112dp</dimen>
     <dimen name="autofill_dropdown_textview_max_width">230dp</dimen>
     <dimen name="dropdown_layout_horizontal_margin">24dp</dimen>
-    <integer name="autofill_max_visible_datasets">5</integer>
-    <dimen name="dropdown_touch_target_min_height">48dp</dimen>
+    <integer name="autofill_max_visible_datasets">4</integer>
+    <dimen name="dropdown_touch_target_min_height">49dp</dimen>
     <dimen name="horizontal_chip_padding">8dp</dimen>
     <dimen name="vertical_chip_padding">6dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 82b47a9..527701c 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -63,9 +63,9 @@
   <!-- This appears as the description body of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
   <string name="choose_provider_body">Select a password manager to save your info and sign in faster next time</string>
   <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is passkey. [CHAR LIMIT=200] -->
-  <string name="choose_create_option_passkey_title">Create passkey for <xliff:g id="appName" example="Tribank">%1$s</xliff:g>?</string>
+  <string name="choose_create_option_passkey_title">Create passkey to sign in to <xliff:g id="appName" example="Tribank">%1$s</xliff:g>?</string>
   <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is password. [CHAR LIMIT=200] -->
-  <string name="choose_create_option_password_title">Save password for <xliff:g id="appName" example="Tribank">%1$s</xliff:g>?</string>
+  <string name="choose_create_option_password_title">Save password to sign in to <xliff:g id="appName" example="Tribank">%1$s</xliff:g>?</string>
   <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is others. [CHAR LIMIT=200] -->
   <string name="choose_create_option_sign_in_title">Save sign-in info for <xliff:g id="appName" example="Tribank">%1$s</xliff:g>?</string>
   <!-- Types which are inserted as a placeholder as credentialTypes for other strings. [CHAR LIMIT=200] -->
diff --git a/packages/CredentialManager/shared/Android.bp b/packages/CredentialManager/shared/Android.bp
index 47ca944..f8ee96e 100644
--- a/packages/CredentialManager/shared/Android.bp
+++ b/packages/CredentialManager/shared/Android.bp
@@ -11,6 +11,7 @@
     name: "CredentialManagerShared",
     manifest: "AndroidManifest.xml",
     srcs: ["src/**/*.kt"],
+    resource_dirs: ["res"],
     static_libs: [
         "androidx.activity_activity-compose",
         "androidx.core_core-ktx",
diff --git a/packages/CredentialManager/shared/res/drawable/ic_passkey_24.xml b/packages/CredentialManager/shared/res/drawable/ic_passkey_24.xml
new file mode 100644
index 0000000..b81f7c5
--- /dev/null
+++ b/packages/CredentialManager/shared/res/drawable/ic_passkey_24.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<!--LINT.IfChange-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="960"
+        android:viewportHeight="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M120,800L120,688Q120,654 137.5,625.5Q155,597 184,582Q246,551 310,535.5Q374,520 440,520Q460,520 480,521.5Q500,523 520,526Q516,584 541,635.5Q566,687 614,720L614,800L120,800ZM760,920L700,860L700,674Q656,661 628,624.5Q600,588 600,540Q600,482 641,441Q682,400 740,400Q798,400 839,441Q880,482 880,540Q880,585 854.5,620Q829,655 790,670L840,720L780,780L840,840L760,920ZM440,480Q374,480 327,433Q280,386 280,320Q280,254 327,207Q374,160 440,160Q506,160 553,207Q600,254 600,320Q600,386 553,433Q506,480 440,480ZM740,560Q757,560 768.5,548.5Q780,537 780,520Q780,503 768.5,491.5Q757,480 740,480Q723,480 711.5,491.5Q700,503 700,520Q700,537 711.5,548.5Q723,560 740,560Z"/>
+</vector>
+<!--LINT.ThenChange(/packages/CredentialManager/res/drawable/ic_passkey_24.xml)-->
\ No newline at end of file
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
index a5f227a..892eabf 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
@@ -30,6 +30,7 @@
 import android.text.TextUtils
 import android.util.Log
 import androidx.activity.result.IntentSenderRequest
+import androidx.credentials.PasswordCredential
 import androidx.credentials.PublicKeyCredential
 import androidx.credentials.provider.Action
 import androidx.credentials.provider.AuthenticationAction
@@ -39,6 +40,7 @@
 import androidx.credentials.provider.PublicKeyCredentialEntry
 import androidx.credentials.provider.RemoteEntry
 import com.android.credentialmanager.IS_AUTO_SELECTED_KEY
+import com.android.credentialmanager.R
 import com.android.credentialmanager.model.get.ActionEntryInfo
 import com.android.credentialmanager.model.get.AuthenticationEntryInfo
 import com.android.credentialmanager.model.get.CredentialEntryInfo
@@ -46,8 +48,9 @@
 import com.android.credentialmanager.model.get.ProviderInfo
 import com.android.credentialmanager.model.get.RemoteEntryInfo
 import com.android.credentialmanager.TAG
+import com.android.credentialmanager.model.EntryInfo
 
-fun CredentialEntryInfo.getIntentSenderRequest(
+fun EntryInfo.getIntentSenderRequest(
     isAutoSelected: Boolean = false
 ): IntentSenderRequest? {
     val entryIntent = fillInIntent?.putExtra(IS_AUTO_SELECTED_KEY, isAutoSelected)
@@ -123,14 +126,19 @@
                     pendingIntent = credentialEntry.pendingIntent,
                     fillInIntent = it.frameworkExtrasIntent,
                     credentialType = CredentialType.PASSWORD,
+                    rawCredentialType = PasswordCredential.TYPE_PASSWORD_CREDENTIAL,
                     credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(),
                     userName = credentialEntry.username.toString(),
                     displayName = credentialEntry.displayName?.toString(),
                     icon = credentialEntry.icon.loadDrawable(context),
-                    shouldTintIcon = credentialEntry.isDefaultIcon,
+                    shouldTintIcon = credentialEntry.hasDefaultIcon,
                     lastUsedTimeMillis = credentialEntry.lastUsedTime,
                     isAutoSelectable = credentialEntry.isAutoSelectAllowed &&
-                            credentialEntry.autoSelectAllowedFromOption,
+                            credentialEntry.isAutoSelectAllowedFromOption,
+                    entryGroupId = credentialEntry.entryGroupId.toString(),
+                    isDefaultIconPreferredAsSingleProvider =
+                            credentialEntry.isDefaultIconPreferredAsSingleProvider,
+                    affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
                 )
                 )
             }
@@ -144,14 +152,21 @@
                     pendingIntent = credentialEntry.pendingIntent,
                     fillInIntent = it.frameworkExtrasIntent,
                     credentialType = CredentialType.PASSKEY,
+                    rawCredentialType = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
                     credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(),
                     userName = credentialEntry.username.toString(),
                     displayName = credentialEntry.displayName?.toString(),
-                    icon = credentialEntry.icon.loadDrawable(context),
-                    shouldTintIcon = credentialEntry.isDefaultIcon,
+                    icon = if (credentialEntry.hasDefaultIcon)
+                        context.getDrawable(R.drawable.ic_passkey_24)
+                    else credentialEntry.icon.loadDrawable(context),
+                    shouldTintIcon = credentialEntry.hasDefaultIcon,
                     lastUsedTimeMillis = credentialEntry.lastUsedTime,
                     isAutoSelectable = credentialEntry.isAutoSelectAllowed &&
-                            credentialEntry.autoSelectAllowedFromOption,
+                            credentialEntry.isAutoSelectAllowedFromOption,
+                    entryGroupId = credentialEntry.entryGroupId.toString(),
+                    isDefaultIconPreferredAsSingleProvider =
+                            credentialEntry.isDefaultIconPreferredAsSingleProvider,
+                    affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
                 )
                 )
             }
@@ -165,15 +180,20 @@
                     pendingIntent = credentialEntry.pendingIntent,
                     fillInIntent = it.frameworkExtrasIntent,
                     credentialType = CredentialType.UNKNOWN,
+                    rawCredentialType = credentialEntry.type,
                     credentialTypeDisplayName =
                     credentialEntry.typeDisplayName?.toString().orEmpty(),
                     userName = credentialEntry.title.toString(),
                     displayName = credentialEntry.subtitle?.toString(),
                     icon = credentialEntry.icon.loadDrawable(context),
-                    shouldTintIcon = credentialEntry.isDefaultIcon,
+                    shouldTintIcon = credentialEntry.hasDefaultIcon,
                     lastUsedTimeMillis = credentialEntry.lastUsedTime,
                     isAutoSelectable = credentialEntry.isAutoSelectAllowed &&
-                            credentialEntry.autoSelectAllowedFromOption,
+                            credentialEntry.isAutoSelectAllowedFromOption,
+                    entryGroupId = credentialEntry.entryGroupId.toString(),
+                    isDefaultIconPreferredAsSingleProvider =
+                            credentialEntry.isDefaultIconPreferredAsSingleProvider,
+                    affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
                 )
                 )
             }
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt
index 9725881..a657e97 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt
@@ -31,6 +31,11 @@
     fillInIntent: Intent?,
     /** Type of this credential used for sorting. Not localized so must not be directly displayed. */
     val credentialType: CredentialType,
+    /**
+     * String type value of this credential used for sorting. Not localized so must not be directly
+     * displayed.
+     */
+    val rawCredentialType: String,
     /** Localized type value of this credential used for display purpose. */
     val credentialTypeDisplayName: String,
     val providerDisplayName: String,
@@ -40,6 +45,10 @@
     val shouldTintIcon: Boolean,
     val lastUsedTimeMillis: Instant?,
     val isAutoSelectable: Boolean,
+    val entryGroupId: String, // Used for deduplication, and displayed as the grouping title
+                              // "For <value-of-entryGroupId>" on the more-option screen.
+    val isDefaultIconPreferredAsSingleProvider: Boolean,
+    val affiliatedDomain: String?,
 ) : EntryInfo(
     providerId,
     entryKey,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 5830b9f..6a1998a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -20,6 +20,7 @@
 import android.content.Context
 import android.content.pm.PackageInfo
 import android.content.pm.PackageManager
+import android.credentials.GetCredentialRequest
 import android.credentials.selection.CreateCredentialProviderData
 import android.credentials.selection.DisabledProviderData
 import android.credentials.selection.Entry
@@ -44,6 +45,9 @@
 import androidx.credentials.CreateCustomCredentialRequest
 import androidx.credentials.CreatePasswordRequest
 import androidx.credentials.CreatePublicKeyCredentialRequest
+import androidx.credentials.PasswordCredential
+import androidx.credentials.PriorityHints
+import androidx.credentials.PublicKeyCredential
 import androidx.credentials.provider.CreateEntry
 import androidx.credentials.provider.RemoteEntry
 import org.json.JSONObject
@@ -162,6 +166,25 @@
 /** Utility functions for converting CredentialManager data structures to or from UI formats. */
 class GetFlowUtils {
     companion object {
+        fun extractTypePriorityMap(request: GetCredentialRequest): Map<String, Int> {
+            val typePriorityMap = mutableMapOf<String, Int>()
+            request.credentialOptions.forEach {option ->
+                // TODO(b/280085288) - use jetpack conversion method when exposed, rather than
+                // parsing from the raw Bundle
+                val priority = option.candidateQueryData.getInt(
+                        "androidx.credentials.BUNDLE_KEY_TYPE_PRIORITY_VALUE",
+                        when (option.type) {
+                            PasswordCredential.TYPE_PASSWORD_CREDENTIAL ->
+                                PriorityHints.PRIORITY_PASSWORD_OR_SIMILAR
+                            PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL -> 100
+                            else -> PriorityHints.PRIORITY_DEFAULT
+                        }
+                )
+                typePriorityMap[option.type] = priority
+            }
+            return typePriorityMap
+        }
+
         // Returns the list (potentially empty) of enabled provider.
         fun toProviderList(
             providerDataList: List<GetCredentialProviderData>,
@@ -193,6 +216,9 @@
                         null
                     }
                 }
+
+            val typePriorityMap = extractTypePriorityMap(getCredentialRequest)
+
             return com.android.credentialmanager.getflow.RequestDisplayInfo(
                 appName = originName?.ifEmpty { null }
                     ?: getAppLabel(context.packageManager, requestInfo.packageName)
@@ -203,6 +229,7 @@
                     // exposed.
                     "androidx.credentials.BUNDLE_KEY_PREFER_IDENTITY_DOC_UI"),
                 preferTopBrandingContent = preferTopBrandingContent,
+                typePriorityMap = typePriorityMap,
             )
         }
     }
@@ -282,6 +309,8 @@
             val appPreferredDefaultProviderId: String? =
                 if (!requestInfo.hasPermissionToOverrideDefault()) null
                 else createCredentialRequestJetpack?.displayInfo?.preferDefaultProvider
+            val typeDisplayIcon = createCredentialRequestJetpack?.displayInfo?.credentialTypeIcon
+                    ?.loadDrawable(context)
             return when (createCredentialRequestJetpack) {
                 is CreatePasswordRequest -> RequestDisplayInfo(
                     createCredentialRequestJetpack.id,
@@ -302,7 +331,6 @@
                     newRequestDisplayInfoFromPasskeyJson(
                         requestJson = createCredentialRequestJetpack.requestJson,
                         appLabel = appLabel,
-                        context = context,
                         preferImmediatelyAvailableCredentials =
                         createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
                         appPreferredDefaultProviderId = appPreferredDefaultProviderId,
@@ -311,6 +339,7 @@
                         // the passkey type. For now, directly parse it ourselves.
                         isAutoSelectRequest = createCredentialRequest.credentialData.getBoolean(
                             Constants.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS, false),
+                        typeIcon = context.getDrawable(R.drawable.ic_passkey_24) ?: return null,
                     )
                 }
                 is CreateCustomCredentialRequest -> {
@@ -323,7 +352,7 @@
                         subtitle = displayInfo.userDisplayName?.toString(),
                         type = CredentialType.UNKNOWN,
                         appName = appLabel,
-                        typeIcon = displayInfo.credentialTypeIcon?.loadDrawable(context)
+                        typeIcon = typeDisplayIcon
                             ?: context.getDrawable(R.drawable.ic_other_sign_in_24) ?: return null,
                         preferImmediatelyAvailableCredentials =
                         createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
@@ -502,7 +531,7 @@
         private fun newRequestDisplayInfoFromPasskeyJson(
             requestJson: String,
             appLabel: String,
-            context: Context,
+            typeIcon: Drawable,
             preferImmediatelyAvailableCredentials: Boolean,
             appPreferredDefaultProviderId: String?,
             userSetDefaultProviderIds: Set<String>,
@@ -525,7 +554,7 @@
                 displayname,
                 CredentialType.PASSKEY,
                 appLabel,
-                context.getDrawable(R.drawable.ic_passkey_24) ?: return null,
+                typeIcon,
                 preferImmediatelyAvailableCredentials,
                 appPreferredDefaultProviderId,
                 userSetDefaultProviderIds,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 0f17217..6ba684d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -21,19 +21,17 @@
 import android.content.Context
 import android.credentials.CredentialManager
 import android.credentials.GetCredentialRequest
-import android.credentials.GetCredentialResponse
-import android.credentials.GetCredentialException
 import android.credentials.GetCandidateCredentialsResponse
 import android.credentials.GetCandidateCredentialsException
 import android.credentials.CredentialOption
 import android.credentials.selection.Entry
 import android.credentials.selection.GetCredentialProviderData
 import android.credentials.selection.ProviderData
+import android.graphics.BlendMode
 import android.graphics.drawable.Icon
 import android.os.Bundle
 import android.os.CancellationSignal
 import android.os.OutcomeReceiver
-import android.provider.Settings
 import android.service.autofill.AutofillService
 import android.service.autofill.Dataset
 import android.service.autofill.Field
@@ -124,13 +122,10 @@
         // TODO(b/324635774): Use callback for validating. If the request is coming
         // directly from the view, there should be a corresponding callback, otherwise
         // we should fail fast,
-        val getCredCallback = getCredManCallback(structure)
         if (getCredRequest == null) {
             Log.i(TAG, "No credential manager request found")
             callback.onFailure("No credential manager request found")
             return
-        } else if (getCredCallback == null) {
-            Log.i(TAG, "No credential manager callback found")
         }
         val credentialManager: CredentialManager =
                 getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager
@@ -140,7 +135,7 @@
             override fun onResult(result: GetCandidateCredentialsResponse) {
                 Log.i(TAG, "getCandidateCredentials onResult")
                 val fillResponse = convertToFillResponse(result, request,
-                    responseClientState)
+                    responseClientState, GetFlowUtils.extractTypePriorityMap(getCredRequest))
                 if (fillResponse != null) {
                     callback.onSuccess(fillResponse)
                 } else {
@@ -197,7 +192,8 @@
     private fun convertToFillResponse(
             getCredResponse: GetCandidateCredentialsResponse,
             filLRequest: FillRequest,
-            responseClientState: Bundle
+            responseClientState: Bundle,
+            typePriorityMap: Map<String, Int>,
     ): FillResponse? {
         val candidateProviders = getCredResponse.candidateProviderDataList
         if (candidateProviders.isEmpty()) {
@@ -213,7 +209,7 @@
         autofillIdToProvidersMap.forEach { (autofillId, providers) ->
             validFillResponse = processProvidersForAutofillId(
                     filLRequest, autofillId, providers, entryIconMap, fillResponseBuilder,
-                    getCredResponse.pendingIntent)
+                    getCredResponse.intent, typePriorityMap)
                     .or(validFillResponse)
         }
         if (!validFillResponse) {
@@ -229,7 +225,8 @@
             providerDataList: ArrayList<GetCredentialProviderData>,
             entryIconMap: Map<String, Icon>,
             fillResponseBuilder: FillResponse.Builder,
-            bottomSheetPendingIntent: PendingIntent?
+            bottomSheetIntent: Intent,
+            typePriorityMap: Map<String, Int>,
     ): Boolean {
         val providerList = GetFlowUtils.toProviderList(
             providerDataList,
@@ -237,23 +234,15 @@
         if (providerList.isEmpty()) {
             return false
         }
-        var totalEntryCount = 0
-        providerList.forEach { provider ->
-            totalEntryCount += provider.credentialEntryList.size
-        }
-        val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerList)
+        val providerDisplayInfo: ProviderDisplayInfo =
+                toProviderDisplayInfo(providerList, typePriorityMap)
+        var totalEntryCount = providerDisplayInfo.sortedUserNameToCredentialEntryList.size
         val inlineSuggestionsRequest = filLRequest.inlineSuggestionsRequest
-        val inlineMaxSuggestedCount = inlineSuggestionsRequest?.maxSuggestionCount ?: 0
         val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs
         val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0
-        val maxDropdownDisplayLimit = this.resources.getInteger(
+        val maxDatasetDisplayLimit = this.resources.getInteger(
                 com.android.credentialmanager.R.integer.autofill_max_visible_datasets)
-        var maxInlineItemCount = totalEntryCount
-        maxInlineItemCount = maxInlineItemCount.coerceAtMost(inlineMaxSuggestedCount)
-        val lastDropdownDatasetIndex = Settings.Global.getInt(this.contentResolver,
-                Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS,
-                (maxDropdownDisplayLimit - 1)).coerceAtMost(totalEntryCount)
-
+                .coerceAtMost(totalEntryCount)
         var i = 0
         var datasetAdded = false
 
@@ -267,6 +256,8 @@
                 }
             }
         }
+        bottomSheetIntent.putExtra(
+                ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, providerDataList)
         providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@{
             val primaryEntry = it.sortedCredentialEntryList.first()
             val pendingIntent = primaryEntry.pendingIntent
@@ -276,7 +267,7 @@
                 Log.e(TAG, "PendingIntent was missing from the entry.")
                 return@usernameLoop
             }
-            if (i >= maxInlineItemCount && i >= lastDropdownDatasetIndex) {
+            if (i >= maxDatasetDisplayLimit) {
                 return@usernameLoop
             }
             val icon: Icon = if (primaryEntry.icon == null) {
@@ -289,7 +280,7 @@
             }
             // Create inline presentation
             var inlinePresentation: InlinePresentation? = null
-            if (inlinePresentationSpecs != null && i < maxInlineItemCount) {
+            if (inlinePresentationSpecs != null && i < maxDatasetDisplayLimit) {
                 val spec: InlinePresentationSpec? = if (i < inlinePresentationSpecsCount) {
                     inlinePresentationSpecs[i]
                 } else {
@@ -303,9 +294,10 @@
                 }
             }
             var dropdownPresentation: RemoteViews? = null
-            if (i < lastDropdownDatasetIndex) {
-                dropdownPresentation = RemoteViewsFactory
-                        .createDropdownPresentation(this, icon, primaryEntry)
+            if (i < maxDatasetDisplayLimit) {
+                dropdownPresentation = RemoteViewsFactory.createDropdownPresentation(
+                    this, icon, primaryEntry, /*isFirstEntry= */ i == 0,
+                    /*isLastEntry= */ (totalEntryCount - i == 1))
             }
 
             val dataSetBuilder = Dataset.Builder()
@@ -329,17 +321,15 @@
                             .build())
             datasetAdded = true
             i++
-
-            if (i == lastDropdownDatasetIndex && bottomSheetPendingIntent != null) {
-                addDropdownMoreOptionsPresentation(bottomSheetPendingIntent, autofillId,
-                        fillResponseBuilder)
-            }
         }
         val pinnedSpec = getLastInlinePresentationSpec(inlinePresentationSpecs,
                 inlinePresentationSpecsCount)
-        if (datasetAdded && bottomSheetPendingIntent != null && pinnedSpec != null) {
-            addPinnedInlineSuggestion(bottomSheetPendingIntent, pinnedSpec, autofillId,
-                    fillResponseBuilder, providerDataList)
+        if (datasetAdded) {
+            addDropdownMoreOptionsPresentation(bottomSheetIntent, autofillId, fillResponseBuilder)
+            if (pinnedSpec != null) {
+                addPinnedInlineSuggestion(pinnedSpec, autofillId,
+                        fillResponseBuilder, bottomSheetIntent)
+            }
         }
         return datasetAdded
     }
@@ -360,6 +350,7 @@
         val sliceBuilder = InlineSuggestionUi
                 .newContentBuilder(pendingIntent)
                 .setTitle(displayName)
+        icon.setTintBlendMode(BlendMode.DST)
         sliceBuilder.setStartIcon(icon)
         if (primaryEntry.credentialType ==
                 CredentialType.PASSKEY && duplicateDisplayNameForPasskeys[displayName] == true) {
@@ -370,13 +361,14 @@
     }
 
     private fun addDropdownMoreOptionsPresentation(
-            bottomSheetPendingIntent: PendingIntent,
+            bottomSheetIntent: Intent,
             autofillId: AutofillId,
             fillResponseBuilder: FillResponse.Builder
     ) {
         val presentationBuilder = Presentations.Builder()
                 .setMenuPresentation(
                         RemoteViewsFactory.createMoreSignInOptionsPresentation(this))
+        val pendingIntent = setUpBottomSheetPendingIntent(bottomSheetIntent)
 
         fillResponseBuilder.addDataset(
                 Dataset.Builder()
@@ -385,7 +377,7 @@
                                 Field.Builder().setPresentations(
                                         presentationBuilder.build())
                                         .build())
-                        .setAuthentication(bottomSheetPendingIntent.intentSender)
+                        .setAuthentication(pendingIntent.intentSender)
                         .build()
         )
     }
@@ -401,37 +393,41 @@
     }
 
     private fun addPinnedInlineSuggestion(
-            bottomSheetPendingIntent: PendingIntent,
             spec: InlinePresentationSpec,
             autofillId: AutofillId,
             fillResponseBuilder: FillResponse.Builder,
-            providerDataList: ArrayList<GetCredentialProviderData>
+            bottomSheetIntent: Intent
     ) {
+        val pendingIntent = setUpBottomSheetPendingIntent(bottomSheetIntent)
+
         val dataSetBuilder = Dataset.Builder()
         val sliceBuilder = InlineSuggestionUi
-                .newContentBuilder(bottomSheetPendingIntent)
+                .newContentBuilder(pendingIntent)
                 .setStartIcon(Icon.createWithResource(this,
                         com.android.credentialmanager.R.drawable.more_horiz_24px))
         val presentationBuilder = Presentations.Builder()
                 .setInlinePresentation(InlinePresentation(
                         sliceBuilder.build().slice, spec, /* pinned= */ true))
 
-        val extrasIntent = Intent()
-        extrasIntent.putExtra(ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, providerDataList)
-
         fillResponseBuilder.addDataset(
                 dataSetBuilder
                         .setField(
                                 autofillId,
                                 Field.Builder().setPresentations(
-                                        presentationBuilder.build())
-                                        .build())
-                        .setAuthentication(bottomSheetPendingIntent.intentSender)
-                        .setCredentialFillInIntent(extrasIntent)
+                                        presentationBuilder.build()
+                                ).build()
+                        )
+                        .setAuthentication(pendingIntent.intentSender)
                         .build()
         )
     }
 
+    private fun setUpBottomSheetPendingIntent(intent: Intent): PendingIntent {
+        intent.setAction(java.util.UUID.randomUUID().toString())
+        return PendingIntent.getActivity(this, /*requestCode=*/0, intent,
+                PendingIntent.FLAG_MUTABLE, /*options=*/null)
+    }
+
     /**
      *  Maps Autofill Id to provider list. For example, passing in a provider info
      *
@@ -528,42 +524,6 @@
         TODO("Not yet implemented")
     }
 
-    private fun getCredManCallback(structure: AssistStructure): OutcomeReceiver<
-            GetCredentialResponse, GetCredentialException>? {
-        return traverseStructureForCallback(structure)
-    }
-
-    private fun traverseStructureForCallback(
-            structure: AssistStructure
-    ): OutcomeReceiver<GetCredentialResponse, GetCredentialException>? {
-        val windowNodes: List<AssistStructure.WindowNode> =
-                structure.run {
-                    (0 until windowNodeCount).map { getWindowNodeAt(it) }
-                }
-
-        windowNodes.forEach { windowNode: AssistStructure.WindowNode ->
-            return traverseNodeForCallback(windowNode.rootViewNode)
-        }
-        return null
-    }
-
-    private fun traverseNodeForCallback(
-            viewNode: AssistStructure.ViewNode
-    ): OutcomeReceiver<GetCredentialResponse, GetCredentialException>? {
-        val children: List<AssistStructure.ViewNode> =
-                viewNode.run {
-                    (0 until childCount).map { getChildAt(it) }
-                }
-
-        children.forEach { childNode: AssistStructure.ViewNode ->
-            if (childNode.isFocused() && childNode.credentialManagerCallback != null) {
-                return childNode.credentialManagerCallback
-            }
-            return traverseNodeForCallback(childNode)
-        }
-        return null
-    }
-
     private fun getCredManRequest(
             structure: AssistStructure,
             sessionId: Int,
@@ -571,7 +531,7 @@
             responseClientState: Bundle
     ): GetCredentialRequest? {
         val credentialOptions: MutableList<CredentialOption> = mutableListOf()
-        traverseStructureForRequest(structure, credentialOptions, responseClientState)
+        traverseStructureForRequest(structure, credentialOptions, responseClientState, sessionId)
 
         if (credentialOptions.isNotEmpty()) {
             val dataBundle = Bundle()
@@ -587,7 +547,8 @@
     private fun traverseStructureForRequest(
             structure: AssistStructure,
             cmRequests: MutableList<CredentialOption>,
-            responseClientState: Bundle
+            responseClientState: Bundle,
+            sessionId: Int
     ) {
         val traversedViewNodes: MutableSet<AutofillId> = mutableSetOf()
         val credentialOptionsFromHints: MutableMap<String, CredentialOption> = mutableMapOf()
@@ -599,7 +560,7 @@
         windowNodes.forEach { windowNode: AssistStructure.WindowNode ->
             traverseNodeForRequest(
                 windowNode.rootViewNode, cmRequests, responseClientState, traversedViewNodes,
-                credentialOptionsFromHints)
+                credentialOptionsFromHints, sessionId)
         }
     }
 
@@ -608,11 +569,18 @@
             cmRequests: MutableList<CredentialOption>,
             responseClientState: Bundle,
             traversedViewNodes: MutableSet<AutofillId>,
-            credentialOptionsFromHints: MutableMap<String, CredentialOption>
+            credentialOptionsFromHints: MutableMap<String, CredentialOption>,
+            sessionId: Int
     ) {
         viewNode.autofillId?.let {
+            val domain = viewNode.webDomain
+            val request = viewNode.pendingCredentialRequest
+            if (domain != null && request != null) {
+                responseClientState.putBoolean(
+                    WEBVIEW_REQUESTED_CREDENTIAL_KEY, true)
+            }
             cmRequests.addAll(getCredentialOptionsFromViewNode(viewNode, it, responseClientState,
-                traversedViewNodes, credentialOptionsFromHints))
+                traversedViewNodes, credentialOptionsFromHints, sessionId))
             traversedViewNodes.add(it)
         }
 
@@ -623,7 +591,7 @@
 
         children.forEach { childNode: AssistStructure.ViewNode ->
             traverseNodeForRequest(childNode, cmRequests, responseClientState, traversedViewNodes,
-                credentialOptionsFromHints)
+                credentialOptionsFromHints, sessionId)
         }
     }
 
@@ -632,23 +600,37 @@
             autofillId: AutofillId,
             responseClientState: Bundle,
             traversedViewNodes: MutableSet<AutofillId>,
-            credentialOptionsFromHints: MutableMap<String, CredentialOption>
+            credentialOptionsFromHints: MutableMap<String, CredentialOption>,
+            sessionId: Int
     ): MutableList<CredentialOption> {
         val credentialOptions: MutableList<CredentialOption> = mutableListOf()
-        if (Flags.autofillCredmanDevIntegration() && viewNode.credentialManagerRequest != null) {
-            viewNode.credentialManagerRequest
+        if (Flags.autofillCredmanDevIntegration() && viewNode.pendingCredentialRequest != null) {
+            viewNode.pendingCredentialRequest
                     ?.getCredentialOptions()
                     ?.forEach { credentialOption ->
                 credentialOption.candidateQueryData
                         .getParcelableArrayList(
                             CredentialProviderService.EXTRA_AUTOFILL_ID, AutofillId::class.java)
                         ?.let { associatedAutofillIds ->
+                            // Set sessionId in autofillIds. The autofillIds stored in Credential
+                            // Options do not have associated session id and will result in
+                            // different hashes than the ones in assistStructure.
+                            associatedAutofillIds.forEach { associatedAutofillId ->
+                                associatedAutofillId.sessionId = sessionId
+                            }
+
                             // Check whether any of the associated autofill ids have already been
                             // traversed. If so, skip, to dedupe on duplicate credential options.
                             if ((traversedViewNodes intersect associatedAutofillIds.toSet())
                                         .isEmpty()) {
                                 credentialOptions.add(credentialOption)
                             }
+
+                            // Set the autofillIds with session id back to credential option.
+                            credentialOption.candidateQueryData.putParcelableArrayList(
+                                CredentialProviderService.EXTRA_AUTOFILL_ID,
+                                associatedAutofillIds
+                            )
                         }
             }
         }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
index d319e4c..e43b09e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
@@ -16,8 +16,15 @@
 
 package com.android.credentialmanager.common.ui
 
+import android.credentials.flags.Flags
+import androidx.compose.animation.animateContentSize
 import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.rememberCoroutineScope
@@ -25,49 +32,77 @@
 import androidx.compose.ui.graphics.Color
 import com.android.compose.rememberSystemUiController
 import com.android.compose.theme.LocalAndroidColorScheme
+import androidx.compose.ui.unit.dp
 import com.android.credentialmanager.common.material.ModalBottomSheetLayout
 import com.android.credentialmanager.common.material.ModalBottomSheetValue
 import com.android.credentialmanager.common.material.rememberModalBottomSheetState
 import com.android.credentialmanager.ui.theme.EntryShape
 import kotlinx.coroutines.launch
 
-
 /** Draws a modal bottom sheet with the same styles and effects shared by various flows. */
 @Composable
+@OptIn(ExperimentalMaterial3Api::class)
 fun ModalBottomSheet(
-    sheetContent: @Composable ColumnScope.() -> Unit,
-    onDismiss: () -> Unit,
-    isInitialRender: Boolean,
-    onInitialRenderComplete: () -> Unit,
-    isAutoSelectFlow: Boolean,
+        sheetContent: @Composable () -> Unit,
+        onDismiss: () -> Unit,
+        isInitialRender: Boolean,
+        onInitialRenderComplete: () -> Unit,
+        isAutoSelectFlow: Boolean,
 ) {
-    val scope = rememberCoroutineScope()
-    val state = rememberModalBottomSheetState(
-        initialValue = if (isAutoSelectFlow) ModalBottomSheetValue.Expanded
-        else ModalBottomSheetValue.Hidden,
-        skipHalfExpanded = true
-    )
-    val sysUiController = rememberSystemUiController()
-    if (state.targetValue == ModalBottomSheetValue.Hidden || isAutoSelectFlow) {
-        setTransparentSystemBarsColor(sysUiController)
+    if (Flags.selectorUiImprovementsEnabled()) {
+        val state = androidx.compose.material3.rememberModalBottomSheetState(
+                skipPartiallyExpanded = true
+        )
+        androidx.compose.material3.ModalBottomSheet(
+                onDismissRequest = onDismiss,
+                containerColor = LocalAndroidColorScheme.current.surfaceBright,
+                sheetState = state,
+                content = {
+                    Box(
+                            modifier = Modifier
+                                    .animateContentSize()
+                                    .wrapContentHeight()
+                                    .fillMaxWidth()
+                    ) {
+                        sheetContent()
+                    }
+                },
+                scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = .32f),
+                shape = EntryShape.TopRoundedCorner,
+                dragHandle = null,
+                // Never take over the full screen. We always want to leave some top scrim space
+                // for exiting and viewing the underlying app to help a user gain context.
+                modifier = Modifier.padding(top = 72.dp),
+        )
     } else {
-        setBottomSheetSystemBarsColor(sysUiController)
-    }
-    ModalBottomSheetLayout(
-        sheetBackgroundColor = LocalAndroidColorScheme.current.surfaceBright,
-        modifier = Modifier.background(Color.Transparent),
-        sheetState = state,
-        sheetContent = sheetContent,
-        sheetShape = EntryShape.TopRoundedCorner,
-    ) {}
-    LaunchedEffect(state.currentValue, state.targetValue) {
-        if (state.currentValue == ModalBottomSheetValue.Hidden) {
-            if (isInitialRender) {
-                onInitialRenderComplete()
-                scope.launch { state.show() }
-            } else if (state.targetValue == ModalBottomSheetValue.Hidden) {
-                // Only dismiss ui when the motion is downwards
-                onDismiss()
+        val scope = rememberCoroutineScope()
+        val state = rememberModalBottomSheetState(
+                initialValue = if (isAutoSelectFlow) ModalBottomSheetValue.Expanded
+                else ModalBottomSheetValue.Hidden,
+                skipHalfExpanded = true
+        )
+        val sysUiController = rememberSystemUiController()
+        if (state.targetValue == ModalBottomSheetValue.Hidden || isAutoSelectFlow) {
+            setTransparentSystemBarsColor(sysUiController)
+        } else {
+            setBottomSheetSystemBarsColor(sysUiController)
+        }
+        ModalBottomSheetLayout(
+                sheetBackgroundColor = LocalAndroidColorScheme.current.surfaceBright,
+                modifier = Modifier.background(Color.Transparent),
+                sheetState = state,
+                sheetContent = { sheetContent() },
+                sheetShape = EntryShape.TopRoundedCorner,
+        ) {}
+        LaunchedEffect(state.currentValue, state.targetValue) {
+            if (state.currentValue == ModalBottomSheetValue.Hidden) {
+                if (isInitialRender) {
+                    onInitialRenderComplete()
+                    scope.launch { state.show() }
+                } else if (state.targetValue == ModalBottomSheetValue.Hidden) {
+                    // Only dismiss ui when the motion is downwards
+                    onDismiss()
+                }
             }
         }
     }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
index bdfe399..006a2d9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
@@ -16,9 +16,13 @@
 
 package com.android.credentialmanager.common.ui
 
+import android.credentials.flags.Flags
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.navigationBars
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.lazy.LazyColumn
@@ -60,12 +64,15 @@
             modifier = Modifier.padding(
                 start = 24.dp,
                 end = 24.dp,
-                bottom = 18.dp,
+                bottom = if (Flags.selectorUiImprovementsEnabled()) 8.dp else 18.dp,
                 top = if (topAppBar == null) 24.dp else 0.dp
             ).fillMaxWidth().wrapContentHeight(),
             horizontalAlignment = Alignment.CenterHorizontally,
             content = content,
             verticalArrangement = contentVerticalArrangement,
+            // The bottom sheet overlaps with the navigation bars but make sure the actual content
+            // in the bottom sheet does not.
+            contentPadding = WindowInsets.navigationBars.asPaddingValues(),
         )
     }
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index a6253b8..99a9409 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -21,6 +21,7 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
@@ -29,13 +30,10 @@
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.ArrowBack
 import androidx.compose.material.icons.outlined.Lock
-import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.SuggestionChip
 import androidx.compose.material3.SuggestionChipDefaults
-import androidx.compose.material3.TopAppBar
-import androidx.compose.material3.TopAppBarDefaults
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -81,6 +79,8 @@
     isLockedAuthEntry: Boolean = false,
     enforceOneLine: Boolean = false,
     onTextLayout: (TextLayoutResult) -> Unit = {},
+    /** Get flow only, if present, where be drawn as a line above the headline. */
+    affiliatedDomainText: String? = null,
 ) {
     val iconPadding = Modifier.wrapContentSize().padding(
         // Horizontal padding should be 16dp, but the suggestion chip itself
@@ -95,7 +95,7 @@
         label = {
             Row(
                 horizontalArrangement = Arrangement.SpaceBetween,
-                modifier = Modifier.fillMaxWidth().padding(
+                modifier = Modifier.fillMaxWidth().heightIn(min = 56.dp).padding(
                     // Total end padding should be 16dp, but the suggestion chip itself
                     // has 8dp horizontal elements padding
                     horizontal = 8.dp, vertical = 16.dp,
@@ -105,6 +105,13 @@
             ) {
                 // Apply weight so that the trailing icon can always show.
                 Column(modifier = Modifier.wrapContentHeight().fillMaxWidth().weight(1f)) {
+                    if (!affiliatedDomainText.isNullOrBlank()) {
+                        BodySmallText(
+                            text = affiliatedDomainText,
+                            enforceOneLine = enforceOneLine,
+                            onTextLayout = onTextLayout,
+                        )
+                    }
                     SmallTitleText(
                         text = entryHeadlineText,
                         enforceOneLine = enforceOneLine,
@@ -146,14 +153,14 @@
                                 },
                             )
                         }
-                    } else if (entrySecondLineText != null) {
+                    } else if (!entrySecondLineText.isNullOrBlank()) {
                         BodySmallText(
                             text = entrySecondLineText,
                             enforceOneLine = enforceOneLine,
                             onTextLayout = onTextLayout,
                         )
                     }
-                    if (entryThirdLineText != null) {
+                    if (!entryThirdLineText.isNullOrBlank()) {
                         BodySmallText(
                             text = entryThirdLineText,
                             enforceOneLine = enforceOneLine,
@@ -252,10 +259,14 @@
         onClick = onClick,
         shape = Shapes.large,
         label = {
-            Column(modifier = Modifier.wrapContentSize()
-                .padding(start = 16.dp, top = 16.dp, bottom = 16.dp)) {
+            Column(
+                    modifier = Modifier.heightIn(min = 56.dp).wrapContentSize().padding(
+                            start = 16.dp, top = 16.dp, bottom = 16.dp
+                    ),
+                    verticalArrangement = Arrangement.Center,
+            ) {
                 SmallTitleText(entryHeadlineText)
-                if (entrySecondLineText != null && entrySecondLineText.isNotEmpty()) {
+                if (!entrySecondLineText.isNullOrBlank()) {
                     BodySmallText(entrySecondLineText)
                 }
             }
@@ -278,31 +289,6 @@
 }
 
 /**
- * A single row of leading icon and text describing a benefit of passkeys, used by the
- * [com.android.credentialmanager.createflow.PasskeyIntroCard].
- */
-@Composable
-fun PasskeyBenefitRow(
-    leadingIconPainter: Painter,
-    text: String,
-) {
-    Row(
-        horizontalArrangement = Arrangement.spacedBy(16.dp),
-        verticalAlignment = Alignment.CenterVertically,
-        modifier = Modifier.fillMaxWidth()
-    ) {
-        Icon(
-            modifier = Modifier.size(24.dp),
-            painter = leadingIconPainter,
-            tint = LocalAndroidColorScheme.current.onSurfaceVariant,
-            // Decorative purpose only.
-            contentDescription = null,
-        )
-        BodyMediumText(text = text)
-    }
-}
-
-/**
  * A single row of one or two CTA buttons for continuing or cancelling the current step.
  */
 @Composable
@@ -327,40 +313,36 @@
     }
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun MoreOptionTopAppBar(
     text: String,
     onNavigationIconClicked: () -> Unit,
     bottomPadding: Dp,
 ) {
-    TopAppBar(
-        title = {
-            LargeTitleText(text = text, modifier = Modifier.padding(horizontal = 4.dp))
-        },
-        navigationIcon = {
-            IconButton(
+    Row(
+            modifier = Modifier.padding(top = 12.dp, bottom = bottomPadding),
+            verticalAlignment = Alignment.CenterVertically,
+    ) {
+        IconButton(
                 modifier = Modifier.padding(top = 8.dp, bottom = 8.dp, start = 4.dp).size(48.dp),
                 onClick = onNavigationIconClicked
-            ) {
-                Box(
+        ) {
+            Box(
                     modifier = Modifier.size(48.dp),
                     contentAlignment = Alignment.Center,
-                ) {
-                    Icon(
+            ) {
+                Icon(
                         imageVector = Icons.Filled.ArrowBack,
                         contentDescription = stringResource(
-                            R.string.accessibility_back_arrow_button
+                                R.string.accessibility_back_arrow_button
                         ),
                         modifier = Modifier.size(24.dp).autoMirrored(),
                         tint = LocalAndroidColorScheme.current.onSurfaceVariant,
-                    )
-                }
+                )
             }
-        },
-        colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
-        modifier = Modifier.padding(top = 12.dp, bottom = bottomPadding)
-    )
+        }
+        LargeTitleText(text = text, modifier = Modifier.padding(horizontal = 4.dp))
+    }
 }
 
 private fun Modifier.autoMirrored() = composed {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt
index 3ebdd20..ff421bc 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt
@@ -21,63 +21,20 @@
 import android.content.Context
 import android.util.Size
 import android.widget.inline.InlinePresentationSpec
-import androidx.autofill.inline.common.TextViewStyle
-import androidx.autofill.inline.common.ViewStyle
-import androidx.autofill.inline.UiVersions
-import androidx.autofill.inline.UiVersions.Style
-import androidx.autofill.inline.v1.InlineSuggestionUi
-import androidx.core.content.ContextCompat
-import android.util.TypedValue
-import android.graphics.Typeface
-
 
 class InlinePresentationsFactory {
     companion object {
-        private const val googleSansMediumFontFamily = "google-sans-medium"
-        private const val googleSansTextFontFamily = "google-sans-text"
-        // There is no min width required for now but this is needed for the spec builder
-        private const val minInlineWidth = 5000
+        // There is no max width required for now but this is needed for the spec builder
+        private const val maxInlineWidth = 5000
 
 
         fun modifyInlinePresentationSpec(context: Context,
                                          originalSpec: InlinePresentationSpec): InlinePresentationSpec {
             return InlinePresentationSpec.Builder(Size(originalSpec.minSize.width, originalSpec
                     .minSize.height),
-                    Size(minInlineWidth, originalSpec
+                    Size(maxInlineWidth, originalSpec
                             .maxSize.height))
-                    .setStyle(UiVersions.newStylesBuilder().addStyle(getStyle(context)).build())
-                    .build()
-        }
-
-
-        fun getStyle(context: Context): Style {
-            val textColorPrimary = ContextCompat.getColor(context,
-                    com.android.credentialmanager.R.color.text_primary)
-            val textColorSecondary = ContextCompat.getColor(context,
-                    com.android.credentialmanager.R.color.text_secondary)
-            val textColorBackground = ContextCompat.getColor(context,
-                    com.android.credentialmanager.R.color.inline_background)
-            val chipHorizontalPadding = context.resources.getDimensionPixelSize(com.android
-                    .credentialmanager.R.dimen.horizontal_chip_padding)
-            val chipVerticalPadding = context.resources.getDimensionPixelSize(com.android
-                    .credentialmanager.R.dimen.vertical_chip_padding)
-            return InlineSuggestionUi.newStyleBuilder()
-                    .setChipStyle(
-                            ViewStyle.Builder().setPadding(chipHorizontalPadding,
-                                    chipVerticalPadding,
-                                    chipHorizontalPadding, chipVerticalPadding).build()
-                    )
-                    .setTitleStyle(
-                            TextViewStyle.Builder().setTextColor(textColorPrimary).setTextSize
-                            (TypedValue.COMPLEX_UNIT_DIP, 14F)
-                                    .setTypeface(googleSansMediumFontFamily,
-                                            Typeface.NORMAL).setBackgroundColor(textColorBackground)
-                                    .build()
-                    )
-                    .setSubtitleStyle(TextViewStyle.Builder().setTextColor(textColorSecondary)
-                            .setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12F).setTypeface
-                            (googleSansTextFontFamily, Typeface.NORMAL).setBackgroundColor
-                            (textColorBackground).build())
+                    .setStyle(originalSpec.getStyle())
                     .build()
         }
     }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
index 02afc54..a46e358 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -31,12 +31,13 @@
         private const val setMaxHeightMethodName = "setMaxHeight"
         private const val setBackgroundResourceMethodName = "setBackgroundResource"
         private const val bulletPoint = "\u2022"
-        private const val passwordCharacterLength = 15
 
         fun createDropdownPresentation(
-                context: Context,
-                icon: Icon,
-                credentialEntryInfo: CredentialEntryInfo
+            context: Context,
+            icon: Icon,
+            credentialEntryInfo: CredentialEntryInfo,
+            isFirstEntry: Boolean,
+            isLastEntry: Boolean,
         ): RemoteViews {
             var layoutId: Int = com.android.credentialmanager.R.layout
                     .credman_dropdown_presentation_layout
@@ -44,42 +45,45 @@
             if (credentialEntryInfo.credentialType == CredentialType.UNKNOWN) {
                 return remoteViews
             }
-            setRemoteViewsPaddings(remoteViews, context, /* primaryTextBottomPadding=*/0)
-            if (credentialEntryInfo.credentialType == CredentialType.PASSKEY) {
-                val displayName = credentialEntryInfo.displayName ?: credentialEntryInfo.userName
-                remoteViews.setTextViewText(android.R.id.text1, displayName)
-                val secondaryText = if (credentialEntryInfo.displayName != null)
+            val displayName = credentialEntryInfo.displayName ?: credentialEntryInfo.userName
+            remoteViews.setTextViewText(android.R.id.text1, displayName)
+            val secondaryText =
+                if (credentialEntryInfo.displayName != null
+                    && (credentialEntryInfo.displayName != credentialEntryInfo.userName))
                     (credentialEntryInfo.userName + " " + bulletPoint + " "
                             + credentialEntryInfo.credentialTypeDisplayName
                             + " " + bulletPoint + " " + credentialEntryInfo.providerDisplayName)
                 else (credentialEntryInfo.credentialTypeDisplayName + " " + bulletPoint + " "
                         + credentialEntryInfo.providerDisplayName)
-                remoteViews.setTextViewText(android.R.id.text2, secondaryText)
-            } else {
-                remoteViews.setTextViewText(android.R.id.text1, credentialEntryInfo.userName)
-                remoteViews.setTextViewText(android.R.id.text2,
-                        bulletPoint.repeat(passwordCharacterLength))
-            }
-            val textColorPrimary = ContextCompat.getColor(context,
-                    com.android.credentialmanager.R.color.text_primary)
-            remoteViews.setTextColor(android.R.id.text1, textColorPrimary)
-            val textColorSecondary = ContextCompat.getColor(context, com.android
-                    .credentialmanager.R.color.text_secondary)
-            remoteViews.setTextColor(android.R.id.text2, textColorSecondary)
+            remoteViews.setTextViewText(android.R.id.text2, secondaryText)
             remoteViews.setImageViewIcon(android.R.id.icon1, icon);
             remoteViews.setBoolean(
-                    android.R.id.icon1, setAdjustViewBoundsMethodName, true);
+                android.R.id.icon1, setAdjustViewBoundsMethodName, true);
             remoteViews.setInt(
-                    android.R.id.icon1,
-                    setMaxHeightMethodName,
-                    context.resources.getDimensionPixelSize(
-                            com.android.credentialmanager.R.dimen.autofill_icon_size));
+                android.R.id.icon1,
+                setMaxHeightMethodName,
+                context.resources.getDimensionPixelSize(
+                    com.android.credentialmanager.R.dimen.autofill_icon_size));
             remoteViews.setContentDescription(android.R.id.icon1, credentialEntryInfo
                     .providerDisplayName);
             val drawableId =
-                    com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one
+                if (isFirstEntry)
+                    com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one else
+                    com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_middle
             remoteViews.setInt(
-                    android.R.id.content, setBackgroundResourceMethodName, drawableId);
+                android.R.id.content, setBackgroundResourceMethodName, drawableId);
+            if (isFirstEntry) remoteViews.setViewPadding(
+                com.android.credentialmanager.R.id.credential_card,
+                /* left=*/0,
+                /* top=*/8,
+                /* right=*/0,
+                /* bottom=*/0)
+            if (isLastEntry) remoteViews.setViewPadding(
+                com.android.credentialmanager.R.id.credential_card,
+                /*left=*/0,
+                /* top=*/0,
+                /* right=*/0,
+                /* bottom=*/8)
             return remoteViews
         }
 
@@ -87,76 +91,21 @@
             var layoutId: Int = com.android.credentialmanager.R.layout
                     .credman_dropdown_bottom_sheet
             val remoteViews = RemoteViews(context.packageName, layoutId)
-            setRemoteViewsPaddings(remoteViews, context)
             remoteViews.setTextViewText(android.R.id.text1, ContextCompat.getString(context,
-                    com.android.credentialmanager
-                            .R.string.dropdown_presentation_more_sign_in_options_text))
-
-            val textColorPrimary = ContextCompat.getColor(context,
-                    com.android.credentialmanager.R.color.text_primary)
-            remoteViews.setTextColor(android.R.id.text1, textColorPrimary)
-            val icon = Icon.createWithResource(context, com
-                    .android.credentialmanager.R.drawable.more_horiz_24px)
-            icon.setTint(ContextCompat.getColor(context,
-                    com.android.credentialmanager.R.color.sign_in_options_icon_color))
-            remoteViews.setImageViewIcon(android.R.id.icon1, icon)
+                com.android.credentialmanager
+                        .R.string.dropdown_presentation_more_sign_in_options_text))
             remoteViews.setBoolean(
-                    android.R.id.icon1, setAdjustViewBoundsMethodName, true);
+                android.R.id.icon1, setAdjustViewBoundsMethodName, true);
             remoteViews.setInt(
-                    android.R.id.icon1,
-                    setMaxHeightMethodName,
-                    context.resources.getDimensionPixelSize(
-                            com.android.credentialmanager.R.dimen.autofill_icon_size));
+                android.R.id.icon1,
+                setMaxHeightMethodName,
+                context.resources.getDimensionPixelSize(
+                    com.android.credentialmanager.R.dimen.autofill_icon_size));
             val drawableId =
-                    com.android.credentialmanager.R.drawable.more_options_list_item
+                com.android.credentialmanager.R.drawable.more_options_list_item
             remoteViews.setInt(
-                    android.R.id.content, setBackgroundResourceMethodName, drawableId);
+                android.R.id.content, setBackgroundResourceMethodName, drawableId);
             return remoteViews
         }
-
-        private fun setRemoteViewsPaddings(
-                remoteViews: RemoteViews, context: Context) {
-            val bottomPadding = context.resources.getDimensionPixelSize(
-                    com.android.credentialmanager.R.dimen.autofill_view_bottom_padding)
-            setRemoteViewsPaddings(remoteViews, context, bottomPadding)
-        }
-
-        private fun setRemoteViewsPaddings(
-                remoteViews: RemoteViews, context: Context, primaryTextBottomPadding: Int) {
-            val leftPadding = context.resources.getDimensionPixelSize(
-                    com.android.credentialmanager.R.dimen.autofill_view_left_padding)
-            val iconToTextPadding = context.resources.getDimensionPixelSize(
-                    com.android.credentialmanager.R.dimen.autofill_view_icon_to_text_padding)
-            val rightPadding = context.resources.getDimensionPixelSize(
-                    com.android.credentialmanager.R.dimen.autofill_view_right_padding)
-            val topPadding = context.resources.getDimensionPixelSize(
-                    com.android.credentialmanager.R.dimen.autofill_view_top_padding)
-            val bottomPadding = context.resources.getDimensionPixelSize(
-                    com.android.credentialmanager.R.dimen.autofill_view_bottom_padding)
-            remoteViews.setViewPadding(
-                    android.R.id.icon1,
-                    leftPadding,
-                    /* top=*/0,
-                    /* right=*/0,
-                    /* bottom=*/0)
-            remoteViews.setViewPadding(
-                    android.R.id.text1,
-                    iconToTextPadding,
-                    /* top=*/topPadding,
-                    /* right=*/rightPadding,
-                    primaryTextBottomPadding)
-            remoteViews.setViewPadding(
-                    android.R.id.text2,
-                    iconToTextPadding,
-                    /* top=*/0,
-                    /* right=*/rightPadding,
-                    /* bottom=*/bottomPadding)
-        }
-
-        private fun isDarkMode(context: Context): Boolean {
-            val currentNightMode = context.resources.configuration.uiMode and
-                    Configuration.UI_MODE_NIGHT_MASK
-            return currentNightMode == Configuration.UI_MODE_NIGHT_YES
-        }
     }
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index d24adb5..af78573 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -16,6 +16,7 @@
 
 package com.android.credentialmanager.createflow
 
+import android.credentials.flags.Flags.selectorUiImprovementsEnabled
 import android.text.TextUtils
 import androidx.activity.compose.ManagedActivityResultLauncher
 import androidx.activity.result.ActivityResult
@@ -329,6 +330,18 @@
             )
         }
         item { Divider(thickness = 24.dp, color = Color.Transparent) }
+
+        val footerDescription = createOptionInfo.footerDescription
+        if (selectorUiImprovementsEnabled()) {
+            if (!footerDescription.isNullOrBlank()) {
+                item {
+                    Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
+                        BodyMediumText(text = footerDescription)
+                    }
+                }
+                item { Divider(thickness = 24.dp, color = Color.Transparent) }
+            }
+        }
         item {
             CredentialContainerCard {
                 PrimaryCreateOptionRow(
@@ -366,18 +379,19 @@
                 },
             )
         }
-        val footerDescription = createOptionInfo.footerDescription
-        if (footerDescription != null && footerDescription.length > 0) {
-            item {
-                Divider(
-                    thickness = 1.dp,
-                    color = LocalAndroidColorScheme.current.outlineVariant,
-                    modifier = Modifier.padding(vertical = 16.dp)
-                )
-            }
-            item {
-                Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
-                    BodySmallText(text = footerDescription)
+        if (!selectorUiImprovementsEnabled()) {
+            if (footerDescription != null && footerDescription.length > 0) {
+                item {
+                    Divider(
+                        thickness = 1.dp,
+                        color = LocalAndroidColorScheme.current.outlineVariant,
+                        modifier = Modifier.padding(vertical = 16.dp)
+                    )
+                }
+                item {
+                    Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
+                        BodySmallText(text = footerDescription)
+                    }
                 }
             }
         }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 4ed84b9..b9c9d89 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -16,8 +16,8 @@
 
 package com.android.credentialmanager.getflow
 
+import android.credentials.flags.Flags.selectorUiImprovementsEnabled
 import android.graphics.drawable.Drawable
-import android.text.TextUtils
 import androidx.activity.compose.ManagedActivityResultLauncher
 import androidx.activity.result.ActivityResult
 import androidx.activity.result.IntentSenderRequest
@@ -40,6 +40,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.graphics.painter.Painter
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.TextLayoutResult
@@ -75,6 +76,7 @@
 import com.android.credentialmanager.model.get.RemoteEntryInfo
 import com.android.credentialmanager.userAndDisplayNameForPasskey
 import com.android.internal.logging.UiEventLogger.UiEventEnum
+import kotlin.math.max
 
 @Composable
 fun GetCredentialScreen(
@@ -110,16 +112,29 @@
                     ProviderActivityState.NOT_APPLICABLE -> {
                         if (getCredentialUiState.currentScreenState
                             == GetScreenState.PRIMARY_SELECTION) {
-                            PrimarySelectionCard(
-                                requestDisplayInfo = getCredentialUiState.requestDisplayInfo,
-                                providerDisplayInfo = getCredentialUiState.providerDisplayInfo,
-                                providerInfoList = getCredentialUiState.providerInfoList,
-                                activeEntry = getCredentialUiState.activeEntry,
-                                onEntrySelected = viewModel::getFlowOnEntrySelected,
-                                onConfirm = viewModel::getFlowOnConfirmEntrySelected,
-                                onMoreOptionSelected = viewModel::getFlowOnMoreOptionSelected,
-                                onLog = { viewModel.logUiEvent(it) },
-                            )
+                            if (selectorUiImprovementsEnabled()) {
+                                PrimarySelectionCardVImpl(
+                                    requestDisplayInfo = getCredentialUiState.requestDisplayInfo,
+                                    providerDisplayInfo = getCredentialUiState.providerDisplayInfo,
+                                    providerInfoList = getCredentialUiState.providerInfoList,
+                                    activeEntry = getCredentialUiState.activeEntry,
+                                    onEntrySelected = viewModel::getFlowOnEntrySelected,
+                                    onConfirm = viewModel::getFlowOnConfirmEntrySelected,
+                                    onMoreOptionSelected = viewModel::getFlowOnMoreOptionSelected,
+                                    onLog = { viewModel.logUiEvent(it) },
+                                )
+                            } else {
+                                PrimarySelectionCard(
+                                    requestDisplayInfo = getCredentialUiState.requestDisplayInfo,
+                                    providerDisplayInfo = getCredentialUiState.providerDisplayInfo,
+                                    providerInfoList = getCredentialUiState.providerInfoList,
+                                    activeEntry = getCredentialUiState.activeEntry,
+                                    onEntrySelected = viewModel::getFlowOnEntrySelected,
+                                    onConfirm = viewModel::getFlowOnConfirmEntrySelected,
+                                    onMoreOptionSelected = viewModel::getFlowOnMoreOptionSelected,
+                                    onLog = { viewModel.logUiEvent(it) },
+                                )
+                            }
                             viewModel.uiMetrics.log(GetCredentialEvent
                                     .CREDMAN_GET_CRED_SCREEN_PRIMARY_SELECTION)
                         } else {
@@ -174,7 +189,8 @@
     }
 }
 
-/** Draws the primary credential selection page. */
+/** Draws the primary credential selection page, used in Android U. */
+// TODO(b/327518384) - remove after flag selectorUiImprovementsEnabled is enabled.
 @Composable
 fun PrimarySelectionCard(
     requestDisplayInfo: RequestDisplayInfo,
@@ -358,6 +374,215 @@
     onLog(GetCredentialEvent.CREDMAN_GET_CRED_PRIMARY_SELECTION_CARD)
 }
 
+internal const val MAX_ENTRY_FOR_PRIMARY_PAGE = 4
+
+/** Draws the primary credential selection page, used starting from android V. */
+@Composable
+fun PrimarySelectionCardVImpl(
+    requestDisplayInfo: RequestDisplayInfo,
+    providerDisplayInfo: ProviderDisplayInfo,
+    providerInfoList: List<ProviderInfo>,
+    activeEntry: EntryInfo?,
+    onEntrySelected: (EntryInfo) -> Unit,
+    onConfirm: () -> Unit,
+    onMoreOptionSelected: () -> Unit,
+    onLog: @Composable (UiEventEnum) -> Unit,
+) {
+    val showMoreForTruncatedEntry = remember { mutableStateOf(false) }
+    val sortedUserNameToCredentialEntryList =
+        providerDisplayInfo.sortedUserNameToCredentialEntryList
+    val authenticationEntryList = providerDisplayInfo.authenticationEntryList
+    // Show at most 4 entries (credential type or locked type) in this primary page
+    val primaryPageCredentialEntryList =
+        sortedUserNameToCredentialEntryList.take(MAX_ENTRY_FOR_PRIMARY_PAGE)
+    val primaryPageLockedEntryList = authenticationEntryList.take(
+        max(0, MAX_ENTRY_FOR_PRIMARY_PAGE - primaryPageCredentialEntryList.size)
+    )
+    SheetContainerCard {
+        val preferTopBrandingContent = requestDisplayInfo.preferTopBrandingContent
+        val singleProviderId = findSingleProviderIdForPrimaryPage(
+                primaryPageCredentialEntryList,
+                primaryPageLockedEntryList
+        )
+        if (preferTopBrandingContent != null) {
+            item {
+                HeadlineProviderIconAndName(
+                    preferTopBrandingContent.icon,
+                    preferTopBrandingContent.displayName
+                )
+            }
+        } else {
+            // When only one provider's entries will be displayed on the primary page, display that
+            // provider's icon + name up top.
+            if (singleProviderId != null) {
+                // First should always work but just to be safe.
+                val providerInfo = providerInfoList.firstOrNull { it.id == singleProviderId }
+                if (providerInfo != null) {
+                    item {
+                        HeadlineProviderIconAndName(
+                            providerInfo.icon,
+                            providerInfo.displayName
+                        )
+                    }
+                }
+            }
+        }
+
+        val hasSingleEntry = primaryPageCredentialEntryList.size +
+                primaryPageLockedEntryList.size == 1
+        val areAllPasswordsOnPrimaryScreen = primaryPageLockedEntryList.isEmpty() &&
+                primaryPageCredentialEntryList.all {
+                    it.sortedCredentialEntryList.first().credentialType == CredentialType.PASSWORD
+                }
+        val areAllPasskeysOnPrimaryScreen = primaryPageLockedEntryList.isEmpty() &&
+                primaryPageCredentialEntryList.all {
+                    it.sortedCredentialEntryList.first().credentialType == CredentialType.PASSKEY
+                }
+        item {
+            if (requestDisplayInfo.preferIdentityDocUi) {
+                HeadlineText(
+                    text = stringResource(
+                        if (hasSingleEntry) {
+                            R.string.get_dialog_title_use_info_on
+                        } else {
+                            R.string.get_dialog_title_choose_option_for
+                        },
+                        requestDisplayInfo.appName
+                    ),
+                )
+            } else {
+                HeadlineText(
+                    text = stringResource(
+                        if (hasSingleEntry) {
+                            if (areAllPasskeysOnPrimaryScreen)
+                                R.string.get_dialog_title_use_passkey_for
+                            else if (areAllPasswordsOnPrimaryScreen)
+                                R.string.get_dialog_title_use_password_for
+                            else if (authenticationEntryList.isNotEmpty())
+                                R.string.get_dialog_title_unlock_options_for
+                            else R.string.get_dialog_title_use_sign_in_for
+                        } else {
+                            if (areAllPasswordsOnPrimaryScreen)
+                                R.string.get_dialog_title_choose_password_for
+                            else if (areAllPasskeysOnPrimaryScreen)
+                                R.string.get_dialog_title_choose_passkey_for
+                            else if (primaryPageLockedEntryList.isNotEmpty() ||
+                                primaryPageCredentialEntryList.any {
+                                    it.sortedCredentialEntryList.first().credentialType !=
+                                            CredentialType.PASSWORD &&
+                                    it.sortedCredentialEntryList.first().credentialType !=
+                                            CredentialType.PASSKEY
+                                }
+                            ) // An unknown typed / locked entry exists, and we can't say it is
+                            // already saved, strictly speaking. Hence use a different title
+                            // without the mention of "saved".
+                                R.string.get_dialog_title_choose_sign_in_for
+                            else // All entries on the primary screen are passkeys or passwords
+                                R.string.get_dialog_title_choose_saved_sign_in_for
+                        },
+                        requestDisplayInfo.appName
+                    ),
+                )
+            }
+        }
+        item { Divider(thickness = 24.dp, color = Color.Transparent) }
+        item {
+            CredentialContainerCard {
+                Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
+                    primaryPageCredentialEntryList.forEach {
+                        val entry = it.sortedCredentialEntryList.first()
+                        CredentialEntryRow(
+                                credentialEntryInfo = entry,
+                                onEntrySelected = onEntrySelected,
+                                enforceOneLine = true,
+                                onTextLayout = {
+                                    showMoreForTruncatedEntry.value = it.hasVisualOverflow
+                                },
+                                hasSingleEntry = hasSingleEntry,
+                                hasSingleProvider = singleProviderId != null,
+                                shouldOverrideIcon = entry.isDefaultIconPreferredAsSingleProvider &&
+                                        (singleProviderId != null),
+                                shouldRemoveTypeDisplayName = areAllPasswordsOnPrimaryScreen ||
+                                        areAllPasskeysOnPrimaryScreen
+                        )
+                    }
+                    primaryPageLockedEntryList.forEach {
+                        AuthenticationEntryRow(
+                                authenticationEntryInfo = it,
+                                onEntrySelected = onEntrySelected,
+                                enforceOneLine = true,
+                        )
+                    }
+                }
+            }
+        }
+        item { Divider(thickness = 24.dp, color = Color.Transparent) }
+        var totalEntriesCount = sortedUserNameToCredentialEntryList
+            .flatMap { it.sortedCredentialEntryList }.size + authenticationEntryList
+            .size + providerInfoList.flatMap { it.actionEntryList }.size
+        if (providerDisplayInfo.remoteEntry != null) totalEntriesCount += 1
+        // Row horizontalArrangement differs on only one actionButton(should place on most
+        // left)/only one confirmButton(should place on most right)/two buttons exist the same
+        // time(should be one on the left, one on the right)
+        item {
+            CtaButtonRow(
+                leftButton = if (totalEntriesCount > 1) {
+                    {
+                        ActionButton(
+                            stringResource(R.string.get_dialog_title_sign_in_options),
+                            onMoreOptionSelected
+                        )
+                    }
+                } else if (showMoreForTruncatedEntry.value) {
+                    {
+                        ActionButton(
+                            stringResource(R.string.button_label_view_more),
+                            onMoreOptionSelected
+                        )
+                    }
+                } else null,
+                rightButton = if (activeEntry != null) { // Only one sign-in options exist
+                    {
+                        ConfirmButton(
+                            stringResource(R.string.string_continue),
+                            onClick = onConfirm
+                        )
+                    }
+                } else null,
+            )
+        }
+    }
+    onLog(GetCredentialEvent.CREDMAN_GET_CRED_PRIMARY_SELECTION_CARD)
+}
+
+/**
+ * Attempt to find a single provider id, if it has supplied all the entries to be displayed on the
+ * front page; otherwise if multiple providers are found, return null.
+ */
+private fun findSingleProviderIdForPrimaryPage(
+    primaryPageCredentialEntryList: List<PerUserNameCredentialEntryList>,
+    primaryPageLockedEntryList: List<AuthenticationEntryInfo>
+): String? {
+    var providerId: String? = null
+    primaryPageCredentialEntryList.forEach {
+        val currProviderId = it.sortedCredentialEntryList.first().providerId
+        if (providerId == null) {
+            providerId = currProviderId
+        } else if (providerId != currProviderId) {
+            return null
+        }
+    }
+    primaryPageLockedEntryList.forEach {
+        val currProviderId = it.providerId
+        if (providerId == null) {
+            providerId = currProviderId
+        } else if (providerId != currProviderId) {
+            return null
+        }
+    }
+    return providerId
+}
+
 /** Draws the secondary credential selection page, where all sign-in options are listed. */
 @Composable
 fun AllSignInOptionCard(
@@ -540,29 +765,59 @@
     onEntrySelected: (EntryInfo) -> Unit,
     enforceOneLine: Boolean = false,
     onTextLayout: (TextLayoutResult) -> Unit = {},
+    // Make optional since the secondary page doesn't care about this value.
+    hasSingleEntry: Boolean = false,
+    // For primary page only, if all display entries come from the same provider AND if that
+    // provider has opted in via isDefaultIconPreferredAsSingleProvider, then we override the
+    // display icon to the default icon for the given credential type.
+    shouldOverrideIcon: Boolean = false,
+    // For primary page only, if all entries come from the same provider, then remove that provider
+    // name from each entry, since that provider icon + name will be shown front and central at
+    // the top of the bottom sheet.
+    hasSingleProvider: Boolean = false,
+    // For primary page only, if all visible entrise are of the same type and that type is passkey
+    // or password, then set this bit to true to remove the type display name from each entry for
+    // simplification, since that info is mentioned in the title.
+    shouldRemoveTypeDisplayName: Boolean = false,
 ) {
     val (username, displayName) = if (credentialEntryInfo.credentialType == CredentialType.PASSKEY)
         userAndDisplayNameForPasskey(
             credentialEntryInfo.userName, credentialEntryInfo.displayName ?: "")
     else Pair(credentialEntryInfo.userName, credentialEntryInfo.displayName)
+
+    // For primary page, if
+    val overrideIcon: Painter? =
+        if (shouldOverrideIcon) {
+            when (credentialEntryInfo.credentialType) {
+                CredentialType.PASSKEY -> painterResource(R.drawable.ic_passkey_24)
+                CredentialType.PASSWORD -> painterResource(R.drawable.ic_password_24)
+                else -> painterResource(R.drawable.ic_other_sign_in_24)
+            }
+        } else null
+
     Entry(
         onClick = { onEntrySelected(credentialEntryInfo) },
-        iconImageBitmap = credentialEntryInfo.icon?.toBitmap()?.asImageBitmap(),
+        iconImageBitmap =
+        if (overrideIcon == null) credentialEntryInfo.icon?.toBitmap()?.asImageBitmap() else null,
         shouldApplyIconImageBitmapTint = credentialEntryInfo.shouldTintIcon,
         // Fall back to iconPainter if iconImageBitmap isn't available
         iconPainter =
-        if (credentialEntryInfo.icon == null) painterResource(R.drawable.ic_other_sign_in_24)
+        if (overrideIcon != null) overrideIcon
+        else if (credentialEntryInfo.icon == null) painterResource(R.drawable.ic_other_sign_in_24)
         else null,
         entryHeadlineText = username,
-        entrySecondLineText = if (
-            credentialEntryInfo.credentialType == CredentialType.PASSWORD) {
-            "••••••••••••"
-        } else {
-            val itemsToDisplay = listOf(
-                displayName,
-                credentialEntryInfo.credentialTypeDisplayName,
-                credentialEntryInfo.providerDisplayName
-            ).filterNot(TextUtils::isEmpty)
+        entrySecondLineText = displayName,
+        entryThirdLineText =
+        (if (hasSingleEntry)
+            if (shouldRemoveTypeDisplayName) emptyList()
+            // Still show the type display name for all non-password/passkey types since it won't be
+            // mentioned in the bottom sheet heading.
+            else listOf(credentialEntryInfo.credentialTypeDisplayName)
+        else listOf(
+                if (shouldRemoveTypeDisplayName) null
+                else credentialEntryInfo.credentialTypeDisplayName,
+                if (hasSingleProvider) null else credentialEntryInfo.providerDisplayName
+        )).filterNot{ it.isNullOrBlank() }.let { itemsToDisplay ->
             if (itemsToDisplay.isEmpty()) null
             else itemsToDisplay.joinToString(
                 separator = stringResource(R.string.get_dialog_sign_in_type_username_separator)
@@ -570,6 +825,7 @@
         },
         enforceOneLine = enforceOneLine,
         onTextLayout = onTextLayout,
+        affiliatedDomainText = credentialEntryInfo.affiliatedDomain,
     )
 }
 
@@ -653,4 +909,4 @@
         contentText = stringResource(R.string.no_sign_in_info_in, lastLocked.providerDisplayName),
     )
     onLog(GetCredentialEvent.CREDMAN_GET_CRED_SCREEN_EMPTY_AUTH_SNACKBAR_SCREEN)
-}
\ 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 458a99a..e35acae 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -16,20 +16,23 @@
 
 package com.android.credentialmanager.getflow
 
+import android.credentials.flags.Flags.selectorUiImprovementsEnabled
 import android.graphics.drawable.Drawable
+import androidx.credentials.PriorityHints
 import com.android.credentialmanager.model.get.ProviderInfo
 import com.android.credentialmanager.model.EntryInfo
-import com.android.credentialmanager.model.CredentialType
 import com.android.credentialmanager.model.get.AuthenticationEntryInfo
 import com.android.credentialmanager.model.get.CredentialEntryInfo
 import com.android.credentialmanager.model.get.RemoteEntryInfo
 import com.android.internal.util.Preconditions
+import java.time.Instant
 
 data class GetCredentialUiState(
     val isRequestForAllOptions: Boolean,
     val providerInfoList: List<ProviderInfo>,
     val requestDisplayInfo: RequestDisplayInfo,
-    val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerInfoList),
+    val providerDisplayInfo: ProviderDisplayInfo =
+            toProviderDisplayInfo(providerInfoList, requestDisplayInfo.typePriorityMap),
     val currentScreenState: GetScreenState = toGetScreenState(
             providerDisplayInfo, isRequestForAllOptions),
     val activeEntry: EntryInfo? = toActiveEntry(providerDisplayInfo),
@@ -78,6 +81,8 @@
     val preferIdentityDocUi: Boolean,
     // A top level branding icon + display name preferred by the app.
     val preferTopBrandingContent: TopBrandingContent?,
+    // Map of credential type -> priority.
+    val typePriorityMap: Map<String, Int>,
 )
 
 data class TopBrandingContent(
@@ -118,7 +123,8 @@
  * @hide
  */
 fun toProviderDisplayInfo(
-    providerInfoList: List<ProviderInfo>
+    providerInfoList: List<ProviderInfo>,
+    typePriorityMap: Map<String, Int>,
 ): ProviderDisplayInfo {
     val userNameToCredentialEntryMap = mutableMapOf<String, MutableList<CredentialEntryInfo>>()
     val authenticationEntryList = mutableListOf<AuthenticationEntryInfo>()
@@ -133,7 +139,7 @@
 
         providerInfo.credentialEntryList.forEach {
             userNameToCredentialEntryMap.compute(
-                it.userName
+                if (selectorUiImprovementsEnabled()) it.entryGroupId else it.userName
             ) { _, v ->
                 if (v == null) {
                     mutableListOf(it)
@@ -146,16 +152,22 @@
     }
 
     // Compose sortedUserNameToCredentialEntryList
-    val comparator = CredentialEntryInfoComparatorByTypeThenTimestamp()
+    val comparator = CredentialEntryInfoComparatorByTypeThenTimestamp(typePriorityMap)
     // Sort per username
     userNameToCredentialEntryMap.values.forEach {
         it.sortWith(comparator)
     }
-    // Transform to list of PerUserNameCredentialEntryLists and then sort across usernames
+    // Transform to list of PerUserNameCredentialEntryLists and then sort the outer list (of
+    // entries grouped by username / entryGroupId) based on the latest timestamp within that
+    // PerUserNameCredentialEntryList
     val sortedUserNameToCredentialEntryList = userNameToCredentialEntryMap.map {
         PerUserNameCredentialEntryList(it.key, it.value)
     }.sortedWith(
-        compareByDescending { it.sortedCredentialEntryList.first().lastUsedTimeMillis }
+        compareByDescending {
+            it.sortedCredentialEntryList.maxByOrNull{ entry ->
+                entry.lastUsedTimeMillis ?: Instant.MIN
+            }?.lastUsedTimeMillis ?: Instant.MIN
+        }
     )
 
     return ProviderDisplayInfo(
@@ -202,16 +214,25 @@
     else GetScreenState.PRIMARY_SELECTION
 }
 
-internal class CredentialEntryInfoComparatorByTypeThenTimestamp : Comparator<CredentialEntryInfo> {
+internal class CredentialEntryInfoComparatorByTypeThenTimestamp(
+        val typePriorityMap: Map<String, Int>,
+) : Comparator<CredentialEntryInfo> {
     override fun compare(p0: CredentialEntryInfo, p1: CredentialEntryInfo): Int {
-        // First prefer passkey type for its security benefits
-        if (p0.credentialType != p1.credentialType) {
-            if (CredentialType.PASSKEY == p0.credentialType) {
+        // First rank by priorities of each credential type.
+        if (p0.rawCredentialType != p1.rawCredentialType) {
+            val p0Priority = typePriorityMap.getOrDefault(
+                    p0.rawCredentialType, PriorityHints.PRIORITY_DEFAULT
+            )
+            val p1Priority = typePriorityMap.getOrDefault(
+                    p1.rawCredentialType, PriorityHints.PRIORITY_DEFAULT
+            )
+            if (p0Priority < p1Priority) {
                 return -1
-            } else if (CredentialType.PASSKEY == p1.credentialType) {
+            } else if (p1Priority < p0Priority) {
                 return 1
             }
         }
+        // Then rank by last used timestamps.
         val p0LastUsedTimeMillis = p0.lastUsedTimeMillis
         val p1LastUsedTimeMillis = p1.lastUsedTimeMillis
         // Then order by last used timestamp
diff --git a/packages/CredentialManager/tests/robotests/Android.bp b/packages/CredentialManager/tests/robotests/Android.bp
index baebfeb..75a0dcc 100644
--- a/packages/CredentialManager/tests/robotests/Android.bp
+++ b/packages/CredentialManager/tests/robotests/Android.bp
@@ -37,7 +37,7 @@
         ":CredentialManagerScreenshotTestFiles",
     ],
 
-    // Do not add any libraries here, instead add them to the ScreenshotTestStub
+    // Do not add any libraries here, instead add them to the ScreenshotTestRobo
     static_libs: [
         "androidx.compose.runtime_runtime",
         "androidx.test.uiautomator_uiautomator",
@@ -45,6 +45,7 @@
         "inline-mockito-robolectric-prebuilt",
         "platform-parametric-runner-lib",
         "uiautomator-helpers",
+        "flag-junit-base",
     ],
     libs: [
         "android.test.runner",
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/phone/dark_landscape_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/phone/dark_landscape_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 0000000..81860e5
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/phone/dark_landscape_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/phone/dark_portrait_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/phone/dark_portrait_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 0000000..8c1fff7
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/phone/dark_portrait_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/phone/light_landscape_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/phone/light_landscape_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 0000000..4eb025f
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/phone/light_landscape_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/phone/light_portrait_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/phone/light_portrait_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 0000000..c709f93
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/phone/light_portrait_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/tablet/dark_landscape_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/tablet/dark_landscape_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 0000000..278c13f
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/tablet/dark_landscape_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/tablet/dark_portrait_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/tablet/dark_portrait_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 0000000..cb85df3
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/tablet/dark_portrait_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/tablet/light_landscape_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/tablet/light_landscape_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 0000000..2eca707
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/tablet/light_landscape_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/tablet/light_portrait_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/tablet/light_portrait_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 0000000..7ee91b3
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/tablet/light_portrait_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
index a0e1fed..28d83ee 100644
--- a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
+++ b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
@@ -16,7 +16,10 @@
 
 package com.android.credentialmanager
 
+import android.credentials.flags.Flags
 import android.content.Context
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.compose.ui.test.isPopup
 import com.android.credentialmanager.getflow.RequestDisplayInfo
 import com.android.credentialmanager.model.CredentialType
 import com.android.credentialmanager.model.get.ProviderInfo
@@ -50,6 +53,7 @@
                 preferImmediatelyAvailableCredentials = false,
                 preferIdentityDocUi = false,
                 preferTopBrandingContent = null,
+                typePriorityMap = emptyMap(),
         )
     }
 
@@ -59,10 +63,13 @@
             CredentialManagerGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
     )
 
+    @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
+
     @Test
-    fun singleCredentialScreen() {
+    fun singleCredentialScreen_M3BottomSheetDisabled() {
+        setFlagsRule.disableFlags(Flags.FLAG_SELECTOR_UI_IMPROVEMENTS_ENABLED)
         val providerInfoList = buildProviderInfoList()
-        val providerDisplayInfo = toProviderDisplayInfo(providerInfoList)
+        val providerDisplayInfo = toProviderDisplayInfo(providerInfoList, emptyMap())
         val activeEntry = toActiveEntry(providerDisplayInfo)
         screenshotRule.screenshotTest("singleCredentialScreen") {
             ModalBottomSheet(
@@ -86,6 +93,39 @@
         }
     }
 
+    @Test
+    fun singleCredentialScreen_M3BottomSheetEnabled() {
+        setFlagsRule.enableFlags(Flags.FLAG_SELECTOR_UI_IMPROVEMENTS_ENABLED)
+        val providerInfoList = buildProviderInfoList()
+        val providerDisplayInfo = toProviderDisplayInfo(providerInfoList, emptyMap())
+        val activeEntry = toActiveEntry(providerDisplayInfo)
+        screenshotRule.screenshotTest(
+                "singleCredentialScreen_newM3BottomSheet",
+                // M3's ModalBottomSheet lives in a new window, meaning we have two windows with
+                // a root. Hence use a different matcher `isPopup`.
+                viewFinder = { screenshotRule.composeRule.onNode(isPopup()) },
+        ) {
+            ModalBottomSheet(
+                    sheetContent = {
+                        PrimarySelectionCard(
+                                requestDisplayInfo = REQUEST_DISPLAY_INFO,
+                                providerDisplayInfo = providerDisplayInfo,
+                                providerInfoList = providerInfoList,
+                                activeEntry = activeEntry,
+                                onEntrySelected = {},
+                                onConfirm = {},
+                                onMoreOptionSelected = {},
+                                onLog = {},
+                        )
+                    },
+                    isInitialRender = true,
+                    onDismiss = {},
+                    onInitialRenderComplete = {},
+                    isAutoSelectFlow = false,
+            )
+        }
+    }
+
     private fun buildProviderInfoList(): List<ProviderInfo> {
         val context = ApplicationProvider.getApplicationContext<Context>()
         val provider1 = ProviderInfo(
@@ -107,7 +147,11 @@
                                 icon = null,
                                 shouldTintIcon = true,
                                 lastUsedTimeMillis = null,
-                                isAutoSelectable = false
+                                isAutoSelectable = false,
+                                entryGroupId = "username",
+                                isDefaultIconPreferredAsSingleProvider = false,
+                                rawCredentialType = "unknown-type",
+                                affiliatedDomain = null,
                         )
                 ),
                 authenticationEntryList = emptyList(),
diff --git a/packages/CredentialManager/wear/res/values/strings.xml b/packages/CredentialManager/wear/res/values/strings.xml
index 9480e64..ee8bb78 100644
--- a/packages/CredentialManager/wear/res/values/strings.xml
+++ b/packages/CredentialManager/wear/res/values/strings.xml
@@ -21,9 +21,6 @@
   <!-- Title of a screen prompting if the user would like to use their saved passkey.
   [CHAR LIMIT=80] -->
   <string name="use_passkey_title">Use passkey?</string>
-  <!-- Title of a screen prompting if the user would like to use their saved passkey.
-[CHAR LIMIT=80] -->
-  <string name="use_sign_in_with_provider_title">Use your sign in for %1$s</string>
   <!-- Title of a screen prompting if the user would like to sign in with provider
   [CHAR LIMIT=80] -->
   <string name="use_password_title">Use password?</string>
@@ -35,6 +32,8 @@
   <string name="dialog_sign_in_options_button">Sign-in Options</string>
   <!-- Title for multiple credentials folded screen. [CHAR LIMIT=NONE] -->
   <string name="sign_in_options_title">Sign-in Options</string>
+  <!-- Provider settings list title. [CHAR LIMIT=NONE] -->
+  <string name="provider_list_title">Manage sign-ins</string>
   <!-- Title for multiple credentials screen. [CHAR LIMIT=NONE] -->
   <string name="choose_sign_in_title">Choose a sign in</string>
   <!-- Title for multiple credentials screen with only passkeys. [CHAR LIMIT=NONE] -->
diff --git a/packages/CredentialManager/wear/robotests/Android.bp b/packages/CredentialManager/wear/robotests/Android.bp
new file mode 100644
index 0000000..c0a1822
--- /dev/null
+++ b/packages/CredentialManager/wear/robotests/Android.bp
@@ -0,0 +1,28 @@
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_robolectric_test {
+    name: "CredentialSelectorTests",
+    srcs: [
+        "src/**/*.kt",
+    ],
+    // Include test libraries.
+    instrumentation_for: "ClockworkCredentialManager",
+    libs: [
+        "androidx.test.runner",
+        "androidx.test.ext.junit",
+        "kotlinx_coroutines_android",
+        "kotlinx_coroutines",
+        "kotlinx-coroutines-core",
+        "kotlinx_coroutines_test",
+        "mockito-robolectric-prebuilt",
+        "mockito-kotlin2",
+        "CredentialManagerShared",
+        "ClockworkCredentialManager",
+        "framework_graphics_flags_java_lib",
+    ],
+    java_resource_dirs: ["config"],
+    upstream: true,
+}
diff --git a/packages/CredentialManager/wear/robotests/config/robolectric.properties b/packages/CredentialManager/wear/robotests/config/robolectric.properties
new file mode 100644
index 0000000..140e42b
--- /dev/null
+++ b/packages/CredentialManager/wear/robotests/config/robolectric.properties
@@ -0,0 +1,16 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+sdk=NEWEST_SDK
+
diff --git a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt
new file mode 100644
index 0000000..3422d3d
--- /dev/null
+++ b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.credentialmanager
+
+import java.time.Instant
+import android.graphics.drawable.Drawable
+import com.android.credentialmanager.model.get.CredentialEntryInfo
+import com.android.credentialmanager.model.get.ActionEntryInfo
+import com.android.credentialmanager.model.get.AuthenticationEntryInfo
+import com.android.credentialmanager.model.Request
+import androidx.test.filters.SmallTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.junit.runner.RunWith
+import com.android.credentialmanager.model.CredentialType
+import com.google.common.truth.Truth.assertThat
+import com.android.credentialmanager.ui.mappers.toGet
+import com.android.credentialmanager.model.get.ProviderInfo
+import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry.PerUserNameEntries
+
+/** Unit tests for [CredentialSelectorUiStateGetMapper]. */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CredentialSelectorUiStateGetMapperTest {
+
+    private val mDrawable = mock<Drawable>()
+
+    private val actionEntryInfo =
+        ActionEntryInfo(
+            providerId = "",
+            entryKey = "",
+            entrySubkey = "",
+            pendingIntent = null,
+            fillInIntent = null,
+            title = "title",
+            icon = mDrawable,
+            subTitle = "subtitle",
+        )
+
+    private val authenticationEntryInfo =
+        AuthenticationEntryInfo(
+            providerId = "",
+            entryKey = "",
+            entrySubkey = "",
+            pendingIntent = null,
+            fillInIntent = null,
+            title = "title",
+            providerDisplayName = "",
+            icon = mDrawable,
+            isUnlockedAndEmpty = true,
+            isLastUnlocked = true
+        )
+
+    val passkeyCredentialEntryInfo =
+        createCredentialEntryInfo(credentialType = CredentialType.PASSKEY, userName = "userName")
+
+    val unknownCredentialEntryInfo =
+        createCredentialEntryInfo(credentialType = CredentialType.UNKNOWN, userName = "userName2")
+
+    val passwordCredentialEntryInfo =
+        createCredentialEntryInfo(credentialType = CredentialType.PASSWORD, userName = "userName")
+
+    val recentlyUsedPasskeyCredential =
+        createCredentialEntryInfo(credentialType =
+    CredentialType.PASSKEY, lastUsedTimeMillis = 2L, userName = "userName")
+
+    val recentlyUsedPasswordCredential =
+        createCredentialEntryInfo(credentialType =
+    CredentialType.PASSWORD, lastUsedTimeMillis = 2L, userName = "userName")
+
+    val credentialList1 = listOf(
+        passkeyCredentialEntryInfo,
+        passwordCredentialEntryInfo
+    )
+
+    val credentialList2 = listOf(
+        passkeyCredentialEntryInfo,
+        passwordCredentialEntryInfo,
+        recentlyUsedPasskeyCredential,
+        unknownCredentialEntryInfo,
+        recentlyUsedPasswordCredential
+    )
+
+    @Test
+    fun `On primary screen, just one account returns SingleEntry`() {
+        val getCredentialUiState = Request.Get(
+            token = null,
+            resultReceiver = null,
+            finalResponseReceiver = null,
+            providerInfos = listOf(createProviderInfo(credentialList1))).toGet(isPrimary = true)
+
+        assertThat(getCredentialUiState).isEqualTo(
+            CredentialSelectorUiState.Get.SingleEntry(passkeyCredentialEntryInfo)
+        ) // prefer passkey over password for selected credential
+    }
+
+    @Test
+    fun `On primary screen, multiple accounts returns SingleEntryPerAccount`() {
+        val getCredentialUiState = Request.Get(
+            token = null,
+            resultReceiver = null,
+            finalResponseReceiver = null,
+            providerInfos = listOf(createProviderInfo(listOf(passkeyCredentialEntryInfo,
+                unknownCredentialEntryInfo)))).toGet(isPrimary = true)
+
+        assertThat(getCredentialUiState).isEqualTo(
+            CredentialSelectorUiState.Get.SingleEntryPerAccount(
+                sortedEntries = listOf(
+                    passkeyCredentialEntryInfo, // userName
+                    unknownCredentialEntryInfo // userName2
+                ),
+                authenticationEntryList = listOf(authenticationEntryInfo)
+            )) // prefer passkey from account 1, then unknown from account 2
+    }
+
+    @Test
+    fun `On secondary screen, a MultipleEntry is returned`() {
+        val getCredentialUiState = Request.Get(
+            token = null,
+            resultReceiver = null,
+            finalResponseReceiver = null,
+            providerInfos = listOf(createProviderInfo(credentialList1))).toGet(isPrimary = false)
+
+        assertThat(getCredentialUiState).isEqualTo(
+            CredentialSelectorUiState.Get.MultipleEntry(
+                listOf(PerUserNameEntries("userName", listOf(
+                    passkeyCredentialEntryInfo,
+                    passwordCredentialEntryInfo))
+                ),
+                listOf(actionEntryInfo),
+                listOf(authenticationEntryInfo)
+            ))
+    }
+
+    @Test
+    fun `Returned multiple entry is sorted by credentialType and lastUsedTimeMillis`() {
+        val getCredentialUiState = Request.Get(
+            token = null,
+            resultReceiver = null,
+            finalResponseReceiver = null,
+            providerInfos = listOf(createProviderInfo(credentialList1),
+                createProviderInfo(credentialList2))).toGet(isPrimary = false)
+
+        assertThat(getCredentialUiState).isEqualTo(
+            CredentialSelectorUiState.Get.MultipleEntry(
+                listOf(
+                    PerUserNameEntries("userName",
+                        listOf(
+                            recentlyUsedPasskeyCredential, // from provider 2
+                            passkeyCredentialEntryInfo, // from provider 1 or 2
+                            passkeyCredentialEntryInfo, // from provider 1 or 2
+                            recentlyUsedPasswordCredential, // from provider 2
+                            passwordCredentialEntryInfo, // from provider 1 or 2
+                            passwordCredentialEntryInfo, // from provider 1 or 2
+                        )),
+                    PerUserNameEntries("userName2", listOf(unknownCredentialEntryInfo)),
+                ),
+                listOf(actionEntryInfo, actionEntryInfo),
+                listOf(authenticationEntryInfo, authenticationEntryInfo)
+            )
+        )
+    }
+
+    fun createCredentialEntryInfo(
+        userName: String,
+        credentialType: CredentialType = CredentialType.PASSKEY,
+        lastUsedTimeMillis: Long = 0L
+    ): CredentialEntryInfo =
+        CredentialEntryInfo(
+            providerId = "",
+            entryKey = "",
+            entrySubkey = "",
+            pendingIntent = null,
+            fillInIntent = null,
+            credentialType = credentialType,
+            rawCredentialType = "",
+            credentialTypeDisplayName = "",
+            providerDisplayName = "",
+            userName = userName,
+            displayName = "",
+            icon = mDrawable,
+            shouldTintIcon = false,
+            lastUsedTimeMillis = Instant.ofEpochMilli(lastUsedTimeMillis),
+            isAutoSelectable = true,
+            entryGroupId = "",
+            isDefaultIconPreferredAsSingleProvider = false,
+            affiliatedDomain = "",
+        )
+
+    fun createProviderInfo(credentials: List<CredentialEntryInfo> = listOf()): ProviderInfo =
+        ProviderInfo(
+            id = "providerInfo",
+            icon = mDrawable,
+            displayName = "displayName",
+            credentialEntryList = credentials,
+            authenticationEntryList = listOf(authenticationEntryInfo),
+            remoteEntry = null,
+            actionEntryList = listOf(actionEntryInfo)
+        )
+}
diff --git a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt
new file mode 100644
index 0000000..b79f34c
--- /dev/null
+++ b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.credentialmanager
+
+import org.mockito.kotlin.whenever
+import com.android.credentialmanager.model.EntryInfo
+import com.android.credentialmanager.model.Request
+import androidx.test.filters.SmallTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.Before
+import java.util.Collections.emptyList
+import org.junit.runner.RunWith
+import android.content.Intent
+import com.android.credentialmanager.client.CredentialManagerClient
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.launchIn
+import android.credentials.selection.BaseDialogResult
+import com.google.common.truth.Truth.assertThat
+import org.mockito.kotlin.doReturn
+import kotlinx.coroutines.Job
+import org.junit.After
+import org.robolectric.shadows.ShadowLooper
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Unit tests for [CredentialSelectorViewModel]. */
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CredentialSelectorViewModelTest {
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+
+    private val stateFlow: MutableStateFlow<Request?> = MutableStateFlow(Request.Create(null))
+    private val credentialManagerClient = mock<CredentialManagerClient>{
+        on { requests } doReturn stateFlow
+    }
+    private val mViewModel = CredentialSelectorViewModel(credentialManagerClient)
+    private lateinit var job: Job
+
+    val testEntryInfo =
+        EntryInfo(
+            providerId = "",
+            entryKey = "",
+            entrySubkey = "",
+            pendingIntent = null,
+            fillInIntent = null,
+            shouldTerminateUiUponSuccessfulProviderResult = true)
+
+    @Before
+    fun setUp() {
+      job = checkNotNull(mViewModel).uiState.launchIn(testScope)
+    }
+
+    @After
+    fun teardown() {
+        job.cancel()
+    }
+
+    @Test
+    fun `Setting state to idle when receiving null request`() {
+        stateFlow.value = null
+        ShadowLooper.idleMainLooper()
+
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Idle)
+    }
+
+    @Test
+    fun `Setting state to cancel when receiving Cancel request`() {
+        stateFlow.value = Request.Cancel(appName = "appName", token = null)
+        ShadowLooper.idleMainLooper()
+
+        assertThat(mViewModel.uiState.value)
+            .isEqualTo(CredentialSelectorUiState.Cancel("appName"))
+    }
+
+    @Test
+    fun `Setting state to create when receiving Create request`() {
+        stateFlow.value = Request.Create(token = null)
+        ShadowLooper.idleMainLooper()
+
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Create)
+    }
+
+    @Test
+    fun `Closing app when receiving Close request`() {
+        stateFlow.value = Request.Close(token = null)
+        ShadowLooper.idleMainLooper()
+
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+    }
+
+    @Test
+    fun `Updates request`() {
+        val intent = Intent()
+
+        mViewModel.updateRequest(intent)
+
+        verify(credentialManagerClient).updateRequest(intent)
+    }
+
+    @Test
+    fun `Back on a single entry screen closes app`() {
+        mViewModel.openSecondaryScreen()
+        stateFlow.value = Request.Get(
+            token = null,
+            resultReceiver = null,
+            finalResponseReceiver = null,
+            providerInfos = emptyList())
+
+        mViewModel.back()
+        ShadowLooper.idleMainLooper()
+
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+    }
+
+    @Test
+    fun `Back on a multiple entry screen gets us back to a primary screen`() {
+        mViewModel.openSecondaryScreen()
+        stateFlow.value = Request.Get(
+            token = null,
+            resultReceiver = null,
+            finalResponseReceiver = null,
+            providerInfos = emptyList())
+
+        mViewModel.back()
+        ShadowLooper.idleMainLooper()
+
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+    }
+
+    @Test
+    fun `Back on create request state closes app`() {
+        stateFlow.value = Request.Create(token = null)
+
+        mViewModel.back()
+        ShadowLooper.idleMainLooper()
+
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+    }
+
+    @Test
+    fun `Back on close request state closes app`() {
+        stateFlow.value = Request.Close(token = null)
+
+        mViewModel.back()
+        ShadowLooper.idleMainLooper()
+
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+    }
+
+    @Test
+    fun `Back on cancel request state closes app`() {
+        stateFlow.value = Request.Cancel(appName = "", token = null)
+
+        mViewModel.back()
+        ShadowLooper.idleMainLooper()
+
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+    }
+
+    @Test
+    fun `Back on idle request state closes app`() {
+        stateFlow.value = null
+
+        mViewModel.back()
+        ShadowLooper.idleMainLooper()
+
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+    }
+
+    @Test
+    fun `Cancel closes the app`() {
+        mViewModel.cancel()
+        ShadowLooper.idleMainLooper()
+
+        verify(credentialManagerClient).sendError(BaseDialogResult.RESULT_CODE_DIALOG_USER_CANCELED)
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+    }
+
+    @Test
+    fun `Send entry selection result closes app and calls client method`() {
+        whenever(credentialManagerClient.sendEntrySelectionResult(
+            entryInfo = testEntryInfo,
+            resultCode = null,
+            resultData = null,
+            isAutoSelected = false
+        )).thenReturn(true)
+
+        mViewModel.sendSelectionResult(
+            entryInfo = testEntryInfo,
+            resultCode = null,
+            resultData = null,
+            isAutoSelected = false)
+        ShadowLooper.idleMainLooper()
+
+        verify(credentialManagerClient).sendEntrySelectionResult(
+            testEntryInfo, null, null, false
+        )
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+    }
+
+    @Test
+    fun `Send entry selection result does not close app on false return`() {
+        whenever(credentialManagerClient.sendEntrySelectionResult(
+            entryInfo = testEntryInfo,
+            resultCode = null,
+            resultData = null,
+            isAutoSelected = false
+        )).thenReturn(false)
+        stateFlow.value = Request.Create(null)
+
+        mViewModel.sendSelectionResult(entryInfo = testEntryInfo, resultCode = null,
+            resultData = null, isAutoSelected = false)
+        ShadowLooper.idleMainLooper()
+
+        verify(credentialManagerClient).sendEntrySelectionResult(
+            entryInfo = testEntryInfo,
+            resultCode = null,
+            resultData = null,
+            isAutoSelected = false
+        )
+        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Create)
+    }
+}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 463c4d1..9d97763 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -29,10 +29,14 @@
 import com.android.credentialmanager.model.get.CredentialEntryInfo
 import com.android.credentialmanager.ui.mappers.toGet
 import android.util.Log
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.compose.runtime.Composable
 import com.android.credentialmanager.CredentialSelectorUiState.Cancel
 import com.android.credentialmanager.CredentialSelectorUiState.Close
 import com.android.credentialmanager.CredentialSelectorUiState.Create
 import com.android.credentialmanager.CredentialSelectorUiState.Idle
+import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
+import com.android.credentialmanager.ktx.getIntentSenderRequest
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
@@ -47,14 +51,18 @@
 ) : FlowEngine, ViewModel() {
     private val isPrimaryScreen = MutableStateFlow(true)
     private val shouldClose = MutableStateFlow(false)
+    private lateinit var selectedEntry: EntryInfo
+    private var isAutoSelected: Boolean = false
     val uiState: StateFlow<CredentialSelectorUiState> =
         combine(
             credentialManagerClient.requests,
             isPrimaryScreen,
             shouldClose
         ) { request, isPrimary, shouldClose ->
+            Log.d(TAG, "Request updated: " + request?.toString() +
+                    " isClose: " + shouldClose.toString() +
+                    " isPrimaryScreen: " + isPrimary.toString())
             if (shouldClose) {
-                Log.d(TAG, "Request finished, closing ")
                 return@combine Close
             }
 
@@ -108,13 +116,35 @@
         )
         shouldClose.value = result
     }
+
+    @Composable
+    override fun getEntrySelector(): (entry: EntryInfo, isAutoSelected: Boolean) -> Unit {
+        val launcher = rememberLauncherForActivityResult(
+            StartBalIntentSenderForResultContract()
+        ) {
+            sendSelectionResult(entryInfo = selectedEntry,
+                resultCode = it.resultCode,
+                resultData = it.data,
+                isAutoSelected = isAutoSelected)
+        }
+        return { selected, autoSelect ->
+            selectedEntry = selected
+            isAutoSelected = autoSelect
+            selected.getIntentSenderRequest()?.let {
+                launcher.launch(it)
+            } ?: Log.w(TAG, "Cannot parse IntentSenderRequest")
+        }
+    }
 }
 
 sealed class CredentialSelectorUiState {
     data object Idle : CredentialSelectorUiState()
     sealed class Get : CredentialSelectorUiState() {
         data class SingleEntry(val entry: CredentialEntryInfo) : Get()
-        data class SingleEntryPerAccount(val sortedEntries: List<CredentialEntryInfo>) : Get()
+        data class SingleEntryPerAccount(
+            val sortedEntries: List<CredentialEntryInfo>,
+            val authenticationEntryList: List<AuthenticationEntryInfo>,
+            ) : Get()
         data class MultipleEntry(
             val accounts: List<PerUserNameEntries>,
             val actionEntryList: List<ActionEntryInfo>,
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt
index e421644..2e80a7c 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt
@@ -17,6 +17,8 @@
 package com.android.credentialmanager
 
 import android.content.Intent
+import androidx.activity.result.IntentSenderRequest
+import androidx.compose.runtime.Composable
 import com.android.credentialmanager.model.EntryInfo
 
 /** Engine of the credential selecting flow. */
@@ -42,4 +44,14 @@
         resultData: Intent? = null,
         isAutoSelected: Boolean = false,
     )
+
+    /**
+     * Helper function to get an entry selector.
+     *
+     * @return selector fun consumes selected [EntryInfo]. Once invoked, [IntentSenderRequest] would
+     * be launched and invocation of [sendSelectionResult] would happen right after launching result
+     * coming back.
+     */
+    @Composable
+    fun getEntrySelector(): (entry: EntryInfo, isAutoSelected: Boolean) -> Unit
 }
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
index f9a58871..bf4c988 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
@@ -29,6 +29,7 @@
 import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
 import androidx.wear.compose.navigation.rememberSwipeDismissableNavHostState
 import com.android.credentialmanager.CredentialSelectorUiState
+import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntryPerAccount
 import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntry
 import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry
 import com.android.credentialmanager.CredentialSelectorViewModel
@@ -43,7 +44,10 @@
 import com.google.android.horologist.compose.navscaffold.composable
 import com.google.android.horologist.compose.navscaffold.scrollable
 import com.android.credentialmanager.model.CredentialType
+import com.android.credentialmanager.model.EntryInfo
 import com.android.credentialmanager.ui.screens.multiple.MultiCredentialsFoldScreen
+import com.android.credentialmanager.ui.screens.multiple.MultiCredentialsFlattenScreen
+
 
 @OptIn(ExperimentalHorologistApi::class)
 @Composable
@@ -56,6 +60,7 @@
     val swipeToDismissBoxState = rememberSwipeToDismissBoxState()
     val navHostState =
         rememberSwipeDismissableNavHostState(swipeToDismissBoxState = swipeToDismissBoxState)
+    val selectEntry = flowEngine.getEntrySelector()
 
     val uiState by viewModel.uiState.collectAsStateWithLifecycle()
     WearNavScaffold(
@@ -76,66 +81,83 @@
 
         scrollable(Screen.SinglePasskeyScreen.route) {
             SinglePasskeyScreen(
-                credentialSelectorUiState = viewModel.uiState.value as SingleEntry,
+                entry = (remember { uiState } as SingleEntry).entry,
                 columnState = it.columnState,
+                flowEngine = flowEngine,
             )
         }
 
         scrollable(Screen.SignInWithProviderScreen.route) {
             SignInWithProviderScreen(
-                credentialSelectorUiState = viewModel.uiState.value as SingleEntry,
+                entry = (remember { uiState } as SingleEntry).entry,
                 columnState = it.columnState,
+                flowEngine = flowEngine,
             )
         }
 
         scrollable(Screen.MultipleCredentialsScreenFold.route) {
             MultiCredentialsFoldScreen(
-                credentialSelectorUiState = viewModel.uiState.value as MultipleEntry,
-                screenIcon = null,
+                credentialSelectorUiState = (remember { uiState } as SingleEntryPerAccount),
                 columnState = it.columnState,
+                flowEngine = flowEngine,
+            )
+        }
+
+        scrollable(Screen.MultipleCredentialsScreenFlatten.route) {
+            MultiCredentialsFlattenScreen(
+                credentialSelectorUiState = (remember { uiState } as MultipleEntry),
+                columnState = it.columnState,
+                flowEngine = flowEngine,
             )
         }
     }
-    BackHandler(true) {
-        viewModel.back()
-    }
-    Log.d(TAG, "uiState change, state: $uiState")
-    when (val state = uiState) {
-        CredentialSelectorUiState.Idle -> {
-            if (navController.currentDestination?.route != Screen.Loading.route) {
-                navController.navigateToLoading()
+        BackHandler(true) {
+            viewModel.back()
+        }
+        Log.d(TAG, "uiState change, state: $uiState")
+        when (val state = uiState) {
+            CredentialSelectorUiState.Idle -> {
+                if (navController.currentDestination?.route != Screen.Loading.route) {
+                    navController.navigateToLoading()
+                }
+            }
+
+            is CredentialSelectorUiState.Get -> {
+                handleGetNavigation(
+                    navController = navController,
+                    state = state,
+                    onCloseApp = onCloseApp,
+                    selectEntry = selectEntry
+                )
+            }
+
+            CredentialSelectorUiState.Create -> {
+                // TODO: b/301206624 - Implement create flow
+                onCloseApp()
+            }
+
+            is CredentialSelectorUiState.Cancel -> {
+                onCloseApp()
+            }
+
+            CredentialSelectorUiState.Close -> {
+                onCloseApp()
             }
         }
-        is CredentialSelectorUiState.Get -> {
-            handleGetNavigation(
-                navController = navController,
-                state = state,
-                onCloseApp = onCloseApp,
-            )
-        }
-
-        CredentialSelectorUiState.Create -> {
-            // TODO: b/301206624 - Implement create flow
-            onCloseApp()
-        }
-
-        is CredentialSelectorUiState.Cancel -> {
-            onCloseApp()
-        }
-
-        CredentialSelectorUiState.Close -> {
-            onCloseApp()
-        }
     }
-}
 
 private fun handleGetNavigation(
     navController: NavController,
     state: CredentialSelectorUiState.Get,
     onCloseApp: () -> Unit,
+    selectEntry: (entry: EntryInfo, isAutoSelected: Boolean) -> Unit,
 ) {
     when (state) {
         is SingleEntry -> {
+            if (state.entry.isAutoSelectable) {
+                selectEntry(state.entry, true)
+                return
+            }
             when (state.entry.credentialType) {
                 CredentialType.UNKNOWN -> {
                     navController.navigateToSignInWithProviderScreen()
@@ -149,13 +171,12 @@
             }
         }
 
-        is MultipleEntry -> {
-            navController.navigateToMultipleCredentialsFoldScreen()
-        }
+            is SingleEntryPerAccount -> {
+                navController.navigateToMultipleCredentialsFoldScreen()
+            }
 
-        else -> {
-            // TODO: b/301206470 - Implement other get flows
-            onCloseApp()
+            is MultipleEntry -> {
+                navController.navigateToMultipleCredentialsFlattenScreen()
+            }
         }
     }
-}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
index 03b0931..7a936b6 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
@@ -32,11 +32,14 @@
     return if (isPrimary) {
         if (accounts.size == 1) {
             CredentialSelectorUiState.Get.SingleEntry(
-                accounts[0].value.minWith(comparator)
+                entry = accounts[0].value.minWith(comparator)
             )
         } else {
             CredentialSelectorUiState.Get.SingleEntryPerAccount(
-                accounts.map { it.value.minWith(comparator) }.sortedWith(comparator)
+                sortedEntries = accounts.map {
+                    it.value.minWith(comparator)
+                }.sortedWith(comparator),
+                authenticationEntryList = providerInfos.flatMap { it.authenticationEntryList }
             )
         }
     } else {
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt
deleted file mode 100644
index 98a9e93..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.screens
-
-import androidx.activity.result.IntentSenderRequest
-
-sealed class UiState {
-    data object CredentialScreen : UiState()
-
-    data class CredentialSelected(
-        val intentSenderRequest: IntentSenderRequest?
-    ) : UiState()
-
-    data object Cancel : UiState()
-}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
index 11188b4..d54103c 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
@@ -15,112 +15,53 @@
  */
 package com.android.credentialmanager.ui.screens.multiple
 
-import android.graphics.drawable.Drawable
-import com.android.credentialmanager.ui.screens.UiState
-import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.rememberNavController
 import androidx.wear.compose.material.MaterialTheme
 import androidx.wear.compose.material.Text
 import com.android.credentialmanager.ui.components.SignInHeader
 import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry
+import com.android.credentialmanager.FlowEngine
 import com.android.credentialmanager.R
-import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
-import com.android.credentialmanager.model.get.ActionEntryInfo
 import com.android.credentialmanager.model.get.CredentialEntryInfo
 import com.android.credentialmanager.ui.components.CredentialsScreenChip
 import com.google.android.horologist.annotations.ExperimentalHorologistApi
 import com.google.android.horologist.compose.layout.ScalingLazyColumn
 import com.google.android.horologist.compose.layout.ScalingLazyColumnState
 
-
 /**
  * Screen that shows multiple credentials to select from, grouped by accounts
  *
  * @param credentialSelectorUiState The app bar view model.
- * @param screenIcon The view model corresponding to the home page.
  * @param columnState ScalingLazyColumn configuration to be be applied
  * @param modifier styling for composable
- * @param viewModel ViewModel that updates ui state for this screen
- * @param navController handles navigation events from this screen
+ * @param flowEngine [FlowEngine] that updates ui state for this screen
  */
 @OptIn(ExperimentalHorologistApi::class)
 @Composable
 fun MultiCredentialsFlattenScreen(
     credentialSelectorUiState: MultipleEntry,
-    screenIcon: Drawable?,
     columnState: ScalingLazyColumnState,
-    modifier: Modifier = Modifier,
-    viewModel: MultiCredentialsFlattenViewModel = hiltViewModel(),
-    navController: NavHostController = rememberNavController(),
+    flowEngine: FlowEngine,
 ) {
-    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
-
-    when (val state = uiState) {
-        UiState.CredentialScreen -> {
-            MultiCredentialsFlattenScreen(
-                state = credentialSelectorUiState,
-                columnState = columnState,
-                screenIcon = screenIcon,
-                onActionEntryClicked = viewModel::onActionEntryClicked,
-                onCredentialClicked = viewModel::onCredentialClicked,
-                modifier = modifier,
-            )
-        }
-
-        is UiState.CredentialSelected -> {
-            val launcher = rememberLauncherForActivityResult(
-                StartBalIntentSenderForResultContract()
-            ) {
-                viewModel.onInfoRetrieved(it.resultCode, null)
-            }
-
-            SideEffect {
-                state.intentSenderRequest?.let {
-                    launcher.launch(it)
-                }
-            }
-        }
-
-        UiState.Cancel -> {
-            navController.popBackStack()
-        }
-    }
-}
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-fun MultiCredentialsFlattenScreen(
-    state: MultipleEntry,
-    columnState: ScalingLazyColumnState,
-    screenIcon: Drawable?,
-    onActionEntryClicked: (entryInfo: ActionEntryInfo) -> Unit,
-    onCredentialClicked: (entryInfo: CredentialEntryInfo) -> Unit,
-    modifier: Modifier,
-) {
+    val selectEntry = flowEngine.getEntrySelector()
     ScalingLazyColumn(
         columnState = columnState,
-        modifier = modifier.fillMaxSize(),
+        modifier = Modifier.fillMaxSize(),
     ) {
         item {
             // make this credential specific if all credentials are same
             SignInHeader(
-                icon = screenIcon,
+                icon = null,
                 title = stringResource(R.string.sign_in_options_title),
             )
         }
 
-        state.accounts.forEach { userNameEntries ->
+        credentialSelectorUiState.accounts.forEach { userNameEntries ->
             item {
                 Text(
                     text = userNameEntries.userName,
@@ -135,17 +76,16 @@
                 item {
                     CredentialsScreenChip(
                         label = credential.userName,
-                        onClick = { onCredentialClicked(credential) },
-                        secondaryLabel = credential.userName,
+                        onClick = { selectEntry(credential, false) },
+                        secondaryLabel = credential.credentialTypeDisplayName,
                         icon = credential.icon,
-                        modifier = modifier,
                     )
                 }
             }
         }
         item {
             Text(
-                text = "Manage Sign-ins",
+                text = stringResource(R.string.provider_list_title),
                 modifier = Modifier
                     .padding(top = 6.dp)
                     .padding(horizontal = 10.dp),
@@ -153,14 +93,13 @@
             )
         }
 
-        state.actionEntryList.forEach {
+        credentialSelectorUiState.actionEntryList.forEach {actionEntry ->
             item {
                     CredentialsScreenChip(
-                        label = it.title,
-                        onClick = { onActionEntryClicked(it) },
+                        label = actionEntry.title,
+                        onClick = { selectEntry(actionEntry, false) },
                         secondaryLabel = null,
-                        icon = it.icon,
-                        modifier = modifier,
+                        icon = actionEntry.icon,
                     )
             }
         }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenViewModel.kt
deleted file mode 100644
index ee5f3f4..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenViewModel.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.screens.multiple
-
-import android.content.Intent
-import android.credentials.selection.ProviderPendingIntentResponse
-import android.credentials.selection.UserSelectionDialogResult
-import androidx.lifecycle.ViewModel
-import com.android.credentialmanager.client.CredentialManagerClient
-import com.android.credentialmanager.ktx.getIntentSenderRequest
-import com.android.credentialmanager.model.Request
-import com.android.credentialmanager.model.get.ActionEntryInfo
-import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.ui.screens.UiState
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import javax.inject.Inject
-
-/** ViewModel for [MultiCredentialsFlattenScreen].*/
-@HiltViewModel
-class MultiCredentialsFlattenViewModel @Inject constructor(
-    private val credentialManagerClient: CredentialManagerClient,
-) : ViewModel() {
-
-    private lateinit var requestGet: Request.Get
-    private lateinit var entryInfo: CredentialEntryInfo
-
-    private val _uiState =
-        MutableStateFlow<UiState>(UiState.CredentialScreen)
-    val uiState: StateFlow<UiState> = _uiState
-
-    fun onCredentialClicked(entryInfo: CredentialEntryInfo) {
-        this.entryInfo = entryInfo
-        _uiState.value = UiState.CredentialSelected(
-            intentSenderRequest = entryInfo.getIntentSenderRequest()
-        )
-    }
-
-    fun onCancelClicked() {
-        _uiState.value = UiState.Cancel
-    }
-
-    fun onInfoRetrieved(
-        resultCode: Int? = null,
-        resultData: Intent? = null,
-    ) {
-        val userSelectionDialogResult = UserSelectionDialogResult(
-            requestGet.token,
-            entryInfo.providerId,
-            entryInfo.entryKey,
-            entryInfo.entrySubkey,
-            if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
-        )
-        credentialManagerClient.sendResult(userSelectionDialogResult)
-    }
-
-    fun onActionEntryClicked(actionEntryInfo: ActionEntryInfo) {
-        // TODO(b/322797032)to be filled out
-    }
-}
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
index 5515c86..6f32c99 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
@@ -16,24 +16,15 @@
 
 package com.android.credentialmanager.ui.screens.multiple
 
-import com.android.credentialmanager.ui.screens.UiState
-import android.graphics.drawable.Drawable
-import androidx.activity.compose.rememberLauncherForActivityResult
 import com.android.credentialmanager.R
 import androidx.compose.ui.res.stringResource
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.rememberNavController
 import com.android.credentialmanager.CredentialSelectorUiState
-import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
+import com.android.credentialmanager.FlowEngine
 import com.android.credentialmanager.model.get.CredentialEntryInfo
 import com.android.credentialmanager.ui.components.DismissChip
 import com.android.credentialmanager.ui.components.CredentialsScreenChip
@@ -49,74 +40,22 @@
  * Screen that shows multiple credentials to select from.
  *
  * @param credentialSelectorUiState The app bar view model.
- * @param screenIcon The view model corresponding to the home page.
  * @param columnState ScalingLazyColumn configuration to be be applied
- * @param modifier styling for composable
- * @param viewModel ViewModel that updates ui state for this screen
- * @param navController handles navigation events from this screen
  */
 @OptIn(ExperimentalHorologistApi::class)
 @Composable
 fun MultiCredentialsFoldScreen(
-    credentialSelectorUiState: CredentialSelectorUiState.Get.MultipleEntry,
-    screenIcon: Drawable?,
+    credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntryPerAccount,
     columnState: ScalingLazyColumnState,
-    modifier: Modifier = Modifier,
-    viewModel: MultiCredentialsFoldViewModel = hiltViewModel(),
-    navController: NavHostController = rememberNavController(),
+    flowEngine: FlowEngine,
 ) {
-    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
-
-    when (val state = uiState) {
-        UiState.CredentialScreen -> {
-            MultiCredentialsFoldScreen(
-                state = credentialSelectorUiState,
-                onSignInOptionsClicked = viewModel::onSignInOptionsClicked,
-                onCredentialClicked = viewModel::onCredentialClicked,
-                onCancelClicked = viewModel::onCancelClicked,
-                screenIcon = screenIcon,
-                columnState = columnState,
-                modifier = modifier
-            )
-        }
-
-        is UiState.CredentialSelected -> {
-            val launcher = rememberLauncherForActivityResult(
-                StartBalIntentSenderForResultContract()
-            ) {
-                viewModel.onInfoRetrieved(it.resultCode, null)
-            }
-
-            SideEffect {
-                state.intentSenderRequest?.let {
-                    launcher.launch(it)
-                }
-            }
-        }
-
-        UiState.Cancel -> {
-            navController.popBackStack()
-        }
-    }
-}
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-fun MultiCredentialsFoldScreen(
-    state: CredentialSelectorUiState.Get.MultipleEntry,
-    onSignInOptionsClicked: () -> Unit,
-    onCredentialClicked: (entryInfo: CredentialEntryInfo) -> Unit,
-    onCancelClicked: () -> Unit,
-    screenIcon: Drawable?,
-    columnState: ScalingLazyColumnState,
-    modifier: Modifier,
-) {
+    val selectEntry = flowEngine.getEntrySelector()
     ScalingLazyColumn(
         columnState = columnState,
-        modifier = modifier.fillMaxSize(),
+        modifier = Modifier.fillMaxSize(),
     ) {
         // flatten all credentials into one
-        val credentials = state.accounts.flatMap { it.sortedCredentialEntryList }
+        val credentials = credentialSelectorUiState.sortedEntries
         item {
             var title = stringResource(R.string.choose_sign_in_title)
             if (credentials.all{ it.credentialType == CredentialType.PASSKEY }) {
@@ -126,7 +65,7 @@
             }
 
             SignInHeader(
-                icon = screenIcon,
+                icon = null,
                 title = title,
                 modifier = Modifier
                     .padding(top = 6.dp),
@@ -137,22 +76,24 @@
                 item {
                     CredentialsScreenChip(
                         label = credential.userName,
-                        onClick = { onCredentialClicked(credential) },
+                        onClick = { selectEntry(credential, false) },
                         secondaryLabel = credential.credentialTypeDisplayName,
                         icon = credential.icon,
                     )
                 }
             }
 
-        state.authenticationEntryList.forEach { authenticationEntryInfo ->
+        credentialSelectorUiState.authenticationEntryList.forEach { authenticationEntryInfo ->
             item {
                 LockedProviderChip(authenticationEntryInfo) {
-                    // TODO(b/322797032) invoke LockedProviderScreen here using flow engine
-                }
+                    selectEntry(authenticationEntryInfo, false) }
             }
         }
-
-        item { SignInOptionsChip(onSignInOptionsClicked)}
-        item { DismissChip(onCancelClicked) }
+        item {
+            SignInOptionsChip { flowEngine.openSecondaryScreen() }
+        }
+        item {
+            DismissChip { flowEngine.cancel() }
+        }
     }
 }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldViewModel.kt
deleted file mode 100644
index 627a63d..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldViewModel.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.screens.multiple
-
-import android.content.Intent
-import android.credentials.selection.ProviderPendingIntentResponse
-import android.credentials.selection.UserSelectionDialogResult
-import androidx.lifecycle.ViewModel
-import com.android.credentialmanager.client.CredentialManagerClient
-import com.android.credentialmanager.ktx.getIntentSenderRequest
-import com.android.credentialmanager.model.Request
-import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.ui.screens.UiState
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import javax.inject.Inject
-
-/** ViewModel for [MultiCredentialsFoldScreen].*/
-@HiltViewModel
-class MultiCredentialsFoldViewModel @Inject constructor(
-    private val credentialManagerClient: CredentialManagerClient,
-) : ViewModel() {
-
-    private lateinit var requestGet: Request.Get
-    private lateinit var entryInfo: CredentialEntryInfo
-
-    private val _uiState =
-        MutableStateFlow<UiState>(UiState.CredentialScreen)
-    val uiState: StateFlow<UiState> = _uiState
-
-    fun onCredentialClicked(entryInfo: CredentialEntryInfo) {
-        this.entryInfo = entryInfo
-        _uiState.value = UiState.CredentialSelected(
-            intentSenderRequest = entryInfo.getIntentSenderRequest()
-        )
-    }
-
-    fun onSignInOptionsClicked() {
-        // TODO(b/322797032) Implement navigation route for single credential screen to multiple
-        // credentials
-    }
-
-    fun onCancelClicked() {
-        _uiState.value = UiState.Cancel
-    }
-
-    fun onInfoRetrieved(
-        resultCode: Int? = null,
-        resultData: Intent? = null,
-    ) {
-        val userSelectionDialogResult = UserSelectionDialogResult(
-            requestGet.token,
-            entryInfo.providerId,
-            entryInfo.entryKey,
-            entryInfo.entrySubkey,
-            if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
-        )
-        credentialManagerClient.sendResult(userSelectionDialogResult)
-    }
-}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
index b2595a1..e79176b 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
@@ -19,122 +19,62 @@
 package com.android.credentialmanager.ui.screens.single.passkey
 
 import androidx.compose.foundation.layout.Column
-import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.rememberNavController
-import com.android.credentialmanager.CredentialSelectorUiState
+import com.android.credentialmanager.FlowEngine
 import com.android.credentialmanager.model.get.CredentialEntryInfo
 import com.android.credentialmanager.R
-import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
 import com.android.credentialmanager.ui.components.AccountRow
 import com.android.credentialmanager.ui.components.ContinueChip
 import com.android.credentialmanager.ui.components.DismissChip
 import com.android.credentialmanager.ui.components.SignInHeader
 import com.android.credentialmanager.ui.components.SignInOptionsChip
 import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
-import com.android.credentialmanager.ui.screens.UiState
 import com.google.android.horologist.annotations.ExperimentalHorologistApi
 import com.google.android.horologist.compose.layout.ScalingLazyColumnState
 
 /**
  * Screen that shows sign in with provider credential.
  *
- * @param credentialSelectorUiState The app bar view model.
+ * @param entry The password entry
  * @param columnState ScalingLazyColumn configuration to be be applied to SingleAccountScreen
  * @param modifier styling for composable
- * @param viewModel ViewModel that updates ui state for this screen
- * @param navController handles navigation events from this screen
+ * @param flowEngine [FlowEngine] that updates ui state for this screen
  */
 @OptIn(ExperimentalHorologistApi::class)
 @Composable
 fun SinglePasskeyScreen(
-    credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry,
-    columnState: ScalingLazyColumnState,
-    modifier: Modifier = Modifier,
-    viewModel: SinglePasskeyScreenViewModel = hiltViewModel(),
-    navController: NavHostController = rememberNavController(),
-) {
-    viewModel.initialize(credentialSelectorUiState.entry)
-
-    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
-
-    when (val state = uiState) {
-        UiState.CredentialScreen -> {
-            SinglePasskeyScreen(
-                credentialSelectorUiState.entry,
-                columnState,
-                modifier,
-                viewModel
-            )
-        }
-
-        is UiState.CredentialSelected -> {
-            val launcher = rememberLauncherForActivityResult(
-                StartBalIntentSenderForResultContract()
-            ) {
-                viewModel.onPasskeyInfoRetrieved(it.resultCode, null)
-            }
-
-            SideEffect {
-                state.intentSenderRequest?.let {
-                    launcher.launch(it)
-                }
-            }
-        }
-
-        UiState.Cancel -> {
-            // TODO(b/322797032) add valid navigation path here for going back
-            navController.popBackStack()
-        }
-    }
-}
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-fun SinglePasskeyScreen(
     entry: CredentialEntryInfo,
     columnState: ScalingLazyColumnState,
     modifier: Modifier = Modifier,
-    viewModel: SinglePasskeyScreenViewModel,
+    flowEngine: FlowEngine,
 ) {
     SingleAccountScreen(
         headerContent = {
             SignInHeader(
                 icon = entry.icon,
-                title = stringResource(R.string.use_passkey_title),
+                title = stringResource(R.string.use_password_title),
             )
         },
         accountContent = {
-            if (entry.displayName != null) {
-                AccountRow(
+            AccountRow(
                     primaryText = checkNotNull(entry.displayName),
                     secondaryText = entry.userName,
                     modifier = Modifier.padding(top = 10.dp),
                 )
-            } else {
-                AccountRow(
-                    primaryText = entry.userName,
-                    modifier = Modifier.padding(top = 10.dp),
-                )
-            }
         },
         columnState = columnState,
         modifier = modifier.padding(horizontal = 10.dp)
     ) {
         item {
+            val selectEntry = flowEngine.getEntrySelector()
             Column {
-                ContinueChip(viewModel::onContinueClick)
-                SignInOptionsChip(viewModel::onSignInOptionsClick)
-                DismissChip(viewModel::onDismissClick)
+                ContinueChip { selectEntry(entry, false) }
+                SignInOptionsChip{ flowEngine.openSecondaryScreen() }
+                DismissChip { flowEngine.cancel() }
             }
         }
     }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt
deleted file mode 100644
index 37ffaca..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.screens.single.passkey
-
-import android.content.Intent
-import android.credentials.selection.UserSelectionDialogResult
-import android.credentials.selection.ProviderPendingIntentResponse
-import androidx.annotation.MainThread
-import androidx.lifecycle.ViewModel
-import com.android.credentialmanager.ktx.getIntentSenderRequest
-import com.android.credentialmanager.model.Request
-import com.android.credentialmanager.client.CredentialManagerClient
-import com.android.credentialmanager.model.get.CredentialEntryInfo
-import dagger.hilt.android.lifecycle.HiltViewModel
-import com.android.credentialmanager.ui.screens.UiState
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import javax.inject.Inject
-
-@HiltViewModel
-class SinglePasskeyScreenViewModel @Inject constructor(
-    private val credentialManagerClient: CredentialManagerClient,
-) : ViewModel() {
-
-    private val _uiState =
-        MutableStateFlow<UiState>(UiState.CredentialScreen)
-    val uiState: StateFlow<UiState> = _uiState
-
-    private lateinit var requestGet: Request.Get
-    private lateinit var entryInfo: CredentialEntryInfo
-
-
-    @MainThread
-    fun initialize(entry: CredentialEntryInfo) {
-        this.entryInfo = entry
-    }
-
-    fun onDismissClick() {
-        _uiState.value = UiState.Cancel
-    }
-
-    fun onContinueClick() {
-        _uiState.value = UiState.CredentialSelected(
-            intentSenderRequest = entryInfo.getIntentSenderRequest()
-        )
-    }
-
-    fun onSignInOptionsClick() {
-    }
-
-    fun onPasskeyInfoRetrieved(
-        resultCode: Int? = null,
-        resultData: Intent? = null,
-    ) {
-        val userSelectionDialogResult = UserSelectionDialogResult(
-            requestGet.token,
-            entryInfo.providerId,
-            entryInfo.entryKey,
-            entryInfo.entrySubkey,
-            if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
-        )
-        credentialManagerClient.sendResult(userSelectionDialogResult)
-    }
-}
-
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 1f1a296..2ca8ef1 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
@@ -18,8 +18,6 @@
 
 package com.android.credentialmanager.ui.screens.single.password
 
-import android.util.Log
-import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
@@ -28,9 +26,6 @@
 import androidx.compose.ui.unit.dp
 import com.android.credentialmanager.FlowEngine
 import com.android.credentialmanager.R
-import com.android.credentialmanager.TAG
-import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
-import com.android.credentialmanager.ktx.getIntentSenderRequest
 import com.android.credentialmanager.ui.components.PasswordRow
 import com.android.credentialmanager.ui.components.ContinueChip
 import com.android.credentialmanager.ui.components.DismissChip
@@ -57,11 +52,7 @@
     modifier: Modifier = Modifier,
     flowEngine: FlowEngine,
 ) {
-    val launcher = rememberLauncherForActivityResult(
-        StartBalIntentSenderForResultContract()
-    ) {
-        flowEngine.sendSelectionResult(entry, it.resultCode, it.data)
-    }
+    val selectEntry = flowEngine.getEntrySelector()
     SingleAccountScreen(
         headerContent = {
             SignInHeader(
@@ -80,11 +71,7 @@
     ) {
         item {
             Column {
-                ContinueChip {
-                    entry.getIntentSenderRequest()?.let {
-                        launcher.launch(it)
-                    } ?: Log.w(TAG, "Cannot parse IntentSenderRequest")
-                }
+                ContinueChip { selectEntry(entry, false) }
                 SignInOptionsChip{ flowEngine.openSecondaryScreen() }
                 DismissChip { flowEngine.cancel() }
             }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt
index b0ece0d..3a86feb 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt
@@ -16,100 +16,43 @@
 
 package com.android.credentialmanager.ui.screens.single.signInWithProvider
 
-import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.rememberNavController
-import com.android.credentialmanager.CredentialSelectorUiState
+import com.android.credentialmanager.FlowEngine
 import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.R
-import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
 import com.android.credentialmanager.ui.components.AccountRow
 import com.android.credentialmanager.ui.components.ContinueChip
 import com.android.credentialmanager.ui.components.DismissChip
 import com.android.credentialmanager.ui.components.SignInHeader
 import com.android.credentialmanager.ui.components.SignInOptionsChip
 import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
-import com.android.credentialmanager.ui.screens.UiState
 import com.google.android.horologist.annotations.ExperimentalHorologistApi
 import com.google.android.horologist.compose.layout.ScalingLazyColumnState
 
 /**
  * Screen that shows sign in with provider credential.
  *
- * @param credentialSelectorUiState The app bar view model.
+ * @param entry The password entry.
  * @param columnState ScalingLazyColumn configuration to be be applied to SingleAccountScreen
  * @param modifier styling for composable
- * @param viewModel ViewModel that updates ui state for this screen
- * @param navController handles navigation events from this screen
+ * @param flowEngine [FlowEngine] that updates ui state for this screen
  */
 @OptIn(ExperimentalHorologistApi::class)
 @Composable
 fun SignInWithProviderScreen(
-    credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry,
-    columnState: ScalingLazyColumnState,
-    modifier: Modifier = Modifier,
-    viewModel: SignInWithProviderViewModel = hiltViewModel(),
-    navController: NavHostController = rememberNavController(),
-) {
-    viewModel.initialize(credentialSelectorUiState.entry)
-
-    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
-
-    when (uiState) {
-        UiState.CredentialScreen -> {
-            SignInWithProviderScreen(
-                credentialSelectorUiState.entry,
-                columnState,
-                modifier,
-                viewModel
-            )
-        }
-
-        is UiState.CredentialSelected -> {
-            val launcher = rememberLauncherForActivityResult(
-                StartBalIntentSenderForResultContract()
-            ) {
-                viewModel.onInfoRetrieved(it.resultCode, null)
-            }
-
-            SideEffect {
-                (uiState as UiState.CredentialSelected).intentSenderRequest?.let {
-                    launcher.launch(it)
-                }
-            }
-        }
-
-        UiState.Cancel -> {
-            // TODO(b/322797032) add valid navigation path here for going back
-            navController.popBackStack()
-        }
-    }
-}
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-fun SignInWithProviderScreen(
     entry: CredentialEntryInfo,
     columnState: ScalingLazyColumnState,
     modifier: Modifier = Modifier,
-    viewModel: SignInWithProviderViewModel,
+    flowEngine: FlowEngine,
 ) {
     SingleAccountScreen(
         headerContent = {
             SignInHeader(
                 icon = entry.icon,
-                title = stringResource(R.string.use_sign_in_with_provider_title,
-                    entry.providerDisplayName),
+                title = entry.providerDisplayName,
             )
         },
         accountContent = {
@@ -130,12 +73,13 @@
         columnState = columnState,
         modifier = modifier.padding(horizontal = 10.dp)
     ) {
-       item {
-           Column {
-               ContinueChip(viewModel::onContinueClick)
-               SignInOptionsChip(viewModel::onSignInOptionsClick)
-               DismissChip(viewModel::onDismissClick)
-           }
-       }
+        item {
+            val selectEntry = flowEngine.getEntrySelector()
+            Column {
+                ContinueChip { selectEntry(entry, false) }
+                SignInOptionsChip{ flowEngine.openSecondaryScreen() }
+                DismissChip { flowEngine.cancel() }
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderViewModel.kt
deleted file mode 100644
index 7ba45e5..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderViewModel.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.screens.single.signInWithProvider
-
-import android.content.Intent
-import android.credentials.selection.ProviderPendingIntentResponse
-import android.credentials.selection.UserSelectionDialogResult
-import androidx.annotation.MainThread
-import androidx.lifecycle.ViewModel
-import com.android.credentialmanager.ktx.getIntentSenderRequest
-import com.android.credentialmanager.model.Request
-import com.android.credentialmanager.client.CredentialManagerClient
-import com.android.credentialmanager.model.get.CredentialEntryInfo
-import dagger.hilt.android.lifecycle.HiltViewModel
-import com.android.credentialmanager.ui.screens.UiState
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import javax.inject.Inject
-
-/** ViewModel for [SignInWithProviderScreen].*/
-@HiltViewModel
-class SignInWithProviderViewModel @Inject constructor(
-    private val credentialManagerClient: CredentialManagerClient,
-) : ViewModel() {
-
-    private val _uiState =
-        MutableStateFlow<UiState>(UiState.CredentialScreen)
-    val uiState: StateFlow<UiState> = _uiState
-
-    private lateinit var requestGet: Request.Get
-    private lateinit var entryInfo: CredentialEntryInfo
-
-    @MainThread
-    fun initialize(entry: CredentialEntryInfo) {
-        this.entryInfo = entry
-    }
-
-    fun onDismissClick() {
-        _uiState.value = UiState.Cancel
-    }
-
-    fun onContinueClick() {
-        _uiState.value = UiState.CredentialSelected(
-            intentSenderRequest = entryInfo.getIntentSenderRequest()
-        )
-    }
-
-    fun onSignInOptionsClick() {
-        // TODO(b/322797032) Implement navigation route for single credential screen to multiple
-        // credentials
-    }
-
-    fun onInfoRetrieved(
-        resultCode: Int? = null,
-        resultData: Intent? = null,
-    ) {
-        val userSelectionDialogResult = UserSelectionDialogResult(
-            requestGet.token,
-            entryInfo.providerId,
-            entryInfo.entryKey,
-            entryInfo.entrySubkey,
-            if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
-        )
-        credentialManagerClient.sendResult(userSelectionDialogResult)
-    }
-}
-
diff --git a/packages/EasterEgg/Android.bp b/packages/EasterEgg/Android.bp
index 0caf505..6f4f9ca 100644
--- a/packages/EasterEgg/Android.bp
+++ b/packages/EasterEgg/Android.bp
@@ -48,6 +48,8 @@
     },
 
     static_libs: [
+        "easter_egg_flags_lib",
+
         "androidx.core_core",
         "androidx.annotation_annotation",
         "androidx.recyclerview_recyclerview",
@@ -72,3 +74,16 @@
 
     kotlincflags: ["-Xjvm-default=all"],
 }
+
+java_aconfig_library {
+    name: "easter_egg_flags_lib",
+    aconfig_declarations: "easter_egg_flags",
+}
+
+aconfig_declarations {
+    name: "easter_egg_flags",
+    package: "com.android.egg.flags",
+    srcs: [
+        "easter_egg_flags.aconfig",
+    ],
+}
diff --git a/packages/EasterEgg/easter_egg_flags.aconfig b/packages/EasterEgg/easter_egg_flags.aconfig
new file mode 100644
index 0000000..3268a4f
--- /dev/null
+++ b/packages/EasterEgg/easter_egg_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.egg.flags"
+
+flag {
+    name: "flag_flag"
+    namespace: "systemui"
+    description: "Flags are planted on planets when you land. Yes, it's a flag for flags."
+    bug: "320150798"
+}
diff --git a/packages/EasterEgg/res/values-night/styles.xml b/packages/EasterEgg/res/values-night/styles.xml
index 4edf692..6ea2eae 100644
--- a/packages/EasterEgg/res/values-night/styles.xml
+++ b/packages/EasterEgg/res/values-night/styles.xml
@@ -15,7 +15,7 @@
 -->
 <resources>
     <style name="AppTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
-        <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
+        <item name="android:windowLayoutInDisplayCutoutMode">always</item>
         <item name="android:windowLightNavigationBar">false</item>
     </style>
 </resources>
diff --git a/packages/EasterEgg/res/values/styles.xml b/packages/EasterEgg/res/values/styles.xml
index e576526..4a2cb48f6 100644
--- a/packages/EasterEgg/res/values/styles.xml
+++ b/packages/EasterEgg/res/values/styles.xml
@@ -16,7 +16,7 @@
 <resources>
 
     <style name="AppTheme" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar.Fullscreen">
-        <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
+        <item name="android:windowLayoutInDisplayCutoutMode">always</item>
         <item name="android:windowLightNavigationBar">true</item>
     </style>
 
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/Universe.kt b/packages/EasterEgg/src/com/android/egg/landroid/Universe.kt
index fec3ab3..11dce61 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/Universe.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/Universe.kt
@@ -174,7 +174,9 @@
 
         ship = Spacecraft()
 
-        ship.pos = star.pos + Vec2.makeWithAngleMag(PIf / 4, PLANET_ORBIT_RANGE.start)
+        // in the test universe, start the ship near the outermost planet
+        ship.pos = planets.last().pos + Vec2(planets.first().radius * 1.5f, 0f)
+
         ship.angle = 0f
         add(ship)
 
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt b/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt
index 24b9c6a..6baf36e 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt
@@ -31,6 +31,8 @@
 import java.lang.Float.max
 import kotlin.math.sqrt
 
+import com.android.egg.flags.Flags.flagFlag
+
 const val DRAW_ORBITS = true
 const val DRAW_GRAVITATIONAL_FIELDS = true
 const val DRAW_STAR_GRAVITATIONAL_FIELDS = true
@@ -279,8 +281,23 @@
 
 fun ZoomedDrawScope.drawLanding(landing: Landing) {
     val v = landing.planet.pos + Vec2.makeWithAngleMag(landing.angle, landing.planet.radius)
-    drawLine(Color.Red, v + Vec2(-5f, -5f), v + Vec2(5f, 5f), strokeWidth = 1f / zoom)
-    drawLine(Color.Red, v + Vec2(5f, -5f), v + Vec2(-5f, 5f), strokeWidth = 1f / zoom)
+
+    if (flagFlag()) {
+        val strokeWidth = 2f / zoom
+        val height = 80f
+        rotateRad(landing.angle, pivot = v) {
+            translate(v.x, v.y) {
+                drawPath(
+                    Path().apply {
+                        moveTo(0f, 0f)
+                        lineTo(height, 0f)
+                        lineTo(height * 0.875f, height * 0.25f)
+                        lineTo(height * 0.75f, 0f)
+                        close()
+                    }, Color.Yellow, style = Stroke(width = strokeWidth))
+            }
+        }
+    }
 }
 
 fun ZoomedDrawScope.drawSpark(spark: Spark) {
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index 98a5a67..79c810c 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -54,6 +54,7 @@
         "androidx.lifecycle_lifecycle-extensions",
         "android.content.pm.flags-aconfig-java",
         "android.os.flags-aconfig-java",
+        "android.multiuser.flags-aconfig-java",
     ],
 
     lint: {
@@ -85,6 +86,7 @@
         "androidx.lifecycle_lifecycle-extensions",
         "android.content.pm.flags-aconfig-java",
         "android.os.flags-aconfig-java",
+        "android.multiuser.flags-aconfig-java",
     ],
     aaptflags: ["--product tablet"],
 
@@ -118,6 +120,7 @@
         "androidx.lifecycle_lifecycle-extensions",
         "android.content.pm.flags-aconfig-java",
         "android.os.flags-aconfig-java",
+        "android.multiuser.flags-aconfig-java",
     ],
     aaptflags: ["--product tv"],
 
diff --git a/packages/PackageInstaller/res/layout/install_content_view.xml b/packages/PackageInstaller/res/layout/install_content_view.xml
index 2ecd2d5..524a88a 100644
--- a/packages/PackageInstaller/res/layout/install_content_view.xml
+++ b/packages/PackageInstaller/res/layout/install_content_view.xml
@@ -24,114 +24,116 @@
     android:paddingLeft="?android:attr/dialogPreferredPadding"
     android:paddingRight="?android:attr/dialogPreferredPadding">
 
-    <LinearLayout
-        android:id="@+id/staging"
+  <LinearLayout
+      android:id="@+id/staging"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:orientation="vertical"
+      android:visibility="invisible">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        style="@android:style/TextAppearance.Material.Subhead"
+        android:text="@string/message_staging" />
+
+    <ProgressBar
+        android:id="@+id/progress_indeterminate"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:orientation="vertical"
-        android:visibility="invisible">
+        android:paddingTop="8dp"
+        style="?android:attr/progressBarStyleHorizontal"
+        android:indeterminate="true" />
 
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@android:style/TextAppearance.Material.Subhead"
-            android:text="@string/message_staging" />
+  </LinearLayout>
 
-        <ProgressBar
-            android:id="@+id/progress_indeterminate"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:paddingTop="8dp"
-            style="?android:attr/progressBarStyleHorizontal"
-            android:indeterminate="true" />
+  <LinearLayout
+      android:id="@+id/installing"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:orientation="vertical"
+      android:visibility="invisible">
 
-    </LinearLayout>
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        style="@android:style/TextAppearance.Material.Subhead"
+        android:text="@string/installing" />
 
-    <LinearLayout
-        android:id="@+id/installing"
+    <ProgressBar
+        android:id="@+id/progress"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:orientation="vertical"
-        android:visibility="invisible">
+        android:paddingTop="8dp"
+        style="?android:attr/progressBarStyleHorizontal"
+        android:indeterminate="true" />
 
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@android:style/TextAppearance.Material.Subhead"
-            android:text="@string/installing" />
+  </LinearLayout>
 
-        <ProgressBar
-            android:id="@+id/progress"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:paddingTop="8dp"
-            style="?android:attr/progressBarStyleHorizontal"
-            android:indeterminate="true" />
+  <TextView
+      android:id="@+id/install_confirm_question"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      style="@android:style/TextAppearance.Material.Subhead"
+      android:text="@string/install_confirm_question"
+      android:visibility="invisible"
+      android:scrollbars="vertical" />
 
-    </LinearLayout>
+  <TextView
+      android:id="@+id/install_confirm_question_update"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      style="@android:style/TextAppearance.Material.Subhead"
+      android:text="@string/install_confirm_question_update"
+      android:visibility="invisible"
+      android:scrollbars="vertical" />
 
-    <TextView
-        android:id="@+id/install_confirm_question"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        style="@android:style/TextAppearance.Material.Subhead"
-        android:text="@string/install_confirm_question"
-        android:visibility="invisible" />
+  <TextView
+      android:id="@+id/install_success"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      style="@android:style/TextAppearance.Material.Subhead"
+      android:text="@string/install_done"
+      android:visibility="invisible" />
 
-    <TextView
-        android:id="@+id/install_confirm_question_update"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        style="@android:style/TextAppearance.Material.Subhead"
-        android:text="@string/install_confirm_question_update"
-        android:visibility="invisible" />
+  <TextView
+      android:id="@+id/install_failed"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      style="@android:style/TextAppearance.Material.Subhead"
+      android:text="@string/install_failed"
+      android:visibility="invisible" />
 
-    <TextView
-        android:id="@+id/install_success"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        style="@android:style/TextAppearance.Material.Subhead"
-        android:text="@string/install_done"
-        android:visibility="invisible" />
+  <TextView
+      android:id="@+id/install_failed_blocked"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      style="@android:style/TextAppearance.Material.Subhead"
+      android:text="@string/install_failed_blocked"
+      android:visibility="invisible" />
 
-    <TextView
-        android:id="@+id/install_failed"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        style="@android:style/TextAppearance.Material.Subhead"
-        android:text="@string/install_failed"
-        android:visibility="invisible" />
+  <TextView
+      android:id="@+id/install_failed_conflict"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      style="@android:style/TextAppearance.Material.Subhead"
+      android:text="@string/install_failed_conflict"
+      android:visibility="invisible" />
 
-    <TextView
-        android:id="@+id/install_failed_blocked"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        style="@android:style/TextAppearance.Material.Subhead"
-        android:text="@string/install_failed_blocked"
-        android:visibility="invisible" />
+  <TextView
+      android:id="@+id/install_failed_incompatible"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      style="@android:style/TextAppearance.Material.Subhead"
+      android:text="@string/install_failed_incompatible"
+      android:visibility="invisible" />
 
-    <TextView
-        android:id="@+id/install_failed_conflict"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        style="@android:style/TextAppearance.Material.Subhead"
-        android:text="@string/install_failed_conflict"
-        android:visibility="invisible" />
+  <TextView
+      android:id="@+id/install_failed_invalid_apk"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      style="@android:style/TextAppearance.Material.Subhead"
+      android:text="@string/install_failed_invalid_apk"
+      android:visibility="invisible" />
 
-    <TextView
-        android:id="@+id/install_failed_incompatible"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        style="@android:style/TextAppearance.Material.Subhead"
-        android:text="@string/install_failed_incompatible"
-        android:visibility="invisible" />
-
-    <TextView
-        android:id="@+id/install_failed_invalid_apk"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        style="@android:style/TextAppearance.Material.Subhead"
-        android:text="@string/install_failed_invalid_apk"
-        android:visibility="invisible" />
-
-</FrameLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/PackageInstaller/res/layout/uninstall_content_view.xml b/packages/PackageInstaller/res/layout/uninstall_content_view.xml
index 5666c0e..434e333 100644
--- a/packages/PackageInstaller/res/layout/uninstall_content_view.xml
+++ b/packages/PackageInstaller/res/layout/uninstall_content_view.xml
@@ -18,31 +18,36 @@
 <!-- Check box that is displayed in the activity resolver UI for the user
      to make their selection the preferred activity. -->
 
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:theme="?android:attr/alertDialogTheme"
-    android:orientation="vertical"
-    android:paddingTop="8dp"
-    android:paddingStart="?android:attr/dialogPreferredPadding"
-    android:paddingEnd="?android:attr/dialogPreferredPadding"
-    android:clipToPadding="false">
+    android:layout_height="wrap_content">
 
-    <TextView
-        android:id="@+id/message"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        style="@android:style/TextAppearance.Material.Subhead" />
+  <LinearLayout
+      xmlns:android="http://schemas.android.com/apk/res/android"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:theme="?android:attr/alertDialogTheme"
+      android:orientation="vertical"
+      android:paddingTop="8dp"
+      android:paddingStart="?android:attr/dialogPreferredPadding"
+      android:paddingEnd="?android:attr/dialogPreferredPadding"
+      android:clipToPadding="false">
 
-    <CheckBox
-        android:id="@+id/keepData"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="8dp"
-        android:layout_marginStart="-8dp"
-        android:paddingLeft="8sp"
-        android:visibility="gone"
-        style="@android:style/TextAppearance.Material.Subhead" />
+      <TextView
+          android:id="@+id/message"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          style="@android:style/TextAppearance.Material.Subhead" />
 
-</LinearLayout>
\ No newline at end of file
+      <CheckBox
+          android:id="@+id/keepData"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:layout_marginTop="8dp"
+          android:layout_marginStart="-8dp"
+          android:paddingLeft="8sp"
+          android:visibility="gone"
+          style="@android:style/TextAppearance.Material.Subhead" />
+
+  </LinearLayout>
+</ScrollView>
diff --git a/packages/PackageInstaller/res/values-el/strings.xml b/packages/PackageInstaller/res/values-el/strings.xml
index 61aa4e6..6f7a433 100644
--- a/packages/PackageInstaller/res/values-el/strings.xml
+++ b/packages/PackageInstaller/res/values-el/strings.xml
@@ -48,7 +48,7 @@
     <string name="update_anyway" msgid="8792432341346261969">"Να ενημερωθεί ούτως ή άλλως"</string>
     <string name="manage_applications" msgid="5400164782453975580">"Διαχ. εφαρμογών"</string>
     <string name="out_of_space_dlg_title" msgid="4156690013884649502">"Δεν υπάρχει χώρος"</string>
-    <string name="out_of_space_dlg_text" msgid="8727714096031856231">"Δεν ήταν δυνατή η εγκατάσταση της εφαρμογής <xliff:g id="APP_NAME">%1$s</xliff:g>. Απελευθερώστε λίγο χώρο και προσπαθήστε ξανά."</string>
+    <string name="out_of_space_dlg_text" msgid="8727714096031856231">"Δεν ήταν δυνατή η εγκατάσταση της εφαρμογής <xliff:g id="APP_NAME">%1$s</xliff:g>. Αποδεσμεύστε λίγο χώρο και προσπαθήστε ξανά."</string>
     <string name="app_not_found_dlg_title" msgid="5107924008597470285">"Η εφαρμογή δεν βρέθηκε"</string>
     <string name="app_not_found_dlg_text" msgid="5219983779377811611">"Η εφαρμογή δεν βρέθηκε στη λίστα εγκατεστημένων εφαρμογών."</string>
     <string name="user_is_not_allowed_dlg_title" msgid="6915293433252210232">"Δεν επιτρέπεται"</string>
@@ -117,7 +117,7 @@
     <string name="unarchive_error_generic_title" msgid="7123457671482449992">"Κάτι πήγε στραβά"</string>
     <string name="unarchive_error_generic_body" msgid="4486803312463813079">"Παρουσιάστηκε κάποιο πρόβλημα κατά την επαναφορά αυτής της εφαρμογής"</string>
     <string name="unarchive_error_storage_title" msgid="5080723357273852630">"Δεν επαρκεί ο αποθηκευτικός χώρος"</string>
-    <string name="unarchive_error_storage_body" msgid="6879544407568780524">"Για να επαναφέρετε αυτή την εφαρμογή, μπορείτε να ελευθερώσετε χώρο στη συσκευή. Απαιτούμενος αποθηκευτικός χώρος: <xliff:g id="BYTES">%1$s</xliff:g>"</string>
+    <string name="unarchive_error_storage_body" msgid="6879544407568780524">"Για να επαναφέρετε αυτή την εφαρμογή, μπορείτε να αποδεσμεύσετε χώρο στη συσκευή. Απαιτούμενος αποθηκευτικός χώρος: <xliff:g id="BYTES">%1$s</xliff:g>"</string>
     <string name="unarchive_action_required_title" msgid="4971245740162604619">"Απαιτούμενη ενέργεια"</string>
     <string name="unarchive_action_required_body" msgid="1679431572983989231">"Ακολουθήστε τα επόμενα βήματα για να επαναφέρετε αυτή την εφαρμογή"</string>
     <string name="unarchive_error_installer_disabled_title" msgid="4815715617014985605">"Το <xliff:g id="INSTALLERNAME">%1$s</xliff:g> είναι απενεργοποιημένο"</string>
diff --git a/packages/PackageInstaller/res/values-fr-feminine/strings.xml b/packages/PackageInstaller/res/values-fr-feminine/strings.xml
new file mode 100644
index 0000000..c6b8de7
--- /dev/null
+++ b/packages/PackageInstaller/res/values-fr-feminine/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Votre téléphone et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être la seule responsable de tout dommage causé à votre téléphone ou de toute perte de données pouvant découler de son utilisation."</string>
+    <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Votre tablette et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être la seule responsable de tout dommage causé à votre tablette ou de toute perte de données pouvant découler de son utilisation."</string>
+    <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Votre téléviseur et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être la seule responsable de tout dommage causé à votre téléviseur ou de toute perte de données pouvant découler de son utilisation."</string>
+    <string name="unarchive_error_offline_title" msgid="4021785324565678605">"Vous n\'êtes pas connectée"</string>
+</resources>
diff --git a/packages/PackageInstaller/res/values-fr-masculine/strings.xml b/packages/PackageInstaller/res/values-fr-masculine/strings.xml
new file mode 100644
index 0000000..b4a40a0
--- /dev/null
+++ b/packages/PackageInstaller/res/values-fr-masculine/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Votre téléphone et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être le seul responsable de tout dommage causé à votre téléphone ou de toute perte de données pouvant découler de son utilisation."</string>
+    <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Votre tablette et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être le seul responsable de tout dommage causé à votre tablette ou de toute perte de données pouvant découler de son utilisation."</string>
+    <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Votre téléviseur et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être le seul responsable de tout dommage causé à votre téléviseur ou de toute perte de données pouvant découler de son utilisation."</string>
+    <string name="unarchive_error_offline_title" msgid="4021785324565678605">"Vous n\'êtes pas connecté"</string>
+</resources>
diff --git a/packages/PackageInstaller/res/values-fr-neuter/strings.xml b/packages/PackageInstaller/res/values-fr-neuter/strings.xml
new file mode 100644
index 0000000..9362b33
--- /dev/null
+++ b/packages/PackageInstaller/res/values-fr-neuter/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Votre téléphone et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être l\'unique responsable de tout dommage causé à votre téléphone ou de toute perte de données pouvant découler de son utilisation."</string>
+    <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Votre tablette et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être l\'unique responsable de tout dommage causé à votre tablette ou de toute perte de données pouvant découler de son utilisation."</string>
+    <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Votre téléviseur et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être l\'unique responsable de tout dommage causé à votre téléviseur ou de toute perte de données pouvant découler de son utilisation."</string>
+    <string name="unarchive_error_offline_title" msgid="4021785324565678605">"Vous n\'êtes pas connecté·e"</string>
+</resources>
diff --git a/packages/PackageInstaller/res/values-fr/strings.xml b/packages/PackageInstaller/res/values-fr/strings.xml
index 5311903..ee0c752 100644
--- a/packages/PackageInstaller/res/values-fr/strings.xml
+++ b/packages/PackageInstaller/res/values-fr/strings.xml
@@ -98,9 +98,9 @@
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Pour votre sécurité, l\'installation d\'applis inconnues provenant de cette source n\'est pas autorisée sur ce téléviseur actuellement. Vous pouvez modifier cette option dans les paramètres."</string>
     <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Pour votre sécurité, l\'installation d\'applis inconnues provenant de cette source n\'est pas autorisée sur cette montre actuellement. Vous pouvez modifier cette option dans les paramètres."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Pour votre sécurité, l\'installation d\'applis inconnues provenant de cette source n\'est pas autorisée sur ce téléphone actuellement. Vous pouvez modifier cette option dans les paramètres."</string>
-    <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Votre téléphone et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être le seul responsable de tout dommage causé à votre téléphone ou de toute perte de données pouvant découler de son utilisation."</string>
-    <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Votre tablette et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être le seul responsable de tout dommage causé à votre tablette ou de toute perte de données pouvant découler de son utilisation."</string>
-    <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Votre téléviseur et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être le seul responsable de tout dommage causé à votre téléviseur ou de toute perte de données pouvant découler de son utilisation."</string>
+    <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Votre téléphone et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être l\'unique responsable de tout dommage causé à votre téléphone ou de toute perte de données pouvant découler de son utilisation."</string>
+    <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Votre tablette et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être l\'unique responsable de tout dommage causé à votre tablette ou de toute perte de données pouvant découler de son utilisation."</string>
+    <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Votre téléviseur et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être l\'unique responsable de tout dommage causé à votre téléviseur ou de toute perte de données pouvant découler de son utilisation."</string>
     <string name="cloned_app_label" msgid="7503612829833756160">"Clone de <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
     <string name="archiving_app_label" msgid="1127085259724124725">"Archiver <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> ?"</string>
     <string name="anonymous_source_continue" msgid="4375745439457209366">"Continuer"</string>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
index 634e067..cf2f85e 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
@@ -20,7 +20,6 @@
 
 import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
 
-import android.Manifest;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -28,10 +27,10 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.pm.Flags;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.res.AssetFileDescriptor;
+import android.Manifest;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
@@ -201,7 +200,7 @@
         params.setPermissionState(Manifest.permission.USE_FULL_SCREEN_INTENT,
                 PackageInstaller.SessionParams.PERMISSION_STATE_DENIED);
 
-        if (pfd != null && Flags.readInstallInfo()) {
+        if (pfd != null) {
             try {
                 final PackageInstaller.InstallInfo result = installer.readInstallInfo(pfd,
                         debugPathName, 0);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index cf6aab6..45bfe54 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -31,7 +31,6 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.Flags;
 import android.content.pm.InstallSourceInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
@@ -51,6 +50,7 @@
 import android.text.Html;
 import android.text.Spanned;
 import android.text.TextUtils;
+import android.text.method.ScrollingMovementMethod;
 import android.util.Log;
 import android.view.View;
 import android.widget.Button;
@@ -174,6 +174,7 @@
         }
 
         viewToEnable.setVisibility(View.VISIBLE);
+        viewToEnable.setMovementMethod(new ScrollingMovementMethod());
 
         mEnableOk = true;
         mOk.setEnabled(true);
@@ -398,10 +399,7 @@
             final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
                     -1 /* defaultValue */);
             final SessionInfo info = mInstaller.getSessionInfo(sessionId);
-            String resolvedPath = null;
-            if (info != null && Flags.getResolvedApkPath()) {
-                resolvedPath = info.getResolvedBaseApkPath();
-            }
+            String resolvedPath = info != null ? info.getResolvedBaseApkPath() : null;
             if (info == null || !info.isSealed() || resolvedPath == null) {
                 Log.w(TAG, "Session " + sessionId + " in funky state; ignoring");
                 finish();
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index 221ca4f..8f5d07c 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -166,6 +166,7 @@
                     messageBuilder.append(getString(
                             R.string.uninstall_application_text_current_user_clone_profile));
                 } else if (Flags.allowPrivateProfile()
+                        && android.multiuser.Flags.enablePrivateSpaceFeatures()
                         && customUserManager.isPrivateProfile()
                         && customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
                     messageBuilder.append(
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index 22caabd..aeabbd5 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -25,7 +25,6 @@
 import android.content.Context
 import android.content.Intent
 import android.content.pm.ApplicationInfo
-import android.content.pm.Flags
 import android.content.pm.PackageInfo
 import android.content.pm.PackageInstaller
 import android.content.pm.PackageInstaller.SessionInfo
@@ -363,7 +362,7 @@
         params.setPermissionState(
             Manifest.permission.USE_FULL_SCREEN_INTENT, SessionParams.PERMISSION_STATE_DENIED
         )
-        if (pfd != null && Flags.readInstallInfo()) {
+        if (pfd != null) {
             try {
                 val installInfo = packageInstaller.readInstallInfo(pfd, debugPathName, 0)
                 params.setAppPackageName(installInfo.packageName)
@@ -426,8 +425,7 @@
 
         if (PackageInstaller.ACTION_CONFIRM_INSTALL == intent.action) {
             val info = packageInstaller.getSessionInfo(sessionId)
-            val resolvedPath =
-                    if (Flags.getResolvedApkPath()) info?.resolvedBaseApkPath else null
+            val resolvedPath = info?.resolvedBaseApkPath
             if (info == null || !info.isSealed || resolvedPath == null) {
                 Log.w(LOG_TAG, "Session $sessionId in funky state; ignoring")
                 return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
index 0fc1845..c6b6d36 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
@@ -235,7 +235,9 @@
                     messageString = context.getString(
                             R.string.uninstall_application_text_current_user_clone_profile
                     )
-                } else if (Flags.allowPrivateProfile() && customUserManager!!.isPrivateProfile()) {
+                } else if (Flags.allowPrivateProfile()
+                        && android.multiuser.Flags.enablePrivateSpaceFeatures()
+                        && customUserManager!!.isPrivateProfile()) {
                     // TODO(b/324244123): Get these Strings from a User Property API.
                     messageString = context.getString(
                             R.string.uninstall_application_text_current_user_private_profile
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
index dbe32cc..0a4aa48 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
@@ -22,6 +22,7 @@
 import android.content.DialogInterface;
 import android.os.Bundle;
 import android.text.Html;
+import android.text.method.ScrollingMovementMethod;
 import android.view.View;
 import android.widget.TextView;
 import androidx.annotation.NonNull;
@@ -94,6 +95,7 @@
             viewToEnable = dialogView.requireViewById(R.id.install_confirm_question);
         }
         viewToEnable.setVisibility(View.VISIBLE);
+        viewToEnable.setMovementMethod(new ScrollingMovementMethod());
 
         return mDialog;
     }
diff --git a/packages/SettingsLib/DataStore/Android.bp b/packages/SettingsLib/DataStore/Android.bp
new file mode 100644
index 0000000..9fafcab
--- /dev/null
+++ b/packages/SettingsLib/DataStore/Android.bp
@@ -0,0 +1,18 @@
+package {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+    name: "SettingsLibDataStore",
+    defaults: [
+        "SettingsLintDefaults",
+    ],
+    srcs: ["src/**/*"],
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.collection_collection-ktx",
+        "androidx.core_core-ktx",
+        "guava",
+    ],
+    kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SettingsLib/DataStore/AndroidManifest.xml b/packages/SettingsLib/DataStore/AndroidManifest.xml
new file mode 100644
index 0000000..fb44627
--- /dev/null
+++ b/packages/SettingsLib/DataStore/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.settingslib.datastore">
+
+    <uses-sdk android:minSdkVersion="21" />
+</manifest>
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupCodec.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupCodec.kt
new file mode 100644
index 0000000..550645f
--- /dev/null
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupCodec.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import androidx.annotation.IntDef
+import java.io.InputStream
+import java.io.OutputStream
+import java.util.zip.Deflater
+import java.util.zip.DeflaterOutputStream
+import java.util.zip.InflaterInputStream
+
+/** Unique id of the codec. */
+@Target(AnnotationTarget.TYPE)
+@IntDef(
+    BackupCodecId.NO_OP.toInt(),
+    BackupCodecId.ZIP.toInt(),
+)
+@Retention(AnnotationRetention.SOURCE)
+annotation class BackupCodecId {
+    companion object {
+        /** Unknown reason of the change. */
+        const val NO_OP: Byte = 0
+        /** Data is updated. */
+        const val ZIP: Byte = 1
+    }
+}
+
+/** How to encode/decode the backup data. */
+interface BackupCodec {
+    /** Unique id of the codec. */
+    val id: @BackupCodecId Byte
+
+    /** Name of the codec. */
+    val name: String
+
+    /** Encodes the backup data. */
+    fun encode(outputStream: OutputStream): OutputStream
+
+    /** Decodes the backup data. */
+    fun decode(inputStream: InputStream): InputStream
+
+    companion object {
+        @JvmStatic
+        fun fromId(id: @BackupCodecId Byte): BackupCodec =
+            when (id) {
+                BackupCodecId.NO_OP -> BackupNoOpCodec()
+                BackupCodecId.ZIP -> BackupZipCodec.BEST_COMPRESSION
+                else -> throw IllegalArgumentException("Unknown codec id $id")
+            }
+    }
+}
+
+/** Codec without any additional encoding/decoding. */
+class BackupNoOpCodec : BackupCodec {
+    override val id
+        get() = BackupCodecId.NO_OP
+
+    override val name
+        get() = "N/A"
+
+    override fun encode(outputStream: OutputStream) = outputStream
+
+    override fun decode(inputStream: InputStream) = inputStream
+}
+
+/** Codec with ZIP compression. */
+class BackupZipCodec(
+    private val compressionLevel: Int,
+    override val name: String,
+) : BackupCodec {
+    override val id
+        get() = BackupCodecId.ZIP
+
+    override fun encode(outputStream: OutputStream) =
+        DeflaterOutputStream(outputStream, Deflater(compressionLevel))
+
+    override fun decode(inputStream: InputStream) = InflaterInputStream(inputStream)
+
+    companion object {
+        val DEFAULT_COMPRESSION = BackupZipCodec(Deflater.DEFAULT_COMPRESSION, "ZipDefault")
+        val BEST_COMPRESSION = BackupZipCodec(Deflater.BEST_COMPRESSION, "ZipBestCompression")
+        val BEST_SPEED = BackupZipCodec(Deflater.BEST_SPEED, "ZipBestSpeed")
+    }
+}
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreContext.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreContext.kt
new file mode 100644
index 0000000..8fe618d
--- /dev/null
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreContext.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import android.app.backup.BackupAgent
+import android.app.backup.BackupDataOutput
+import android.app.backup.BackupHelper
+import android.os.Build
+import androidx.annotation.RequiresApi
+
+/**
+ * Context for backup.
+ *
+ * @see BackupHelper.performBackup
+ * @see BackupDataOutput
+ */
+class BackupContext
+internal constructor(
+    /** An open, read/write BackupDataOutput pointing to the backup data destination. */
+    private val data: BackupDataOutput,
+) {
+    /**
+     * The quota in bytes for the application's current backup operation.
+     *
+     * @see [BackupDataOutput.getQuota]
+     */
+    val quota: Long
+        @RequiresApi(Build.VERSION_CODES.O) get() = data.quota
+
+    /**
+     * Additional information about the backup transport.
+     *
+     * See [BackupAgent] for supported flags.
+     *
+     * @see [BackupDataOutput.getTransportFlags]
+     */
+    val transportFlags: Int
+        @RequiresApi(Build.VERSION_CODES.P) get() = data.transportFlags
+}
+
+/**
+ * Context for restore.
+ *
+ * @param key Entity key
+ */
+class RestoreContext internal constructor(val key: String)
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt
new file mode 100644
index 0000000..817ee4c
--- /dev/null
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import android.app.backup.BackupDataOutput
+import android.app.backup.BackupHelper
+import androidx.annotation.BinderThread
+import java.io.IOException
+import java.io.InputStream
+import java.io.OutputStream
+
+/** Entity for back up and restore. */
+interface BackupRestoreEntity {
+    /**
+     * Key of the entity.
+     *
+     * The key string must be unique within the data set. Note that it is invalid if the first
+     * character is \uFF00 or higher.
+     *
+     * @see BackupDataOutput.writeEntityHeader
+     */
+    val key: String
+
+    /**
+     * Codec used to encode/decode the backup data.
+     *
+     * When it is null, the [BackupRestoreStorage.defaultCodec] will be used.
+     */
+    fun codec(): BackupCodec? = null
+
+    /**
+     * Backs up the entity.
+     *
+     * @param backupContext context for backup
+     * @param outputStream output stream to back up data
+     * @return false if backup file is deleted, otherwise true
+     */
+    @BinderThread
+    @Throws(IOException::class)
+    fun backup(backupContext: BackupContext, outputStream: OutputStream): EntityBackupResult
+
+    /**
+     * Restores the entity.
+     *
+     * @param restoreContext context for restore
+     * @param inputStream An open input stream from which the backup data can be read.
+     * @see BackupHelper.restoreEntity
+     */
+    @BinderThread
+    @Throws(IOException::class)
+    fun restore(restoreContext: RestoreContext, inputStream: InputStream)
+}
+
+/** Result of the backup operation. */
+enum class EntityBackupResult {
+    /** Update the entity. */
+    UPDATE,
+    /** Leave the entity intact. */
+    INTACT,
+    /** Delete the entity. */
+    DELETE,
+}
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt
new file mode 100644
index 0000000..9d3fb66
--- /dev/null
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import android.app.backup.BackupDataInputStream
+import android.content.Context
+import android.util.Log
+import java.io.File
+import java.io.InputStream
+import java.io.OutputStream
+import java.util.zip.CheckedInputStream
+
+/**
+ * File archiver to handle backup and restore for all the [BackupRestoreFileStorage] subclasses.
+ *
+ * Compared with [android.app.backup.FileBackupHelper], this class supports forward-compatibility
+ * like the [com.google.android.libraries.backup.PersistentBackupAgentHelper]: the app does not need
+ * to know the list of files in advance at restore time.
+ */
+internal class BackupRestoreFileArchiver(
+    private val context: Context,
+    private val fileStorages: List<BackupRestoreFileStorage>,
+) : BackupRestoreStorage() {
+    override val name: String
+        get() = "file_archiver"
+
+    override fun createBackupRestoreEntities(): List<BackupRestoreEntity> =
+        fileStorages.map { it.toBackupRestoreEntity() }
+
+    override fun wrapBackupOutputStream(codec: BackupCodec, outputStream: OutputStream) =
+        outputStream
+
+    override fun wrapRestoreInputStream(codec: BackupCodec, inputStream: InputStream) = inputStream
+
+    override fun restoreEntity(data: BackupDataInputStream) {
+        val key = data.key
+        val fileStorage = fileStorages.firstOrNull { it.storageFilePath == key }
+        val file =
+            if (fileStorage != null) {
+                if (!fileStorage.enableRestore()) {
+                    Log.i(LOG_TAG, "[$name] $key restore disabled")
+                    return
+                }
+                fileStorage.restoreFile
+            } else { // forward-compatibility
+                Log.i(LOG_TAG, "Restore unknown file $key")
+                File(context.dataDirCompat, key)
+            }
+        Log.i(LOG_TAG, "[$name] Restore ${data.size()} bytes for $key to $file")
+        val inputStream = LimitedNoCloseInputStream(data)
+        val checksum = createChecksum()
+        val checkedInputStream = CheckedInputStream(inputStream, checksum)
+        try {
+            val codec = BackupCodec.fromId(checkedInputStream.read().toByte())
+            if (fileStorage != null && fileStorage.defaultCodec().id != codec.id) {
+                Log.i(
+                    LOG_TAG,
+                    "[$name] $key different codec: ${codec.id}, ${fileStorage.defaultCodec().id}"
+                )
+            }
+            file.parentFile?.mkdirs() // ensure parent folders are created
+            val wrappedInputStream = codec.decode(checkedInputStream)
+            val bytesCopied = file.outputStream().use { wrappedInputStream.copyTo(it) }
+            Log.i(LOG_TAG, "[$name] $key restore $bytesCopied bytes with ${codec.name}")
+            fileStorage?.onRestoreFinished(file)
+            entityStates[key] = checksum.value
+        } catch (e: Exception) {
+            Log.e(LOG_TAG, "[$name] Fail to restore $key", e)
+        }
+    }
+
+    override fun onRestoreFinished() {
+        fileStorages.forEach { it.onRestoreFinished() }
+    }
+}
+
+private fun BackupRestoreFileStorage.toBackupRestoreEntity() =
+    object : BackupRestoreEntity {
+        override val key: String
+            get() = storageFilePath
+
+        override fun backup(
+            backupContext: BackupContext,
+            outputStream: OutputStream,
+        ): EntityBackupResult {
+            if (!enableBackup(backupContext)) {
+                Log.i(LOG_TAG, "[$name] $key backup disabled")
+                return EntityBackupResult.INTACT
+            }
+            val file = backupFile
+            prepareBackup(file)
+            if (!file.exists()) {
+                Log.i(LOG_TAG, "[$name] $key not exist")
+                return EntityBackupResult.DELETE
+            }
+            val codec = codec() ?: defaultCodec()
+            // MUST close to flush the data
+            wrapBackupOutputStream(codec, outputStream).use { stream ->
+                val bytesCopied = file.inputStream().use { it.copyTo(stream) }
+                Log.i(LOG_TAG, "[$name] $key backup $bytesCopied bytes with ${codec.name}")
+            }
+            onBackupFinished(file)
+            return EntityBackupResult.UPDATE
+        }
+
+        override fun restore(restoreContext: RestoreContext, inputStream: InputStream) {
+            // no-op, BackupRestoreFileArchiver#restoreEntity will restore files
+        }
+    }
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileStorage.kt
new file mode 100644
index 0000000..b531bd1
--- /dev/null
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileStorage.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import android.content.Context
+import androidx.core.content.ContextCompat
+import java.io.File
+
+/**
+ * A file-based storage with backup and restore support.
+ *
+ * [BackupRestoreFileArchiver] will handle the backup and restore based on file path for all
+ * subclasses.
+ *
+ * @param context Context to retrieve data dir
+ * @param storageFilePath Storage file path, which MUST be relative to the [Context.getDataDir]
+ *   folder. This is used as the entity name for backup and restore.
+ */
+abstract class BackupRestoreFileStorage(
+    val context: Context,
+    val storageFilePath: String,
+) : BackupRestoreStorage() {
+
+    /** The absolute path of the file to backup. */
+    open val backupFile: File
+        get() = File(context.dataDirCompat, storageFilePath)
+
+    /** The absolute path of the file to restore. */
+    open val restoreFile: File
+        get() = backupFile
+
+    fun checkFilePaths() {
+        if (storageFilePath.isEmpty() || storageFilePath[0] == File.separatorChar) {
+            throw IllegalArgumentException("$storageFilePath is not valid path")
+        }
+        if (!backupFile.isAbsolute) {
+            throw IllegalArgumentException("backupFile is not absolute")
+        }
+        if (!restoreFile.isAbsolute) {
+            throw IllegalArgumentException("restoreFile is not absolute")
+        }
+    }
+
+    /**
+     * Callback before [backupFile] is backed up.
+     *
+     * @param file equals to [backupFile]
+     */
+    open fun prepareBackup(file: File) {}
+
+    /**
+     * Callback when [backupFile] is restored.
+     *
+     * @param file equals to [backupFile]
+     */
+    open fun onBackupFinished(file: File) {}
+
+    /**
+     * Callback when [restoreFile] is restored.
+     *
+     * @param file equals to [restoreFile]
+     */
+    open fun onRestoreFinished(file: File) {}
+
+    final override fun createBackupRestoreEntities(): List<BackupRestoreEntity> = listOf()
+}
+
+internal val Context.dataDirCompat: File
+    get() = ContextCompat.getDataDir(this)!!
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
new file mode 100644
index 0000000..c4c00cb
--- /dev/null
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import android.app.backup.BackupAgentHelper
+import android.app.backup.BackupDataInputStream
+import android.app.backup.BackupDataOutput
+import android.app.backup.BackupHelper
+import android.os.ParcelFileDescriptor
+import android.util.Log
+import androidx.collection.MutableScatterMap
+import com.google.common.io.ByteStreams
+import java.io.ByteArrayOutputStream
+import java.io.DataInputStream
+import java.io.DataOutputStream
+import java.io.EOFException
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.io.FilterInputStream
+import java.io.InputStream
+import java.io.OutputStream
+import java.util.zip.CRC32
+import java.util.zip.CheckedInputStream
+import java.util.zip.CheckedOutputStream
+import java.util.zip.Checksum
+
+internal const val LOG_TAG = "BackupRestoreStorage"
+
+/**
+ * Storage with backup and restore support. Subclass must implement either [Observable] or
+ * [KeyedObservable] interface.
+ *
+ * The storage is identified by a unique string [name] and data set is split into entities
+ * ([BackupRestoreEntity]).
+ */
+abstract class BackupRestoreStorage : BackupHelper {
+    /**
+     * A unique string used to disambiguate the various storages within backup agent.
+     *
+     * It will be used as the `keyPrefix` of [BackupAgentHelper.addHelper].
+     */
+    abstract val name: String
+
+    /**
+     * Entity states represented by checksum.
+     *
+     * Map key is the entity key, map value is the checksum of backup data.
+     */
+    protected val entityStates = MutableScatterMap<String, Long>()
+
+    /** Entities created by [createBackupRestoreEntities]. This field is for restore only. */
+    private var entities: List<BackupRestoreEntity>? = null
+
+    /** Entities to back up and restore. */
+    abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity>
+
+    /** Default codec used to encode/decode the entity data. */
+    open fun defaultCodec(): BackupCodec = BackupZipCodec.BEST_COMPRESSION
+
+    final override fun performBackup(
+        oldState: ParcelFileDescriptor?,
+        data: BackupDataOutput,
+        newState: ParcelFileDescriptor,
+    ) {
+        oldState.readEntityStates(entityStates)
+        val backupContext = BackupContext(data)
+        if (!enableBackup(backupContext)) {
+            Log.i(LOG_TAG, "[$name] Backup disabled")
+            return
+        }
+        Log.i(LOG_TAG, "[$name] Backup start")
+        val checksum = createChecksum()
+        // recreate entities for backup to avoid stale states
+        val entities = createBackupRestoreEntities()
+        for (entity in entities) {
+            val key = entity.key
+            val outputStream = ByteArrayOutputStream()
+            checksum.reset()
+            val checkedOutputStream = CheckedOutputStream(outputStream, checksum)
+            val codec = entity.codec() ?: defaultCodec()
+            val result =
+                try {
+                    entity.backup(backupContext, wrapBackupOutputStream(codec, checkedOutputStream))
+                } catch (exception: Exception) {
+                    Log.e(LOG_TAG, "[$name] Fail to backup entity $key", exception)
+                    continue
+                }
+            when (result) {
+                EntityBackupResult.UPDATE -> {
+                    val value = checksum.value
+                    if (entityStates.put(key, value) != value) {
+                        val payload = outputStream.toByteArray()
+                        val size = payload.size
+                        data.writeEntityHeader(key, size)
+                        data.writeEntityData(payload, size)
+                        Log.i(LOG_TAG, "[$name] Backup entity $key: $size bytes")
+                    } else {
+                        Log.i(
+                            LOG_TAG,
+                            "[$name] Backup entity $key unchanged: ${outputStream.size()} bytes"
+                        )
+                    }
+                }
+                EntityBackupResult.INTACT -> {
+                    Log.i(LOG_TAG, "[$name] Backup entity $key intact")
+                }
+                EntityBackupResult.DELETE -> {
+                    entityStates.remove(key)
+                    data.writeEntityHeader(key, -1)
+                    Log.i(LOG_TAG, "[$name] Backup entity $key deleted")
+                }
+            }
+        }
+        newState.writeAndClearEntityStates()
+        Log.i(LOG_TAG, "[$name] Backup end")
+    }
+
+    /** Returns if backup is enabled. */
+    open fun enableBackup(backupContext: BackupContext): Boolean = true
+
+    open fun wrapBackupOutputStream(codec: BackupCodec, outputStream: OutputStream): OutputStream {
+        // write a codec id header for safe restore
+        outputStream.write(codec.id.toInt())
+        return codec.encode(outputStream)
+    }
+
+    /** This callback is invoked for every backed up entity. */
+    override fun restoreEntity(data: BackupDataInputStream) {
+        val key = data.key
+        if (!enableRestore()) {
+            Log.i(LOG_TAG, "[$name] Restore disabled, ignore entity $key")
+            return
+        }
+        val entity = ensureEntities().firstOrNull { it.key == key }
+        if (entity == null) {
+            Log.w(LOG_TAG, "[$name] Cannot find handler for entity $key")
+            return
+        }
+        Log.i(LOG_TAG, "[$name] Restore $key: ${data.size()} bytes")
+        val restoreContext = RestoreContext(key)
+        val codec = entity.codec() ?: defaultCodec()
+        val inputStream = LimitedNoCloseInputStream(data)
+        val checksum = createChecksum()
+        val checkedInputStream = CheckedInputStream(inputStream, checksum)
+        try {
+            entity.restore(restoreContext, wrapRestoreInputStream(codec, checkedInputStream))
+            entityStates[key] = checksum.value
+        } catch (exception: Exception) {
+            Log.e(LOG_TAG, "[$name] Fail to restore entity $key", exception)
+        }
+    }
+
+    private fun ensureEntities(): List<BackupRestoreEntity> =
+        entities ?: createBackupRestoreEntities().also { entities = it }
+
+    /** Returns if restore is enabled. */
+    open fun enableRestore(): Boolean = true
+
+    open fun wrapRestoreInputStream(
+        codec: BackupCodec,
+        inputStream: InputStream,
+    ): InputStream {
+        // read the codec id first to check if it is expected codec
+        val id = inputStream.read()
+        val expectedId = codec.id.toInt()
+        if (id == expectedId) return codec.decode(inputStream)
+        Log.i(LOG_TAG, "Expect codec id $expectedId but got $id")
+        return BackupCodec.fromId(id.toByte()).decode(inputStream)
+    }
+
+    final override fun writeNewStateDescription(newState: ParcelFileDescriptor) {
+        entities = null // clear to reduce memory footprint
+        newState.writeAndClearEntityStates()
+        onRestoreFinished()
+    }
+
+    /** Callbacks when restore finished. */
+    open fun onRestoreFinished() {}
+
+    private fun ParcelFileDescriptor?.readEntityStates(state: MutableScatterMap<String, Long>) {
+        state.clear()
+        if (this == null) return
+        // do not close the streams
+        val fileInputStream = FileInputStream(fileDescriptor)
+        val dataInputStream = DataInputStream(fileInputStream)
+        try {
+            val version = dataInputStream.readByte()
+            if (version != STATE_VERSION) {
+                Log.w(
+                    LOG_TAG,
+                    "[$name] Unexpected state version, read:$version, expected:$STATE_VERSION"
+                )
+                return
+            }
+            var count = dataInputStream.readInt()
+            while (count-- > 0) {
+                val key = dataInputStream.readUTF()
+                val checksum = dataInputStream.readLong()
+                state[key] = checksum
+            }
+        } catch (exception: Exception) {
+            if (exception is EOFException) {
+                Log.d(LOG_TAG, "[$name] Hit EOF when read state file")
+            } else {
+                Log.e(LOG_TAG, "[$name] Fail to read state file", exception)
+            }
+            state.clear()
+        }
+    }
+
+    private fun ParcelFileDescriptor.writeAndClearEntityStates() {
+        // do not close the streams
+        val fileOutputStream = FileOutputStream(fileDescriptor)
+        val dataOutputStream = DataOutputStream(fileOutputStream)
+        try {
+            dataOutputStream.writeByte(STATE_VERSION.toInt())
+            dataOutputStream.writeInt(entityStates.size)
+            entityStates.forEach { key, value ->
+                dataOutputStream.writeUTF(key)
+                dataOutputStream.writeLong(value)
+            }
+        } catch (exception: Exception) {
+            Log.e(LOG_TAG, "[$name] Fail to write state file", exception)
+        }
+        entityStates.clear()
+        entityStates.trim() // trim to reduce memory footprint
+    }
+
+    companion object {
+        private const val STATE_VERSION: Byte = 0
+
+        /** Checksum for entity backup data. */
+        fun createChecksum(): Checksum = CRC32()
+    }
+}
+
+/**
+ * Wrapper of [BackupDataInputStream], limiting the number of bytes that can be read and make
+ * [close] no-op.
+ */
+class LimitedNoCloseInputStream(inputStream: BackupDataInputStream) :
+    FilterInputStream(ByteStreams.limit(inputStream, inputStream.size().toLong())) {
+    override fun close() {
+        // do not close original input stream
+    }
+}
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt
new file mode 100644
index 0000000..cfdcaff
--- /dev/null
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import android.app.Application
+import android.app.backup.BackupAgentHelper
+import android.app.backup.BackupManager
+import android.content.Context
+import android.util.Log
+import com.google.common.util.concurrent.MoreExecutors
+import java.util.concurrent.ConcurrentHashMap
+
+/** Manager of [BackupRestoreStorage]. */
+class BackupRestoreStorageManager private constructor(private val application: Application) {
+    private val storageWrappers = ConcurrentHashMap<String, StorageWrapper>()
+
+    private val executor = MoreExecutors.directExecutor()
+
+    /**
+     * Adds all the registered [BackupRestoreStorage] as the helpers of given [BackupAgentHelper].
+     *
+     * All [BackupRestoreFileStorage]s will be wrapped as a single [BackupRestoreFileArchiver].
+     *
+     * @see BackupAgentHelper.addHelper
+     */
+    fun addBackupAgentHelpers(backupAgentHelper: BackupAgentHelper) {
+        val fileStorages = mutableListOf<BackupRestoreFileStorage>()
+        for ((keyPrefix, storageWrapper) in storageWrappers) {
+            val storage = storageWrapper.storage
+            if (storage is BackupRestoreFileStorage) {
+                fileStorages.add(storage)
+            } else {
+                backupAgentHelper.addHelper(keyPrefix, storage)
+            }
+        }
+        // Always add file archiver even fileStorages is empty to handle forward compatibility
+        val fileArchiver = BackupRestoreFileArchiver(application, fileStorages)
+        backupAgentHelper.addHelper(fileArchiver.name, fileArchiver)
+    }
+
+    /**
+     * Callback when restore finished.
+     *
+     * The observers of the storages will be notified.
+     */
+    fun onRestoreFinished() {
+        for (storageWrapper in storageWrappers.values) {
+            storageWrapper.notifyRestoreFinished()
+        }
+    }
+
+    /**
+     * Adds a list of storages.
+     *
+     * The storage MUST implement [KeyedObservable] or [Observable].
+     */
+    fun add(vararg storages: BackupRestoreStorage) {
+        for (storage in storages) add(storage)
+    }
+
+    /**
+     * Adds a storage.
+     *
+     * The storage MUST implement [KeyedObservable] or [Observable].
+     */
+    fun add(storage: BackupRestoreStorage) {
+        if (storage is BackupRestoreFileStorage) storage.checkFilePaths()
+        val name = storage.name
+        val oldStorage = storageWrappers.put(name, StorageWrapper(storage))?.storage
+        if (oldStorage != null) {
+            throw IllegalStateException(
+                "Storage name '$name' conflicts:\n\told: $oldStorage\n\tnew: $storage"
+            )
+        }
+    }
+
+    /** Removes all the storages. */
+    fun removeAll() {
+        for ((name, _) in storageWrappers) remove(name)
+    }
+
+    /** Removes storage with given name. */
+    fun remove(name: String): BackupRestoreStorage? {
+        val storageWrapper = storageWrappers.remove(name)
+        storageWrapper?.removeObserver()
+        return storageWrapper?.storage
+    }
+
+    /** Returns storage with given name. */
+    fun get(name: String): BackupRestoreStorage? = storageWrappers[name]?.storage
+
+    /** Returns storage with given name, exception is raised if not found. */
+    fun getOrThrow(name: String): BackupRestoreStorage = storageWrappers[name]!!.storage
+
+    private inner class StorageWrapper(val storage: BackupRestoreStorage) :
+        Observer, KeyedObserver<Any?> {
+        init {
+            when (storage) {
+                is KeyedObservable<*> -> storage.addObserver(this, executor)
+                is Observable -> storage.addObserver(this, executor)
+                else ->
+                    throw IllegalArgumentException(
+                        "$this does not implement either KeyedObservable or Observable"
+                    )
+            }
+        }
+
+        override fun onChanged(reason: Int) = onKeyChanged(null, reason)
+
+        override fun onKeyChanged(key: Any?, reason: Int) {
+            notifyBackupManager(key, reason)
+        }
+
+        private fun notifyBackupManager(key: Any?, reason: Int) {
+            val name = storage.name
+            // prefer not triggering backup immediately after restore
+            if (reason == ChangeReason.RESTORE) {
+                Log.d(
+                    LOG_TAG,
+                    "Notify BackupManager dataChanged ignored for restore: storage=$name key=$key"
+                )
+                return
+            }
+            Log.d(
+                LOG_TAG,
+                "Notify BackupManager dataChanged: storage=$name key=$key reason=$reason"
+            )
+            BackupManager.dataChanged(application.packageName)
+        }
+
+        fun removeObserver() {
+            when (storage) {
+                is KeyedObservable<*> -> storage.removeObserver(this)
+                is Observable -> storage.removeObserver(this)
+            }
+        }
+
+        fun notifyRestoreFinished() {
+            when (storage) {
+                is KeyedObservable<*> -> storage.notifyChange(ChangeReason.RESTORE)
+                is Observable -> storage.notifyChange(ChangeReason.RESTORE)
+            }
+        }
+    }
+
+    companion object {
+        @Volatile private var instance: BackupRestoreStorageManager? = null
+
+        /** Returns the singleton of manager. */
+        @JvmStatic
+        fun getInstance(context: Context): BackupRestoreStorageManager {
+            val result = instance
+            if (result != null) return result
+            synchronized(this) {
+                if (instance == null) {
+                    instance =
+                        BackupRestoreStorageManager(context.applicationContext as Application)
+                }
+            }
+            return instance!!
+        }
+    }
+}
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
new file mode 100644
index 0000000..3ed4d46
--- /dev/null
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import androidx.annotation.AnyThread
+import androidx.annotation.GuardedBy
+import androidx.collection.MutableScatterMap
+import java.util.WeakHashMap
+import java.util.concurrent.Executor
+
+/**
+ * Callback to be informed of changes in [KeyedObservable] object.
+ *
+ * The observer is weakly referenced, a strong reference must be kept.
+ */
+fun interface KeyedObserver<in K> {
+    /**
+     * Called by [KeyedObservable] in the event of changes.
+     *
+     * This callback will run in the given [Executor] when observer is added.
+     *
+     * @param key key that has been changed
+     * @param reason the reason of change
+     * @see KeyedObservable.addObserver
+     */
+    fun onKeyChanged(key: K, @ChangeReason reason: Int)
+}
+
+/**
+ * A key-value observable object allows to observe change with [KeyedObserver].
+ *
+ * Notes:
+ * - The order in which observers will be notified is unspecified.
+ * - The observer is weakly referenced to avoid memory leaking, the call site must keep a strong
+ *   reference of the observer.
+ * - It is possible that the callback may be triggered even there is no real data change. For
+ *   example, when data restore/clear happens, it might be too complex to check if data is really
+ *   changed, thus all the registered observers are notified directly.
+ */
+@AnyThread
+interface KeyedObservable<K> {
+    /**
+     * Adds an observer for any key.
+     *
+     * The observer will be notified whenever a change happens. The [KeyedObserver.onKeyChanged]
+     * callback will be invoked with specific key that is modified. However, `null` key is passed in
+     * the cases that a bunch of keys are changed simultaneously (e.g. clear data, restore happens).
+     *
+     * @param observer observer to be notified
+     * @param executor executor to run the callback
+     */
+    fun addObserver(observer: KeyedObserver<K?>, executor: Executor)
+
+    /**
+     * Adds an observer on given key.
+     *
+     * The observer will be notified only when the given key is changed.
+     *
+     * @param key key to observe
+     * @param observer observer to be notified
+     * @param executor executor to run the callback
+     */
+    fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor)
+
+    /** Removes observer. */
+    fun removeObserver(observer: KeyedObserver<K?>)
+
+    /** Removes observer on given key. */
+    fun removeObserver(key: K, observer: KeyedObserver<K>)
+
+    /**
+     * Notifies all observers that a change occurs.
+     *
+     * All the any key and keyed observers are notified.
+     *
+     * @param reason reason of the change
+     */
+    fun notifyChange(@ChangeReason reason: Int)
+
+    /**
+     * Notifies observers that a change occurs on given key.
+     *
+     * The any key and specific key observers are notified.
+     *
+     * @param key key of the change
+     * @param reason reason of the change
+     */
+    fun notifyChange(key: K, @ChangeReason reason: Int)
+}
+
+/** A thread safe implementation of [KeyedObservable]. */
+class KeyedDataObservable<K> : KeyedObservable<K> {
+    // Instead of @GuardedBy("this"), guarded by itself because KeyedDataObservable object could be
+    // synchronized outside by the holder
+    @GuardedBy("itself") private val observers = WeakHashMap<KeyedObserver<K?>, Executor>()
+
+    @GuardedBy("itself")
+    private val keyedObservers = MutableScatterMap<K, WeakHashMap<KeyedObserver<K>, Executor>>()
+
+    override fun addObserver(observer: KeyedObserver<K?>, executor: Executor) {
+        val oldExecutor = synchronized(observers) { observers.put(observer, executor) }
+        if (oldExecutor != null && oldExecutor != executor) {
+            throw IllegalStateException("Add $observer twice, old=$oldExecutor, new=$executor")
+        }
+    }
+
+    override fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor) {
+        val oldExecutor =
+            synchronized(keyedObservers) {
+                keyedObservers.getOrPut(key) { WeakHashMap() }.put(observer, executor)
+            }
+        if (oldExecutor != null && oldExecutor != executor) {
+            throw IllegalStateException("Add $observer twice, old=$oldExecutor, new=$executor")
+        }
+    }
+
+    override fun removeObserver(observer: KeyedObserver<K?>) {
+        synchronized(observers) { observers.remove(observer) }
+    }
+
+    override fun removeObserver(key: K, observer: KeyedObserver<K>) {
+        synchronized(keyedObservers) {
+            val observers = keyedObservers[key]
+            if (observers?.remove(observer) != null && observers.isEmpty()) {
+                keyedObservers.remove(key)
+            }
+        }
+    }
+
+    override fun notifyChange(@ChangeReason reason: Int) {
+        // make a copy to avoid potential ConcurrentModificationException
+        val observers = synchronized(observers) { observers.entries.toTypedArray() }
+        val keyedObservers = synchronized(keyedObservers) { keyedObservers.copy() }
+        for (entry in observers) {
+            val observer = entry.key // avoid reference "entry"
+            entry.value.execute { observer.onKeyChanged(null, reason) }
+        }
+        for (pair in keyedObservers) {
+            val key = pair.first
+            for (entry in pair.second) {
+                val observer = entry.key // avoid reference "entry"
+                entry.value.execute { observer.onKeyChanged(key, reason) }
+            }
+        }
+    }
+
+    private fun MutableScatterMap<K, WeakHashMap<KeyedObserver<K>, Executor>>.copy():
+        List<Pair<K, Array<Map.Entry<KeyedObserver<K>, Executor>>>> {
+        val result = ArrayList<Pair<K, Array<Map.Entry<KeyedObserver<K>, Executor>>>>(size)
+        forEach { key, value -> result.add(Pair(key, value.entries.toTypedArray())) }
+        return result
+    }
+
+    override fun notifyChange(key: K, @ChangeReason reason: Int) {
+        // make a copy to avoid potential ConcurrentModificationException
+        val observers = synchronized(observers) { observers.entries.toTypedArray() }
+        val keyedObservers =
+            synchronized(keyedObservers) { keyedObservers[key]?.entries?.toTypedArray() }
+                ?: arrayOf()
+        for (entry in observers) {
+            val observer = entry.key // avoid reference "entry"
+            entry.value.execute { observer.onKeyChanged(key, reason) }
+        }
+        for (entry in keyedObservers) {
+            val observer = entry.key // avoid reference "entry"
+            entry.value.execute { observer.onKeyChanged(key, reason) }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKey.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/ObservableBackupRestoreStorage.kt
similarity index 66%
rename from packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKey.kt
rename to packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/ObservableBackupRestoreStorage.kt
index 87332ae..0e399c0 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKey.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/ObservableBackupRestoreStorage.kt
@@ -14,13 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.scene.shared.model
+package com.android.settingslib.datastore
 
 /**
- * Key for a transition. This can be used to specify which transition spec should be used when
- * starting the transition between two scenes.
+ * A [BackupRestoreStorage] that implements [Observable].
+ *
+ * This class provides the [Observable] implementations on top of [DataObservable] by delegation.
  */
-data class TransitionKey(
-    val debugName: String,
-    val identity: Any = Object(),
-)
+abstract class ObservableBackupRestoreStorage :
+    BackupRestoreStorage(), Observable by DataObservable()
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/Observer.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/Observer.kt
new file mode 100644
index 0000000..6d0ca669
--- /dev/null
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/Observer.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import androidx.annotation.AnyThread
+import androidx.annotation.GuardedBy
+import androidx.annotation.IntDef
+import java.util.WeakHashMap
+import java.util.concurrent.Executor
+
+/** The reason of a change. */
+@IntDef(
+    ChangeReason.UNKNOWN,
+    ChangeReason.UPDATE,
+    ChangeReason.DELETE,
+    ChangeReason.RESTORE,
+    ChangeReason.SYNC_ACROSS_PROFILES,
+)
+@Retention(AnnotationRetention.SOURCE)
+annotation class ChangeReason {
+    companion object {
+        /** Unknown reason of the change. */
+        const val UNKNOWN = 0
+        /** Data is updated. */
+        const val UPDATE = 1
+        /** Data is deleted. */
+        const val DELETE = 2
+        /** Data is restored from backup/restore framework. */
+        const val RESTORE = 3
+        /** Data is synced from another profile (e.g. personal profile to work profile). */
+        const val SYNC_ACROSS_PROFILES = 4
+    }
+}
+
+/**
+ * Callback to be informed of changes in [Observable] object.
+ *
+ * The observer is weakly referenced, a strong reference must be kept.
+ */
+fun interface Observer {
+    /**
+     * Called by [Observable] in the event of changes.
+     *
+     * This callback will run in the given [Executor] when observer is added.
+     *
+     * @param reason the reason of change
+     * @see [Observable.addObserver] for the notices.
+     */
+    fun onChanged(@ChangeReason reason: Int)
+}
+
+/** An observable object allows to observe change with [Observer]. */
+@AnyThread
+interface Observable {
+    /**
+     * Adds an observer.
+     *
+     * Notes:
+     * - The order in which observers will be notified is unspecified.
+     * - The observer is weakly referenced to avoid memory leaking, the call site must keep a strong
+     *   reference of the observer.
+     * - It is possible that the callback may be triggered even there is no real data change. For
+     *   example, when data restore/clear happens, it might be too complex to check if data is
+     *   really changed, thus all the registered observers are notified directly.
+     *
+     * @param observer observer to be notified
+     * @param executor executor to run the [Observer.onChanged] callback
+     */
+    fun addObserver(observer: Observer, executor: Executor)
+
+    /** Removes given observer. */
+    fun removeObserver(observer: Observer)
+
+    /**
+     * Notifies observers that a change occurs.
+     *
+     * @param reason reason of the change
+     */
+    fun notifyChange(@ChangeReason reason: Int)
+}
+
+/** A thread safe implementation of [Observable]. */
+class DataObservable : Observable {
+    // Instead of @GuardedBy("this"), guarded by itself because DataObservable object could be
+    // synchronized outside by the holder
+    @GuardedBy("itself") private val observers = WeakHashMap<Observer, Executor>()
+
+    override fun addObserver(observer: Observer, executor: Executor) {
+        val oldExecutor = synchronized(observers) { observers.put(observer, executor) }
+        if (oldExecutor != null && oldExecutor != executor) {
+            throw IllegalStateException("Add $observer twice, old=$oldExecutor, new=$executor")
+        }
+    }
+
+    override fun removeObserver(observer: Observer) {
+        synchronized(observers) { observers.remove(observer) }
+    }
+
+    override fun notifyChange(@ChangeReason reason: Int) {
+        // make a copy to avoid potential ConcurrentModificationException
+        val entries = synchronized(observers) { observers.entries.toTypedArray() }
+        for (entry in entries) {
+            val observer = entry.key // avoid reference "entry"
+            entry.value.execute { observer.onChanged(reason) }
+        }
+    }
+}
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt
new file mode 100644
index 0000000..0c1b417
--- /dev/null
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.os.Build
+import android.util.Log
+import androidx.core.content.ContextCompat
+import java.io.File
+
+/**
+ * [SharedPreferences] based storage.
+ *
+ * The backup and restore is handled by [BackupRestoreFileArchiver] to achieve forward-compatibility
+ * just like `PersistentBackupAgentHelper`.
+ *
+ * Simple file based backup and restore is not safe, which incurs multi-thread file writes in
+ * SharedPreferences file. Additionally, SharedPreferences has in-memory state, so reload is needed.
+ * However, there is no public reload API on SharedPreferences and listeners are not notified in
+ * current private implementation. As such, an intermediate SharedPreferences file is introduced for
+ * backup and restore.
+ *
+ * Note that existing entries in the SharedPreferences will NOT be deleted before restore.
+ *
+ * @param context Context to get SharedPreferences
+ * @param name Name of the SharedPreferences
+ * @param mode Operating mode, see [Context.getSharedPreferences]
+ * @param verbose Verbose logging on key/value pairs during backup/restore. Enable for dev only!
+ * @param filter Filter of key/value pairs for backup and restore.
+ */
+class SharedPreferencesStorage
+@JvmOverloads
+constructor(
+    context: Context,
+    override val name: String,
+    mode: Int,
+    private val verbose: Boolean = (Build.TYPE == "eng"),
+    private val filter: (String, Any?) -> Boolean = { _, _ -> true },
+) :
+    BackupRestoreFileStorage(context, context.getSharedPreferencesFilePath(name)),
+    KeyedObservable<String> by KeyedDataObservable() {
+
+    private val sharedPreferences = context.getSharedPreferences(name, mode)
+
+    /** Name of the intermediate SharedPreferences. */
+    private val intermediateName: String
+        get() = "_br_$name"
+
+    private val intermediateSharedPreferences: SharedPreferences
+        get() {
+            // use MODE_MULTI_PROCESS to ensure a reload
+            return context.getSharedPreferences(intermediateName, Context.MODE_MULTI_PROCESS)
+        }
+
+    private val sharedPreferencesListener =
+        SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
+            if (key != null) {
+                notifyChange(key, ChangeReason.UPDATE)
+            } else {
+                // On Android >= R, SharedPreferences.Editor.clear() will trigger this case
+                notifyChange(ChangeReason.DELETE)
+            }
+        }
+
+    init {
+        // listener is weakly referenced, so unregister is optional
+        sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
+    }
+
+    override val backupFile: File
+        // use a different file to avoid multi-thread file write
+        get() = context.getSharedPreferencesFile(intermediateName)
+
+    override fun prepareBackup(file: File) {
+        val editor = intermediateSharedPreferences.merge(sharedPreferences.all, "Backup")
+        // commit to ensure data is write to disk synchronously
+        if (!editor.commit()) {
+            Log.w(LOG_TAG, "[$name] fail to commit")
+        }
+    }
+
+    override fun onBackupFinished(file: File) {
+        intermediateSharedPreferences.delete(intermediateName)
+    }
+
+    override fun onRestoreFinished(file: File) {
+        // Unregister listener to avoid notify observer during restore because there might be
+        // dependency between keys. BackupRestoreStorageManager.onRestoreFinished will notify
+        // observers consistently once restore finished.
+        sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferencesListener)
+        val restored = intermediateSharedPreferences
+        val editor = sharedPreferences.merge(restored.all, "Restore")
+        editor.apply() // apply to avoid blocking
+        sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
+        // clear the intermediate SharedPreferences
+        restored.delete(intermediateName)
+    }
+
+    private fun SharedPreferences.delete(name: String) {
+        if (deleteSharedPreferences(name)) {
+            Log.i(LOG_TAG, "SharedPreferences $name deleted")
+        } else {
+            edit().clear().apply()
+        }
+    }
+
+    private fun deleteSharedPreferences(name: String): Boolean =
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            context.deleteSharedPreferences(name)
+        } else {
+            false
+        }
+
+    private fun SharedPreferences.merge(
+        entries: Map<String, Any?>,
+        operation: String
+    ): SharedPreferences.Editor {
+        val editor = edit()
+        for ((key, value) in entries) {
+            if (!filter.invoke(key, value)) {
+                if (verbose) Log.v(LOG_TAG, "[$name] $operation skips $key=$value")
+                continue
+            }
+            when (value) {
+                is Boolean -> {
+                    editor.putBoolean(key, value)
+                    if (verbose) Log.v(LOG_TAG, "[$name] $operation Boolean $key=$value")
+                }
+                is Float -> {
+                    editor.putFloat(key, value)
+                    if (verbose) Log.v(LOG_TAG, "[$name] $operation Float $key=$value")
+                }
+                is Int -> {
+                    editor.putInt(key, value)
+                    if (verbose) Log.v(LOG_TAG, "[$name] $operation Int $key=$value")
+                }
+                is Long -> {
+                    editor.putLong(key, value)
+                    if (verbose) Log.v(LOG_TAG, "[$name] $operation Long $key=$value")
+                }
+                is String -> {
+                    editor.putString(key, value)
+                    if (verbose) Log.v(LOG_TAG, "[$name] $operation String $key=$value")
+                }
+                is Set<*> -> {
+                    val nonString = value.firstOrNull { it !is String }
+                    if (nonString != null) {
+                        Log.e(
+                            LOG_TAG,
+                            "[$name] $operation StringSet $key=$value" +
+                                " but non string found: $nonString (${nonString.javaClass})",
+                        )
+                    } else {
+                        @Suppress("UNCHECKED_CAST") editor.putStringSet(key, value as Set<String>)
+                        if (verbose) Log.v(LOG_TAG, "[$name] $operation StringSet $key=$value")
+                    }
+                }
+                else -> {
+                    Log.e(
+                        LOG_TAG,
+                        "[$name] $operation $key=$value, unknown type: ${value?.javaClass}"
+                    )
+                }
+            }
+        }
+        return editor
+    }
+
+    companion object {
+        private fun Context.getSharedPreferencesFilePath(name: String): String {
+            val file = getSharedPreferencesFile(name)
+            return file.relativeTo(ContextCompat.getDataDir(this)!!).toString()
+        }
+
+        /** Returns the absolute path of shared preferences file. */
+        @JvmStatic
+        fun Context.getSharedPreferencesFile(name: String): File {
+            // ContextImpl.getSharedPreferencesPath is private
+            return File(getSharedPreferencesDir(), "$name.xml")
+        }
+
+        private fun Context.getSharedPreferencesDir() = File(dataDirCompat, "shared_prefs")
+    }
+}
diff --git a/packages/SettingsLib/DataStore/tests/Android.bp b/packages/SettingsLib/DataStore/tests/Android.bp
new file mode 100644
index 0000000..8770dfa
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/Android.bp
@@ -0,0 +1,24 @@
+package {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_app {
+    name: "SettingsLibDataStoreShell",
+    platform_apis: true,
+}
+
+android_robolectric_test {
+    name: "SettingsLibDataStoreTest",
+    srcs: ["src/**/*"],
+    static_libs: [
+        "SettingsLibDataStore",
+        "androidx.test.ext.junit",
+        "guava",
+        "mockito-robolectric-prebuilt", // mockito deps order matters!
+        "mockito-kotlin2",
+    ],
+    java_resource_dirs: ["config"],
+    instrumentation_for: "SettingsLibDataStoreShell",
+    coverage_libs: ["SettingsLibDataStore"],
+    upstream: true,
+}
diff --git a/packages/SettingsLib/DataStore/tests/AndroidManifest.xml b/packages/SettingsLib/DataStore/tests/AndroidManifest.xml
new file mode 100644
index 0000000..ffc24e4
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.settingslib.datastore.test">
+
+    <application android:debuggable="true" />
+</manifest>
diff --git a/packages/SettingsLib/DataStore/tests/config/robolectric.properties b/packages/SettingsLib/DataStore/tests/config/robolectric.properties
new file mode 100644
index 0000000..fab7251
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/config/robolectric.properties
@@ -0,0 +1 @@
+sdk=NEWEST_SDK
diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt
new file mode 100644
index 0000000..bb791dc
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors
+import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicInteger
+import org.junit.Assert.assertThrows
+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.mockito.kotlin.any
+import org.mockito.kotlin.never
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
+
+@RunWith(AndroidJUnit4::class)
+class ObserverTest {
+    @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+    @Mock private lateinit var observer1: Observer
+
+    @Mock private lateinit var observer2: Observer
+
+    @Mock private lateinit var executor: Executor
+
+    private val observable = DataObservable()
+
+    @Test
+    fun addObserver_sameExecutor() {
+        observable.addObserver(observer1, executor)
+        observable.addObserver(observer1, executor)
+    }
+
+    @Test
+    fun addObserver_differentExecutor() {
+        observable.addObserver(observer1, executor)
+        assertThrows(IllegalStateException::class.java) {
+            observable.addObserver(observer1, MoreExecutors.directExecutor())
+        }
+    }
+
+    @Test
+    fun addObserver_weaklyReferenced() {
+        val counter = AtomicInteger()
+        var observer: Observer? = Observer { counter.incrementAndGet() }
+        observable.addObserver(observer!!, MoreExecutors.directExecutor())
+
+        observable.notifyChange(ChangeReason.UPDATE)
+        assertThat(counter.get()).isEqualTo(1)
+
+        // trigger GC, the observer callback should not be invoked
+        @Suppress("unused")
+        observer = null
+        System.gc()
+        System.runFinalization()
+
+        observable.notifyChange(ChangeReason.UPDATE)
+        assertThat(counter.get()).isEqualTo(1)
+    }
+
+    @Test
+    fun addObserver_notifyObservers_removeObserver() {
+        observable.addObserver(observer1, MoreExecutors.directExecutor())
+        observable.addObserver(observer2, executor)
+
+        observable.notifyChange(ChangeReason.DELETE)
+
+        verify(observer1).onChanged(ChangeReason.DELETE)
+        verify(observer2, never()).onChanged(any())
+        verify(executor).execute(any())
+
+        reset(observer1, executor)
+        observable.removeObserver(observer2)
+
+        observable.notifyChange(ChangeReason.UPDATE)
+        verify(observer1).onChanged(ChangeReason.UPDATE)
+        verify(executor, never()).execute(any())
+    }
+
+    @Test
+    fun notifyChange_addObserverWithinCallback() {
+        // ConcurrentModificationException is raised if it is not implemented correctly
+        observable.addObserver(
+            { observable.addObserver(observer1, executor) },
+            MoreExecutors.directExecutor()
+        )
+        observable.notifyChange(ChangeReason.UPDATE)
+    }
+}
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
index 5f5f1d5..5966c9f 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -5,6 +5,7 @@
 dsandler@android.com
 edgarwang@google.com
 evanlaird@google.com
+jiannan@google.com
 juliacr@google.com
 ykhung@google.com
 
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 335725c..c755623 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@
 
 allprojects {
     extra["androidTop"] = androidTop
-    extra["jetpackComposeVersion"] = "1.7.0-alpha03"
+    extra["jetpackComposeVersion"] = "1.7.0-alpha04"
 }
 
 subprojects {
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 609a82e..ff2a1e8 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,7 +15,7 @@
 #
 
 [versions]
-agp = "8.2.2"
+agp = "8.3.0"
 compose-compiler = "1.5.10"
 dexmaker-mockito = "2.28.3"
 jvm = "17"
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.6-bin.zip b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.6-bin.zip
new file mode 100644
index 0000000..5c96347
--- /dev/null
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.6-bin.zip
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index 516749d..50ff9df 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
@@ -16,8 +16,6 @@
 
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
-networkTimeout=10000
-validateDistributionUrl=true
+distributionUrl=gradle-8.6-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index a193a2f..f2b9235 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.3.0-alpha01")
+    api("androidx.compose.material3:material3:1.3.0-alpha02")
     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.8.0-alpha02")
+    api("androidx.navigation:navigation-compose:2.8.0-alpha03")
     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/widget/preference/MainSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
index fc8de80..0a469b8 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
@@ -47,7 +47,7 @@
                 onCheckedChange = model.onCheckedChange,
                 paddingStart = 20.dp,
                 paddingEnd = 20.dp,
-                paddingVertical = 18.dp,
+                paddingVertical = 24.dp,
             )
         }
     }
@@ -55,7 +55,7 @@
 
 @Preview
 @Composable
-fun MainSwitchPreferencePreview() {
+private fun MainSwitchPreferencePreview() {
     SettingsTheme {
         Column {
             MainSwitchPreference(object : SwitchPreferenceModel {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
index bb7e857..3acf075 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
@@ -18,7 +18,6 @@
 
 import androidx.compose.foundation.clickable
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.vector.ImageVector
 import com.android.settingslib.spa.framework.common.EntryMacro
@@ -107,14 +106,9 @@
 ) {
     val onClickWithLog = wrapOnClickWithLog(model.onClick)
     val enabled = model.enabled()
-    val modifier = remember(enabled) {
-        if (onClickWithLog != null) {
-            Modifier.clickable(
-                enabled = enabled,
-                onClick = onClickWithLog
-            )
-        } else Modifier
-    }
+    val modifier = if (onClickWithLog != null) {
+        Modifier.clickable(enabled = enabled, onClick = onClickWithLog)
+    } else Modifier
     EntryHighlight {
         BasePreference(
             title = model.title,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt
index 9866023..3f74ed5 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt
@@ -36,7 +36,7 @@
         TwoTargetPreference(
             title = title,
             summary = summary,
-            onClick = onClick,
+            primaryOnClick = onClick,
             icon = icon,
         ) {
             IconButton(onClick = onButtonClick) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
index 3216e37..3f68804 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
@@ -34,7 +34,8 @@
 internal fun TwoTargetPreference(
     title: String,
     summary: () -> String,
-    onClick: () -> Unit,
+    primaryEnabled: () -> Boolean = { true },
+    primaryOnClick: (() -> Unit)?,
     icon: @Composable (() -> Unit)? = null,
     widget: @Composable () -> Unit,
 ) {
@@ -50,7 +51,8 @@
                     override val title = title
                     override val summary = summary
                     override val icon = icon
-                    override val onClick = onClick
+                    override val enabled = primaryEnabled
+                    override val onClick = primaryOnClick
                 }
             )
         }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
index 7eed745..8b546b4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
@@ -24,13 +24,15 @@
 fun TwoTargetSwitchPreference(
     model: SwitchPreferenceModel,
     icon: @Composable (() -> Unit)? = null,
-    onClick: () -> Unit,
+    primaryEnabled: () -> Boolean = { true },
+    primaryOnClick: (() -> Unit)?,
 ) {
     EntryHighlight {
         TwoTargetPreference(
             title = model.title,
             summary = model.summary,
-            onClick = onClick,
+            primaryEnabled = primaryEnabled,
+            primaryOnClick = primaryOnClick,
             icon = icon,
         ) {
             SettingsSwitch(
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreferenceTest.kt
index 3455851..0acf287 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreferenceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreferenceTest.kt
@@ -23,6 +23,7 @@
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotEnabled
 import androidx.compose.ui.test.assertIsOff
 import androidx.compose.ui.test.assertIsOn
 import androidx.compose.ui.test.isToggleable
@@ -46,7 +47,7 @@
             TestTwoTargetSwitchPreference(changeable = true)
         }
 
-        composeTestRule.onNodeWithText("TwoTargetSwitchPreference").assertIsDisplayed()
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
     }
 
     @Test
@@ -79,7 +80,7 @@
     }
 
     @Test
-    fun clickable_canBeClick() {
+    fun clickable_primaryEnabled_canBeClick() {
         var clicked = false
         composeTestRule.setContent {
             TestTwoTargetSwitchPreference(changeable = false) {
@@ -87,26 +88,54 @@
             }
         }
 
-        composeTestRule.onNodeWithText("TwoTargetSwitchPreference").performClick()
+        composeTestRule.onNodeWithText(TITLE).performClick()
         assertThat(clicked).isTrue()
     }
-}
 
-@Composable
-private fun TestTwoTargetSwitchPreference(
-    changeable: Boolean,
-    onClick: () -> Unit = {},
-) {
-    var checked by rememberSaveable { mutableStateOf(false) }
-    TwoTargetSwitchPreference(
-        model = remember {
-            object : SwitchPreferenceModel {
-                override val title = "TwoTargetSwitchPreference"
-                override val checked = { checked }
-                override val changeable = { changeable }
-                override val onCheckedChange = { newChecked: Boolean -> checked = newChecked }
+    @Test
+    fun clickable_primaryNotEnabled_assertIsNotEnabled() {
+        composeTestRule.setContent {
+            TestTwoTargetSwitchPreference(changeable = false, primaryEnabled = false)
+        }
+
+        composeTestRule.onNodeWithText(TITLE).assertIsNotEnabled()
+    }
+
+    @Test
+    fun clickable_primaryNotEnabled_canNotBeClick() {
+        var clicked = false
+        composeTestRule.setContent {
+            TestTwoTargetSwitchPreference(changeable = false, primaryEnabled = false) {
+                clicked = true
             }
-        },
-        onClick = onClick,
-    )
+        }
+
+        composeTestRule.onNodeWithText(TITLE).performClick()
+        assertThat(clicked).isFalse()
+    }
+
+    @Composable
+    private fun TestTwoTargetSwitchPreference(
+        changeable: Boolean,
+        primaryEnabled: Boolean = true,
+        primaryOnClick: () -> Unit = {},
+    ) {
+        var checked by rememberSaveable { mutableStateOf(false) }
+        TwoTargetSwitchPreference(
+            model = remember {
+                object : SwitchPreferenceModel {
+                    override val title = TITLE
+                    override val checked = { checked }
+                    override val changeable = { changeable }
+                    override val onCheckedChange = { newChecked: Boolean -> checked = newChecked }
+                }
+            },
+            primaryEnabled = { primaryEnabled },
+            primaryOnClick = primaryOnClick,
+        )
+    }
+
+    private companion object {
+        const val TITLE = "Title"
+    }
 }
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 988afd7..a395266 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
@@ -26,6 +26,7 @@
 import android.content.pm.PackageManager.ApplicationInfoFlags
 import android.content.pm.ResolveInfo
 import android.os.SystemProperties
+import android.util.Log
 import com.android.internal.R
 import com.android.settingslib.spaprivileged.framework.common.userManager
 import kotlinx.coroutines.async
@@ -85,19 +86,24 @@
         userId: Int,
         loadInstantApps: Boolean,
         matchAnyUserForAdmin: Boolean,
-    ): List<ApplicationInfo> = coroutineScope {
-        val hiddenSystemModulesDeferred = async { packageManager.getHiddenSystemModules() }
-        val hideWhenDisabledPackagesDeferred = async {
-            context.resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)
-        }
-        val installedApplicationsAsUser =
-            getInstalledApplications(userId, matchAnyUserForAdmin)
+    ): List<ApplicationInfo> = try {
+        coroutineScope {
+            val hiddenSystemModulesDeferred = async { packageManager.getHiddenSystemModules() }
+            val hideWhenDisabledPackagesDeferred = async {
+                context.resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)
+            }
+            val installedApplicationsAsUser =
+                getInstalledApplications(userId, matchAnyUserForAdmin)
 
-        val hiddenSystemModules = hiddenSystemModulesDeferred.await()
-        val hideWhenDisabledPackages = hideWhenDisabledPackagesDeferred.await()
-        installedApplicationsAsUser.filter { app ->
-            app.isInAppList(loadInstantApps, hiddenSystemModules, hideWhenDisabledPackages)
+            val hiddenSystemModules = hiddenSystemModulesDeferred.await()
+            val hideWhenDisabledPackages = hideWhenDisabledPackagesDeferred.await()
+            installedApplicationsAsUser.filter { app ->
+                app.isInAppList(loadInstantApps, hiddenSystemModules, hideWhenDisabledPackages)
+            }
         }
+    } catch (e: Exception) {
+        Log.e(TAG, "loadApps failed", e)
+        emptyList()
     }
 
     private suspend fun getInstalledApplications(
@@ -210,6 +216,8 @@
     }
 
     companion object {
+        private const val TAG = "AppListRepository"
+
         private fun ApplicationInfo.isInAppList(
             showInstantApps: Boolean,
             hiddenSystemModules: Set<String>,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
index bea14c3..5c2d770 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
@@ -38,6 +38,6 @@
             override val onCheckedChange = onCheckedChange
         },
         icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) },
-        onClick = onClick,
+        primaryOnClick = onClick,
     )
 }
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
index ac85dd4..389b3c1 100644
--- 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
@@ -54,14 +54,27 @@
         return
     }
     val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
-    val restrictedSwitchModel = remember(restrictedMode) {
+    val restrictedModel = remember(restrictedMode) {
         RestrictedPreferenceModel(model, restrictedMode)
     }
-    restrictedSwitchModel.RestrictionWrapper {
-        Preference(restrictedSwitchModel)
+    restrictedModel.RestrictionWrapper {
+        Preference(restrictedModel)
     }
 }
 
+internal fun RestrictedMode?.restrictEnabled(enabled: () -> Boolean) = when (this) {
+    NoRestricted -> enabled
+    else -> ({ false })
+}
+
+internal fun <T> RestrictedMode?.restrictOnClick(onClick: T): T? = when (this) {
+    NoRestricted -> onClick
+    // Need to passthrough onClick for clickable semantics, although since enabled is false so
+    // this will not be called.
+    BaseUserRestricted -> onClick
+    else -> null
+}
+
 private class RestrictedPreferenceModel(
     model: PreferenceModel,
     private val restrictedMode: RestrictedMode?,
@@ -69,19 +82,8 @@
     override val title = model.title
     override val summary = model.summary
     override val icon = model.icon
-
-    override val enabled = when (restrictedMode) {
-        NoRestricted -> model.enabled
-        else -> ({ 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
-    }
+    override val enabled = restrictedMode.restrictEnabled(model.enabled)
+    override val onClick = restrictedMode.restrictOnClick(model.onClick)
 
     @Composable
     fun RestrictionWrapper(content: @Composable () -> Unit) {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
index aba3460..5dfecb0 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
@@ -60,18 +60,9 @@
         is BlockedByEcm -> model.checked
     }
 
-    override val changeable = if (restrictedMode is NoRestricted) model.changeable else ({ false })
+    override val changeable = restrictedMode.restrictEnabled(model.changeable)
 
-    override val onCheckedChange = when (restrictedMode) {
-        null -> null
-        is NoRestricted -> model.onCheckedChange
-        // 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
-        is BlockedByEcm -> null
-    }
+    override val onCheckedChange = restrictedMode.restrictOnClick(model.onCheckedChange)
 
     @Composable
     fun RestrictionWrapper(content: @Composable () -> Unit) {
@@ -116,13 +107,12 @@
 
     companion object {
         @Composable
-        fun RestrictionsProviderFactory.RestrictedSwitchWrapper(
+        fun RestrictedSwitchWrapper(
             model: SwitchPreferenceModel,
-            restrictions: Restrictions,
+            restrictedMode: RestrictedMode?,
             content: @Composable (SwitchPreferenceModel) -> Unit,
         ) {
             val context = LocalContext.current
-            val restrictedMode = rememberRestrictedMode(restrictions).value
             val restrictedSwitchPreferenceModel = remember(restrictedMode) {
                 RestrictedSwitchPreferenceModel(context, model, restrictedMode)
             }
@@ -131,6 +121,15 @@
             }
         }
 
+        @Composable
+        fun RestrictionsProviderFactory.RestrictedSwitchWrapper(
+            model: SwitchPreferenceModel,
+            restrictions: Restrictions,
+            content: @Composable (SwitchPreferenceModel) -> Unit,
+        ) {
+            RestrictedSwitchWrapper(model, rememberRestrictedMode(restrictions).value, content)
+        }
+
         fun getSummary(
             context: Context,
             restrictedModeSupplier: () -> RestrictedMode?,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt
new file mode 100644
index 0000000..e100773
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.preference
+
+import androidx.annotation.VisibleForTesting
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
+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.RestrictedSwitchPreferenceModel.Companion.RestrictedSwitchWrapper
+
+@Composable
+fun RestrictedTwoTargetSwitchPreference(
+    model: SwitchPreferenceModel,
+    icon: @Composable (() -> Unit)? = null,
+    restrictions: Restrictions,
+    primaryEnabled: () -> Boolean = { true },
+    primaryOnClick: (() -> Unit)?,
+) {
+    RestrictedTwoTargetSwitchPreference(
+        model = model,
+        icon = icon,
+        primaryEnabled = primaryEnabled,
+        primaryOnClick = primaryOnClick,
+        restrictions = restrictions,
+        restrictionsProviderFactory = ::RestrictionsProviderImpl,
+    )
+}
+
+@VisibleForTesting
+@Composable
+internal fun RestrictedTwoTargetSwitchPreference(
+    model: SwitchPreferenceModel,
+    icon: @Composable (() -> Unit)? = null,
+    primaryEnabled: () -> Boolean = { true },
+    primaryOnClick: (() -> Unit)?,
+    restrictions: Restrictions,
+    restrictionsProviderFactory: RestrictionsProviderFactory,
+) {
+    if (restrictions.isEmpty()) {
+        TwoTargetSwitchPreference(model, icon, primaryEnabled, primaryOnClick)
+        return
+    }
+    val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
+    RestrictedSwitchWrapper(model, restrictedMode) { restrictedModel ->
+        TwoTargetSwitchPreference(
+            model = restrictedModel,
+            icon = icon,
+            primaryEnabled = restrictedMode.restrictEnabled(primaryEnabled),
+            primaryOnClick = restrictedMode.restrictOnClick(primaryOnClick),
+        )
+    }
+}
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 efd53a4..c60ce41 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
@@ -28,6 +28,8 @@
 import android.content.pm.ResolveInfo
 import android.content.pm.UserInfo
 import android.content.res.Resources
+import android.os.BadParcelableException
+import android.os.DeadObjectException
 import android.os.UserManager
 import android.platform.test.flag.junit.SetFlagsRule
 import androidx.test.core.app.ApplicationProvider
@@ -44,6 +46,7 @@
 import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.doAnswer
 import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.doThrow
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.spy
@@ -311,6 +314,19 @@
     }
 
     @Test
+    fun loadApps_hasException_returnEmptyList() = runTest {
+        packageManager.stub {
+            on {
+                getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(ADMIN_USER_ID))
+            } doThrow BadParcelableException(DeadObjectException())
+        }
+
+        val appList = repository.loadApps(userId = ADMIN_USER_ID)
+
+        assertThat(appList).isEmpty()
+    }
+
+    @Test
     fun showSystemPredicate_showSystem() = runTest {
         val app = SYSTEM_APP
 
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreferenceTest.kt
new file mode 100644
index 0000000..bdff89f
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreferenceTest.kt
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.preference
+
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.isOff
+import androidx.compose.ui.test.isOn
+import androidx.compose.ui.test.isToggleable
+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.SwitchPreferenceModel
+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.FakeBlockedByEcm
+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 RestrictedTwoTargetSwitchPreferenceTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val fakeBlockedByAdmin = FakeBlockedByAdmin()
+    private val fakeBlockedByEcm = FakeBlockedByEcm()
+
+    private val fakeRestrictionsProvider = FakeRestrictionsProvider()
+
+    private val switchPreferenceModel = object : SwitchPreferenceModel {
+        override val title = TITLE
+        private val checkedState = mutableStateOf(true)
+        override val checked = { checkedState.value }
+        override val onCheckedChange: (Boolean) -> Unit = { checkedState.value = it }
+    }
+
+    @Test
+    fun whenRestrictionsKeysIsEmpty_enabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+        composeTestRule.onNode(isOn()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenRestrictionsKeysIsEmpty_toggleable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+        setContent(restrictions)
+        composeTestRule.onNode(isToggleable()).performClick()
+
+        composeTestRule.onNode(isOff()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenNoRestricted_enabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+        composeTestRule.onNode(isOn()).assertIsDisplayed().assertIsEnabled()
+    }
+
+    @Test
+    fun whenNoRestricted_toggleable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+        setContent(restrictions)
+        composeTestRule.onNode(isToggleable()).performClick()
+
+        composeTestRule.onNode(isOff()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenBaseUserRestricted_disabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsNotEnabled()
+        composeTestRule.onNode(isOff()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenBaseUserRestricted_notToggleable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+        setContent(restrictions)
+        composeTestRule.onNode(isToggleable()).performClick()
+
+        composeTestRule.onNode(isOff()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenBlockedByAdmin_disabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+        composeTestRule.onNodeWithText(FakeBlockedByAdmin.SUMMARY).assertIsDisplayed()
+        composeTestRule.onNode(isOn()).assertIsDisplayed().assertIsEnabled()
+    }
+
+    @Test
+    fun whenBlockedByAdmin_clickPrimary() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+        setContent(restrictions)
+        composeTestRule.onNodeWithText(TITLE).performClick()
+
+        assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue()
+    }
+
+    @Test
+    fun whenBlockedByAdmin_clickSwitch() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+        setContent(restrictions)
+        composeTestRule.onNode(isToggleable()).performClick()
+
+        assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue()
+    }
+
+    @Test
+    fun whenBlockedByEcm_disabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByEcm
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+        composeTestRule.onNodeWithText(FakeBlockedByEcm.SUMMARY).assertIsDisplayed()
+        composeTestRule.onNode(isOn()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenBlockedByEcm_click() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByEcm
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        assertThat(fakeBlockedByEcm.showRestrictedSettingsDetailsIsCalled).isTrue()
+    }
+
+    private fun setContent(restrictions: Restrictions, primaryOnClick: (() -> Unit)? = {}) {
+        composeTestRule.setContent {
+            RestrictedTwoTargetSwitchPreference(
+                model = switchPreferenceModel,
+                primaryOnClick = primaryOnClick,
+                restrictions = 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.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 6a1ee3a..54c5a14 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -43,4 +43,11 @@
   namespace: "pixel_cross_device_control"
   description: "Gates whether to enable LE audio private broadcast sharing via QR code"
   bug: "308368124"
-}
\ No newline at end of file
+}
+
+flag {
+  name: "enable_hide_exclusively_managed_bluetooth_device"
+  namespace: "dck_framework"
+  description: "Hide exclusively managed Bluetooth devices in BT settings menu."
+  bug: "324475542"
+}
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar.xml
new file mode 100644
index 0000000..d9a417f
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="14dp"
+        android:height="14dp"
+        android:viewportWidth="14.0"
+        android:viewportHeight="14.0">
+    <path
+        android:pathData="M8.25,3L9.25,3A0.5,0.5 0,0 1,9.75 3.5L9.75,13.5A0.5,0.5 0,0 1,9.25 14L8.25,14A0.5,0.5 0,0 1,7.75 13.5L7.75,3.5A0.5,0.5 0,0 1,8.25 3z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M11.75,0L12.75,0A0.5,0.5 0,0 1,13.25 0.5L13.25,13.5A0.5,0.5 0,0 1,12.75 14L11.75,14A0.5,0.5 0,0 1,11.25 13.5L11.25,0.5A0.5,0.5 0,0 1,11.75 0z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M1.25,10L2.25,10A0.5,0.5 0,0 1,2.75 10.5L2.75,13.5A0.5,0.5 0,0 1,2.25 14L1.25,14A0.5,0.5 0,0 1,0.75 13.5L0.75,10.5A0.5,0.5 0,0 1,1.25 10z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M4.75,6.5L5.75,6.5A0.5,0.5 0,0 1,6.25 7L6.25,13.5A0.5,0.5 0,0 1,5.75 14L4.75,14A0.5,0.5 0,0 1,4.25 13.5L4.25,7A0.5,0.5 0,0 1,4.75 6.5z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml
new file mode 100644
index 0000000..facc285
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml
@@ -0,0 +1,28 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="15dp"
+        android:height="14dp"
+        android:viewportWidth="15.0"
+        android:viewportHeight="14.0">
+    <path
+        android:pathData="M7,3.5C7,3.224 7.224,3 7.5,3H8.5C8.776,3 9,3.224 9,3.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V3.5Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M0,10.5C0,10.224 0.224,10 0.5,10H1.5C1.776,10 2,10.224 2,10.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V10.5Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M3.5,7C3.5,6.724 3.724,6.5 4,6.5H5C5.276,6.5 5.5,6.724 5.5,7V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V7Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M11,0C10.724,0 10.5,0.224 10.5,0.5V3H12.5V0.5C12.5,0.224 12.276,0 12,0H11Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M12.25,13C12.25,12.448 12.698,12 13.25,12C13.802,12 14.25,12.448 14.25,13C14.25,13.552 13.802,14 13.25,14C12.698,14 12.25,13.552 12.25,13Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M12.25,5C12.25,4.724 12.474,4.5 12.75,4.5H13.75C14.026,4.5 14.25,4.724 14.25,5V10C14.25,10.276 14.026,10.5 13.75,10.5H12.75C12.474,10.5 12.25,10.276 12.25,10V5Z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar.xml
new file mode 100644
index 0000000..2c05a93
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="16dp"
+        android:height="14dp"
+        android:viewportWidth="16.0"
+        android:viewportHeight="14.0">
+    <path
+        android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar_error.xml
new file mode 100644
index 0000000..328e45e
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar_error.xml
@@ -0,0 +1,32 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="18dp"
+        android:height="14dp"
+        android:viewportWidth="18.0"
+        android:viewportHeight="14.0">
+    <path
+        android:pathData="M14,0.5C14,0.224 14.224,0 14.5,0H15.5C15.776,0 16,0.224 16,0.5V3H14V0.5Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M0.5,11C0.224,11 0,11.224 0,11.5V13.5C0,13.776 0.224,14 0.5,14H1.5C1.776,14 2,13.776 2,13.5V11.5C2,11.224 1.776,11 1.5,11H0.5Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M4,8C3.724,8 3.5,8.224 3.5,8.5V13.5C3.5,13.776 3.724,14 4,14H5C5.276,14 5.5,13.776 5.5,13.5V8.5C5.5,8.224 5.276,8 5,8H4Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar.xml
new file mode 100644
index 0000000..b9054ba
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="14dp"
+        android:height="14dp"
+        android:viewportWidth="14.0"
+        android:viewportHeight="14.0">
+    <path
+        android:pathData="M8.25,3L9.25,3A0.5,0.5 0,0 1,9.75 3.5L9.75,13.5A0.5,0.5 0,0 1,9.25 14L8.25,14A0.5,0.5 0,0 1,7.75 13.5L7.75,3.5A0.5,0.5 0,0 1,8.25 3z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M11.75,0L12.75,0A0.5,0.5 0,0 1,13.25 0.5L13.25,13.5A0.5,0.5 0,0 1,12.75 14L11.75,14A0.5,0.5 0,0 1,11.25 13.5L11.25,0.5A0.5,0.5 0,0 1,11.75 0z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M1.25,10L2.25,10A0.5,0.5 0,0 1,2.75 10.5L2.75,13.5A0.5,0.5 0,0 1,2.25 14L1.25,14A0.5,0.5 0,0 1,0.75 13.5L0.75,10.5A0.5,0.5 0,0 1,1.25 10z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M4.75,6.5L5.75,6.5A0.5,0.5 0,0 1,6.25 7L6.25,13.5A0.5,0.5 0,0 1,5.75 14L4.75,14A0.5,0.5 0,0 1,4.25 13.5L4.25,7A0.5,0.5 0,0 1,4.75 6.5z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml
new file mode 100644
index 0000000..03a9349
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml
@@ -0,0 +1,27 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="15dp"
+        android:height="14dp"
+        android:viewportWidth="15.0"
+        android:viewportHeight="14.0">
+    <path
+        android:pathData="M7,3.5C7,3.224 7.224,3 7.5,3H8.5C8.776,3 9,3.224 9,3.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V3.5Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M0,10.5C0,10.224 0.224,10 0.5,10H1.5C1.776,10 2,10.224 2,10.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V10.5Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M3.5,7C3.5,6.724 3.724,6.5 4,6.5H5C5.276,6.5 5.5,6.724 5.5,7V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V7Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M11,0C10.724,0 10.5,0.224 10.5,0.5V3H12.5V0.5C12.5,0.224 12.276,0 12,0H11Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M12.25,13C12.25,12.448 12.698,12 13.25,12C13.802,12 14.25,12.448 14.25,13C14.25,13.552 13.802,14 13.25,14C12.698,14 12.25,13.552 12.25,13Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M12.25,5C12.25,4.724 12.474,4.5 12.75,4.5H13.75C14.026,4.5 14.25,4.724 14.25,5V10C14.25,10.276 14.026,10.5 13.75,10.5H12.75C12.474,10.5 12.25,10.276 12.25,10V5Z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar.xml
new file mode 100644
index 0000000..774e917
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar.xml
@@ -0,0 +1,40 @@
+<!--
+     Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="16dp"
+        android:height="14dp"
+        android:viewportWidth="16.0"
+        android:viewportHeight="14.0">
+    <path
+        android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar_error.xml
new file mode 100644
index 0000000..343ec1b
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar_error.xml
@@ -0,0 +1,31 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="18dp"
+        android:height="14dp"
+        android:viewportWidth="18.0"
+        android:viewportHeight="14.0">
+    <path
+        android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M0,11.5C0,11.224 0.224,11 0.5,11H1.5C1.776,11 2,11.224 2,11.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V11.5Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M3.5,8.5C3.5,8.224 3.724,8 4,8H5C5.276,8 5.5,8.224 5.5,8.5V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V8.5Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M14.5,0C14.224,0 14,0.224 14,0.5V3H16V0.5C16,0.224 15.776,0 15.5,0H14.5Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar.xml
new file mode 100644
index 0000000..b699203
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="14dp"
+        android:height="14dp"
+        android:viewportWidth="14.0"
+        android:viewportHeight="14.0">
+    <path
+        android:pathData="M8.25,3L9.25,3A0.5,0.5 0,0 1,9.75 3.5L9.75,13.5A0.5,0.5 0,0 1,9.25 14L8.25,14A0.5,0.5 0,0 1,7.75 13.5L7.75,3.5A0.5,0.5 0,0 1,8.25 3z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M11.75,0L12.75,0A0.5,0.5 0,0 1,13.25 0.5L13.25,13.5A0.5,0.5 0,0 1,12.75 14L11.75,14A0.5,0.5 0,0 1,11.25 13.5L11.25,0.5A0.5,0.5 0,0 1,11.75 0z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M1.25,10L2.25,10A0.5,0.5 0,0 1,2.75 10.5L2.75,13.5A0.5,0.5 0,0 1,2.25 14L1.25,14A0.5,0.5 0,0 1,0.75 13.5L0.75,10.5A0.5,0.5 0,0 1,1.25 10z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M4.75,6.5L5.75,6.5A0.5,0.5 0,0 1,6.25 7L6.25,13.5A0.5,0.5 0,0 1,5.75 14L4.75,14A0.5,0.5 0,0 1,4.25 13.5L4.25,7A0.5,0.5 0,0 1,4.75 6.5z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar_error.xml
new file mode 100644
index 0000000..ba8649b
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar_error.xml
@@ -0,0 +1,26 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="15dp"
+        android:height="14dp"
+        android:viewportWidth="15.0"
+        android:viewportHeight="14.0">
+    <path
+        android:pathData="M7,3.5C7,3.224 7.224,3 7.5,3H8.5C8.776,3 9,3.224 9,3.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V3.5Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M0,10.5C0,10.224 0.224,10 0.5,10H1.5C1.776,10 2,10.224 2,10.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V10.5Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M3.5,7C3.5,6.724 3.724,6.5 4,6.5H5C5.276,6.5 5.5,6.724 5.5,7V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V7Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M11,0C10.724,0 10.5,0.224 10.5,0.5V3H12.5V0.5C12.5,0.224 12.276,0 12,0H11Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M12.25,13C12.25,12.448 12.698,12 13.25,12C13.802,12 14.25,12.448 14.25,13C14.25,13.552 13.802,14 13.25,14C12.698,14 12.25,13.552 12.25,13Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M12.25,5C12.25,4.724 12.474,4.5 12.75,4.5H13.75C14.026,4.5 14.25,4.724 14.25,5V10C14.25,10.276 14.026,10.5 13.75,10.5H12.75C12.474,10.5 12.25,10.276 12.25,10V5Z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar.xml
new file mode 100644
index 0000000..43fa734
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="16dp"
+        android:height="14dp"
+        android:viewportWidth="16.0"
+        android:viewportHeight="14.0">
+    <path
+        android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar_error.xml
new file mode 100644
index 0000000..6309e17
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar_error.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="18dp"
+        android:height="14dp"
+        android:viewportWidth="18.0"
+        android:viewportHeight="14.0">
+    <path
+        android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M0,11.5C0,11.224 0.224,11 0.5,11H1.5C1.776,11 2,11.224 2,11.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V11.5Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M3.5,8.5C3.5,8.224 3.724,8 4,8H5C5.276,8 5.5,8.224 5.5,8.5V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V8.5Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M14.5,0C14.224,0 14,0.224 14,0.5V3H16V0.5C16,0.224 15.776,0 15.5,0H14.5Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar.xml
new file mode 100644
index 0000000..6a218b3
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="14dp"
+        android:height="14dp"
+        android:viewportWidth="14.0"
+        android:viewportHeight="14.0">
+    <path
+        android:pathData="M8.25,3L9.25,3A0.5,0.5 0,0 1,9.75 3.5L9.75,13.5A0.5,0.5 0,0 1,9.25 14L8.25,14A0.5,0.5 0,0 1,7.75 13.5L7.75,3.5A0.5,0.5 0,0 1,8.25 3z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M11.75,0L12.75,0A0.5,0.5 0,0 1,13.25 0.5L13.25,13.5A0.5,0.5 0,0 1,12.75 14L11.75,14A0.5,0.5 0,0 1,11.25 13.5L11.25,0.5A0.5,0.5 0,0 1,11.75 0z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M1.25,10L2.25,10A0.5,0.5 0,0 1,2.75 10.5L2.75,13.5A0.5,0.5 0,0 1,2.25 14L1.25,14A0.5,0.5 0,0 1,0.75 13.5L0.75,10.5A0.5,0.5 0,0 1,1.25 10z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M4.75,6.5L5.75,6.5A0.5,0.5 0,0 1,6.25 7L6.25,13.5A0.5,0.5 0,0 1,5.75 14L4.75,14A0.5,0.5 0,0 1,4.25 13.5L4.25,7A0.5,0.5 0,0 1,4.75 6.5z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml
new file mode 100644
index 0000000..27433c7
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml
@@ -0,0 +1,25 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="15dp"
+        android:height="14dp"
+        android:viewportWidth="15.0"
+        android:viewportHeight="14.0">
+    <path
+        android:pathData="M7,3.5C7,3.224 7.224,3 7.5,3H8.5C8.776,3 9,3.224 9,3.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V3.5Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M0,10.5C0,10.224 0.224,10 0.5,10H1.5C1.776,10 2,10.224 2,10.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V10.5Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M3.5,7C3.5,6.724 3.724,6.5 4,6.5H5C5.276,6.5 5.5,6.724 5.5,7V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V7Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M11,0C10.724,0 10.5,0.224 10.5,0.5V3H12.5V0.5C12.5,0.224 12.276,0 12,0H11Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M12.25,13C12.25,12.448 12.698,12 13.25,12C13.802,12 14.25,12.448 14.25,13C14.25,13.552 13.802,14 13.25,14C12.698,14 12.25,13.552 12.25,13Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M12.25,5C12.25,4.724 12.474,4.5 12.75,4.5H13.75C14.026,4.5 14.25,4.724 14.25,5V10C14.25,10.276 14.026,10.5 13.75,10.5H12.75C12.474,10.5 12.25,10.276 12.25,10V5Z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar.xml
new file mode 100644
index 0000000..158ae01
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="16dp"
+        android:height="14dp"
+        android:viewportWidth="16.0"
+        android:viewportHeight="14.0">
+    <path
+        android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar_error.xml
new file mode 100644
index 0000000..e0517cf
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar_error.xml
@@ -0,0 +1,29 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="18dp"
+        android:height="14dp"
+        android:viewportWidth="18.0"
+        android:viewportHeight="14.0">
+    <path
+        android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M0,11.5C0,11.224 0.224,11 0.5,11H1.5C1.776,11 2,11.224 2,11.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V11.5Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M3.5,8.5C3.5,8.224 3.724,8 4,8H5C5.276,8 5.5,8.224 5.5,8.5V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V8.5Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M14.5,0C14.224,0 14,0.224 14,0.5V3H16V0.5C16,0.224 15.776,0 15.5,0H14.5Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar.xml
new file mode 100644
index 0000000..1ebd396
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="14dp"
+        android:height="14dp"
+        android:viewportWidth="14.0"
+        android:viewportHeight="14.0">
+    <path
+        android:pathData="M8.25,3L9.25,3A0.5,0.5 0,0 1,9.75 3.5L9.75,13.5A0.5,0.5 0,0 1,9.25 14L8.25,14A0.5,0.5 0,0 1,7.75 13.5L7.75,3.5A0.5,0.5 0,0 1,8.25 3z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M11.75,0L12.75,0A0.5,0.5 0,0 1,13.25 0.5L13.25,13.5A0.5,0.5 0,0 1,12.75 14L11.75,14A0.5,0.5 0,0 1,11.25 13.5L11.25,0.5A0.5,0.5 0,0 1,11.75 0z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M1.25,10L2.25,10A0.5,0.5 0,0 1,2.75 10.5L2.75,13.5A0.5,0.5 0,0 1,2.25 14L1.25,14A0.5,0.5 0,0 1,0.75 13.5L0.75,10.5A0.5,0.5 0,0 1,1.25 10z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M4.75,6.5L5.75,6.5A0.5,0.5 0,0 1,6.25 7L6.25,13.5A0.5,0.5 0,0 1,5.75 14L4.75,14A0.5,0.5 0,0 1,4.25 13.5L4.25,7A0.5,0.5 0,0 1,4.75 6.5z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml
new file mode 100644
index 0000000..4473c29
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml
@@ -0,0 +1,24 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="15dp"
+        android:height="14dp"
+        android:viewportWidth="15.0"
+        android:viewportHeight="14.0">
+    <path
+        android:pathData="M7,3.5C7,3.224 7.224,3 7.5,3H8.5C8.776,3 9,3.224 9,3.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V3.5Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M0,10.5C0,10.224 0.224,10 0.5,10H1.5C1.776,10 2,10.224 2,10.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V10.5Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M3.5,7C3.5,6.724 3.724,6.5 4,6.5H5C5.276,6.5 5.5,6.724 5.5,7V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V7Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M11,0C10.724,0 10.5,0.224 10.5,0.5V3H12.5V0.5C12.5,0.224 12.276,0 12,0H11Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M12.25,13C12.25,12.448 12.698,12 13.25,12C13.802,12 14.25,12.448 14.25,13C14.25,13.552 13.802,14 13.25,14C12.698,14 12.25,13.552 12.25,13Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M12.25,5C12.25,4.724 12.474,4.5 12.75,4.5H13.75C14.026,4.5 14.25,4.724 14.25,5V10C14.25,10.276 14.026,10.5 13.75,10.5H12.75C12.474,10.5 12.25,10.276 12.25,10V5Z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar.xml
new file mode 100644
index 0000000..1ed6ac8
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="16dp"
+        android:height="14dp"
+        android:viewportWidth="16.0"
+        android:viewportHeight="14.0">
+    <path
+        android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar_error.xml
new file mode 100644
index 0000000..703e3ac
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar_error.xml
@@ -0,0 +1,28 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="18dp"
+        android:height="14dp"
+        android:viewportWidth="18.0"
+        android:viewportHeight="14.0">
+    <path
+        android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M0,11.5C0,11.224 0.224,11 0.5,11H1.5C1.776,11 2,11.224 2,11.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V11.5Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M3.5,8.5C3.5,8.224 3.724,8 4,8H5C5.276,8 5.5,8.224 5.5,8.5V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V8.5Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M14.5,0C14.224,0 14,0.224 14,0.5V3H16V0.5C16,0.224 15.776,0 15.5,0H14.5Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar.xml
new file mode 100644
index 0000000..420ffb6
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="16dp"
+        android:height="14dp"
+        android:viewportWidth="16.0"
+        android:viewportHeight="14.0">
+    <path
+        android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar_error.xml
new file mode 100644
index 0000000..e63ca77
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar_error.xml
@@ -0,0 +1,27 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="18dp"
+        android:height="14dp"
+        android:viewportWidth="18.0"
+        android:viewportHeight="14.0">
+    <path
+        android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M0,11.5C0,11.224 0.224,11 0.5,11H1.5C1.776,11 2,11.224 2,11.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V11.5Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M3.5,8.5C3.5,8.224 3.724,8 4,8H5C5.276,8 5.5,8.224 5.5,8.5V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V8.5Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M14.5,0C14.224,0 14,0.224 14,0.5V3H16V0.5C16,0.224 15.776,0 15.5,0H14.5Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_level_list.xml b/packages/SettingsLib/res/drawable/ic_mobile_level_list.xml
new file mode 100644
index 0000000..6ec6793
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_level_list.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<!-- In order to pack the 0-4 and 0-5 ranges into a single element, we use a range offset. See
+SignalDrawable.java for usage. -->
+<level-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:minLevel="0" android:maxLevel="0" android:drawable="@drawable/ic_mobile_0_4_bar" />
+    <item android:minLevel="1" android:maxLevel="1" android:drawable="@drawable/ic_mobile_1_4_bar" />
+    <item android:minLevel="2" android:maxLevel="2" android:drawable="@drawable/ic_mobile_2_4_bar" />
+    <item android:minLevel="3" android:maxLevel="3" android:drawable="@drawable/ic_mobile_3_4_bar" />
+    <item android:minLevel="4" android:maxLevel="4" android:drawable="@drawable/ic_mobile_4_4_bar" />
+    <item android:minLevel="10" android:maxLevel="10" android:drawable="@drawable/ic_mobile_0_5_bar" />
+    <item android:minLevel="11" android:maxLevel="11" android:drawable="@drawable/ic_mobile_1_5_bar" />
+    <item android:minLevel="12" android:maxLevel="12" android:drawable="@drawable/ic_mobile_2_5_bar" />
+    <item android:minLevel="13" android:maxLevel="13" android:drawable="@drawable/ic_mobile_3_5_bar" />
+    <item android:minLevel="14" android:maxLevel="14" android:drawable="@drawable/ic_mobile_4_5_bar" />
+    <item android:minLevel="15" android:maxLevel="15" android:drawable="@drawable/ic_mobile_5_5_bar" />
+    
+    <!-- Error states, so we don't have to draw them manually anymore -->
+    <item android:minLevel="20" android:maxLevel="20" android:drawable="@drawable/ic_mobile_0_4_bar_error" />
+    <item android:minLevel="21" android:maxLevel="21" android:drawable="@drawable/ic_mobile_1_4_bar_error" />
+    <item android:minLevel="22" android:maxLevel="22" android:drawable="@drawable/ic_mobile_2_4_bar_error" />
+    <item android:minLevel="23" android:maxLevel="23" android:drawable="@drawable/ic_mobile_3_4_bar_error" />
+    <item android:minLevel="24" android:maxLevel="24" android:drawable="@drawable/ic_mobile_4_4_bar_error" />
+
+    <item android:minLevel="30" android:maxLevel="30" android:drawable="@drawable/ic_mobile_0_5_bar_error" />
+    <item android:minLevel="31" android:maxLevel="31" android:drawable="@drawable/ic_mobile_1_5_bar_error" />
+    <item android:minLevel="32" android:maxLevel="32" android:drawable="@drawable/ic_mobile_2_5_bar_error" />
+    <item android:minLevel="33" android:maxLevel="33" android:drawable="@drawable/ic_mobile_3_5_bar_error" />
+    <item android:minLevel="34" android:maxLevel="34" android:drawable="@drawable/ic_mobile_4_5_bar_error" />
+    <item android:minLevel="35" android:maxLevel="35" android:drawable="@drawable/ic_mobile_5_5_bar_error" />
+</level-list>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_0.xml b/packages/SettingsLib/res/drawable/ic_wifi_0.xml
new file mode 100644
index 0000000..8ff6554
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_0.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="18dp"
+        android:height="13dp"
+        android:viewportWidth="18.0"
+        android:viewportHeight="13.0">
+    <path
+        android:pathData="M0.523,3.314C0.32,3.502 0.32,3.819 0.516,4.015L1.223,4.722C1.418,4.917 1.734,4.916 1.938,4.73C5.936,1.09 12.066,1.09 16.064,4.73C16.268,4.916 16.584,4.917 16.779,4.722L17.486,4.015C17.682,3.819 17.682,3.502 17.479,3.314C12.698,-1.105 5.304,-1.105 0.523,3.314Z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M15.011,6.49C15.207,6.294 15.207,5.976 15.002,5.792C11.592,2.736 6.411,2.736 3,5.792C2.795,5.976 2.795,6.294 2.991,6.49L3.698,7.197C3.893,7.392 4.209,7.39 4.417,7.209C7.042,4.93 10.96,4.93 13.585,7.209C13.793,7.39 14.109,7.392 14.304,7.197L15.011,6.49Z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M5.465,8.964C5.27,8.769 5.269,8.45 5.481,8.273C7.515,6.576 10.487,6.576 12.521,8.273C12.733,8.45 12.732,8.769 12.537,8.964L11.83,9.672C11.634,9.867 11.319,9.863 11.099,9.698C9.859,8.767 8.143,8.767 6.904,9.698C6.683,9.863 6.368,9.867 6.173,9.672L5.465,8.964Z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M10.062,11.439C10.257,11.244 10.259,10.92 10.022,10.779C9.395,10.407 8.608,10.407 7.98,10.779C7.743,10.92 7.745,11.244 7.94,11.439L8.647,12.146C8.843,12.342 9.159,12.342 9.355,12.146L10.062,11.439Z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_0_error.xml b/packages/SettingsLib/res/drawable/ic_wifi_0_error.xml
new file mode 100644
index 0000000..db31b9d
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_0_error.xml
@@ -0,0 +1,28 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="17dp"
+        android:height="13dp"
+        android:viewportWidth="17.0"
+        android:viewportHeight="13.0">
+    <path
+        android:pathData="M0.146,4.015C-0.05,3.819 -0.05,3.502 0.153,3.314C4.002,-0.244 9.545,-0.937 14.055,1.234C13.339,1.449 12.792,2.053 12.66,2.801C8.998,1.281 4.65,1.924 1.568,4.73C1.364,4.916 1.048,4.917 0.853,4.722L0.146,4.015Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M12.63,4.435C9.406,2.836 5.424,3.288 2.63,5.792C2.424,5.976 2.425,6.294 2.621,6.49L3.328,7.197C3.523,7.392 3.839,7.39 4.047,7.209C6.484,5.094 10.033,4.942 12.63,6.753V4.435Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M5.095,8.964C4.9,8.769 4.899,8.45 5.111,8.273C7.145,6.576 10.117,6.576 12.151,8.273C12.363,8.45 12.362,8.769 12.166,8.964L11.459,9.672C11.264,9.867 10.949,9.863 10.728,9.698C9.489,8.767 7.773,8.767 6.533,9.698C6.313,9.863 5.998,9.867 5.802,9.672L5.095,8.964Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M9.652,10.779C9.889,10.92 9.887,11.244 9.692,11.439L8.984,12.146C8.789,12.342 8.473,12.342 8.277,12.146L7.57,11.439C7.375,11.244 7.373,10.92 7.61,10.779C8.237,10.407 9.024,10.407 9.652,10.779Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M14.63,11.15C14.63,10.598 15.078,10.15 15.63,10.15C16.182,10.15 16.63,10.598 16.63,11.15C16.63,11.703 16.182,12.15 15.63,12.15C15.078,12.15 14.63,11.703 14.63,11.15Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M14.63,3.15C14.63,2.874 14.854,2.65 15.13,2.65H16.13C16.406,2.65 16.63,2.874 16.63,3.15V8.15C16.63,8.427 16.406,8.65 16.13,8.65H15.13C14.854,8.65 14.63,8.427 14.63,8.15V3.15Z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_1.xml b/packages/SettingsLib/res/drawable/ic_wifi_1.xml
new file mode 100644
index 0000000..e170f1d
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_1.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="18dp"
+        android:height="13dp"
+        android:viewportWidth="18.0"
+        android:viewportHeight="13.0">
+    <path
+        android:pathData="M0.523,3.314C0.32,3.502 0.32,3.819 0.516,4.015L1.223,4.722C1.418,4.917 1.734,4.916 1.938,4.73C5.936,1.09 12.066,1.09 16.064,4.73C16.268,4.916 16.584,4.917 16.779,4.722L17.486,4.015C17.682,3.819 17.682,3.502 17.479,3.314C12.698,-1.105 5.304,-1.105 0.523,3.314Z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M15.011,6.49C15.207,6.294 15.207,5.976 15.002,5.792C11.592,2.736 6.411,2.736 3,5.792C2.795,5.976 2.795,6.294 2.991,6.49L3.698,7.197C3.893,7.392 4.209,7.39 4.417,7.209C7.042,4.93 10.96,4.93 13.585,7.209C13.793,7.39 14.109,7.392 14.304,7.197L15.011,6.49Z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M5.465,8.964C5.27,8.769 5.269,8.45 5.481,8.273C7.515,6.576 10.487,6.576 12.521,8.273C12.733,8.45 12.732,8.769 12.537,8.964L11.83,9.672C11.634,9.867 11.319,9.863 11.099,9.698C9.859,8.767 8.143,8.767 6.904,9.698C6.683,9.863 6.368,9.867 6.173,9.672L5.465,8.964Z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M10.062,11.439C10.257,11.244 10.259,10.92 10.022,10.779C9.395,10.407 8.608,10.407 7.98,10.779C7.743,10.92 7.745,11.244 7.94,11.439L8.647,12.146C8.843,12.342 9.159,12.342 9.355,12.146L10.062,11.439Z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_1_error.xml b/packages/SettingsLib/res/drawable/ic_wifi_1_error.xml
new file mode 100644
index 0000000..a4d6a5c
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_1_error.xml
@@ -0,0 +1,27 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="17dp"
+        android:height="13dp"
+        android:viewportWidth="17.0"
+        android:viewportHeight="13.0">
+    <path
+        android:pathData="M0.146,4.015C-0.05,3.819 -0.05,3.502 0.153,3.314C4.002,-0.244 9.545,-0.937 14.055,1.234C13.339,1.449 12.792,2.053 12.66,2.801C8.998,1.281 4.65,1.924 1.568,4.73C1.364,4.916 1.048,4.917 0.853,4.722L0.146,4.015Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M12.63,4.435C9.406,2.836 5.424,3.288 2.63,5.792C2.424,5.976 2.425,6.294 2.621,6.49L3.328,7.197C3.523,7.392 3.839,7.39 4.047,7.209C6.484,5.094 10.033,4.942 12.63,6.753V4.435Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M5.095,8.964C4.9,8.769 4.899,8.45 5.111,8.273C7.145,6.576 10.117,6.576 12.151,8.273C12.363,8.45 12.362,8.769 12.166,8.964L11.459,9.672C11.264,9.867 10.949,9.863 10.728,9.698C9.489,8.767 7.773,8.767 6.533,9.698C6.313,9.863 5.998,9.867 5.802,9.672L5.095,8.964Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M9.652,10.779C9.889,10.92 9.887,11.244 9.692,11.439L8.984,12.146C8.789,12.342 8.473,12.342 8.277,12.146L7.57,11.439C7.375,11.244 7.373,10.92 7.61,10.779C8.237,10.407 9.024,10.407 9.652,10.779Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M14.63,11.15C14.63,10.598 15.078,10.15 15.63,10.15C16.182,10.15 16.63,10.598 16.63,11.15C16.63,11.703 16.182,12.15 15.63,12.15C15.078,12.15 14.63,11.703 14.63,11.15Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M14.63,3.15C14.63,2.874 14.854,2.65 15.13,2.65H16.13C16.406,2.65 16.63,2.874 16.63,3.15V8.15C16.63,8.427 16.406,8.65 16.13,8.65H15.13C14.854,8.65 14.63,8.427 14.63,8.15V3.15Z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_2.xml b/packages/SettingsLib/res/drawable/ic_wifi_2.xml
new file mode 100644
index 0000000..fc62267
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_2.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="18dp"
+        android:height="13dp"
+        android:viewportWidth="18.0"
+        android:viewportHeight="13.0">
+    <path
+        android:pathData="M0.523,3.314C0.32,3.502 0.32,3.819 0.516,4.015L1.223,4.722C1.418,4.917 1.734,4.916 1.938,4.73C5.936,1.09 12.066,1.09 16.064,4.73C16.268,4.916 16.584,4.917 16.779,4.722L17.486,4.015C17.682,3.819 17.682,3.502 17.479,3.314C12.698,-1.105 5.304,-1.105 0.523,3.314Z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M15.011,6.49C15.207,6.294 15.207,5.976 15.002,5.792C11.592,2.736 6.411,2.736 3,5.792C2.795,5.976 2.795,6.294 2.991,6.49L3.698,7.197C3.893,7.392 4.209,7.39 4.417,7.209C7.042,4.93 10.96,4.93 13.585,7.209C13.793,7.39 14.109,7.392 14.304,7.197L15.011,6.49Z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M5.465,8.964C5.27,8.769 5.269,8.45 5.481,8.273C7.515,6.576 10.487,6.576 12.521,8.273C12.733,8.45 12.732,8.769 12.537,8.964L11.83,9.672C11.634,9.867 11.319,9.863 11.099,9.698C9.859,8.767 8.143,8.767 6.904,9.698C6.683,9.863 6.368,9.867 6.173,9.672L5.465,8.964Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M10.062,11.439C10.257,11.244 10.259,10.92 10.022,10.779C9.395,10.407 8.608,10.407 7.98,10.779C7.743,10.92 7.745,11.244 7.94,11.439L8.647,12.146C8.843,12.342 9.159,12.342 9.355,12.146L10.062,11.439Z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_2_error.xml b/packages/SettingsLib/res/drawable/ic_wifi_2_error.xml
new file mode 100644
index 0000000..65f40ef
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_2_error.xml
@@ -0,0 +1,26 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="17dp"
+        android:height="13dp"
+        android:viewportWidth="17.0"
+        android:viewportHeight="13.0">
+    <path
+        android:pathData="M0.146,4.015C-0.05,3.819 -0.05,3.502 0.153,3.314C4.002,-0.244 9.545,-0.937 14.055,1.234C13.339,1.449 12.792,2.053 12.66,2.801C8.998,1.281 4.65,1.924 1.568,4.73C1.364,4.916 1.048,4.917 0.853,4.722L0.146,4.015Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M12.63,4.435C9.406,2.836 5.424,3.288 2.63,5.792C2.424,5.976 2.425,6.294 2.621,6.49L3.328,7.197C3.523,7.392 3.839,7.39 4.047,7.209C6.484,5.094 10.033,4.942 12.63,6.753V4.435Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M5.095,8.964C4.9,8.769 4.899,8.45 5.111,8.273C7.145,6.576 10.117,6.576 12.151,8.273C12.363,8.45 12.362,8.769 12.166,8.964L11.459,9.672C11.264,9.867 10.949,9.863 10.728,9.698C9.489,8.767 7.773,8.767 6.533,9.698C6.313,9.863 5.998,9.867 5.802,9.672L5.095,8.964Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M9.652,10.779C9.889,10.92 9.887,11.244 9.692,11.439L8.984,12.146C8.789,12.342 8.473,12.342 8.277,12.146L7.57,11.439C7.375,11.244 7.373,10.92 7.61,10.779C8.237,10.407 9.024,10.407 9.652,10.779Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M14.63,11.15C14.63,10.598 15.078,10.15 15.63,10.15C16.182,10.15 16.63,10.598 16.63,11.15C16.63,11.703 16.182,12.15 15.63,12.15C15.078,12.15 14.63,11.703 14.63,11.15Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M14.63,3.15C14.63,2.874 14.854,2.65 15.13,2.65H16.13C16.406,2.65 16.63,2.874 16.63,3.15V8.15C16.63,8.427 16.406,8.65 16.13,8.65H15.13C14.854,8.65 14.63,8.427 14.63,8.15V3.15Z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_3.xml b/packages/SettingsLib/res/drawable/ic_wifi_3.xml
new file mode 100644
index 0000000..9079daf
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_3.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="18dp"
+        android:height="13dp"
+        android:viewportWidth="18.0"
+        android:viewportHeight="13.0">
+    <path
+        android:pathData="M0.523,3.314C0.32,3.502 0.32,3.819 0.516,4.015L1.223,4.722C1.418,4.917 1.734,4.916 1.938,4.73C5.936,1.09 12.066,1.09 16.064,4.73C16.268,4.916 16.584,4.917 16.779,4.722L17.486,4.015C17.682,3.819 17.682,3.502 17.479,3.314C12.698,-1.105 5.304,-1.105 0.523,3.314Z"
+        android:fillAlpha="0.24"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M15.011,6.49C15.207,6.294 15.207,5.976 15.002,5.792C11.592,2.736 6.411,2.736 3,5.792C2.795,5.976 2.795,6.294 2.991,6.49L3.698,7.197C3.893,7.392 4.209,7.39 4.417,7.209C7.042,4.93 10.96,4.93 13.585,7.209C13.793,7.39 14.109,7.392 14.304,7.197L15.011,6.49Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M5.465,8.964C5.27,8.769 5.269,8.45 5.481,8.273C7.515,6.576 10.487,6.576 12.521,8.273C12.733,8.45 12.732,8.769 12.537,8.964L11.83,9.672C11.634,9.867 11.319,9.863 11.099,9.698C9.859,8.767 8.143,8.767 6.904,9.698C6.683,9.863 6.368,9.867 6.173,9.672L5.465,8.964Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M10.062,11.439C10.257,11.244 10.259,10.92 10.022,10.779C9.395,10.407 8.608,10.407 7.98,10.779C7.743,10.92 7.745,11.244 7.94,11.439L8.647,12.146C8.843,12.342 9.159,12.342 9.355,12.146L10.062,11.439Z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_3_error.xml b/packages/SettingsLib/res/drawable/ic_wifi_3_error.xml
new file mode 100644
index 0000000..940781b
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_3_error.xml
@@ -0,0 +1,25 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="17dp"
+        android:height="13dp"
+        android:viewportWidth="17.0"
+        android:viewportHeight="13.0">
+    <path
+        android:pathData="M0.146,4.015C-0.05,3.819 -0.05,3.502 0.153,3.314C4.002,-0.244 9.545,-0.937 14.055,1.234C13.339,1.449 12.792,2.053 12.66,2.801C8.998,1.281 4.65,1.924 1.568,4.73C1.364,4.916 1.048,4.917 0.853,4.722L0.146,4.015Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M12.63,4.435C9.406,2.836 5.424,3.288 2.63,5.792C2.424,5.976 2.425,6.294 2.621,6.49L3.328,7.197C3.523,7.392 3.839,7.39 4.047,7.209C6.484,5.094 10.033,4.942 12.63,6.753V4.435Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M5.095,8.964C4.9,8.769 4.899,8.45 5.111,8.273C7.145,6.576 10.117,6.576 12.151,8.273C12.363,8.45 12.362,8.769 12.166,8.964L11.459,9.672C11.264,9.867 10.949,9.863 10.728,9.698C9.489,8.767 7.773,8.767 6.533,9.698C6.313,9.863 5.998,9.867 5.802,9.672L5.095,8.964Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M9.652,10.779C9.889,10.92 9.887,11.244 9.692,11.439L8.984,12.146C8.789,12.342 8.473,12.342 8.277,12.146L7.57,11.439C7.375,11.244 7.373,10.92 7.61,10.779C8.237,10.407 9.024,10.407 9.652,10.779Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M14.63,11.15C14.63,10.598 15.078,10.15 15.63,10.15C16.182,10.15 16.63,10.598 16.63,11.15C16.63,11.703 16.182,12.15 15.63,12.15C15.078,12.15 14.63,11.703 14.63,11.15Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M14.63,3.15C14.63,2.874 14.854,2.65 15.13,2.65H16.13C16.406,2.65 16.63,2.874 16.63,3.15V8.15C16.63,8.427 16.406,8.65 16.13,8.65H15.13C14.854,8.65 14.63,8.427 14.63,8.15V3.15Z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_4.xml b/packages/SettingsLib/res/drawable/ic_wifi_4.xml
new file mode 100644
index 0000000..6185e4a
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_4.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="18dp"
+        android:height="13dp"
+        android:viewportWidth="18.0"
+        android:viewportHeight="13.0">
+    <path
+        android:pathData="M0.523,3.314C0.32,3.502 0.32,3.819 0.516,4.015L1.223,4.722C1.418,4.917 1.734,4.916 1.938,4.73C5.936,1.09 12.066,1.09 16.064,4.73C16.268,4.916 16.584,4.917 16.779,4.722L17.486,4.015C17.682,3.819 17.682,3.502 17.479,3.314C12.698,-1.105 5.304,-1.105 0.523,3.314Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M15.011,6.49C15.207,6.294 15.207,5.976 15.002,5.792C11.592,2.736 6.411,2.736 3,5.792C2.795,5.976 2.795,6.294 2.991,6.49L3.698,7.197C3.893,7.392 4.209,7.39 4.417,7.209C7.042,4.93 10.96,4.93 13.585,7.209C13.793,7.39 14.109,7.392 14.304,7.197L15.011,6.49Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M5.465,8.964C5.27,8.769 5.269,8.45 5.481,8.273C7.515,6.576 10.487,6.576 12.521,8.273C12.733,8.45 12.732,8.769 12.537,8.964L11.83,9.672C11.634,9.867 11.319,9.863 11.099,9.698C9.859,8.767 8.143,8.767 6.904,9.698C6.683,9.863 6.368,9.867 6.173,9.672L5.465,8.964Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M10.062,11.439C10.257,11.244 10.259,10.92 10.022,10.779C9.395,10.407 8.608,10.407 7.98,10.779C7.743,10.92 7.745,11.244 7.94,11.439L8.647,12.146C8.843,12.342 9.159,12.342 9.355,12.146L10.062,11.439Z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_4_error.xml b/packages/SettingsLib/res/drawable/ic_wifi_4_error.xml
new file mode 100644
index 0000000..715aaa0
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_4_error.xml
@@ -0,0 +1,24 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="17dp"
+        android:height="13dp"
+        android:viewportWidth="17.0"
+        android:viewportHeight="13.0">
+    <path
+        android:pathData="M0.146,4.015C-0.05,3.819 -0.05,3.502 0.153,3.314C4.002,-0.244 9.545,-0.937 14.055,1.234C13.339,1.449 12.792,2.053 12.66,2.801C8.998,1.281 4.65,1.924 1.568,4.73C1.364,4.916 1.048,4.917 0.853,4.722L0.146,4.015Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M12.63,4.435C9.406,2.836 5.424,3.288 2.63,5.792C2.424,5.976 2.425,6.294 2.621,6.49L3.328,7.197C3.523,7.392 3.839,7.39 4.047,7.209C6.484,5.094 10.033,4.942 12.63,6.753V4.435Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M5.095,8.964C4.9,8.769 4.899,8.45 5.111,8.273C7.145,6.576 10.117,6.576 12.151,8.273C12.363,8.45 12.362,8.769 12.166,8.964L11.459,9.672C11.264,9.867 10.949,9.863 10.728,9.698C9.489,8.767 7.773,8.767 6.533,9.698C6.313,9.863 5.998,9.867 5.802,9.672L5.095,8.964Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M9.652,10.779C9.889,10.92 9.887,11.244 9.692,11.439L8.984,12.146C8.789,12.342 8.473,12.342 8.277,12.146L7.57,11.439C7.375,11.244 7.373,10.92 7.61,10.779C8.237,10.407 9.024,10.407 9.652,10.779Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M14.63,11.15C14.63,10.598 15.078,10.15 15.63,10.15C16.182,10.15 16.63,10.598 16.63,11.15C16.63,11.703 16.182,12.15 15.63,12.15C15.078,12.15 14.63,11.703 14.63,11.15Z"
+        android:fillColor="#000"/>
+    <path
+        android:pathData="M14.63,3.15C14.63,2.874 14.854,2.65 15.13,2.65H16.13C16.406,2.65 16.63,2.874 16.63,3.15V8.15C16.63,8.427 16.406,8.65 16.13,8.65H15.13C14.854,8.65 14.63,8.427 14.63,8.15V3.15Z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 8d3582a..cc23f6e 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Geneutraliseer deur <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Laaiproses is onderbreek om battery te beskerm"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Kontroleer tans laaibykomstigheid"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Gaan die laaibykomstigheid na"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g> oor"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g> oor (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g> oor gegrond op jou gebruik"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> oor tot vol"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> oor tot vol"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – Laaiproses word geoptimeer"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Laai tans"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Onbekend"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Laai"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Laai tans vinnig"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index f6931ad..0ecd376 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"በ<xliff:g id="TITLE">%1$s</xliff:g> ተሽሯል"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ባትሪን ለመጠበቅ ኃይል መሙላት በይቆይ ላይ"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - የኃይል መሙያ መለዋወጫዎችን በመፈተሽ ላይ"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - የኃይል መሙላት መለዋወጫን ይፈትሹ"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ገደማ ቀርቷል"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>) ገደማ ቀርቷል"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"በአጠቃቀምዎ መሠረት <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ገደማ ቀርቷል"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"እስኪሞላ ድረስ <xliff:g id="TIME">%1$s</xliff:g> ይቀራል"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - እስኪሞላ ድረስ <xliff:g id="TIME">%2$s</xliff:g> ይቀራል"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - ኃይል መሙላት እንዲተባ ተደርጓል"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - ኃይል በመሙላት ላይ"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ያልታወቀ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ኃይል በመሙላት ላይ"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ኃይል በፍጥነት በመሙላት ላይ"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 92dc69c..1f313ae 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"تم الاستبدال بـ <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"‫<xliff:g id="LEVEL">%1$s</xliff:g> - الشحن معلَّق لحماية البطارية"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"‫<xliff:g id="LEVEL">%1$s</xliff:g> - يجب فحص ملحق الشحن"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - فحص ملحق الشحن"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"يتبقى <xliff:g id="TIME_REMAINING">%1$s</xliff:g> تقريبًا."</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"يتبقى <xliff:g id="TIME_REMAINING">%1$s</xliff:g> تقريبًا (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"يتبقى <xliff:g id="TIME_REMAINING">%1$s</xliff:g> تقريبًا، بناءً على استخدامك"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"يتبقّى <xliff:g id="TIME">%1$s</xliff:g> حتى اكتمال شحن البطارية."</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - يتبقّى <xliff:g id="TIME">%2$s</xliff:g> حتى اكتمال شحن البطارية."</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - تم تحسين الشحن"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"‫<xliff:g id="LEVEL">%1$s</xliff:g>: جارٍ الشحن"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"غير معروف"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"جارٍ الشحن"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"جارٍ الشحن سريعًا"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index 48d3df4..a4be8e9 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g>ৰ দ্বাৰা অগ্ৰাহ্য কৰা হৈছে"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - বেটাৰী সুৰক্ষিত কৰিবলৈ চাৰ্জিং স্থগিত ৰখা হৈছে"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - চাৰ্জিঙৰ আনুষংগিক বস্তু পৰীক্ষা কৰি থকা হৈছে"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - চাৰ্জিঙৰ সৈতে জড়িত আনুষংগিক সামগ্ৰী পৰীক্ষা কৰক"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"প্রায় <xliff:g id="TIME_REMAINING">%1$s</xliff:g> বাকী আছে"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"প্রায় <xliff:g id="TIME_REMAINING">%1$s</xliff:g> বাকী আছে (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"আপোনাৰ ব্যৱহাৰৰ ওপৰত ভিত্তি কৰি প্ৰায় <xliff:g id="TIME_REMAINING">%1$s</xliff:g> বাকী আছে"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"সম্পূৰ্ণ হ’বলৈ <xliff:g id="TIME">%1$s</xliff:g> বাকী আছে"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"সম্পূৰ্ণ হ’বলৈ <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> বাকী আছে"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - চাৰ্জিং অপ্টিমাইজ কৰা হৈছে"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ চাৰ্জ হৈ আছে"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"অজ্ঞাত"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"চাৰ্জ কৰি থকা হৈছে"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"দ্ৰুততাৰে চাৰ্জ হৈছে"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index e27a40c..df6dad2 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> tərəfindən qəbul edilmir"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Batareyanı qorumaq üçün şarj gözlədilir"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Şarj aksesuarı yoxlanır"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Şarj aksesuarını yoxlayın"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Təxminən <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qalıb"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Təxminən <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qalıb (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"İstifadəyə əsasən təxminən <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qalıb"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Tam şarj edilənədək <xliff:g id="TIME">%1$s</xliff:g> qalıb"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - tam şarj edilənədək <xliff:g id="TIME">%2$s</xliff:g> qalıb"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Şarj optimallaşdırılıb"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - Şarj edilir"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Naməlum"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Enerji doldurma"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Sürətlə doldurulur"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index dd48b70..8bb8c83 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Zamenjuje ga <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>–<xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje je na čekanju da bi se zaštitila baterija"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – provera dodatne opreme za punjenje"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Proverite dodatnu opremu za punjenje"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Preostalo je oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Preostalo je oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Preostalo je oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g> na osnovu korišćenja"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do kraja punjenja"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do kraja punjenja"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje je optimizovano"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Punjenje"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Puni se"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Brzo se puni"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 48247a0..433c243 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Перавызначаны <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарадка прыпынена, каб абараніць акумулятар"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – правяраецца зарадная прылада"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g>, праверце зарадную прыладу"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Зараду хопіць прыблізна на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Зараду (<xliff:g id="LEVEL">%2$s</xliff:g>) хопіць прыблізна на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Зараду пры такім выкарыстанні хопіць прыблізна на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Да поўнай зарадкі засталося <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – да поўнай зарадкі засталося: <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – Зарадка аптымізавана"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – зараджаецца"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Невядома"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарадка"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Хуткая зарадка"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 035b1dd..27e27be 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Заменено от „<xliff:g id="TITLE">%1$s</xliff:g>“"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Зареждането е поставено на пауза с цел запазване на батерията"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Аксесоарът за зареждане се проверява"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Проверете аксесоара за зареждане"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Още около <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Още около <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Още около <xliff:g id="TIME_REMAINING">%1$s</xliff:g> въз основа на използването"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Оставащо време до пълно зареждане: <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – Оставащо време до пълно зареждане: <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – Зареждането е оптимизирано"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарежда се"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Неизвестно"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарежда се"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Зарежда се бързо"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 9a2f88c..2cf0556 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> এর দ্বারা ওভাররাইড করা হয়েছে"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ব্যাটারিকে সুরক্ষিত রাখতে চার্জিং হোল্ড করা হয়েছে"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - চার্জিংয়ের সরঞ্জাম চেক করা হচ্ছে"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - চার্জিং অ্যাক্সেসরি চেক করুন"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"আর আনুমানিক <xliff:g id="TIME_REMAINING">%1$s</xliff:g> চলবে"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"আর আনুমানিক <xliff:g id="TIME_REMAINING">%1$s</xliff:g> চলবে (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"ব্যবহারের উপর ভিত্তি করে আর আনুমানিক <xliff:g id="TIME_REMAINING">%1$s</xliff:g> চলবে"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g>-এ ব্যাটারি পুরো চার্জ হয়ে যাবে"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>-এ ব্যাটারি পুরো চার্জ হয়ে যাবে"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - চার্জিং অপ্টিমাইজ করা হয়েছে"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - চার্জ করা হচ্ছে"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"অজানা"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"চার্জ হচ্ছে"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"দ্রুত চার্জ হচ্ছে"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index 01662dd..64ccf98 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Zamjenjuje <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje je na čekanju radi zaštite baterije"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – provjera opreme za punjenje"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – provjerite opremu za punjenje"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Preostalo je još oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Preostalo je još oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Preostalo je još oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g> na osnovu vaše potrošnje"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do potpune napunjenosti"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do potpune napunjenosti"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje je optimizirano"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Punjenje"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Brzo punjenje"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 61898881..81f9e97a 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"S\'ha substituït per <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>: <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g>: la càrrega s\'ha posat en espera per protegir la bateria"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g>: s\'està comprovant l\'accessori de càrrega"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g>: revisa l\'accessori de càrrega"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Temps restant aproximat: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Temps restant aproximat: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Temps restant aproximat segons l\'ús que en fas: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> per completar la càrrega"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> per completar la càrrega"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g>: càrrega optimitzada"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g>: s\'està carregant"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconegut"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"S\'està carregant"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregant ràpidament"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index ec104b4..cb8c36b 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Přepsáno nastavením <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Nabíjení je pozastaveno za účelem ochrany baterie"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Kontrola nabíjecího příslušenství"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Zkontrolujte nabíjecí příslušenství"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Zbývá asi <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Zbývá asi <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Při vašem obvyklém využití zbývá asi <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do úplného nabití"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do úplného nabití"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – optimalizované nabíjení"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Nabíjení"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznámé"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Nabíjí se"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Rychlé nabíjení"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index b92fc04..625f05b 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Tilsidesat af <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Opladningen er sat på pause for at beskytte batteriet"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Tjekker opladningstilbehøret"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Tjek opladningstilbehøret"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ca. <xliff:g id="TIME_REMAINING">%1$s</xliff:g> tilbage"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Ca. <xliff:g id="TIME_REMAINING">%1$s</xliff:g> tilbage (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Ca. <xliff:g id="TIME_REMAINING">%1$s</xliff:g> tilbage, alt efter hvordan du bruger enheden"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Fuldt opladet om <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – fuldt opladet om <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – opladning er optimeret"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – oplades"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Ukendt"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Oplader"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Oplader hurtigt"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index debdb1e..f622f61 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Außer Kraft gesetzt von \"<xliff:g id="TITLE">%1$s</xliff:g>\""</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ladevorgang zum Schutz des Akkus angehalten"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ladezubehör wird geprüft"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ladezubehör prüfen"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Noch etwa <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Noch etwa <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Bei deinem Nutzungsmuster hast du noch ca. <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Voll in <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – voll in <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – Laden wird optimiert"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Wird geladen"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Unbekannt"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Wird aufgeladen"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Schnelles Aufladen"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 6cf0559..861f472 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Αντικαταστάθηκε από <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Η φόρτιση τέθηκε σε αναμονή για προστασία της μπαταρίας"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Έλεγχος αξεσουάρ φόρτισης"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Έλεγχος αξεσουάρ φόρτισης"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Απομένει/ουν περίπου <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Απομένει/ουν περίπου <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Απομένει/ουν περίπου <xliff:g id="TIME_REMAINING">%1$s</xliff:g>, βάσει της χρήσης σας"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Απομένουν <xliff:g id="TIME">%1$s</xliff:g> για πλήρη φόρτιση"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - Απομένουν <xliff:g id="TIME">%2$s</xliff:g> για πλήρη φόρτιση"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Η φόρτιση βελτιστοποιήθηκε"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ Φόρτιση"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Άγνωστο"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Φόρτιση"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ταχεία φόρτιση"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index b2c5718..e6923de 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Charging on hold to protect battery"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Checking charging accessory"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – check charging accessory"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left based on your usage"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging optimised"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – charging"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 21fff5e..36dfd2f 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging on hold to protect battery"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Checking charging accessory"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Check charging accessory"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left based on your usage"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index b2c5718..e6923de 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Charging on hold to protect battery"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Checking charging accessory"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – check charging accessory"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left based on your usage"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging optimised"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – charging"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index b2c5718..e6923de 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Charging on hold to protect battery"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Checking charging accessory"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – check charging accessory"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left based on your usage"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging optimised"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – charging"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index 25e2779..8b05e12 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‏‏‎‏‎‎‏‎‏‎‏‎‏‎‏‎‏‎‏‏‎‏‎‎‎‎‏‎‏‏‎‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎Overridden by ‎‏‎‎‏‏‎<xliff:g id="TITLE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‏‎‏‏‏‎‎‏‏‎‎‎‎‏‏‎‎‏‏‏‎‎‎‏‏‏‎‎‏‏‎‎‏‎‎‏‏‎‎‏‏‎‏‎‎‎‎‏‏‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ - ‎‏‎‎‏‏‎<xliff:g id="TIME_STRING">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‎‏‎‎‎‎‎‎‏‎‎‎‏‎‎‏‏‏‎‏‎‏‎‎‎‏‎‎‏‎‎‎‎‏‏‎‎‎‎‏‎‎‏‏‏‏‎‎‎‎‎‎‎‏‎‎‏‏‎<xliff:g id="LEVEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎ - Charging on hold to protect battery‎‏‎‎‏‎"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‎‎‎‏‎‏‎‏‎‏‎‎‎‏‎‎‏‏‎‏‎‎‎‎‎‎‏‎‏‏‏‎‏‏‏‎‏‎‎‎‎‏‎‏‏‏‎‏‏‏‏‎‎‎‎‏‎‎‏‏‎<xliff:g id="LEVEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎ - Checking charging accessory‎‏‎‎‏‎"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‏‎‎‏‎‏‏‎‎‎‏‏‎‏‏‏‏‎‎‎‏‎‏‎‏‎‏‏‎‏‎‎‏‎‏‎‎‎‎‏‎‎‎‏‏‎‏‎‏‏‎‏‏‎‎‎‎‎‎‎‏‎‎‏‏‎<xliff:g id="LEVEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎ - Check charging accessory‎‏‎‎‏‎"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‎‎‎‏‎‏‎‏‎‏‎‏‎‏‏‎‎‏‎‏‏‎‎‏‎‏‏‎‏‏‎‎‏‎‏‏‎‏‏‏‎‏‏‎‏‎‎‏‏‏‎About ‎‏‎‎‏‏‎<xliff:g id="TIME_REMAINING">%1$s</xliff:g>‎‏‎‎‏‏‏‎ left‎‏‎‎‏‎"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‎‏‏‏‏‎‎‎‎‏‎‏‏‎‏‏‎‏‏‎‎‎‎‏‎‎‎‎‎‏‎‏‏‎‎‎‎‎‎‎‎‏‎‏‎‏‏‏‎‎‏‏‎‎‏‎‎‎About ‎‏‎‎‏‏‎<xliff:g id="TIME_REMAINING">%1$s</xliff:g>‎‏‎‎‏‏‏‎ left (‎‏‎‎‏‏‎<xliff:g id="LEVEL">%2$s</xliff:g>‎‏‎‎‏‏‏‎)‎‏‎‎‏‎"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‏‎‎‎‏‎‏‎‎‏‎‏‏‎‎‏‏‏‎‎‏‏‏‎‏‏‎‏‎‏‏‎‏‏‎‏‎‎‎‏‏‎‎‎‏‎‎‎‎‏‎‎‎‎‎‏‎‎About ‎‏‎‎‏‏‎<xliff:g id="TIME_REMAINING">%1$s</xliff:g>‎‏‎‎‏‏‏‎ left based on your usage‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index d0ba34c..a6fdf56 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Reemplazado por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Se detuvo la carga para proteger la batería"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Verificando el accesorio de carga"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Verifica el accesorio de carga"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tiempo restante: aproximadamente <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Tiempo restante: aproximadamente <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Tiempo restante: aproximadamente <xliff:g id="TIME_REMAINING">%1$s</xliff:g> en función de tu uso"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> para completar"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> para completar"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carga optimizada"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - Cargando"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconocido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Cargando rápidamente"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 77049e0..d57a33a 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Anulado por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>: <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carga pausada para proteger la batería"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Comprobando accesorio de carga"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Comprueba el accesorio de carga"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tiempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Tiempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Tiempo restante aproximado según tu uso: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> hasta la carga completa"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> hasta la carga completa"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carga optimizada"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ Cargar"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconocido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carga rápida"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index 6a3c208..dfe78d8 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Alistas <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – laadimine on aku kaitsmiseks ootele pandud"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – laadimistarviku kontrollimine"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – kontrollige laadimistarvikut"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ligikaudu <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäänud"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Ligikaudu <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäänud (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Teie kasutuse põhjal on jäänud ligikaudu <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Täislaadimiseks kulub <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – täislaadimiseks kulub <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – laadimine on optimeeritud"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – laadimine"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Tundmatu"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Laadimine"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Kiirlaadimine"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 64e372a..3c6309d 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> hobespena gainjarri zaio"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g>: kargatze-prozesua zain dago bateria babesteko"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g>: kargatzeko osagarria egiaztatzen"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Eman begiratu bat kargatzeko osagarriari"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> inguru gelditzen dira"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> inguru gelditzen dira (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Erabilera kontuan izanda, <xliff:g id="TIME_REMAINING">%1$s</xliff:g> inguru gelditzen dira"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> guztiz kargatu arte"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> guztiz kargatu arte"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Kargatzeko modu optimizatua"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ Kargatzen"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Ezezaguna"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Kargatzen"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Bizkor kargatzen"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 01d4618..2cb1873 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"توسط <xliff:g id="TITLE">%1$s</xliff:g> لغو شد"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - برای محافظت از باتری، شارژ موقتاً متوقف شده است"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - بررسی لوازم شارژ"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - لوازم شارژ را بررسی کنید"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> شارژ باقی مانده است"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> شارژ باقی مانده است (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"براساس مصرفتان، تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> شارژ باقی مانده است"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> تا شارژ کامل باقی مانده است"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> تا شارژ کامل باقی مانده است"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - شارژ بهینه شده است"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - درحال شارژ"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ناشناس"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"در حال شارژ شدن"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"درحال شارژ شدن سریع"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 529bed5..29459ca 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Tämän ohittaa <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Lataus on keskeytetty akun suojaamiseksi"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Tarkistetaan latauslisävarustetta"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Tarkista latauslisävaruste"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Noin <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäljellä"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Noin <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäljellä (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Noin <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäljellä käyttösi perusteella"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> kunnes täynnä"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> kunnes täynnä"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – Lataus optimoitu"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ladataan"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Tuntematon"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Ladataan"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Nopea lataus"</string>
diff --git a/packages/SettingsLib/res/values-fr-feminine/strings.xml b/packages/SettingsLib/res/values-fr-feminine/strings.xml
new file mode 100644
index 0000000..377c42c
--- /dev/null
+++ b/packages/SettingsLib/res/values-fr-feminine/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"L\'association vous permet d\'accéder à vos contacts et à l\'historique des appels lorsque vous êtes connectée."</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-fr-masculine/strings.xml b/packages/SettingsLib/res/values-fr-masculine/strings.xml
new file mode 100644
index 0000000..5f7d58a
--- /dev/null
+++ b/packages/SettingsLib/res/values-fr-masculine/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"L\'association vous permet d\'accéder à vos contacts et à l\'historique des appels lorsque vous êtes connecté."</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-fr-neuter/strings.xml b/packages/SettingsLib/res/values-fr-neuter/strings.xml
new file mode 100644
index 0000000..6970587
--- /dev/null
+++ b/packages/SettingsLib/res/values-fr-neuter/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"L\'association vous permet d\'accéder à vos contacts et à l\'historique des appels lorsque vous êtes connecté·e."</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index ea23834..f97b36e 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -257,7 +257,7 @@
     <string name="adb_wireless_verifying_qrcode_text" msgid="6123192424916029207">"Association de l\'appareil en cours…"</string>
     <string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"Échec de l\'association de l\'appareil Soit le code QR est incorrect, soit l\'appareil n\'est pas connecté au même réseau."</string>
     <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"Adresse IP et port"</string>
-    <string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"Numériser le code QR"</string>
+    <string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"Balayer le code QR"</string>
     <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Associer l\'appareil par Wi-Fi en numérisant un code QR"</string>
     <string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Veuillez vous connecter à un réseau Wi-Fi"</string>
     <string name="keywords_adb_wireless" msgid="6507505581882171240">"adb, débogage, développeur"</string>
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Remplacé par <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> : <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – La recharge a été mise en pause pour protéger la pile"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Vérification de l\'accessoire de recharge en cours…"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Vérifier l\'accessoire de recharge"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Il reste environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Il reste environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Il reste environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> en fonction de votre usage"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> jusqu\'à la recharge complète"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> (<xliff:g id="TIME">%2$s</xliff:g> jusqu\'à la recharge complète)"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Recharge optimisée"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Recharge en cours…"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Inconnu"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Charge en cours…"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Recharge rapide"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index dbf107a..7f7ba63 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Remplacé par <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Recharge en pause pour protéger la batterie"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Vérification de l\'accessoire de recharge"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> : vérifiez l\'accessoire de recharge"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Temps restant : environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Temps restant : environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Temps restant en fonction de votre utilisation : environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Chargée à 100 %% dans <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - chargée à 100 %% dans <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Recharge optimisée"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - En charge"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Inconnu"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Batterie en charge"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charge rapide"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 233c9d4..179d946 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Anulado por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g>. A carga púxose en pausa para protexer a batería"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g>. Comprobando accesorio de carga"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g>. Comproba o accesorio de carga"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Tempo restante aproximado (<xliff:g id="LEVEL">%2$s</xliff:g>): <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Tempo restante aproximado en función do uso: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> para completar a carga"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> (<xliff:g id="TIME">%2$s</xliff:g> para completar a carga)"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> (carga optimizada)"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> (cargando)"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Descoñecido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Cargando rapidamente"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 762ed28..cafe86c 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> દ્વારા ઓવરરાઇડ થયું"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - બૅટરીને સુરક્ષિત રાખવા માટે, ચાર્જિંગ હોલ્ડ પર રાખવામાં આવ્યું છે"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - ચાર્જિંગ ઍક્સેસરી ચેક કરી રહ્યાં છીએ"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - ચાર્જિંગ ઍક્સેસરી ચેક કરો"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"લગભગ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> બાકી છે"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"લગભગ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> બાકી છે (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"તમારા વપરાશના આધારે લગભગ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> બાકી છે"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"પૂર્ણ ચાર્જ થવામાં <xliff:g id="TIME">%1$s</xliff:g> બાકી છે"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - પૂર્ણ ચાર્જ થવામાં <xliff:g id="TIME">%2$s</xliff:g> બાકી છે"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - ચાર્જિંગ ઑપ્ટિમાઇઝ કરવામાં આવ્યું છે"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - ચાર્જિંગ"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"અજાણ્યું"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ચાર્જ થઈ રહ્યું છે"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ઝડપથી ચાર્જ થાય છે"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 6a0e3e0..53cf724 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> के द्वारा ओवरराइड किया गया"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - बैटरी को सुरक्षित रखने के लिए, फ़ोन को चार्ज होने से रोक दिया गया है"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्जिंग ऐक्सेसरी की जांच की जा रही है"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्जिंग ऐक्सेसरी की जांच करें"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"बैटरी करीब <xliff:g id="TIME_REMAINING">%1$s</xliff:g> में खत्म हो जाएगी"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"बैटरी करीब <xliff:g id="TIME_REMAINING">%1$s</xliff:g> में खत्म हो जाएगी (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"आपके इस्तेमाल के हिसाब से बैटरी करीब <xliff:g id="TIME_REMAINING">%1$s</xliff:g> में खत्म हो जाएगी"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> में बैटरी पूरी चार्ज हो जाएगी"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> में बैटरी पूरी चार्ज हो जाएगी"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्जिंग को ऑप्टिमाइज़ किया गया"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्ज हो रही है"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज हो रही है"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"तेज़ चार्ज हो रही है"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index f019936..f06baad 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Premošćeno postavkom <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje je pauzirano radi zaštite baterije"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – provjera dodatka za punjenje"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – provjerite dodatak za punjenje"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Još oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Još otprilike <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Još otprilike <xliff:g id="TIME_REMAINING">%1$s</xliff:g> na temelju vaše upotrebe"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do napunjenosti"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do napunjenosti"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje se optimizira"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Punjenje"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Brzo punjenje"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index d3d2960..39d905f 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Felülírva erre: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Az akkumulátor védelme érdekében a töltés szünetel"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Akkumulátortartozék ellenőrzése…"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ellenőrizze a töltőtartozékot"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Körülbelül <xliff:g id="TIME_REMAINING">%1$s</xliff:g> maradt hátra"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Körülbelül <xliff:g id="TIME_REMAINING">%1$s</xliff:g> maradt hátra (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Körülbelül <xliff:g id="TIME_REMAINING">%1$s</xliff:g> maradt hátra az eszköz használata alapján"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> a teljes töltöttségig"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> a teljes töltöttségig"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – Optimalizált töltés"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Töltés…"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Ismeretlen"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Töltés"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Gyorstöltés"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 4c14ae1..55f09e2 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Գերազանցված է <xliff:g id="TITLE">%1$s</xliff:g>-ից"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Լիցքավորումը դադարեցվել է՝ մարտկոցը պաշտպանելու համար"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Լիցքավորման սարքը ստուգվում է"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ստուգեք լիցքավորիչը"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Լիցքը կբավարարի մոտ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Լիցքը (<xliff:g id="LEVEL">%2$s</xliff:g>) կբավարարի մոտ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Լիցքը կբավարարի մոտ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>՝ կախված օգտագործման եղանակից"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> մինչև լրիվ լիցքավորումը"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> մինչև լրիվ լիցքավորումը"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – Լիցքավորումն օպտիմալացված է"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> — Լիցքավորում"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Անհայտ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Լիցքավորում"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Արագ լիցքավորում"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 1379ecf..885729d 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Digantikan oleh <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pengisian daya dihentikan sementara untuk melindungi baterai"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Memeriksa aksesori pengisi daya"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Periksa aksesori pengisian daya"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Sekitar <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Sekitar <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Sekitar <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi berdasarkan penggunaan Anda"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> lagi sampai penuh"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> lagi sampai penuh"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pengisian daya dioptimalkan"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - Mengisi daya"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Tidak diketahui"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Mengisi daya"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Mengisi daya cepat"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index e48d063..020191b 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Hnekkt af <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Hleðsla í bið til að vernda rafhlöðuna"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Athugar hleðslutæki"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Athugaðu hleðslubúnaðinn"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Um það bil <xliff:g id="TIME_REMAINING">%1$s</xliff:g> eftir"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Um það bil <xliff:g id="TIME_REMAINING">%1$s</xliff:g> eftir (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Um það bil <xliff:g id="TIME_REMAINING">%1$s</xliff:g> eftir miðað við notkun þína"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> fram að fullri hleðslu"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> fram að fullri hleðslu"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – Hleðsla fínstillt"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ Í hleðslu"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Óþekkt"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Í hleðslu"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Hröð hleðsla"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index b335514..37a2067 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Valore sostituito da <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ricarica in sospeso per proteggere la batteria"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Controllo dell\'accessorio di ricarica"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Controlla l\'accessorio di ricarica"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo rimanente: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> circa"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Tempo rimanente: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> circa (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Tempo rimanente in base al tuo utilizzo: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> circa"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> alla ricarica completa"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> alla ricarica completa"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ricarica ottimizzata"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ In carica"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Sconosciuta"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"In carica"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ricarica veloce"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index a559cc6..3353374 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"נעקף על ידי <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – הטעינה הושהתה כדי להגן על הסוללה"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – מתבצעת בדיקה של אביזר הטעינה"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"‫<xliff:g id="LEVEL">%1$s</xliff:g> – צריך לבדוק את אביזר הטעינה"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"הזמן הנותר: בערך <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"הזמן הנותר: בערך <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"הזמן הנותר על סמך השימוש שלך: בערך <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"הזמן הנותר לטעינה מלאה: <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – הזמן הנותר לטעינה מלאה: <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – הטעינה עברה אופטימיזציה"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"‫<xliff:g id="LEVEL">%1$s</xliff:g> – בטעינה"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"לא ידוע"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"בטעינה"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"הסוללה נטענת מהר"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index a7be818..d87d0f8 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g>によって上書き済み"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - バッテリーを保護するため、充電を一時停止しています"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充電用アクセサリを確認しています"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充電用アクセサリを確認してください"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"残り時間: 約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"残り時間: 約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>(<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"残り時間: 約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>(使用状況に基づく)"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"完了まであと <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - 完了まであと <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充電が最適化されています"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充電中"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"不明"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"急速充電中"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index eb06df3..7812f0b 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"უკუგებულია <xliff:g id="TITLE">%1$s</xliff:g>-ის მიერ"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – დატენვა შეჩერებულია ბატარეის დასაცავად"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – მიმდინარეობს დამტენი აქსესუარის შემოწმება"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g>: მიმდინარეობს დამტენი აქსესუარის შემოწმება"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"დარჩა დაახლოებით <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"დარჩა დაახლოებით <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"დარჩა დაახლოებით <xliff:g id="TIME_REMAINING">%1$s</xliff:g>, ბატარეის მოხმარების გათვალისწინებით"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"სრულ დატენვამდე დარჩენილია <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> — სრულ დატენვამდე დარჩენილია <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - დატენვა ოპტიმიზირებულია"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – იტენება"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"უცნობი"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"იტენება"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"სწრაფად იტენება"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 86da0a4..300d4e5 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> үстінен басқан"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g>: батареяны қорғау үшін зарядтау кідіртіледі."</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g>: зарядтау құрылғысы тексеріледі."</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарядтау құрылғысын тексеріңіз"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Шамамен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> қалды"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Шамамен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> қалды (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Пайдалану деректеріңізге сәйкес енді шамамен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> қалды"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Толық зарядталғанға дейін <xliff:g id="TIME">%1$s</xliff:g> қалды."</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: толық зарядталуға <xliff:g id="TIME">%2$s</xliff:g> қалды"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарядтау оңтайландырылды"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Зарядталып жатыр"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Белгісіз"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарядталуда"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Жылдам зарядтау"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 848e904..373cfc6 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"បដិសេធ​ដោយ <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - កំពុងផ្អាកការសាកថ្ម ដើម្បីការពារថ្ម"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - កំពុងពិនិត្យមើលគ្រឿងសាកថ្ម"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - ពិនិត្យមើលគ្រឿងសាកថ្ម"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"នៅសល់​ប្រហែល <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ទៀត"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"នៅសល់​ប្រហែល <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ទៀត (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"នៅសល់​ប្រហែល <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ទៀត ផ្អែក​លើការ​ប្រើប្រាស់​របស់អ្នក"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> ទៀតទើបពេញ"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - នៅសល់ <xliff:g id="TIME">%2$s</xliff:g> ទៀតទើបពេញ"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - បានបង្កើនប្រសិទ្ធភាពនៃការសាក"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - កំពុងសាកថ្ម"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"មិន​ស្គាល់"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"កំពុងសាក​ថ្ម"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"កំពុងសាកថ្មយ៉ាងឆាប់រហ័ស"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index d13019c..bf84cc48 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ಮೂಲಕ ಅತಿಕ್ರಮಿಸುತ್ತದೆ"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಬ್ಯಾಟರಿಯನ್ನು ರಕ್ಷಿಸಲು ಚಾರ್ಜಿಂಗ್ ಅನ್ನು ಹೋಲ್ಡ್‌ ಮಾಡಲಾಗಿದೆ"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಚಾರ್ಜಿಂಗ್ ಪರಿಕರವನ್ನು ಪರಿಶೀಲಿಸಲಾಗುತ್ತಿದೆ"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಚಾರ್ಜಿಂಗ್ ಆ್ಯಕ್ಸೆಸರಿಯನ್ನು ಪರಿಶೀಲಿಸಿ"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ಸಮಯ ಬಾಕಿ ಉಳಿದಿದೆ"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"(<xliff:g id="LEVEL">%2$s</xliff:g>) ತಲುಪಲು <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ಸಮಯ ಬಾಕಿ ಉಳಿದಿದೆ"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"ನಿಮ್ಮ ಬಳಕೆಯ ಆಧಾರದ ಮೇಲೆ ಸುಮಾರು <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ಸಮಯ ಬಾಕಿ ಉಳಿದಿದೆ"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> - ಸಮಯದಲ್ಲಿ ಪೂರ್ತಿ ಚಾರ್ಜ್ ಆಗುತ್ತದೆ"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ಸಮಯದಲ್ಲಿ ಪೂರ್ತಿ ಚಾರ್ಜ್ ಆಗುತ್ತದೆ"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಚಾರ್ಜಿಂಗ್ ಅನ್ನು ಆಪ್ಟಿಮೈಸ್ ಮಾಡಲಾಗಿದೆ"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಚಾರ್ಜಿಂಗ್ ಆಗುತ್ತಿದೆ"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ಅಪರಿಚಿತ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ವೇಗದ ಚಾರ್ಜಿಂಗ್"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 3c8ad01..b184ef4 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> 우선 적용됨"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>, <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - 배터리 보호를 위해 충전 일시중지"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - 충전 액세서리 확인 중"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - 충전 액세서리 확인"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"남은 시간: 약 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"남은 시간 약 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>(<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"내 사용량을 기준으로 약 <xliff:g id="TIME_REMAINING">%1$s</xliff:g> 남음"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> 후 충전 완료"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> 후 충전 완료"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - 충전 최적화됨"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ 충전 중"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"알 수 없음"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"충전 중"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"고속 충전 중"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index cec61bb..e565e44 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> менен алмаштырылган"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Батареяны коргоо үчүн кубаттоо тындырылды"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Кубаттоо шайманы текшерилүүдө"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Кубаттоо шайманын текшериңиз"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Болжол менен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> калды"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Болжол менен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> калды (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Колдонгонуңузга караганда болжол менен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> калды"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> кийин толук кубатталат"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> кийин толук кубатталат"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> — Кубаттоо жакшыртылды"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - Кубатталууда"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Белгисиз"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Кубатталууда"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ыкчам кубатталууда"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 4209259..6caaf95 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"ຖືກແທນໂດຍ <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ຢຸດການສາກຊົ່ວຄາວເພື່ອປົກປ້ອງແບັດເຕີຣີ"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - ກຳລັງກວດສອບອຸປະກອນເສີມສຳລັບການສາກ"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - ກວດສອບອຸປະກອນເສີມສຳລັບການສາກ"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"ເຫຼືອອີກປະມານ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"ເຫຼືອອີກປະມານ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"ເຫຼືອອີກປະມານ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ອ້າງອີງຈາກການນຳໃຊ້ຂອງທ່ານ"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"ຍັງເຫຼືອອີກ <xliff:g id="TIME">%1$s</xliff:g> ຈຶ່ງຈະສາກເຕັມ"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"ຍັງເຫຼືອອີກ <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ຈຶ່ງຈະສາກເຕັມ"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - ການສາກຖືກປັບໃຫ້ເໝາະສົມແລ້ວ"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - ກຳລັງສາກໄຟ"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ບໍ່ຮູ້ຈັກ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ກຳລັງສາກໄຟ"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ກຳລັງສາກໄຟດ່ວນ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index cb0069d..bc66a80 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Nepaisyta naudojant nuostatą „<xliff:g id="TITLE">%1$s</xliff:g>“"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – įkrovimas pristabdytas, siekiant apsaugoti akumuliatorių"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – tikrinamas įkrovimo priedas"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – patikrinkite įkrovimo priedą"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Liko maždaug <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Liko maždaug <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Liko maždaug <xliff:g id="TIME_REMAINING">%1$s</xliff:g>, atsižvelgiant į naudojimą"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Liko <xliff:g id="TIME">%1$s</xliff:g>, kol bus visiškai įkrauta"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – liko <xliff:g id="TIME">%2$s</xliff:g>, kol bus visiškai įkrauta"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – įkrovimas optimizuotas"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – įkraunama"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nežinomas"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Kraunasi..."</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Greitai įkraunama"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index db566881..c540945 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Jaunā preference: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> — <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> — uzlāde apturēta, lai aizsargātu akumulatoru"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> — notiek uzlādes piederuma pārbaude"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> — pārbaudiet uzlādes piederumu"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Aptuvenais atlikušais laiks: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Aptuvenais atlikušais laiks: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Ņemot vērā lietojumu, atlikušais laiks: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> līdz pilnai uzlādei"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> — <xliff:g id="TIME">%2$s</xliff:g> līdz pilnai uzlādei"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> — uzlāde optimizēta"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> — notiek uzlāde"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nezināms"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Uzlāde"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Notiek ātrā uzlāde"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 6738e20..70bcc210 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Прескокнато според <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - полнењето е паузирано за да се заштити батеријата"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - се проверува додатокот за полнење"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Проверете го додатокот за полнење"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Уште околу <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Уште околу <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Уште околу <xliff:g id="TIME_REMAINING">%1$s</xliff:g> според вашето користење"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> до полна батерија"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до полна батерија"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Полнењето е оптимизирано"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ се полни"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Непознато"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Се полни"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Брзо полнење"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index a5c38bc..9a297e5 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ഉപയോഗിച്ച് അസാധുവാക്കി"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ബാറ്ററി പരിരക്ഷിക്കാൻ ചാർജിംഗ് ഹോൾഡിലാണ്"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - ചാർജിംഗ് ആക്സസറി പരിശോധിക്കുന്നു"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - ചാർജിംഗ് ആക്‌സസറി പരിശോധിക്കുക"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"ഏതാണ്ട് <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ശേഷിക്കുന്നു"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"ഏതാണ്ട് <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ശേഷിക്കുന്നു (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"നിങ്ങളുടെ ഉപയോഗത്തെ അടിസ്ഥാനമാക്കി ഏതാണ്ട് <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ശേഷിക്കുന്നു"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"പൂർണ്ണമാകാൻ <xliff:g id="TIME">%1$s</xliff:g> ശേഷിക്കുന്നു"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - പൂർണ്ണമാകാൻ <xliff:g id="TIME">%2$s</xliff:g> ശേഷിക്കുന്നു"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - ചാർജിംഗ് ഒപ്റ്റിമൈസ് ചെയ്തു"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ ചാർജ് ചെയ്യുന്നു"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"അജ്ഞാതം"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ചാർജ് ചെയ്യുന്നു"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"അതിവേഗ ചാർജിംഗ്"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index c03c89f..000e306 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Давхарласан <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Батарейг хамгаалахын тулд цэнэглэхийг хүлээлгэсэн"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Цэнэглэх нэмэлт хэрэгслийг шалгаж байна"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Цэнэглэх нэмэлт хэрэгслийг шалгах"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ойролцоогоор <xliff:g id="TIME_REMAINING">%1$s</xliff:g> үлдсэн"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Ойролцоогоор <xliff:g id="TIME_REMAINING">%1$s</xliff:g> үлдсэн (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Таны хэрэглээнд үндэслэн ойролцоогоор <xliff:g id="TIME_REMAINING">%1$s</xliff:g> үлдсэн"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Дүүрэх хүртэл <xliff:g id="TIME">%1$s</xliff:g> үлдсэн"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - дүүрэх хүртэл <xliff:g id="TIME">%2$s</xliff:g> үлдсэн"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Цэнэглэх явцыг оновчилсон"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - Цэнэглэж байна"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Тодорхойгүй"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Цэнэглэж байна"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Хурдан цэнэглэж байна"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index bbe782d..6af0fbd 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> द्वारे अधिलिखित"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - बॅटरीचे संरक्षण करण्यासाठी चार्जिंग थांबवले आहे"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्जिंगसंबंधित ॲक्सेसरी तपासत आहे"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्जिंगसंबंधित ॲक्सेसरी तपासा"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"अंदाजे <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाकी आहे"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"अंदाजे <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाकी आहे (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"तुमच्‍या वापरावर आधारित अंदाजे <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाकी आहे"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"पूर्ण चार्ज होण्यासाठी <xliff:g id="TIME">%1$s</xliff:g> शिल्लक आहेत"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - पूर्ण चार्ज होण्यासाठी <xliff:g id="TIME">%2$s</xliff:g> शिल्लक आहे"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्जिंग ऑप्टिमाइझ केले"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ चार्ज होत आहे"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज होत आहे"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"वेगाने चार्ज होत आहे"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 3da4f08..621b469 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Diatasi oleh <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pengecasan ditunda untuk melindungi bateri"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Memeriksa aksesori pengecasan"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Periksa aksesori pengecasan"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Kira-kira <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Kira-kira <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Kira-kira <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi berdasarkan penggunaan anda"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> lagi sebelum penuh"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> lagi sebelum penuh"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pengecasan dioptimumkan"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - Mengecas"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Tidak diketahui"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Mengecas"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Mengecas dgn cepat"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index f4b6697..60161c3 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> မှ ကျော်၍ လုပ်ထားသည်။"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ဘက်ထရီကာကွယ်ရန် အားသွင်းခြင်းကို ခဏရပ်ထားသည်"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - အားသွင်းပစ္စည်း စစ်ဆေးနေသည်"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - အားသွင်းပစ္စည်း စစ်ရန်"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ခန့် ကျန်သည်"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ခန့် ကျန်သည် (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"သင်၏ အသုံးပြုမှု အပေါ် မူတည်၍ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ခန့် ကျန်သည်"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"အားပြည့်ရန် <xliff:g id="TIME">%1$s</xliff:g> လိုသည်"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - အားပြည့်ရန် <xliff:g id="TIME">%2$s</xliff:g> လိုသည်"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - အားသွင်းခြင်းကို အကောင်းဆုံးပြင်ဆင်ထားသည်"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - အားသွင်းနေသည်"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"မသိ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"အားသွင်းနေပါသည်"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"အမြန် အားသွင်းနေသည်"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index fbdc9e3..22162bb 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overstyres av <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ladingen er satt på vent for å beskytte batteriet"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Sjekker ladetilbehøret"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – sjekk ladetilbehøret"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Omtrent <xliff:g id="TIME_REMAINING">%1$s</xliff:g> igjen"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Omtrent <xliff:g id="TIME_REMAINING">%1$s</xliff:g> igjen (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Omtrent <xliff:g id="TIME_REMAINING">%1$s</xliff:g> igjen basert på bruken din"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Fulladet om <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – Fulladet om <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ladingen er optimalisert"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – lader"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Ukjent"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Lader"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Lader raskt"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 245a31f9..fdd965a 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> द्वारा अधिरोहित"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ब्याट्री जोगाउन चार्जिङ होल्ड गरिएको छ"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्जिङ एक्सेसरीको जाँच गरिँदै छ"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्जिङ एक्सेसरी जाँच्नुहोस्"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"लगभग <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाँकी छ"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"लगभग <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाँकी छ (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"तपाईंको प्रयोगको आधारमा लगभग <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाँकी छ"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"पूरा चार्ज हुन <xliff:g id="TIME">%1$s</xliff:g> लाग्ने छ"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - पूरा चार्ज हुन <xliff:g id="TIME">%2$s</xliff:g> लाग्ने छ"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्ज गर्ने प्रक्रिया अप्टिमाइज गरिएको छ"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्ज गरिँदै छ"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज हुँदै छ"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"द्रुत गतिमा चार्ज गरिँदै छ"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 4eafb57..72e57af 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overschreven door <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g>: opladen is in de wacht gezet om de batterij te beschermen"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g>: oplaadaccessoire checken"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Oplaadaccessoire checken"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Nog ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Nog ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Nog ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g> op basis van je gebruik"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Vol over <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - vol over <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Opladen geoptimaliseerd"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ Opladen"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Onbekend"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Opladen"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Snel opladen"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 5a8bed4..723af10 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ଦ୍ୱାରା ଓଭର୍‌ରାଇଡ୍‌ କରାଯାଇଛି"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ବେଟେରୀକୁ ସୁରକ୍ଷିତ ରଖିବା ପାଇଁ ଚାର୍ଜିଂ ହୋଲ୍ଡରେ ଅଛି"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - ଚାର୍ଜିଂ ଆକସେସୋରୀକୁ ଯାଞ୍ଚ କରାଯାଉଛି"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - ଚାର୍ଜିଂ ଆକସେସୋରୀକୁ ଯାଞ୍ଚ କରନ୍ତୁ"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"ପାଖାପାଖି <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ବଳକା ଅଛି"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"ପାଖାପାଖି <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ପାଇଁ (<xliff:g id="LEVEL">%2$s</xliff:g>) ବଳକା ଅଛି"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"ଆପଣଙ୍କ ବ୍ୟବହାରକୁ ଆଧାର କରି ପାଖାପାଖି <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ବଳକା ଅଛି"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"ପୂର୍ଣ୍ଣ ହେବାକୁ ଆଉ <xliff:g id="TIME">%1$s</xliff:g> ବାକି ଅଛି"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - ପୂର୍ଣ୍ଣ ହେବାକୁ ଆଉ <xliff:g id="TIME">%2$s</xliff:g> ବାକି ଅଛି"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - ଚାର୍ଜିଂକୁ ଅପ୍ଟିମାଇଜ କରାଯାଇଛି"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ ଚାର୍ଜିଂ"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ଅଜ୍ଞାତ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ଚାର୍ଜ ହେଉଛି"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ଶୀଘ୍ର ଚାର୍ଜ ହେଉଛି"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index df1fea1..8a77c12 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ਦੁਆਰਾ ਓਵਰਰਾਈਡ ਕੀਤਾ"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਬੈਟਰੀ ਦੀ ਸੁਰੱਖਿਆ ਲਈ ਚਾਰਜਿੰਗ ਨੂੰ ਰੋਕਿਆ ਗਿਆ ਹੈ"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਚਾਰਜਿੰਗ ਐਕਸੈਸਰੀ ਦੀ ਜਾਂਚ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਚਾਰਜਿੰਗ ਐਕਸੈਸਰੀ ਦੀ ਜਾਂਚ ਕਰੋ"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"ਲਗਭਗ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ਬਾਕੀ"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"ਲਗਭਗ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ਬਾਕੀ (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"ਤੁਹਾਡੀ ਵਰਤੋਂ ਦੇ ਆਧਾਰ \'ਤੇ ਲਗਭਗ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ਬਾਕੀ"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"ਬੈਟਰੀ ਪੂਰੀ ਚਾਰਜ ਹੋਣ ਵਿੱਚ <xliff:g id="TIME">%1$s</xliff:g> ਬਾਕੀ"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਬੈਟਰੀ ਪੂਰੀ ਚਾਰਜ ਹੋਣ ਵਿੱਚ <xliff:g id="TIME">%2$s</xliff:g> ਬਾਕੀ"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਚਾਰਜਿੰਗ ਨੂੰ ਸੁਯੋਗ ਬਣਾਇਆ ਗਿਆ"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਚਾਰਜ ਹੋ ਰਹੀ ਹੈ"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ਅਗਿਆਤ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ਚਾਰਜ ਹੋ ਰਹੀ ਹੈ"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ਤੇਜ਼ ਚਾਰਜ ਹੋ ਰਹੀ ਹੈ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index e9f9da8..375a35f 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Nadpisana przez <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – wstrzymano ładowanie, aby chronić baterię"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – sprawdzam akcesoria do ładowania"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – sprawdź akcesoria do ładowania"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Jeszcze około <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Jeszcze około <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Jeszcze około <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (na podstawie Twojego sposobu korzystania)"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do pełnego naładowania"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do pełnego naładowania"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – ładowanie zoptymalizowane"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – ładowanie"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nieznane"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Ładowanie"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Szybkie ładowanie"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 4ee1cbd..55b9db5 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carregamento suspenso para proteger a bateria"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Verificando o acessório de carregamento"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g>: verifique o acessório de carregamento"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Tempo restante aproximado, com base no seu uso: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> até a conclusão"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> até a conclusão"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carregamento otimizado"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> (carregando)"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Carregando"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregando rápido"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index eae5cc9..ca704e7 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carregamento em espera para proteger a bateria"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – A verificar o acessório de carregamento"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Verificar acessório de carregamento"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Resta(m) cerca de <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Resta(m) cerca de <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Resta(m) cerca de <xliff:g id="TIME_REMAINING">%1$s</xliff:g> com base na sua utilização"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> até à carga máxima"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> até à carga máxima"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g>: carregamento otimizado"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – A carregar"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"A carregar"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregamento rápido"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 4ee1cbd..55b9db5 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carregamento suspenso para proteger a bateria"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Verificando o acessório de carregamento"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g>: verifique o acessório de carregamento"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Tempo restante aproximado, com base no seu uso: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> até a conclusão"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> até a conclusão"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carregamento otimizado"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> (carregando)"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Carregando"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregando rápido"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 91261d4..fa89c1a 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Valoare înlocuită de <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Încărcarea s-a întrerupt pentru a proteja bateria"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Se verifică accesoriul de încărcare"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Verifică accesoriul de încărcare"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Timp aproximativ rămas: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Timp aproximativ rămas: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"În baza utilizării, timpul rămas este de aproximativ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> până la finalizare"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> până la finalizare"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – Încărcare optimizată"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Se încarcă"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Necunoscut"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Se încarcă"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Se încarcă rapid"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 87b2a93..d853070 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Новая настройка: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"Уровень заряда – <xliff:g id="PERCENTAGE">%1$s</xliff:g>. <xliff:g id="TIME_STRING">%2$s</xliff:g>."</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g>, зарядка приостановлена для защиты батареи"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g>, проверяется зарядное устройство"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g>, проверьте зарядное устройство"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Заряда хватит примерно на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Заряда (<xliff:g id="LEVEL">%2$s</xliff:g>) хватит примерно на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Заряда хватит примерно на <xliff:g id="TIME_REMAINING">%1$s</xliff:g> при текущем уровне расхода"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> до полной зарядки"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до полной зарядки"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарядка оптимизирована"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – заряжается"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Неизвестно"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Идет зарядка"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Быстрая зарядка"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 72bd507..093f216 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> මගින් ඉක්මවන ලදී"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - බැටරිය ආරක්ෂා කිරීම සඳහා ආරෝපණය රඳවා තබා ඇත"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - ආරෝපණ ආයිත්තම පරීක්ෂා කිරීම"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - ආරෝපණ ආයිත්තම පරීක්ෂා කරන්න"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ක් පමණ ඉතිරියි"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ක් පමණ ඉතිරියි (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"ඔබේ භාවිතය මත පදනම්ව <xliff:g id="TIME_REMAINING">%1$s</xliff:g> පමණ ඉතිරිව ඇත"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"සම්පූර්ණ වීමට <xliff:g id="TIME">%1$s</xliff:g>ක් ඉතිරියි"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - සම්පූර්ණ වීමට <xliff:g id="TIME">%2$s</xliff:g>ක් ඉතිරියි"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - ආරෝපණය ප්‍රශස්ත කර ඇත"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - ආරෝපණය වේ"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"නොදනී"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ආරෝපණය වෙමින්"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ශීඝ්‍ර ආරෝපණය"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index dd8be3a..1c82933 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Prekonané predvoľbou <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – nabíjanie je pozastavené, aby sa chránila batéria"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – kontroluje sa nabíjacie príslušenstvo"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – skontrolujte nabíjacie príslušenstvo"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ešte približne <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Zostáva približne <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Zostáva približne <xliff:g id="TIME_REMAINING">%1$s</xliff:g> – závisí to od intenzity využitia"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do úplného nabitia"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do úplného nabitia"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Nabíjanie je optimalizované"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – nabíja sa"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznáme"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Nabíja sa"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Rýchle nabíjanie"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index eefb119..f97dd78 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Preglasila nastavitev: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Zaradi zaščite baterije je polnjenje na čakanju"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Preverjanje pripomočka za polnjenje"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Preverite pripomoček za polnjenje"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Še približno <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Še približno <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Glede na način uporabe še približno <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Še <xliff:g id="TIME">%1$s</xliff:g> do napolnjenosti"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – še <xliff:g id="TIME">%2$s</xliff:g> do napolnjenosti"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – polnjenje je optimizirano"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – polnjenje"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznano"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Polnjenje"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Hitro polnjenje"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index e847c51..a802f11 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Mbivendosur nga <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Karikimi është vendosur në pritje për të mbrojtur baterinë"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Po kontrollohet aksesori i karikimit"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Kontrollo aksesorin e karikimit"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Rreth <xliff:g id="TIME_REMAINING">%1$s</xliff:g> të mbetura"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Rreth <xliff:g id="TIME_REMAINING">%1$s</xliff:g> të mbetura (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Rreth <xliff:g id="TIME_REMAINING">%1$s</xliff:g> të mbetura bazuar në përdorimin tënd"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> derisa të mbushet"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> derisa të mbushet"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Karikimi u optimizua"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - Po karikohet"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"I panjohur"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Po karikohet"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Karikim i shpejtë"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index ed0e9a6..df22159 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Замењује га <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>–<xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – пуњење је на чекању да би се заштитила батерија"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – провера додатне опреме за пуњење"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Проверите додатну опрему за пуњење"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Преостало је око <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Преостало је око <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Преостало је око <xliff:g id="TIME_REMAINING">%1$s</xliff:g> на основу коришћења"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> до краја пуњења"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до краја пуњења"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – пуњење је оптимизовано"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Пуњење"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Непознато"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Пуни се"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Брзо се пуни"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index b4de504..79b8399 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Har åsidosatts av <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Laddningen har pausats för att skydda batteriet"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Kontrollerar laddningstillbehöret"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Kontrollera laddningstillbehöret"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Cirka <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kvar"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Cirka <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kvar (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Cirka <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kvar utifrån din användning"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> kvar tills fulladdat"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> kvar tills fulladdat"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Laddningen har optimerats"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – laddas"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Okänd"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Laddar"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Laddas snabbt"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 40725c9..c0ace580 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Imetanguliwa na <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Imesitisha kuchaji ili kulinda betri yako"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Inakagua kifaa cha kuchaji"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Kagua kifaa cha kuchaji"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Zimesalia takribani <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Zimesalia takribani <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Zimesalia takribani <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kulingana na jinsi unavyoitumia"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Zimesalia <xliff:g id="TIME">%1$s</xliff:g> ijae chaji"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> zimesalia ijae chaji"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Hali ya kuchaji imeboreshwa"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - Inachaji"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Haijulikani"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Inachaji"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Inachaji kwa kasi"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 2fe243a..ebca26d 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> மூலம் மேலெழுதப்பட்டது"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - பேட்டரியைப் பாதுகாப்பதற்காகச் சார்ஜிங் இடைநிறுத்தப்பட்டுள்ளது"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - சார்ஜிங் துணைக்கருவியைச் சரிபார்க்கிறது"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - சார்ஜிங் துணைக்கருவியைச் சரிபாருங்கள்"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"கிட்டத்தட்ட <xliff:g id="TIME_REMAINING">%1$s</xliff:g> மீதமுள்ளது"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"கிட்டத்தட்ட <xliff:g id="TIME_REMAINING">%1$s</xliff:g> மீதமுள்ளது (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"உபயோகத்தின் அடிப்படையில் கிட்டத்தட்ட <xliff:g id="TIME_REMAINING">%1$s</xliff:g> மீதமுள்ளது"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"முழுவதும் சார்ஜாக <xliff:g id="TIME">%1$s</xliff:g> ஆகும்"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - முழுவதும் சார்ஜாக <xliff:g id="TIME">%2$s</xliff:g> ஆகும்"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - சார்ஜிங் மேம்படுத்தப்பட்டது"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ சார்ஜாகிறது"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"அறியப்படாத"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"சார்ஜ் ஆகிறது"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"வேகமாக சார்ஜாகிறது"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 43f2b83..cf3c08a 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ద్వారా భర్తీ చేయబడింది"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - బ్యాటరీని రక్షించడానికి ఛార్జింగ్ హోల్డ్‌లో ఉంచబడింది"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - ఛార్జ్ చేసే పరికరాన్ని చెక్ చేయండి"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - ఛార్జింగ్ యాక్సెసరీని ఎంచుకోండి"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> సమయం మిగిలి ఉంది"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"దాదాపు <xliff:g id="TIME_REMAINING">%1$s</xliff:g> సమయం మిగిలి ఉంది (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"మీ వినియోగం ఆధారంగా దాదాపు <xliff:g id="TIME_REMAINING">%1$s</xliff:g> సమయం మిగిలి ఉంది"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g>లో పూర్తిగా ఛార్జ్ అవుతుంది"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>లో పూర్తిగా ఛార్జ్ అవుతుంది"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - ఛార్జింగ్ ఆప్టిమైజ్ చేయబడింది"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - ఛార్జ్ అవుతోంది"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"తెలియదు"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ఛార్జ్ అవుతోంది"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"వేగవంతమైన ఛార్జింగ్"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 8395a51..a74265e 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"แทนที่โดย <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - หยุดการชาร์จชั่วคราวเพื่อถนอมแบตเตอรี่"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - กำลังตรวจสอบอุปกรณ์เสริมสำหรับการชาร์จ"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - ตรวจสอบอุปกรณ์เสริมสำหรับการชาร์จ"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"เหลืออีกประมาณ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"เหลืออีกประมาณ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"เหลืออีกประมาณ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ขึ้นอยู่กับการใช้งานของคุณ"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"อีก <xliff:g id="TIME">%1$s</xliff:g>จึงจะเต็ม"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - อีก <xliff:g id="TIME">%2$s</xliff:g> จึงจะเต็ม"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - ปรับการชาร์จให้เหมาะสมแล้ว"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ กำลังชาร์จ"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ไม่ทราบ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"กำลังชาร์จ"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"กำลังชาร์จอย่างเร็ว"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index a337bd8..18d3d3b 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Na-override ng <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Naka-hold ang pag-charge para protektahan ang baterya"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Sinusuri ang accessory sa pag-charge"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Suriin ang accessory sa pag-charge"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Humigit-kumulang <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ang natitira"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Humigit-kumulang <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ang natitira (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Humigit-kumulang <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ang natitira batay sa iyong paggamit"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> na lang bago mapuno"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> na lang bago mapuno"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Naka-optimize ang pag-charge"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - Nagcha-charge"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Hindi Kilala"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Nagcha-charge"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Mabilis na charge"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 43e18ed..c424115 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> tarafından geçersiz kılındı"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pili korumak için şarj beklemede"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Şarj aksesuarı kontrol ediliyor"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Şarj aksesuarını kontrol edin"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Yaklaşık <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kaldı"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Yaklaşık <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kaldı (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Kullanımınıza dayalı olarak yaklaşık <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kaldı"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Tamamen şarj olmasına <xliff:g id="TIME">%1$s</xliff:g> kaldı"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - Tamamen şarj olmasına <xliff:g id="TIME">%2$s</xliff:g> kaldı"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Şarj işlemi optimize edildi"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ Şarj ediliyor"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Bilinmiyor"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Şarj oluyor"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Hızlı şarj oluyor"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 984bb79..97592f9 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Замінено на <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – заряджання призупинено, щоб захистити акумулятор"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – перевірка зарядного пристрою"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – перевірте зарядний пристрій"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Залишилося приблизно <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Залишилося приблизно <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Згідно з даними про використання залишилося приблизно <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> до повного заряду"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до повного заряду"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – заряджання оптимізовано"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – заряджається"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Невідомо"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Заряджається"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Швидке заряджання"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index bc8a4674..9d34052 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> کے ذریعہ منسوخ کردیا گیا"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - بیٹری کی حفاظت کرنے کے لیے چارجنگ ہولڈ پر ہے"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - چارجنگ ایکسیسری کی جانچ کی جا رہی ہے"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - چارجنگ ایکسیسری چیک کریں"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> باقی ہے"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> باقی ہے (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"آپ کے استعمال کی بنیاد پر تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> باقی ہے"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"مکمل چارج ہونے میں <xliff:g id="TIME">%1$s</xliff:g> باقی ہے"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"مکمل چارج ہونے میں <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> باقی ہے"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - چارجنگ کو بہتر بنایا گیا"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - چارج ہو رہی ہے"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"نامعلوم"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"چارج ہو رہا ہے"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"تیزی سے چارج ہو رہا ہے"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index a655dc0..bd38bb4 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> bilan almashtirildi"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Batareyani himoyalash uchun quvvatlash toʻxtatildi"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Quvvatlash aksessuari tekshirilmoqda"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Quvvatlash aksessuarini tekshiring"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Taxminan <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qoldi"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Taxminan <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qoldi (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Quvvati tugashiga taxminan <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qoldi"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Toʻlishiga <xliff:g id="TIME">%1$s</xliff:g> qoldi"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – Toʻlishiga <xliff:g id="TIME">%2$s</xliff:g> qoldi"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Quvvatlash optimallashtirildi"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - Quvvatlanmoqda"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Noma’lum"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Quvvat olmoqda"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Tezkor quvvat olmoqda"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 643f8ca..5b91df3 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Bị ghi đè bởi <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Đang tạm ngưng sạc để bảo vệ pin"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Đang kiểm tra phụ kiện sạc"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Hãy kiểm tra phụ kiện sạc"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Còn khoảng <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Còn khoảng <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Còn khoảng <xliff:g id="TIME_REMAINING">%1$s</xliff:g> dựa trên mức sử dụng của bạn"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> nữa là pin đầy"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> nữa là pin đầy"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – Quá trình sạc được tối ưu hoá"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Đang sạc"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Không xác định"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Đang sạc"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Đang sạc nhanh"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 3b08e1f..594922a 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"已被“<xliff:g id="TITLE">%1$s</xliff:g>”覆盖"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - 为保护电池,已暂停充电"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - 正在检查充电配件"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - 请检查充电配件"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"大约还可使用<xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"大约还可使用<xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"根据您的使用情况,大约还可使用<xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"还需<xliff:g id="TIME">%1$s</xliff:g>充满"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - 还需<xliff:g id="TIME">%2$s</xliff:g>充满"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充电方式已优化"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - 正在充电"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"未知"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"正在充电"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"正在快速充电"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index eba782e..b3aafae 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"已由「<xliff:g id="TITLE">%1$s</xliff:g>」覆寫"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - 為保護電池,目前暫停充電"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - 正在檢查充電配件"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - 檢查充電配件"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"還有大約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"還有大約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"根據你的使用情況,還有大約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g>後充滿電"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>後充滿電"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - 已優化充電"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ 充電中"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"未知"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"快速充電中"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 099ad03..c3be21a 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"已改為<xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - 為保護電池,目前暫停充電"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - 正在檢查充電配件"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> · 請檢查充電配件"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"還能使用約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"目前電量為 <xliff:g id="LEVEL">%2$s</xliff:g>,還能使用約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"根據你的使用情形,還能使用約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g>後充飽"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>後充飽"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充電效能已最佳化"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充電中"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"不明"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"快速充電中"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 4d652f8..f6aaf81 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -457,7 +457,7 @@
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Igitshezwe ngaphezulu yi-<xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ukushaja kumisiwe ukuze kuvikelwe ibhethri"</string>
-    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Kuhlolwa okokushaja"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Hlola insiza yokushaja"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Cishe u-<xliff:g id="TIME_REMAINING">%1$s</xliff:g> osele"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Cishe u-<xliff:g id="TIME_REMAINING">%1$s</xliff:g> osele (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Cishe u-<xliff:g id="TIME_REMAINING">%1$s</xliff:g> osele ngokususelwe ekusebenziseni wakho"</string>
@@ -478,8 +478,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> okusele kuze kugcwale"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> okusele kuze kugcwale"</string>
     <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ukushaja kuthuthukisiwe"</string>
-    <!-- no translation found for power_charging_future_paused (1809543660923642799) -->
-    <skip />
+    <string name="power_charging_future_paused" msgid="1809543660923642799">"Iku-<xliff:g id="LEVEL">%1$s</xliff:g> ‑ Iyashaja"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Akwaziwa"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Iyashaja"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ishaja ngokushesha"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 1150ac1..87b4c0f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -785,30 +785,6 @@
         return false;
     }
 
-    /** Whether to show the wireless charging notification. */
-    public static boolean shouldShowWirelessChargingNotification(
-            @NonNull Context context, @NonNull String tag) {
-        try {
-            return shouldShowWirelessChargingNotificationInternal(context, tag);
-        } catch (Exception e) {
-            Log.e(tag, "shouldShowWirelessChargingNotification()", e);
-            return false;
-        }
-    }
-
-    /** Stores the timestamp of the wireless charging notification. */
-    public static void updateWirelessChargingNotificationTimestamp(
-            @NonNull Context context, long timestamp, @NonNull String tag) {
-        try {
-            Secure.putLong(
-                    context.getContentResolver(),
-                    WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP,
-                    timestamp);
-        } catch (Exception e) {
-            Log.e(tag, "setWirelessChargingNotificationTimestamp()", e);
-        }
-    }
-
     /** Whether to show the wireless charging warning in Settings. */
     public static boolean shouldShowWirelessChargingWarningTip(
             @NonNull Context context, @NonNull String tag) {
@@ -833,37 +809,4 @@
             Log.e(tag, "setWirelessChargingWarningEnabled()", e);
         }
     }
-
-    private static boolean shouldShowWirelessChargingNotificationInternal(
-            @NonNull Context context, @NonNull String tag) {
-        final long lastNotificationTimeMillis =
-                Secure.getLong(
-                        context.getContentResolver(),
-                        WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP,
-                        WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
-        if (isWirelessChargingNotificationDisabled(lastNotificationTimeMillis)) {
-            return false;
-        }
-        if (isInitialWirelessChargingNotification(lastNotificationTimeMillis)) {
-            updateWirelessChargingNotificationTimestamp(context, System.currentTimeMillis(), tag);
-            updateWirelessChargingWarningEnabled(context, /* enabled= */ true, tag);
-            return true;
-        }
-        final long durationMillis = System.currentTimeMillis() - lastNotificationTimeMillis;
-        final boolean show = durationMillis > WIRELESS_CHARGING_NOTIFICATION_THRESHOLD_MILLIS;
-        Log.d(tag, "shouldShowWirelessChargingNotification = " + show);
-        if (show) {
-            updateWirelessChargingNotificationTimestamp(context, System.currentTimeMillis(), tag);
-            updateWirelessChargingWarningEnabled(context, /* enabled= */ true, tag);
-        }
-        return show;
-    }
-
-    private static boolean isWirelessChargingNotificationDisabled(long lastNotificationTimeMillis) {
-        return lastNotificationTimeMillis == Long.MIN_VALUE;
-    }
-
-    private static boolean isInitialWirelessChargingNotification(long lastNotificationTimeMillis) {
-        return lastNotificationTimeMillis == WIRELESS_CHARGING_DEFAULT_TIMESTAMP;
-    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index e489bc5..2889ce2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1703,6 +1703,7 @@
 
         public boolean isPrivateProfile() {
             return android.os.Flags.allowPrivateProfile()
+                    && android.multiuser.Flags.enablePrivateSpaceFeatures()
                     && UserManager.USER_TYPE_PROFILE_PRIVATE.equals(mProfileType);
         }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
index da1fd55..0c7d6f0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
@@ -311,7 +311,7 @@
         }
 
         builder.apply {
-            setSourceDevice(device, sourceAddrType)
+            setSourceDevice(device, addrType)
             setSourceAdvertisingSid(sourceAdvertiserSid)
             setBroadcastId(broadcastId)
             setBroadcastName(broadcastName)
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 61c3ce7..c2c82b3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1764,40 +1764,4 @@
     boolean getUnpairing() {
         return mUnpairing;
     }
-
-    ListenableFuture<Void> syncProfileForMemberDevice() {
-        return ThreadUtils.getBackgroundExecutor()
-            .submit(
-                () -> {
-                    List<Pair<LocalBluetoothProfile, Boolean>> toSync =
-                        Stream.of(
-                            mProfileManager.getA2dpProfile(),
-                            mProfileManager.getHeadsetProfile(),
-                            mProfileManager.getHearingAidProfile(),
-                            mProfileManager.getLeAudioProfile(),
-                            mProfileManager.getLeAudioBroadcastAssistantProfile())
-                        .filter(Objects::nonNull)
-                        .map(profile -> new Pair<>(profile, profile.isEnabled(mDevice)))
-                        .toList();
-
-                    for (var t : toSync) {
-                        LocalBluetoothProfile profile = t.first;
-                        boolean enabledForMain = t.second;
-
-                        for (var member : mMemberDevices) {
-                            BluetoothDevice btDevice = member.getDevice();
-
-                            if (enabledForMain != profile.isEnabled(btDevice)) {
-                                Log.d(TAG, "Syncing profile " + profile + " to "
-                                        + enabledForMain + " for member device "
-                                        + btDevice.getAnonymizedAddress() + " of main device "
-                                        + mDevice.getAnonymizedAddress());
-                                profile.setEnabled(btDevice, enabledForMain);
-                            }
-                        }
-                    }
-                    return null;
-                }
-            );
-    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 32eec7e..4e52c77 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -363,7 +363,6 @@
         if (profileId == BluetoothProfile.HEADSET
                 || profileId == BluetoothProfile.A2DP
                 || profileId == BluetoothProfile.LE_AUDIO
-                || profileId == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
                 || profileId == BluetoothProfile.CSIP_SET_COORDINATOR) {
             return mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice,
                 state);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index e67ec48..a49314a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -379,7 +379,6 @@
         if (hasChanged) {
             log("addMemberDevicesIntoMainDevice: After changed, CachedBluetoothDevice list: "
                     + mCachedDevices);
-            preferredMainDevice.syncProfileForMemberDevice();
         }
         return hasChanged;
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index ca47efd..1069b71 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -346,11 +346,15 @@
         } else {
             long hiSyncId = asha.getHiSyncId(cachedDevice.getDevice());
             if (isValidHiSyncId(hiSyncId)) {
-                final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
+                final HearingAidInfo info = new HearingAidInfo.Builder()
                         .setAshaDeviceSide(asha.getDeviceSide(cachedDevice.getDevice()))
                         .setAshaDeviceMode(asha.getDeviceMode(cachedDevice.getDevice()))
-                        .setHiSyncId(hiSyncId);
-                return infoBuilder.build();
+                        .setHiSyncId(hiSyncId)
+                        .build();
+                if (DEBUG) {
+                    Log.d(TAG, "generateHearingAidInfo, " + cachedDevice + ", info=" + info);
+                }
+                return info;
             }
         }
 
@@ -358,15 +362,20 @@
         final LeAudioProfile leAudioProfile = profileManager.getLeAudioProfile();
         if (hapClientProfile == null || leAudioProfile == null) {
             Log.w(TAG, "HapClientProfile or LeAudioProfile is not supported on this device");
-        } else {
+        } else if (cachedDevice.getProfiles().stream().anyMatch(
+                p -> p instanceof HapClientProfile)) {
             int audioLocation = leAudioProfile.getAudioLocation(cachedDevice.getDevice());
             int hearingAidType = hapClientProfile.getHearingAidType(cachedDevice.getDevice());
             if (audioLocation != BluetoothLeAudio.AUDIO_LOCATION_INVALID
                     && hearingAidType != HapClientProfile.HearingAidType.TYPE_INVALID) {
-                final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
+                final HearingAidInfo info = new HearingAidInfo.Builder()
                         .setLeAudioLocation(audioLocation)
-                        .setHapDeviceType(hearingAidType);
-                return infoBuilder.build();
+                        .setHapDeviceType(hearingAidType)
+                        .build();
+                if (DEBUG) {
+                    Log.d(TAG, "generateHearingAidInfo, " + cachedDevice + ", info=" + info);
+                }
+                return info;
             }
         }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 6ee403d..bd27c89 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -19,6 +19,7 @@
 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothCsipSetCoordinator;
@@ -35,6 +36,7 @@
 import android.bluetooth.BluetoothProfile.ServiceListener;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Build;
@@ -52,6 +54,8 @@
 
 import com.google.common.collect.ImmutableList;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -71,6 +75,18 @@
  * result callback.
  */
 public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
+    public static final String ACTION_LE_AUDIO_SHARING_STATE_CHANGE =
+            "com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_STATE_CHANGE";
+    public static final String EXTRA_LE_AUDIO_SHARING_STATE = "BLUETOOTH_LE_AUDIO_SHARING_STATE";
+    public static final int BROADCAST_STATE_UNKNOWN = 0;
+    public static final int BROADCAST_STATE_ON = 1;
+    public static final int BROADCAST_STATE_OFF = 2;
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+            prefix = {"BROADCAST_STATE_"},
+            value = {BROADCAST_STATE_UNKNOWN, BROADCAST_STATE_ON, BROADCAST_STATE_OFF})
+    public @interface BroadcastState {}
+    private static final String SETTINGS_PKG = "com.android.settings";
     private static final String TAG = "LocalBluetoothLeBroadcast";
     private static final boolean DEBUG = BluetoothUtils.D;
 
@@ -89,7 +105,6 @@
                 Settings.Secure.getUriFor(
                         Settings.Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY),
             };
-
     private final Context mContext;
     private final CachedBluetoothDeviceManager mDeviceManager;
     private BluetoothLeBroadcast mServiceBroadcast;
@@ -200,6 +215,7 @@
                         Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = " + broadcastId);
                     }
                     setLatestBluetoothLeBroadcastMetadata(metadata);
+                    notifyBroadcastStateChange(BROADCAST_STATE_ON);
                 }
 
                 @Override
@@ -212,7 +228,7 @@
                                         + ", broadcastId = "
                                         + broadcastId);
                     }
-
+                    notifyBroadcastStateChange(BROADCAST_STATE_OFF);
                     stopLocalSourceReceivers();
                     resetCacheInfo();
                 }
@@ -1005,10 +1021,6 @@
 
     /** Update fallback active device if needed. */
     public void updateFallbackActiveDeviceIfNeeded() {
-        if (!isEnabled(null)) {
-            Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to no ongoing broadcast");
-            return;
-        }
         if (mServiceBroadcastAssistant == null) {
             Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to assistant profile is null");
             return;
@@ -1078,4 +1090,15 @@
                 "bluetooth_le_broadcast_fallback_active_group_id",
                 BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
     }
+
+    private void notifyBroadcastStateChange(@BroadcastState int state) {
+        if (!mContext.getPackageName().equals(SETTINGS_PKG)) {
+            Log.d(TAG, "Skip notifyBroadcastStateChange, not triggered by Settings.");
+            return;
+        }
+        Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_STATE_CHANGE);
+        intent.putExtra(EXTRA_LE_AUDIO_SHARING_STATE, state);
+        intent.setPackage(mContext.getPackageName());
+        mContext.sendBroadcast(intent);
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
index 9a19f93..ef0f6cb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
@@ -14,6 +14,8 @@
 
 package com.android.settingslib.graph;
 
+import static com.android.settingslib.flags.Flags.newStatusBarIcons;
+
 import android.animation.ArgbEvaluator;
 import android.annotation.IntRange;
 import android.content.Context;
@@ -67,6 +69,9 @@
 
     private static final long DOT_DELAY = 1000;
 
+    // Check the config for which icon we want to use
+    private static final int ICON_RES = SignalDrawable.getIconRes();
+
     private final Paint mForegroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     private final Paint mTransparentPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     private final int mDarkModeFillColor;
@@ -85,7 +90,7 @@
     private int mCurrentDot;
 
     public SignalDrawable(Context context) {
-        super(context.getDrawable(com.android.internal.R.drawable.ic_signal_cellular));
+        super(context.getDrawable(ICON_RES));
         final String attributionPathString = context.getString(
                 com.android.internal.R.string.config_signalAttributionPath);
         mAttributionPath.set(PathParser.createPathFromPathData(attributionPathString));
@@ -147,9 +152,17 @@
 
     private int unpackLevel(int packedState) {
         int numBins = (packedState & NUM_LEVEL_MASK) >> NUM_LEVEL_SHIFT;
+        int cutOutOffset = 0;
         int levelOffset = numBins == (CellSignalStrength.getNumSignalStrengthLevels() + 1) ? 10 : 0;
         int level = (packedState & LEVEL_MASK);
-        return level + levelOffset;
+
+        if (newStatusBarIcons()) {
+            if (isInState(STATE_CUT)) {
+                cutOutOffset = 20;
+            }
+        }
+
+        return level + levelOffset + cutOutOffset;
     }
 
     public void setDarkIntensity(float darkIntensity) {
@@ -214,7 +227,7 @@
             drawDotAndPadding(x - dotSpacing * 2, y, dotPadding, dotSize, 0);
             canvas.drawPath(mCutoutPath, mTransparentPaint);
             canvas.drawPath(mForegroundPath, mForegroundPaint);
-        } else if (isInState(STATE_CUT)) {
+        } else if (!newStatusBarIcons() && isInState(STATE_CUT)) {
             float cutX = (mCutoutWidthFraction * width / VIEWPORT);
             float cutY = (mCutoutHeightFraction * height / VIEWPORT);
             mCutoutPath.moveTo(width, height);
@@ -300,4 +313,12 @@
     public static int getCarrierChangeState(int numLevels) {
         return (STATE_CARRIER_CHANGE << STATE_SHIFT) | (numLevels << NUM_LEVEL_SHIFT);
     }
+
+    private static int getIconRes() {
+        if (newStatusBarIcons()) {
+            return R.drawable.ic_mobile_level_list;
+        } else {
+            return com.android.internal.R.drawable.ic_signal_cellular;
+        }
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 50e2f9c..e34c50e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -44,7 +44,6 @@
 import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
 
 import android.annotation.TargetApi;
-import android.app.Notification;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.content.ComponentName;
@@ -67,6 +66,7 @@
 import com.android.settingslib.media.flags.Flags;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
@@ -74,16 +74,50 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 /** InfoMediaManager provide interface to get InfoMediaDevice list. */
 @RequiresApi(Build.VERSION_CODES.R)
-public abstract class InfoMediaManager extends MediaManager {
+public abstract class InfoMediaManager {
+    /** Callback for notifying device is added, removed and attributes changed. */
+    public interface MediaDeviceCallback {
 
-    private static final String TAG = "InfoMediaManager";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+        /**
+         * Callback for notifying MediaDevice list is added.
+         *
+         * @param devices the MediaDevice list
+         */
+        void onDeviceListAdded(@NonNull List<MediaDevice> devices);
+
+        /**
+         * Callback for notifying MediaDevice list is removed.
+         *
+         * @param devices the MediaDevice list
+         */
+        void onDeviceListRemoved(@NonNull List<MediaDevice> devices);
+
+        /**
+         * Callback for notifying connected MediaDevice is changed.
+         *
+         * @param id the id of MediaDevice
+         */
+        void onConnectedDeviceChanged(@Nullable String id);
+
+        /**
+         * Callback for notifying that transferring is failed.
+         *
+         * @param reason the reason that the request has failed. Can be one of followings: {@link
+         *     android.media.MediaRoute2ProviderService#REASON_UNKNOWN_ERROR}, {@link
+         *     android.media.MediaRoute2ProviderService#REASON_REJECTED}, {@link
+         *     android.media.MediaRoute2ProviderService#REASON_NETWORK_ERROR}, {@link
+         *     android.media.MediaRoute2ProviderService#REASON_ROUTE_NOT_AVAILABLE}, {@link
+         *     android.media.MediaRoute2ProviderService#REASON_INVALID_COMMAND},
+         */
+        void onRequestFailed(int reason);
+    }
 
     /** Checked exception that signals the specified package is not present in the system. */
     public static class PackageNotAvailableException extends Exception {
@@ -92,19 +126,22 @@
         }
     }
 
+    private static final String TAG = "InfoMediaManager";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    protected final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
+    @NonNull protected final Context mContext;
     @NonNull protected final String mPackageName;
+    private final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
     private MediaDevice mCurrentConnectedDevice;
     private final LocalBluetoothManager mBluetoothManager;
     private final Map<String, RouteListingPreference.Item> mPreferenceItemMap =
             new ConcurrentHashMap<>();
 
     /* package */ InfoMediaManager(
-            Context context,
+            @NonNull Context context,
             @NonNull String packageName,
-            Notification notification,
-            LocalBluetoothManager localBluetoothManager) {
-        super(context, notification);
-
+            @NonNull LocalBluetoothManager localBluetoothManager) {
+        mContext = context;
         mBluetoothManager = localBluetoothManager;
         mPackageName = packageName;
     }
@@ -113,7 +150,6 @@
     public static InfoMediaManager createInstance(
             Context context,
             @Nullable String packageName,
-            Notification notification,
             LocalBluetoothManager localBluetoothManager) {
 
         // The caller is only interested in system routes (headsets, built-in speakers, etc), and is
@@ -125,21 +161,17 @@
 
         if (Flags.useMediaRouter2ForInfoMediaManager()) {
             try {
-                return new RouterInfoMediaManager(
-                        context, packageName, notification, localBluetoothManager);
+                return new RouterInfoMediaManager(context, packageName, localBluetoothManager);
             } catch (PackageNotAvailableException ex) {
                 // TODO: b/293578081 - Propagate this exception to callers for proper handling.
                 Log.w(TAG, "Returning a no-op InfoMediaManager for package " + packageName);
-                return new NoOpInfoMediaManager(
-                        context, packageName, notification, localBluetoothManager);
+                return new NoOpInfoMediaManager(context, packageName, localBluetoothManager);
             }
         } else {
-            return new ManagerInfoMediaManager(
-                    context, packageName, notification, localBluetoothManager);
+            return new ManagerInfoMediaManager(context, packageName, localBluetoothManager);
         }
     }
 
-    @Override
     public void startScan() {
         mMediaDevices.clear();
         startScanOnRouter();
@@ -156,7 +188,6 @@
         }
     }
 
-    @Override
     public abstract void stopScan();
 
     protected abstract void startScanOnRouter();
@@ -229,6 +260,48 @@
         Api34Impl.onRouteListingPreferenceUpdated(routeListingPreference, mPreferenceItemMap);
     }
 
+    protected final MediaDevice findMediaDevice(@NonNull String id) {
+        for (MediaDevice mediaDevice : mMediaDevices) {
+            if (mediaDevice.getId().equals(id)) {
+                return mediaDevice;
+            }
+        }
+        Log.e(TAG, "findMediaDevice() can't find device with id: " + id);
+        return null;
+    }
+
+    protected final void registerCallback(MediaDeviceCallback callback) {
+        if (!mCallbacks.contains(callback)) {
+            mCallbacks.add(callback);
+        }
+    }
+
+    protected final void unregisterCallback(MediaDeviceCallback callback) {
+        mCallbacks.remove(callback);
+    }
+
+    private void dispatchDeviceListAdded(@NonNull List<MediaDevice> devices) {
+        for (MediaDeviceCallback callback : getCallbacks()) {
+            callback.onDeviceListAdded(new ArrayList<>(devices));
+        }
+    }
+
+    private void dispatchConnectedDeviceChanged(String id) {
+        for (MediaDeviceCallback callback : getCallbacks()) {
+            callback.onConnectedDeviceChanged(id);
+        }
+    }
+
+    protected void dispatchOnRequestFailed(int reason) {
+        for (MediaDeviceCallback callback : getCallbacks()) {
+            callback.onRequestFailed(reason);
+        }
+    }
+
+    private Collection<MediaDeviceCallback> getCallbacks() {
+        return new CopyOnWriteArrayList<>(mCallbacks);
+    }
+
     /**
      * Get current device that played media.
      * @return MediaDevice
@@ -435,7 +508,7 @@
 
     protected final synchronized void refreshDevices() {
         rebuildDeviceList();
-        dispatchDeviceListAdded();
+        dispatchDeviceListAdded(mMediaDevices);
     }
 
     // MediaRoute2Info.getType was made public on API 34, but exists since API 30.
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 5925492..63056b6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -138,8 +138,7 @@
         }
 
         mInfoMediaManager =
-                InfoMediaManager.createInstance(
-                        context, packageName, notification, mLocalBluetoothManager);
+                InfoMediaManager.createInstance(context, packageName, mLocalBluetoothManager);
     }
 
     /**
@@ -505,9 +504,9 @@
         return new CopyOnWriteArrayList<>(mCallbacks);
     }
 
-    class MediaDeviceCallback implements MediaManager.MediaDeviceCallback {
+    class MediaDeviceCallback implements InfoMediaManager.MediaDeviceCallback {
         @Override
-        public void onDeviceListAdded(List<MediaDevice> devices) {
+        public void onDeviceListAdded(@NonNull List<MediaDevice> devices) {
             synchronized (mMediaDevicesLock) {
                 mMediaDevices.clear();
                 mMediaDevices.addAll(devices);
@@ -637,7 +636,7 @@
         }
 
         @Override
-        public void onDeviceListRemoved(List<MediaDevice> devices) {
+        public void onDeviceListRemoved(@NonNull List<MediaDevice> devices) {
             synchronized (mMediaDevicesLock) {
                 mMediaDevices.removeAll(devices);
             }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
index 453e807..c4fac35 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.media;
 
-import android.app.Notification;
 import android.content.Context;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2Manager;
@@ -54,9 +53,8 @@
     /* package */ ManagerInfoMediaManager(
             Context context,
             @NonNull String packageName,
-            Notification notification,
             LocalBluetoothManager localBluetoothManager) {
-        super(context, packageName, notification, localBluetoothManager);
+        super(context, packageName, localBluetoothManager);
 
         mRouterManager = MediaRouter2Manager.getInstance(context);
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
deleted file mode 100644
index dfbf23f..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.settingslib.media;
-
-import android.app.Notification;
-import android.content.Context;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-/**
- * MediaManager provide interface to get MediaDevice list.
- */
-public abstract class MediaManager {
-
-    private static final String TAG = "MediaManager";
-
-    protected final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
-    protected final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
-
-    protected Context mContext;
-    protected Notification mNotification;
-
-    MediaManager(Context context, Notification notification) {
-        mContext = context;
-        mNotification = notification;
-    }
-
-    protected void registerCallback(MediaDeviceCallback callback) {
-        if (!mCallbacks.contains(callback)) {
-            mCallbacks.add(callback);
-        }
-    }
-
-    protected void unregisterCallback(MediaDeviceCallback callback) {
-        if (mCallbacks.contains(callback)) {
-            mCallbacks.remove(callback);
-        }
-    }
-
-    /**
-     * Start scan connected MediaDevice
-     */
-    public abstract void startScan();
-
-    /**
-     * Stop scan MediaDevice
-     */
-    public abstract void stopScan();
-
-    protected MediaDevice findMediaDevice(String id) {
-        for (MediaDevice mediaDevice : mMediaDevices) {
-            if (mediaDevice.getId().equals(id)) {
-                return mediaDevice;
-            }
-        }
-        Log.e(TAG, "findMediaDevice() can't found device");
-        return null;
-    }
-
-    protected void dispatchDeviceListAdded() {
-        for (MediaDeviceCallback callback : getCallbacks()) {
-            callback.onDeviceListAdded(new ArrayList<>(mMediaDevices));
-        }
-    }
-
-    protected void dispatchDeviceListRemoved(List<MediaDevice> devices) {
-        for (MediaDeviceCallback callback : getCallbacks()) {
-            callback.onDeviceListRemoved(devices);
-        }
-    }
-
-    protected void dispatchConnectedDeviceChanged(String id) {
-        for (MediaDeviceCallback callback : getCallbacks()) {
-            callback.onConnectedDeviceChanged(id);
-        }
-    }
-
-    protected void dispatchOnRequestFailed(int reason) {
-        for (MediaDeviceCallback callback : getCallbacks()) {
-            callback.onRequestFailed(reason);
-        }
-    }
-
-    private Collection<MediaDeviceCallback> getCallbacks() {
-        return new CopyOnWriteArrayList<>(mCallbacks);
-    }
-
-    /**
-     * Callback for notifying device is added, removed and attributes changed.
-     */
-    public interface MediaDeviceCallback {
-
-        /**
-         * Callback for notifying MediaDevice list is added.
-         *
-         * @param devices the MediaDevice list
-         */
-        void onDeviceListAdded(List<MediaDevice> devices);
-
-        /**
-         * Callback for notifying MediaDevice list is removed.
-         *
-         * @param devices the MediaDevice list
-         */
-        void onDeviceListRemoved(List<MediaDevice> devices);
-
-        /**
-         * Callback for notifying connected MediaDevice is changed.
-         *
-         * @param id the id of MediaDevice
-         */
-        void onConnectedDeviceChanged(String id);
-
-        /**
-         * Callback for notifying that transferring is failed.
-         *
-         * @param reason the reason that the request has failed. Can be one of followings:
-         * {@link android.media.MediaRoute2ProviderService#REASON_UNKNOWN_ERROR},
-         * {@link android.media.MediaRoute2ProviderService#REASON_REJECTED},
-         * {@link android.media.MediaRoute2ProviderService#REASON_NETWORK_ERROR},
-         * {@link android.media.MediaRoute2ProviderService#REASON_ROUTE_NOT_AVAILABLE},
-         * {@link android.media.MediaRoute2ProviderService#REASON_INVALID_COMMAND},
-         */
-        void onRequestFailed(int reason);
-    }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
index ea4de39..2b8c2dd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.media;
 
-import android.app.Notification;
 import android.content.Context;
 import android.media.MediaRoute2Info;
 import android.media.RouteListingPreference;
@@ -38,13 +37,29 @@
  */
 // TODO - b/293578081: Remove once PackageNotAvailableException is propagated to library clients.
 /* package */ final class NoOpInfoMediaManager extends InfoMediaManager {
+    /**
+     * Placeholder routing session to return as active session of {@link NoOpInfoMediaManager}.
+     *
+     * <p>Returning this routing session avoids crashes in {@link InfoMediaManager} and maintains
+     * the same client-facing behaviour as if no routing session was found for the target package
+     * name.
+     *
+     * <p>Volume and max volume are set to {@code -1} to emulate a non-existing routing session in
+     * {@link #getSessionVolume()} and {@link #getSessionVolumeMax()}.
+     */
+    private static final RoutingSessionInfo PLACEHOLDER_SESSION =
+            new RoutingSessionInfo.Builder(
+                            /* id */ "FAKE_ROUTING_SESSION", /* clientPackageName */ "")
+                    .addSelectedRoute(/* routeId */ "FAKE_SELECTED_ROUTE_ID")
+                    .setVolumeMax(-1)
+                    .setVolume(-1)
+                    .build();
 
     NoOpInfoMediaManager(
             Context context,
             @NonNull String packageName,
-            Notification notification,
             LocalBluetoothManager localBluetoothManager) {
-        super(context, packageName, notification, localBluetoothManager);
+        super(context, packageName, localBluetoothManager);
     }
 
     @Override
@@ -120,7 +135,7 @@
     @NonNull
     @Override
     protected List<RoutingSessionInfo> getRoutingSessionsForPackage() {
-        return Collections.emptyList();
+        return List.of(PLACEHOLDER_SESSION);
     }
 
     @Nullable
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index df03167..9c82cb1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -17,7 +17,6 @@
 package com.android.settingslib.media;
 
 import android.annotation.SuppressLint;
-import android.app.Notification;
 import android.content.Context;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2;
@@ -71,10 +70,9 @@
     /* package */ RouterInfoMediaManager(
             Context context,
             @NonNull String packageName,
-            Notification notification,
             LocalBluetoothManager localBluetoothManager)
             throws PackageNotAvailableException {
-        super(context, packageName, notification, localBluetoothManager);
+        super(context, packageName, localBluetoothManager);
 
         MediaRouter2 router = null;
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt b/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt
index 2a4658b..7e3f38b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt
@@ -24,19 +24,38 @@
 interface SpatializerRepository {
 
     /**
+     * Returns true when head tracking is available for the [audioDeviceAttributes] and false the
+     * otherwise.
+     */
+    suspend fun isHeadTrackingAvailableForDevice(
+        audioDeviceAttributes: AudioDeviceAttributes
+    ): Boolean
+
+    /**
      * Returns true when Spatial audio feature is supported for the [audioDeviceAttributes] and
      * false the otherwise.
      */
-    suspend fun isAvailableForDevice(audioDeviceAttributes: AudioDeviceAttributes): Boolean
+    suspend fun isSpatialAudioAvailableForDevice(
+        audioDeviceAttributes: AudioDeviceAttributes
+    ): Boolean
 
     /** Returns a list [AudioDeviceAttributes] that are compatible with spatial audio. */
-    suspend fun getCompatibleDevices(): Collection<AudioDeviceAttributes>
+    suspend fun getSpatialAudioCompatibleDevices(): Collection<AudioDeviceAttributes>
 
-    /** Adds a [audioDeviceAttributes] to [getCompatibleDevices] list. */
-    suspend fun addCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes)
+    /** Adds a [audioDeviceAttributes] to [getSpatialAudioCompatibleDevices] list. */
+    suspend fun addSpatialAudioCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes)
 
-    /** Removes a [audioDeviceAttributes] to [getCompatibleDevices] list. */
-    suspend fun removeCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes)
+    /** Removes a [audioDeviceAttributes] from [getSpatialAudioCompatibleDevices] list. */
+    suspend fun removeSpatialAudioCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes)
+
+    /** Checks if the head tracking is enabled for the [audioDeviceAttributes]. */
+    suspend fun isHeadTrackingEnabled(audioDeviceAttributes: AudioDeviceAttributes): Boolean
+
+    /** Sets head tracking [isEnabled] for the [audioDeviceAttributes]. */
+    suspend fun setHeadTrackingEnabled(
+        audioDeviceAttributes: AudioDeviceAttributes,
+        isEnabled: Boolean,
+    )
 }
 
 class SpatializerRepositoryImpl(
@@ -44,7 +63,13 @@
     private val backgroundContext: CoroutineContext,
 ) : SpatializerRepository {
 
-    override suspend fun isAvailableForDevice(
+    override suspend fun isHeadTrackingAvailableForDevice(
+        audioDeviceAttributes: AudioDeviceAttributes
+    ): Boolean {
+        return withContext(backgroundContext) { spatializer.hasHeadTracker(audioDeviceAttributes) }
+    }
+
+    override suspend fun isSpatialAudioAvailableForDevice(
         audioDeviceAttributes: AudioDeviceAttributes
     ): Boolean {
         return withContext(backgroundContext) {
@@ -52,18 +77,36 @@
         }
     }
 
-    override suspend fun getCompatibleDevices(): Collection<AudioDeviceAttributes> =
+    override suspend fun getSpatialAudioCompatibleDevices(): Collection<AudioDeviceAttributes> =
         withContext(backgroundContext) { spatializer.compatibleAudioDevices }
 
-    override suspend fun addCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes) {
+    override suspend fun addSpatialAudioCompatibleDevice(
+        audioDeviceAttributes: AudioDeviceAttributes
+    ) {
         withContext(backgroundContext) {
             spatializer.addCompatibleAudioDevice(audioDeviceAttributes)
         }
     }
 
-    override suspend fun removeCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes) {
+    override suspend fun removeSpatialAudioCompatibleDevice(
+        audioDeviceAttributes: AudioDeviceAttributes
+    ) {
         withContext(backgroundContext) {
             spatializer.removeCompatibleAudioDevice(audioDeviceAttributes)
         }
     }
+
+    override suspend fun isHeadTrackingEnabled(
+        audioDeviceAttributes: AudioDeviceAttributes
+    ): Boolean =
+        withContext(backgroundContext) { spatializer.isHeadTrackerEnabled(audioDeviceAttributes) }
+
+    override suspend fun setHeadTrackingEnabled(
+        audioDeviceAttributes: AudioDeviceAttributes,
+        isEnabled: Boolean,
+    ) {
+        withContext(backgroundContext) {
+            spatializer.setHeadTrackerEnabled(isEnabled, audioDeviceAttributes)
+        }
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt
index c3cc340..5589733 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt
@@ -21,19 +21,37 @@
 
 class SpatializerInteractor(private val repository: SpatializerRepository) {
 
-    suspend fun isAvailable(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
-        repository.isAvailableForDevice(audioDeviceAttributes)
+    /** Checks if spatial audio is available. */
+    suspend fun isSpatialAudioAvailable(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
+        repository.isSpatialAudioAvailableForDevice(audioDeviceAttributes)
+
+    /** Checks if head tracking is available. */
+    suspend fun isHeadTrackingAvailable(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
+        repository.isHeadTrackingAvailableForDevice(audioDeviceAttributes)
 
     /** Checks if spatial audio is enabled for the [audioDeviceAttributes]. */
-    suspend fun isEnabled(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
-        repository.getCompatibleDevices().contains(audioDeviceAttributes)
+    suspend fun isSpatialAudioEnabled(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
+        repository.getSpatialAudioCompatibleDevices().contains(audioDeviceAttributes)
 
-    /** Enblaes or disables spatial audio for [audioDeviceAttributes]. */
-    suspend fun setEnabled(audioDeviceAttributes: AudioDeviceAttributes, isEnabled: Boolean) {
+    /** Enables or disables spatial audio for [audioDeviceAttributes]. */
+    suspend fun setSpatialAudioEnabled(
+        audioDeviceAttributes: AudioDeviceAttributes,
+        isEnabled: Boolean
+    ) {
         if (isEnabled) {
-            repository.addCompatibleDevice(audioDeviceAttributes)
+            repository.addSpatialAudioCompatibleDevice(audioDeviceAttributes)
         } else {
-            repository.removeCompatibleDevice(audioDeviceAttributes)
+            repository.removeSpatialAudioCompatibleDevice(audioDeviceAttributes)
         }
     }
+
+    /** Checks if head tracking is enabled for the [audioDeviceAttributes]. */
+    suspend fun isHeadTrackingEnabled(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
+        repository.isHeadTrackingEnabled(audioDeviceAttributes)
+
+    /** Enables or disables head tracking for the [audioDeviceAttributes]. */
+    suspend fun setHeadTrackingEnabled(
+        audioDeviceAttributes: AudioDeviceAttributes,
+        isEnabled: Boolean,
+    ) = repository.setHeadTrackingEnabled(audioDeviceAttributes, isEnabled)
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
index 840c936..b7108c9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
@@ -236,7 +236,8 @@
             // Handle specific carrier config values for the default data SIM
             int defaultDataSubId = SubscriptionManager.from(context)
                     .getDefaultDataSubscriptionId();
-            PersistableBundle b = configMgr.getConfigForSubId(defaultDataSubId);
+            PersistableBundle b = configMgr == null ? null
+                        : configMgr.getConfigForSubId(defaultDataSubId);
             if (b != null) {
                 config.alwaysShowDataRatIcon = b.getBoolean(
                         CarrierConfigManager.KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL);
diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt
index 794cf83..7719c4b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt
@@ -48,6 +48,9 @@
     /** Checks if [notificationPolicy] allows media. */
     val isMediaAllowed: Flow<Boolean?> = notificationPolicy.map { it?.allowMedia() }
 
+    /** Checks if [notificationPolicy] allows system sounds. */
+    val isSystemAllowed: Flow<Boolean?> = notificationPolicy.map { it?.allowSystem() }
+
     /** Checks if [notificationPolicy] allows ringer. */
     val isRingerAllowed: Flow<Boolean?> =
         notificationPolicy.map { policy ->
@@ -62,31 +65,29 @@
             areAlarmsAllowed.filterNotNull(),
             isMediaAllowed.filterNotNull(),
             isRingerAllowed.filterNotNull(),
-        ) { zenMode, areAlarmsAllowed, isMediaAllowed, isRingerAllowed ->
-            if (zenMode.zenMode == Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) {
-                return@combine true
+            isSystemAllowed.filterNotNull(),
+        ) { zenMode, areAlarmsAllowed, isMediaAllowed, isRingerAllowed, isSystemAllowed ->
+            when (zenMode.zenMode) {
+                // Everything is muted
+                Settings.Global.ZEN_MODE_NO_INTERRUPTIONS -> return@combine true
+                Settings.Global.ZEN_MODE_ALARMS ->
+                    return@combine stream.value == AudioManager.STREAM_RING ||
+                        stream.value == AudioManager.STREAM_NOTIFICATION ||
+                        stream.value == AudioManager.STREAM_SYSTEM
+                Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS -> {
+                    when {
+                        stream.value == AudioManager.STREAM_ALARM && !areAlarmsAllowed ->
+                            return@combine true
+                        stream.value == AudioManager.STREAM_MUSIC && !isMediaAllowed ->
+                            return@combine true
+                        stream.value == AudioManager.STREAM_SYSTEM && !isSystemAllowed ->
+                            return@combine true
+                        (stream.value == AudioManager.STREAM_RING ||
+                            stream.value == AudioManager.STREAM_NOTIFICATION) && !isRingerAllowed ->
+                            return@combine true
+                    }
+                }
             }
-
-            val isNotificationOrRing =
-                stream.value == AudioManager.STREAM_RING ||
-                    stream.value == AudioManager.STREAM_NOTIFICATION
-            if (isNotificationOrRing && zenMode.zenMode == Settings.Global.ZEN_MODE_ALARMS) {
-                return@combine true
-            }
-            if (zenMode.zenMode != Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
-                return@combine false
-            }
-
-            if (stream.value == AudioManager.STREAM_ALARM && !areAlarmsAllowed) {
-                return@combine true
-            }
-            if (stream.value == AudioManager.STREAM_MUSIC && !isMediaAllowed) {
-                return@combine true
-            }
-            if (isNotificationOrRing && !isRingerAllowed) {
-                return@combine true
-            }
-
             return@combine false
         }
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
index 53daef1..69c7410 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
@@ -242,6 +242,7 @@
                         .setMessage(messageResId)
                         .setNegativeButtonText(R.string.cancel)
                         .setPositiveButtonText(R.string.next);
+                mCustomDialogHelper.requestFocusOnTitle();
                 break;
             case GRANT_ADMIN_DIALOG:
                 mEditUserInfoView.setVisibility(View.GONE);
@@ -254,6 +255,7 @@
                         .setMessage(R.string.user_grant_admin_message)
                         .setNegativeButtonText(R.string.back)
                         .setPositiveButtonText(R.string.next);
+                mCustomDialogHelper.requestFocusOnTitle();
                 if (mIsAdmin == null) {
                     mCustomDialogHelper.setButtonEnabled(false);
                 }
@@ -265,6 +267,7 @@
                         .setTitle(R.string.user_info_settings_title)
                         .setNegativeButtonText(R.string.back)
                         .setPositiveButtonText(R.string.done);
+                mCustomDialogHelper.requestFocusOnTitle();
                 mEditUserInfoView.setVisibility(View.VISIBLE);
                 mGrantAdminView.setVisibility(View.GONE);
                 break;
@@ -273,7 +276,6 @@
                         && mEditUserPhotoController.getNewUserPhotoDrawable() != null)
                         ? mEditUserPhotoController.getNewUserPhotoDrawable()
                         : mSavedDrawable;
-
                 String newName = mUserNameView.getText().toString().trim();
                 String defaultName = mActivity.getString(R.string.user_new_user_name);
                 mUserName = !newName.isEmpty() ? newName : defaultName;
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
index 5201b3d..4cf3bc2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
@@ -23,6 +23,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
 import android.widget.Button;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -282,4 +283,13 @@
         }
         return this;
     }
+
+    /**
+     * Requests focus on dialog title when used. Used to let talkback know that the dialog content
+     * is updated and needs to be read from the beginning.
+     */
+    public void requestFocusOnTitle() {
+        mDialogTitle.requestFocus();
+        mDialogTitle.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
index 56b0bf7..1586b8f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
@@ -23,8 +23,8 @@
 import com.android.settingslib.volume.shared.model.AudioStreamModel
 import com.android.settingslib.volume.shared.model.RingerMode
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 
 /** Provides audio stream state and an ability to change it */
@@ -43,6 +43,9 @@
             streamModel.copy(volume = processVolume(streamModel, ringerMode, isZenMuted))
         }
 
+    val ringerMode: StateFlow<RingerMode>
+        get() = audioRepository.ringerMode
+
     suspend fun setVolume(audioStream: AudioStream, volume: Int) =
         audioRepository.setVolume(audioStream, volume)
 
@@ -52,9 +55,14 @@
     /** Checks if the volume can be changed via the UI. */
     fun canChangeVolume(audioStream: AudioStream): Flow<Boolean> {
         return if (audioStream.value == AudioManager.STREAM_NOTIFICATION) {
-            getAudioStream(AudioStream(AudioManager.STREAM_RING)).map { !it.isMuted }
+            combine(
+                notificationsSoundPolicyInteractor.isZenMuted(audioStream),
+                getAudioStream(AudioStream(AudioManager.STREAM_RING)).map { it.isMuted },
+            ) { isZenMuted, isRingMuted ->
+                !isZenMuted && !isRingMuted
+            }
         } else {
-            flowOf(true)
+            notificationsSoundPolicyInteractor.isZenMuted(audioStream).map { !it }
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
deleted file mode 100644
index b9a4647..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
+++ /dev/null
@@ -1,417 +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.wifi;
-
-import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED;
-import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.getMaxNetworkSelectionDisableReason;
-
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.icu.text.MessageFormat;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.util.Log;
-
-import androidx.annotation.VisibleForTesting;
-
-import com.android.settingslib.R;
-
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-
-public class WifiUtils {
-
-    private static final String TAG = "WifiUtils";
-
-    private static final int INVALID_RSSI = -127;
-
-    /**
-     * The intent action shows Wi-Fi dialog to connect Wi-Fi network.
-     * <p>
-     * Input: The calling package should put the chosen
-     * com.android.wifitrackerlib.WifiEntry#getKey() to a string extra in the request bundle into
-     * the {@link #EXTRA_CHOSEN_WIFI_ENTRY_KEY}.
-     * <p>
-     * Output: Nothing.
-     */
-    @VisibleForTesting
-    static final String ACTION_WIFI_DIALOG = "com.android.settings.WIFI_DIALOG";
-
-    /**
-     * Specify a key that indicates the WifiEntry to be configured.
-     */
-    @VisibleForTesting
-    static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key";
-
-    /**
-     * The lookup key for a boolean that indicates whether a chosen WifiEntry request to connect to.
-     * {@code true} means a chosen WifiEntry request to connect to.
-     */
-    @VisibleForTesting
-    static final String EXTRA_CONNECT_FOR_CALLER = "connect_for_caller";
-
-    /**
-     * The intent action shows network details settings to allow configuration of Wi-Fi.
-     * <p>
-     * In some cases, a matching Activity may not exist, so ensure you
-     * safeguard against this.
-     * <p>
-     * Input: The calling package should put the chosen
-     * com.android.wifitrackerlib.WifiEntry#getKey() to a string extra in the request bundle into
-     * the {@link #KEY_CHOSEN_WIFIENTRY_KEY}.
-     * <p>
-     * Output: Nothing.
-     */
-    public static final String ACTION_WIFI_DETAILS_SETTINGS =
-            "android.settings.WIFI_DETAILS_SETTINGS";
-    public static final String KEY_CHOSEN_WIFIENTRY_KEY = "key_chosen_wifientry_key";
-    public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
-
-    static final int[] WIFI_PIE = {
-            com.android.internal.R.drawable.ic_wifi_signal_0,
-            com.android.internal.R.drawable.ic_wifi_signal_1,
-            com.android.internal.R.drawable.ic_wifi_signal_2,
-            com.android.internal.R.drawable.ic_wifi_signal_3,
-            com.android.internal.R.drawable.ic_wifi_signal_4
-    };
-
-    static final int[] NO_INTERNET_WIFI_PIE = {
-            R.drawable.ic_no_internet_wifi_signal_0,
-            R.drawable.ic_no_internet_wifi_signal_1,
-            R.drawable.ic_no_internet_wifi_signal_2,
-            R.drawable.ic_no_internet_wifi_signal_3,
-            R.drawable.ic_no_internet_wifi_signal_4
-    };
-
-    public static String buildLoggingSummary(AccessPoint accessPoint, WifiConfiguration config) {
-        final StringBuilder summary = new StringBuilder();
-        final WifiInfo info = accessPoint.getInfo();
-        // Add RSSI/band information for this config, what was seen up to 6 seconds ago
-        // verbose WiFi Logging is only turned on thru developers settings
-        if (accessPoint.isActive() && info != null) {
-            summary.append(" f=" + Integer.toString(info.getFrequency()));
-        }
-        summary.append(" " + getVisibilityStatus(accessPoint));
-        if (config != null
-                && (config.getNetworkSelectionStatus().getNetworkSelectionStatus()
-                        != NETWORK_SELECTION_ENABLED)) {
-            summary.append(" (" + config.getNetworkSelectionStatus().getNetworkStatusString());
-            if (config.getNetworkSelectionStatus().getDisableTime() > 0) {
-                long now = System.currentTimeMillis();
-                long diff = (now - config.getNetworkSelectionStatus().getDisableTime()) / 1000;
-                long sec = diff % 60; //seconds
-                long min = (diff / 60) % 60; //minutes
-                long hour = (min / 60) % 60; //hours
-                summary.append(", ");
-                if (hour > 0) summary.append(Long.toString(hour) + "h ");
-                summary.append(Long.toString(min) + "m ");
-                summary.append(Long.toString(sec) + "s ");
-            }
-            summary.append(")");
-        }
-
-        if (config != null) {
-            NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
-            for (int reason = 0; reason <= getMaxNetworkSelectionDisableReason(); reason++) {
-                if (networkStatus.getDisableReasonCounter(reason) != 0) {
-                    summary.append(" ")
-                            .append(NetworkSelectionStatus
-                                    .getNetworkSelectionDisableReasonString(reason))
-                            .append("=")
-                            .append(networkStatus.getDisableReasonCounter(reason));
-                }
-            }
-        }
-
-        return summary.toString();
-    }
-
-    /**
-     * Returns the visibility status of the WifiConfiguration.
-     *
-     * @return autojoin debugging information
-     * TODO: use a string formatter
-     * ["rssi 5Ghz", "num results on 5GHz" / "rssi 5Ghz", "num results on 5GHz"]
-     * For instance [-40,5/-30,2]
-     */
-    @VisibleForTesting
-    static String getVisibilityStatus(AccessPoint accessPoint) {
-        final WifiInfo info = accessPoint.getInfo();
-        StringBuilder visibility = new StringBuilder();
-        StringBuilder scans24GHz = new StringBuilder();
-        StringBuilder scans5GHz = new StringBuilder();
-        StringBuilder scans60GHz = new StringBuilder();
-        String bssid = null;
-
-        if (accessPoint.isActive() && info != null) {
-            bssid = info.getBSSID();
-            if (bssid != null) {
-                visibility.append(" ").append(bssid);
-            }
-            visibility.append(" standard = ").append(info.getWifiStandard());
-            visibility.append(" rssi=").append(info.getRssi());
-            visibility.append(" ");
-            visibility.append(" score=").append(info.getScore());
-            if (accessPoint.getSpeed() != AccessPoint.Speed.NONE) {
-                visibility.append(" speed=").append(accessPoint.getSpeedLabel());
-            }
-            visibility.append(String.format(" tx=%.1f,", info.getSuccessfulTxPacketsPerSecond()));
-            visibility.append(String.format("%.1f,", info.getRetriedTxPacketsPerSecond()));
-            visibility.append(String.format("%.1f ", info.getLostTxPacketsPerSecond()));
-            visibility.append(String.format("rx=%.1f", info.getSuccessfulRxPacketsPerSecond()));
-        }
-
-        int maxRssi5 = INVALID_RSSI;
-        int maxRssi24 = INVALID_RSSI;
-        int maxRssi60 = INVALID_RSSI;
-        final int maxDisplayedScans = 4;
-        int num5 = 0; // number of scanned BSSID on 5GHz band
-        int num24 = 0; // number of scanned BSSID on 2.4Ghz band
-        int num60 = 0; // number of scanned BSSID on 60Ghz band
-        int numBlockListed = 0;
-
-        // TODO: sort list by RSSI or age
-        long nowMs = SystemClock.elapsedRealtime();
-        for (ScanResult result : accessPoint.getScanResults()) {
-            if (result == null) {
-                continue;
-            }
-            if (result.frequency >= AccessPoint.LOWER_FREQ_5GHZ
-                    && result.frequency <= AccessPoint.HIGHER_FREQ_5GHZ) {
-                // Strictly speaking: [4915, 5825]
-                num5++;
-
-                if (result.level > maxRssi5) {
-                    maxRssi5 = result.level;
-                }
-                if (num5 <= maxDisplayedScans) {
-                    scans5GHz.append(
-                            verboseScanResultSummary(accessPoint, result, bssid,
-                                    nowMs));
-                }
-            } else if (result.frequency >= AccessPoint.LOWER_FREQ_24GHZ
-                    && result.frequency <= AccessPoint.HIGHER_FREQ_24GHZ) {
-                // Strictly speaking: [2412, 2482]
-                num24++;
-
-                if (result.level > maxRssi24) {
-                    maxRssi24 = result.level;
-                }
-                if (num24 <= maxDisplayedScans) {
-                    scans24GHz.append(
-                            verboseScanResultSummary(accessPoint, result, bssid,
-                                    nowMs));
-                }
-            } else if (result.frequency >= AccessPoint.LOWER_FREQ_60GHZ
-                    && result.frequency <= AccessPoint.HIGHER_FREQ_60GHZ) {
-                // Strictly speaking: [60000, 61000]
-                num60++;
-
-                if (result.level > maxRssi60) {
-                    maxRssi60 = result.level;
-                }
-                if (num60 <= maxDisplayedScans) {
-                    scans60GHz.append(
-                            verboseScanResultSummary(accessPoint, result, bssid,
-                                    nowMs));
-                }
-            }
-        }
-        visibility.append(" [");
-        if (num24 > 0) {
-            visibility.append("(").append(num24).append(")");
-            if (num24 > maxDisplayedScans) {
-                visibility.append("max=").append(maxRssi24).append(",");
-            }
-            visibility.append(scans24GHz.toString());
-        }
-        visibility.append(";");
-        if (num5 > 0) {
-            visibility.append("(").append(num5).append(")");
-            if (num5 > maxDisplayedScans) {
-                visibility.append("max=").append(maxRssi5).append(",");
-            }
-            visibility.append(scans5GHz.toString());
-        }
-        visibility.append(";");
-        if (num60 > 0) {
-            visibility.append("(").append(num60).append(")");
-            if (num60 > maxDisplayedScans) {
-                visibility.append("max=").append(maxRssi60).append(",");
-            }
-            visibility.append(scans60GHz.toString());
-        }
-        if (numBlockListed > 0) {
-            visibility.append("!").append(numBlockListed);
-        }
-        visibility.append("]");
-
-        return visibility.toString();
-    }
-
-    @VisibleForTesting
-    /* package */ static String verboseScanResultSummary(AccessPoint accessPoint, ScanResult result,
-            String bssid, long nowMs) {
-        StringBuilder stringBuilder = new StringBuilder();
-        stringBuilder.append(" \n{").append(result.BSSID);
-        if (result.BSSID.equals(bssid)) {
-            stringBuilder.append("*");
-        }
-        stringBuilder.append("=").append(result.frequency);
-        stringBuilder.append(",").append(result.level);
-        int speed = getSpecificApSpeed(result, accessPoint.getScoredNetworkCache());
-        if (speed != AccessPoint.Speed.NONE) {
-            stringBuilder.append(",")
-                    .append(accessPoint.getSpeedLabel(speed));
-        }
-        int ageSeconds = (int) (nowMs - result.timestamp / 1000) / 1000;
-        stringBuilder.append(",").append(ageSeconds).append("s");
-        stringBuilder.append("}");
-        return stringBuilder.toString();
-    }
-
-    @AccessPoint.Speed
-    private static int getSpecificApSpeed(ScanResult result,
-            Map<String, TimestampedScoredNetwork> scoredNetworkCache) {
-        TimestampedScoredNetwork timedScore = scoredNetworkCache.get(result.BSSID);
-        if (timedScore == null) {
-            return AccessPoint.Speed.NONE;
-        }
-        // For debugging purposes we may want to use mRssi rather than result.level as the average
-        // speed wil be determined by mRssi
-        return timedScore.getScore().calculateBadge(result.level);
-    }
-
-    public static String getMeteredLabel(Context context, WifiConfiguration config) {
-        // meteredOverride is whether the user manually set the metered setting or not.
-        // meteredHint is whether the network itself is telling us that it is metered
-        if (config.meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED
-                || (config.meteredHint && !isMeteredOverridden(config))) {
-            return context.getString(R.string.wifi_metered_label);
-        }
-        return context.getString(R.string.wifi_unmetered_label);
-    }
-
-    /**
-     * Returns the Internet icon resource for a given RSSI level.
-     *
-     * @param level The number of bars to show (0-4)
-     * @param noInternet True if a connected Wi-Fi network cannot access the Internet
-     */
-    public static int getInternetIconResource(int level, boolean noInternet) {
-        int wifiLevel = level;
-        if (wifiLevel < 0) {
-            Log.e(TAG, "Wi-Fi level is out of range! level:" + level);
-            wifiLevel = 0;
-        } else if (level >= WIFI_PIE.length) {
-            Log.e(TAG, "Wi-Fi level is out of range! level:" + level);
-            wifiLevel = WIFI_PIE.length - 1;
-        }
-        return noInternet ? NO_INTERNET_WIFI_PIE[wifiLevel] : WIFI_PIE[wifiLevel];
-    }
-
-    /**
-     * Returns the Hotspot network icon resource.
-     *
-     * @param deviceType The device type of Hotspot network
-     */
-    public static int getHotspotIconResource(int deviceType) {
-        return switch (deviceType) {
-            case NetworkProviderInfo.DEVICE_TYPE_PHONE -> R.drawable.ic_hotspot_phone;
-            case NetworkProviderInfo.DEVICE_TYPE_TABLET -> R.drawable.ic_hotspot_tablet;
-            case NetworkProviderInfo.DEVICE_TYPE_LAPTOP -> R.drawable.ic_hotspot_laptop;
-            case NetworkProviderInfo.DEVICE_TYPE_WATCH -> R.drawable.ic_hotspot_watch;
-            case NetworkProviderInfo.DEVICE_TYPE_AUTO -> R.drawable.ic_hotspot_auto;
-            default -> R.drawable.ic_hotspot_phone;  // Return phone icon as default.
-        };
-    }
-
-    /**
-     * Wrapper the {@link #getInternetIconResource} for testing compatibility.
-     */
-    public static class InternetIconInjector {
-
-        protected final Context mContext;
-
-        public InternetIconInjector(Context context) {
-            mContext = context;
-        }
-
-        /**
-         * Returns the Internet icon for a given RSSI level.
-         *
-         * @param noInternet True if a connected Wi-Fi network cannot access the Internet
-         * @param level The number of bars to show (0-4)
-         */
-        public Drawable getIcon(boolean noInternet, int level) {
-            return mContext.getDrawable(WifiUtils.getInternetIconResource(level, noInternet));
-        }
-    }
-
-    public static boolean isMeteredOverridden(WifiConfiguration config) {
-        return config.meteredOverride != WifiConfiguration.METERED_OVERRIDE_NONE;
-    }
-
-    /**
-     * Returns the Intent for Wi-Fi dialog.
-     *
-     * @param key              The Wi-Fi entry key
-     * @param connectForCaller True if a chosen WifiEntry request to connect to
-     */
-    public static Intent getWifiDialogIntent(String key, boolean connectForCaller) {
-        final Intent intent = new Intent(ACTION_WIFI_DIALOG);
-        intent.putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, key);
-        intent.putExtra(EXTRA_CONNECT_FOR_CALLER, connectForCaller);
-        return intent;
-    }
-
-    /**
-     * Returns the Intent for Wi-Fi network details settings.
-     *
-     * @param key The Wi-Fi entry key
-     */
-    public static Intent getWifiDetailsSettingsIntent(String key) {
-        final Intent intent = new Intent(ACTION_WIFI_DETAILS_SETTINGS);
-        final Bundle bundle = new Bundle();
-        bundle.putString(KEY_CHOSEN_WIFIENTRY_KEY, key);
-        intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, bundle);
-        return intent;
-    }
-
-    /**
-     * Returns the string of Wi-Fi tethering summary for connected devices.
-     *
-     * @param context          The application context
-     * @param connectedDevices The count of connected devices
-     */
-    public static String getWifiTetherSummaryForConnectedDevices(Context context,
-            int connectedDevices) {
-        MessageFormat msgFormat = new MessageFormat(
-                context.getResources().getString(R.string.wifi_tether_connected_summary),
-                Locale.getDefault());
-        Map<String, Object> arguments = new HashMap<>();
-        arguments.put("count", connectedDevices);
-        return msgFormat.format(arguments);
-    }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
new file mode 100644
index 0000000..d5444cf
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
@@ -0,0 +1,506 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.wifi
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.drawable.Drawable
+import android.icu.text.MessageFormat
+import android.net.wifi.ScanResult
+import android.net.wifi.WifiConfiguration
+import android.net.wifi.WifiConfiguration.NetworkSelectionStatus
+import android.net.wifi.WifiManager
+import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo
+import android.os.Bundle
+import android.os.SystemClock
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import com.android.settingslib.R
+import com.android.settingslib.flags.Flags.newStatusBarIcons
+import java.util.Locale
+import kotlin.coroutines.resume
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
+
+
+open class WifiUtils {
+    /**
+     * Wrapper the [.getInternetIconResource] for testing compatibility.
+     */
+    open class InternetIconInjector(protected val context: Context) {
+        /**
+         * Returns the Internet icon for a given RSSI level.
+         *
+         * @param noInternet True if a connected Wi-Fi network cannot access the Internet
+         * @param level The number of bars to show (0-4)
+         */
+        open fun getIcon(noInternet: Boolean, level: Int): Drawable? {
+            return context.getDrawable(getInternetIconResource(level, noInternet))
+        }
+    }
+
+    companion object {
+        private const val TAG = "WifiUtils"
+        private const val INVALID_RSSI = -127
+
+        /**
+         * The intent action shows Wi-Fi dialog to connect Wi-Fi network.
+         *
+         *
+         * Input: The calling package should put the chosen
+         * com.android.wifitrackerlib.WifiEntry#getKey() to a string extra in the request bundle into
+         * the [.EXTRA_CHOSEN_WIFI_ENTRY_KEY].
+         *
+         *
+         * Output: Nothing.
+         */
+        @JvmField
+        @VisibleForTesting
+        val ACTION_WIFI_DIALOG = "com.android.settings.WIFI_DIALOG"
+
+        /**
+         * Specify a key that indicates the WifiEntry to be configured.
+         */
+        @JvmField
+        @VisibleForTesting
+        val EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key"
+
+        /**
+         * The lookup key for a boolean that indicates whether a chosen WifiEntry request to connect to.
+         * `true` means a chosen WifiEntry request to connect to.
+         */
+        @JvmField
+        @VisibleForTesting
+        val EXTRA_CONNECT_FOR_CALLER = "connect_for_caller"
+
+        /**
+         * The intent action shows network details settings to allow configuration of Wi-Fi.
+         *
+         *
+         * In some cases, a matching Activity may not exist, so ensure you
+         * safeguard against this.
+         *
+         *
+         * Input: The calling package should put the chosen
+         * com.android.wifitrackerlib.WifiEntry#getKey() to a string extra in the request bundle into
+         * the [.KEY_CHOSEN_WIFIENTRY_KEY].
+         *
+         *
+         * Output: Nothing.
+         */
+        const val ACTION_WIFI_DETAILS_SETTINGS = "android.settings.WIFI_DETAILS_SETTINGS"
+        const val KEY_CHOSEN_WIFIENTRY_KEY = "key_chosen_wifientry_key"
+        const val EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args"
+
+        @JvmField
+        val WIFI_PIE = getIconsBasedOnFlag()
+
+        private fun getIconsBasedOnFlag(): IntArray {
+            return if (newStatusBarIcons()) {
+                intArrayOf(
+                    R.drawable.ic_wifi_0,
+                    R.drawable.ic_wifi_1,
+                    R.drawable.ic_wifi_2,
+                    R.drawable.ic_wifi_3,
+                    R.drawable.ic_wifi_4
+                )
+            } else {
+                intArrayOf(
+                    com.android.internal.R.drawable.ic_wifi_signal_0,
+                    com.android.internal.R.drawable.ic_wifi_signal_1,
+                    com.android.internal.R.drawable.ic_wifi_signal_2,
+                    com.android.internal.R.drawable.ic_wifi_signal_3,
+                    com.android.internal.R.drawable.ic_wifi_signal_4
+                )
+            }
+        }
+
+        val NO_INTERNET_WIFI_PIE = getErrorIconsBasedOnFlag()
+
+        private fun getErrorIconsBasedOnFlag(): IntArray {
+            return if (newStatusBarIcons()) {
+                intArrayOf(
+                    R.drawable.ic_wifi_0_error,
+                    R.drawable.ic_wifi_1_error,
+                    R.drawable.ic_wifi_2_error,
+                    R.drawable.ic_wifi_3_error,
+                    R.drawable.ic_wifi_4_error
+                )
+            } else {
+                intArrayOf(
+                    R.drawable.ic_no_internet_wifi_signal_0,
+                    R.drawable.ic_no_internet_wifi_signal_1,
+                    R.drawable.ic_no_internet_wifi_signal_2,
+                    R.drawable.ic_no_internet_wifi_signal_3,
+                    R.drawable.ic_no_internet_wifi_signal_4
+                )
+            }
+        }
+
+        @JvmStatic
+        fun buildLoggingSummary(accessPoint: AccessPoint, config: WifiConfiguration?): String {
+            val summary = StringBuilder()
+            val info = accessPoint.info
+            // Add RSSI/band information for this config, what was seen up to 6 seconds ago
+            // verbose WiFi Logging is only turned on thru developers settings
+            if (accessPoint.isActive && info != null) {
+                summary.append(" f=" + info.frequency.toString())
+            }
+            summary.append(" " + getVisibilityStatus(accessPoint))
+            if (config != null && (config.networkSelectionStatus.networkSelectionStatus
+                    != NetworkSelectionStatus.NETWORK_SELECTION_ENABLED)
+            ) {
+                summary.append(" (" + config.networkSelectionStatus.networkStatusString)
+                if (config.networkSelectionStatus.disableTime > 0) {
+                    val now = System.currentTimeMillis()
+                    val diff = (now - config.networkSelectionStatus.disableTime) / 1000
+                    val sec = diff % 60 // seconds
+                    val min = diff / 60 % 60 // minutes
+                    val hour = min / 60 % 60 // hours
+                    summary.append(", ")
+                    if (hour > 0) summary.append(hour.toString() + "h ")
+                    summary.append(min.toString() + "m ")
+                    summary.append(sec.toString() + "s ")
+                }
+                summary.append(")")
+            }
+            if (config != null) {
+                val networkStatus = config.networkSelectionStatus
+                for (reason in 0..NetworkSelectionStatus.getMaxNetworkSelectionDisableReason()) {
+                    if (networkStatus.getDisableReasonCounter(reason) != 0) {
+                        summary.append(" ")
+                            .append(
+                                NetworkSelectionStatus
+                                    .getNetworkSelectionDisableReasonString(reason)
+                            )
+                            .append("=")
+                            .append(networkStatus.getDisableReasonCounter(reason))
+                    }
+                }
+            }
+            return summary.toString()
+        }
+
+        /**
+         * Returns the visibility status of the WifiConfiguration.
+         *
+         * @return autojoin debugging information
+         * TODO: use a string formatter
+         * ["rssi 5Ghz", "num results on 5GHz" / "rssi 5Ghz", "num results on 5GHz"]
+         * For instance [-40,5/-30,2]
+         */
+        @JvmStatic
+        @VisibleForTesting
+        fun getVisibilityStatus(accessPoint: AccessPoint): String {
+            val info = accessPoint.info
+            val visibility = StringBuilder()
+            val scans24GHz = StringBuilder()
+            val scans5GHz = StringBuilder()
+            val scans60GHz = StringBuilder()
+            var bssid: String? = null
+            if (accessPoint.isActive && info != null) {
+                bssid = info.bssid
+                if (bssid != null) {
+                    visibility.append(" ").append(bssid)
+                }
+                visibility.append(" standard = ").append(info.wifiStandard)
+                visibility.append(" rssi=").append(info.rssi)
+                visibility.append(" ")
+                visibility.append(" score=").append(info.getScore())
+                if (accessPoint.speed != AccessPoint.Speed.NONE) {
+                    visibility.append(" speed=").append(accessPoint.speedLabel)
+                }
+                visibility.append(String.format(" tx=%.1f,", info.successfulTxPacketsPerSecond))
+                visibility.append(String.format("%.1f,", info.retriedTxPacketsPerSecond))
+                visibility.append(String.format("%.1f ", info.lostTxPacketsPerSecond))
+                visibility.append(String.format("rx=%.1f", info.successfulRxPacketsPerSecond))
+            }
+            var maxRssi5 = INVALID_RSSI
+            var maxRssi24 = INVALID_RSSI
+            var maxRssi60 = INVALID_RSSI
+            val maxDisplayedScans = 4
+            var num5 = 0 // number of scanned BSSID on 5GHz band
+            var num24 = 0 // number of scanned BSSID on 2.4Ghz band
+            var num60 = 0 // number of scanned BSSID on 60Ghz band
+            val numBlockListed = 0
+
+            // TODO: sort list by RSSI or age
+            val nowMs = SystemClock.elapsedRealtime()
+            for (result in accessPoint.getScanResults()) {
+                if (result == null) {
+                    continue
+                }
+                if (result.frequency >= AccessPoint.LOWER_FREQ_5GHZ &&
+                    result.frequency <= AccessPoint.HIGHER_FREQ_5GHZ
+                ) {
+                    // Strictly speaking: [4915, 5825]
+                    num5++
+                    if (result.level > maxRssi5) {
+                        maxRssi5 = result.level
+                    }
+                    if (num5 <= maxDisplayedScans) {
+                        scans5GHz.append(
+                            verboseScanResultSummary(
+                                accessPoint, result, bssid,
+                                nowMs
+                            )
+                        )
+                    }
+                } else if (result.frequency >= AccessPoint.LOWER_FREQ_24GHZ &&
+                    result.frequency <= AccessPoint.HIGHER_FREQ_24GHZ
+                ) {
+                    // Strictly speaking: [2412, 2482]
+                    num24++
+                    if (result.level > maxRssi24) {
+                        maxRssi24 = result.level
+                    }
+                    if (num24 <= maxDisplayedScans) {
+                        scans24GHz.append(
+                            verboseScanResultSummary(
+                                accessPoint, result, bssid,
+                                nowMs
+                            )
+                        )
+                    }
+                } else if (result.frequency >= AccessPoint.LOWER_FREQ_60GHZ &&
+                    result.frequency <= AccessPoint.HIGHER_FREQ_60GHZ
+                ) {
+                    // Strictly speaking: [60000, 61000]
+                    num60++
+                    if (result.level > maxRssi60) {
+                        maxRssi60 = result.level
+                    }
+                    if (num60 <= maxDisplayedScans) {
+                        scans60GHz.append(
+                            verboseScanResultSummary(
+                                accessPoint, result, bssid,
+                                nowMs
+                            )
+                        )
+                    }
+                }
+            }
+            visibility.append(" [")
+            if (num24 > 0) {
+                visibility.append("(").append(num24).append(")")
+                if (num24 > maxDisplayedScans) {
+                    visibility.append("max=").append(maxRssi24).append(",")
+                }
+                visibility.append(scans24GHz.toString())
+            }
+            visibility.append(";")
+            if (num5 > 0) {
+                visibility.append("(").append(num5).append(")")
+                if (num5 > maxDisplayedScans) {
+                    visibility.append("max=").append(maxRssi5).append(",")
+                }
+                visibility.append(scans5GHz.toString())
+            }
+            visibility.append(";")
+            if (num60 > 0) {
+                visibility.append("(").append(num60).append(")")
+                if (num60 > maxDisplayedScans) {
+                    visibility.append("max=").append(maxRssi60).append(",")
+                }
+                visibility.append(scans60GHz.toString())
+            }
+            if (numBlockListed > 0) {
+                visibility.append("!").append(numBlockListed)
+            }
+            visibility.append("]")
+            return visibility.toString()
+        }
+
+        @JvmStatic
+        @VisibleForTesting /* package */ fun verboseScanResultSummary(
+            accessPoint: AccessPoint,
+            result: ScanResult,
+            bssid: String?,
+            nowMs: Long
+        ): String {
+            val stringBuilder = StringBuilder()
+            stringBuilder.append(" \n{").append(result.BSSID)
+            if (result.BSSID == bssid) {
+                stringBuilder.append("*")
+            }
+            stringBuilder.append("=").append(result.frequency)
+            stringBuilder.append(",").append(result.level)
+            val speed = getSpecificApSpeed(result, accessPoint.scoredNetworkCache)
+            if (speed != AccessPoint.Speed.NONE) {
+                stringBuilder.append(",")
+                    .append(accessPoint.getSpeedLabel(speed))
+            }
+            val ageSeconds = (nowMs - result.timestamp / 1000).toInt() / 1000
+            stringBuilder.append(",").append(ageSeconds).append("s")
+            stringBuilder.append("}")
+            return stringBuilder.toString()
+        }
+
+        @AccessPoint.Speed
+        private fun getSpecificApSpeed(
+            result: ScanResult,
+            scoredNetworkCache: Map<String, TimestampedScoredNetwork>
+        ): Int {
+            val timedScore = scoredNetworkCache[result.BSSID] ?: return AccessPoint.Speed.NONE
+            // For debugging purposes we may want to use mRssi rather than result.level as the average
+            // speed wil be determined by mRssi
+            return timedScore.score.calculateBadge(result.level)
+        }
+
+        @JvmStatic
+        fun getMeteredLabel(context: Context, config: WifiConfiguration): String {
+            // meteredOverride is whether the user manually set the metered setting or not.
+            // meteredHint is whether the network itself is telling us that it is metered
+            return if (config.meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED ||
+                config.meteredHint && !isMeteredOverridden(
+                    config
+                )
+            ) {
+                context.getString(R.string.wifi_metered_label)
+            } else context.getString(R.string.wifi_unmetered_label)
+        }
+
+        /**
+         * Returns the Internet icon resource for a given RSSI level.
+         *
+         * @param level The number of bars to show (0-4)
+         * @param noInternet True if a connected Wi-Fi network cannot access the Internet
+         */
+        @JvmStatic
+        fun getInternetIconResource(level: Int, noInternet: Boolean): Int {
+            var wifiLevel = level
+            if (wifiLevel < 0) {
+                Log.e(TAG, "Wi-Fi level is out of range! level:$level")
+                wifiLevel = 0
+            } else if (level >= WIFI_PIE.size) {
+                Log.e(TAG, "Wi-Fi level is out of range! level:$level")
+                wifiLevel = WIFI_PIE.size - 1
+            }
+            return if (noInternet) NO_INTERNET_WIFI_PIE[wifiLevel] else WIFI_PIE[wifiLevel]
+        }
+
+        /**
+         * Returns the Hotspot network icon resource.
+         *
+         * @param deviceType The device type of Hotspot network
+         */
+        @JvmStatic
+        fun getHotspotIconResource(deviceType: Int): Int {
+            return when (deviceType) {
+                NetworkProviderInfo.DEVICE_TYPE_PHONE -> R.drawable.ic_hotspot_phone
+                NetworkProviderInfo.DEVICE_TYPE_TABLET -> R.drawable.ic_hotspot_tablet
+                NetworkProviderInfo.DEVICE_TYPE_LAPTOP -> R.drawable.ic_hotspot_laptop
+                NetworkProviderInfo.DEVICE_TYPE_WATCH -> R.drawable.ic_hotspot_watch
+                NetworkProviderInfo.DEVICE_TYPE_AUTO -> R.drawable.ic_hotspot_auto
+                else -> R.drawable.ic_hotspot_phone
+            }
+        }
+
+        @JvmStatic
+        fun isMeteredOverridden(config: WifiConfiguration): Boolean {
+            return config.meteredOverride != WifiConfiguration.METERED_OVERRIDE_NONE
+        }
+
+        /**
+         * Returns the Intent for Wi-Fi dialog.
+         *
+         * @param key              The Wi-Fi entry key
+         * @param connectForCaller True if a chosen WifiEntry request to connect to
+         */
+        @JvmStatic
+        fun getWifiDialogIntent(key: String?, connectForCaller: Boolean): Intent {
+            val intent = Intent(ACTION_WIFI_DIALOG)
+            intent.putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, key)
+            intent.putExtra(EXTRA_CONNECT_FOR_CALLER, connectForCaller)
+            return intent
+        }
+
+        /**
+         * Returns the Intent for Wi-Fi network details settings.
+         *
+         * @param key The Wi-Fi entry key
+         */
+        @JvmStatic
+        fun getWifiDetailsSettingsIntent(key: String?): Intent {
+            val intent = Intent(ACTION_WIFI_DETAILS_SETTINGS)
+            val bundle = Bundle()
+            bundle.putString(KEY_CHOSEN_WIFIENTRY_KEY, key)
+            intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, bundle)
+            return intent
+        }
+
+        /**
+         * Returns the string of Wi-Fi tethering summary for connected devices.
+         *
+         * @param context          The application context
+         * @param connectedDevices The count of connected devices
+         */
+        @JvmStatic
+        fun getWifiTetherSummaryForConnectedDevices(
+            context: Context,
+            connectedDevices: Int
+        ): String {
+            val msgFormat = MessageFormat(
+                context.resources.getString(R.string.wifi_tether_connected_summary),
+                Locale.getDefault()
+            )
+            val arguments: MutableMap<String, Any> = HashMap()
+            arguments["count"] = connectedDevices
+            return msgFormat.format(arguments)
+        }
+
+        @JvmStatic
+        fun checkWepAllowed(
+            context: Context,
+            lifecycleOwner: LifecycleOwner,
+            ssid: String,
+            onAllowed: () -> Unit,
+        ) {
+            lifecycleOwner.lifecycleScope.launch {
+                val wifiManager = context.getSystemService(WifiManager::class.java) ?: return@launch
+                if (wifiManager.queryWepAllowed()) {
+                    onAllowed()
+                } else {
+                    val intent = Intent(Intent.ACTION_MAIN).apply {
+                        component = ComponentName(
+                            "com.android.settings",
+                            "com.android.settings.network.WepNetworkDialogActivity"
+                        )
+                        putExtra(SSID, ssid)
+                    }
+                    context.startActivity(intent)
+                }
+            }
+        }
+
+        private suspend fun WifiManager.queryWepAllowed(): Boolean =
+            withContext(Dispatchers.Default) {
+                suspendCancellableCoroutine { continuation ->
+                    queryWepAllowed(Dispatchers.Default.asExecutor()) {
+                        continuation.resume(it)
+                    }
+                }
+            }
+
+        const val SSID = "ssid"
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
index 1ad7d49..fe83ffb 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -319,7 +319,8 @@
 
     @Test
     public void testPrivateProfileFilterDisplaysCorrectApps() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
 
         mEntry.showInPersonalTab = true;
         mEntry.mProfileType = UserManager.USER_TYPE_FULL_SYSTEM;
@@ -334,7 +335,8 @@
 
     @Test
     public void testPrivateProfileFilterDisplaysCorrectAppsWhenFlagDisabled() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.disableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
 
         mEntry.showInPersonalTab = false;
         mEntry.mProfileType = UserManager.USER_TYPE_PROFILE_PRIVATE;
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
index c647cbb..f0185b9 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
@@ -64,22 +64,21 @@
     @RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
     public void createInstance_withMR2FlagOn_returnsRouterInfoMediaManager() {
         InfoMediaManager manager =
-                InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null, null);
+                InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null);
         assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
     }
 
     @Test
     @RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
     public void createInstance_withMR2FlagOn_withFakePackage_returnsNoOpInfoMediaManager() {
-        InfoMediaManager manager =
-                InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null, null);
+        InfoMediaManager manager = InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null);
         assertThat(manager).isInstanceOf(NoOpInfoMediaManager.class);
     }
 
     @Test
     @RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
     public void createInstance_withMR2FlagOn_withNullPackage_returnsRouterInfoMediaManager() {
-        InfoMediaManager manager = InfoMediaManager.createInstance(mContext, null, null, null);
+        InfoMediaManager manager = InfoMediaManager.createInstance(mContext, null, null);
         assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
     }
 
@@ -87,7 +86,7 @@
     @RequiresFlagsDisabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
     public void createInstance_withMR2FlagOff_returnsManagerInfoMediaManager() {
         InfoMediaManager manager =
-                InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null, null);
+                InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null);
         assertThat(manager).isInstanceOf(ManagerInfoMediaManager.class);
     }
 }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/FakeSpatializerRepository.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/FakeSpatializerRepository.kt
deleted file mode 100644
index 3f52f24..0000000
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/FakeSpatializerRepository.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- *  Licensed under the Apache License, Version 2.0 (the "License");
- *  you may not use this file except in compliance with the License.
- *  You may obtain a copy of the License at
- *
- *       http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package com.android.settingslib.media.domain.interactor
-
-import android.media.AudioDeviceAttributes
-import com.android.settingslib.media.data.repository.SpatializerRepository
-
-class FakeSpatializerRepository : SpatializerRepository {
-
-    private val availabilityByDevice: MutableMap<AudioDeviceAttributes, Boolean> = mutableMapOf()
-    private val compatibleDevices: MutableList<AudioDeviceAttributes> = mutableListOf()
-
-    override suspend fun isAvailableForDevice(
-        audioDeviceAttributes: AudioDeviceAttributes
-    ): Boolean = availabilityByDevice.getOrDefault(audioDeviceAttributes, false)
-
-    override suspend fun getCompatibleDevices(): Collection<AudioDeviceAttributes> =
-        compatibleDevices
-
-    override suspend fun addCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes) {
-        compatibleDevices.add(audioDeviceAttributes)
-    }
-
-    override suspend fun removeCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes) {
-        compatibleDevices.remove(audioDeviceAttributes)
-    }
-
-    fun setIsAvailable(audioDeviceAttributes: AudioDeviceAttributes, isAvailable: Boolean) {
-        availabilityByDevice[audioDeviceAttributes] = isAvailable
-    }
-}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/SpatializerInteractorTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/SpatializerInteractorTest.kt
deleted file mode 100644
index a44baeb..0000000
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/SpatializerInteractorTest.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- *  Licensed under the Apache License, Version 2.0 (the "License");
- *  you may not use this file except in compliance with the License.
- *  You may obtain a copy of the License at
- *
- *       http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package com.android.settingslib.media.domain.interactor
-
-import android.media.AudioDeviceAttributes
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class SpatializerInteractorTest {
-
-    private val testScope = TestScope()
-    private val underTest = SpatializerInteractor(FakeSpatializerRepository())
-
-    @Test
-    fun setEnabledFalse_isEnabled_false() {
-        testScope.runTest {
-            underTest.setEnabled(deviceAttributes, false)
-
-            assertThat(underTest.isEnabled(deviceAttributes)).isFalse()
-        }
-    }
-
-    @Test
-    fun setEnabledTrue_isEnabled_true() {
-        testScope.runTest {
-            underTest.setEnabled(deviceAttributes, true)
-
-            assertThat(underTest.isEnabled(deviceAttributes)).isTrue()
-        }
-    }
-
-    private companion object {
-        val deviceAttributes = AudioDeviceAttributes(0, 0, "test_device")
-    }
-}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppCopyingHelperTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppCopyingHelperTest.java
index 2a5c43c..9ab17c4 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppCopyingHelperTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppCopyingHelperTest.java
@@ -37,9 +37,10 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.os.UserHandle;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArraySet;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.settingslib.BaseTest;
 
 import org.mockito.ArgumentMatcher;
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java
index 2522e95..ab28d06 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java
@@ -41,9 +41,10 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.view.inputmethod.InputMethodInfo;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.settingslib.BaseTest;
 
 import org.mockito.ArgumentMatcher;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 2d07e5d..6f31fad 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -16,9 +16,7 @@
 package com.android.settingslib;
 
 import static com.android.settingslib.Utils.STORAGE_MANAGER_ENABLED_PROPERTY;
-import static com.android.settingslib.Utils.WIRELESS_CHARGING_DEFAULT_TIMESTAMP;
 import static com.android.settingslib.Utils.shouldShowWirelessChargingWarningTip;
-import static com.android.settingslib.Utils.updateWirelessChargingNotificationTimestamp;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -62,7 +60,6 @@
 import org.robolectric.annotation.Implements;
 import org.robolectric.shadows.ShadowSettings;
 
-import java.time.Duration;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -547,77 +544,6 @@
     }
 
     @Test
-    public void shouldShowWirelessChargingNotification_neverSendNotification_returnTrue() {
-        updateWirelessChargingNotificationTimestamp(
-                mContext, WIRELESS_CHARGING_DEFAULT_TIMESTAMP, TAG);
-
-        assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isTrue();
-    }
-
-    @Test
-    public void shouldShowNotification_neverSendNotification_updateTimestampAndEnabledState() {
-        updateWirelessChargingNotificationTimestamp(
-                mContext, WIRELESS_CHARGING_DEFAULT_TIMESTAMP, TAG);
-
-        Utils.shouldShowWirelessChargingNotification(mContext, TAG);
-
-        assertThat(getWirelessChargingNotificationTimestamp())
-                .isNotEqualTo(WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
-        assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isTrue();
-    }
-
-    @Test
-    public void shouldShowWirelessChargingNotification_notificationDisabled_returnFalse() {
-        updateWirelessChargingNotificationTimestamp(mContext, CURRENT_TIMESTAMP, TAG);
-
-        assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isFalse();
-    }
-
-    @Test
-    public void shouldShowWirelessChargingNotification_withinTimeThreshold_returnFalse() {
-        updateWirelessChargingNotificationTimestamp(mContext, CURRENT_TIMESTAMP, TAG);
-
-        assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isFalse();
-    }
-
-    @Test
-    public void shouldShowWirelessChargingNotification_exceedTimeThreshold_returnTrue() {
-        final long monthAgo = Duration.ofDays(31).toMillis();
-        final long timestamp = CURRENT_TIMESTAMP - monthAgo;
-        updateWirelessChargingNotificationTimestamp(mContext, timestamp, TAG);
-
-        assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isTrue();
-    }
-
-    @Test
-    public void shouldShowNotification_exceedTimeThreshold_updateTimestampAndEnabledState() {
-        final long monthAgo = Duration.ofDays(31).toMillis();
-        final long timestamp = CURRENT_TIMESTAMP - monthAgo;
-        updateWirelessChargingNotificationTimestamp(mContext, timestamp, TAG);
-
-        Utils.shouldShowWirelessChargingNotification(mContext, TAG);
-
-        assertThat(getWirelessChargingNotificationTimestamp()).isNotEqualTo(timestamp);
-        assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isTrue();
-    }
-
-    @Test
-    public void updateWirelessChargingNotificationTimestamp_dismissForever_setMinValue() {
-        updateWirelessChargingNotificationTimestamp(mContext, Long.MIN_VALUE, TAG);
-
-        assertThat(getWirelessChargingNotificationTimestamp()).isEqualTo(Long.MIN_VALUE);
-    }
-
-    @Test
-    public void updateWirelessChargingNotificationTimestamp_notDismissForever_setTimestamp() {
-        updateWirelessChargingNotificationTimestamp(mContext, CURRENT_TIMESTAMP, TAG);
-
-        assertThat(getWirelessChargingNotificationTimestamp())
-                .isNotEqualTo(WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
-        assertThat(getWirelessChargingNotificationTimestamp()).isNotEqualTo(Long.MIN_VALUE);
-    }
-
-    @Test
     public void shouldShowWirelessChargingWarningTip_enabled_returnTrue() {
         Utils.updateWirelessChargingWarningEnabled(mContext, true, TAG);
 
@@ -644,11 +570,4 @@
         when(mUsbPortStatus.isConnected()).thenReturn(true);
         when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[] {complianceWarningType});
     }
-
-    private long getWirelessChargingNotificationTimestamp() {
-        return Settings.Secure.getLong(
-                mContext.getContentResolver(),
-                Utils.WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP,
-                WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
-    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index 827d8fa..3b18aa3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -72,6 +72,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -499,6 +500,7 @@
         verify(mApplicationsState, never()).clearEntries();
     }
 
+    @Ignore("b/328332487")
     @Test
     public void removeProfileApp_workprofileExists_doResumeIfNeededLocked_shouldClearEntries()
             throws RemoteException {
@@ -573,6 +575,7 @@
         verify(mApplicationsState).clearEntries();
     }
 
+    @Ignore("b/328332487")
     @Test
     public void removeOwnerApp_workprofileExists_doResumeIfNeededLocked_shouldClearEntries()
             throws RemoteException {
@@ -654,6 +657,7 @@
         verify(mApplicationsState).clearEntries();
     }
 
+    @Ignore("b/328332487")
     @Test
     public void noAppRemoved_workprofileExists_doResumeIfNeededLocked_shouldNotClearEntries()
             throws RemoteException {
@@ -773,6 +777,7 @@
         assertThat(primaryUserApp.shouldShowInPersonalTab(um, appInfo.uid)).isTrue();
     }
 
+    @Ignore("b/328332487")
     @Test
     public void shouldShowInPersonalTab_userProfilePreU_returnsFalse() {
         UserManager um = RuntimeEnvironment.application.getSystemService(UserManager.class);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 461ecf5d..5996dbb 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -18,9 +18,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -58,8 +56,6 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.shadow.api.Shadow;
 
-import java.util.concurrent.ExecutionException;
-
 @RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowBluetoothAdapter.class})
 public class CachedBluetoothDeviceTest {
@@ -1823,52 +1819,6 @@
         assertThat(mCachedDevice.isConnectedHearingAidDevice()).isFalse();
     }
 
-    @Test
-    public void syncProfileForMemberDevice_hasDiff_shouldSync()
-            throws ExecutionException, InterruptedException {
-        mCachedDevice.addMemberDevice(mSubCachedDevice);
-        when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
-        when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
-        when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
-
-        when(mA2dpProfile.isEnabled(mDevice)).thenReturn(true);
-        when(mHearingAidProfile.isEnabled(mDevice)).thenReturn(true);
-        when(mLeAudioProfile.isEnabled(mDevice)).thenReturn(true);
-
-        when(mA2dpProfile.isEnabled(mSubDevice)).thenReturn(true);
-        when(mHearingAidProfile.isEnabled(mSubDevice)).thenReturn(false);
-        when(mLeAudioProfile.isEnabled(mSubDevice)).thenReturn(false);
-
-        mCachedDevice.syncProfileForMemberDevice().get();
-
-        verify(mA2dpProfile, never()).setEnabled(any(BluetoothDevice.class), anyBoolean());
-        verify(mHearingAidProfile).setEnabled(any(BluetoothDevice.class), eq(true));
-        verify(mLeAudioProfile).setEnabled(any(BluetoothDevice.class), eq(true));
-    }
-
-    @Test
-    public void syncProfileForMemberDevice_noDiff_shouldNotSync()
-            throws ExecutionException, InterruptedException {
-        mCachedDevice.addMemberDevice(mSubCachedDevice);
-        when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
-        when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
-        when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
-
-        when(mA2dpProfile.isEnabled(mDevice)).thenReturn(false);
-        when(mHearingAidProfile.isEnabled(mDevice)).thenReturn(false);
-        when(mLeAudioProfile.isEnabled(mDevice)).thenReturn(true);
-
-        when(mA2dpProfile.isEnabled(mSubDevice)).thenReturn(false);
-        when(mHearingAidProfile.isEnabled(mSubDevice)).thenReturn(false);
-        when(mLeAudioProfile.isEnabled(mSubDevice)).thenReturn(true);
-
-        mCachedDevice.syncProfileForMemberDevice().get();
-
-        verify(mA2dpProfile, never()).setEnabled(any(BluetoothDevice.class), anyBoolean());
-        verify(mHearingAidProfile, never()).setEnabled(any(BluetoothDevice.class), anyBoolean());
-        verify(mLeAudioProfile, never()).setEnabled(any(BluetoothDevice.class), anyBoolean());
-    }
-
     private HearingAidInfo getLeftAshaHearingAidInfo() {
         return new HearingAidInfo.Builder()
                 .setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT)
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java
index 3705267..70ba415 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java
@@ -20,9 +20,7 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 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.annotation.SuppressLint;
 import android.content.Context;
@@ -94,19 +92,6 @@
     }
 
     @Test
-    public void updateConnectivity_notAvailable_notCalled() {
-        boolean mCalled = false;
-        mController = spy(new ConcreteWifiMacAddressPreferenceController(mContext, mLifecycle) {
-            @Override
-            public boolean isAvailable() {
-                return false;
-            }
-        });
-        mController.displayPreference(mScreen);
-        verify(mController, never()).updateConnectivity();
-    }
-
-    @Test
     public void updateConnectivity_null_setMacUnavailable() {
         doReturn(null).when(mWifiManager).getFactoryMacAddresses();
         mController.displayPreference(mScreen);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index c159d5e..d85d253 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -91,7 +91,7 @@
     @Mock
     private LocalBluetoothManager mLocalBluetoothManager;
     @Mock
-    private MediaManager.MediaDeviceCallback mCallback;
+    private InfoMediaManager.MediaDeviceCallback mCallback;
     @Mock
     private MediaSessionManager mMediaSessionManager;
     @Mock
@@ -109,8 +109,7 @@
         doReturn(mMediaSessionManager).when(mContext).getSystemService(
                 Context.MEDIA_SESSION_SERVICE);
         mInfoMediaManager =
-                new ManagerInfoMediaManager(
-                        mContext, TEST_PACKAGE_NAME, null, mLocalBluetoothManager);
+                new ManagerInfoMediaManager(mContext, TEST_PACKAGE_NAME, mLocalBluetoothManager);
         mShadowRouter2Manager = ShadowRouter2Manager.getShadow();
         mInfoMediaManager.mRouterManager = MediaRouter2Manager.getInstance(mContext);
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 9a7d4f1..693b7d0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -26,6 +26,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
@@ -35,7 +36,6 @@
 import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.media.MediaRoute2Info;
-import android.media.MediaRouter2Manager;
 import android.media.RoutingSessionInfo;
 
 import com.android.settingslib.bluetooth.A2dpProfile;
@@ -75,8 +75,6 @@
     private static final String TEST_ADDRESS = "00:01:02:03:04:05";
 
     @Mock
-    private InfoMediaManager mInfoMediaManager;
-    @Mock
     private LocalBluetoothManager mLocalBluetoothManager;
     @Mock
     private LocalMediaManager.DeviceCallback mCallback;
@@ -87,8 +85,6 @@
     @Mock
     private LocalBluetoothProfileManager mLocalProfileManager;
     @Mock
-    private MediaRouter2Manager mMediaRouter2Manager;
-    @Mock
     private MediaRoute2Info mRouteInfo1;
     @Mock
     private MediaRoute2Info mRouteInfo2;
@@ -97,6 +93,7 @@
 
     private Context mContext;
     private LocalMediaManager mLocalMediaManager;
+    private InfoMediaManager mInfoMediaManager;
     private ShadowBluetoothAdapter mShadowBluetoothAdapter;
     private InfoMediaDevice mInfoMediaDevice1;
     private InfoMediaDevice mInfoMediaDevice2;
@@ -116,10 +113,16 @@
         when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
         when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHapProfile);
 
+        // Need to call constructor to initialize final fields.
+        mInfoMediaManager = mock(
+                InfoMediaManager.class,
+                withSettings().useConstructor(mContext, TEST_PACKAGE_NAME, mLocalBluetoothManager));
+
         mInfoMediaDevice1 = spy(new InfoMediaDevice(mContext, mRouteInfo1));
         mInfoMediaDevice2 = new InfoMediaDevice(mContext, mRouteInfo2);
-        mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager,
-                mInfoMediaManager, "com.test.packagename");
+        mLocalMediaManager =
+                new LocalMediaManager(
+                        mContext, mLocalBluetoothManager, mInfoMediaManager, TEST_PACKAGE_NAME);
         mLocalMediaManager.mAudioManager = mAudioManager;
     }
 
@@ -146,7 +149,6 @@
 
         mLocalMediaManager.registerCallback(mCallback);
         assertThat(mLocalMediaManager.connectDevice(device)).isTrue();
-
         verify(mInfoMediaManager).connectToDevice(device);
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java
deleted file mode 100644
index 3b73192..0000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright 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.settingslib.media;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-@RunWith(RobolectricTestRunner.class)
-public class MediaManagerTest {
-
-    private static final String TEST_ID = "test_id";
-
-    @Mock
-    private MediaManager.MediaDeviceCallback mCallback;
-    @Mock
-    private MediaDevice mDevice;
-
-    private MediaManager mMediaManager;
-    private Context mContext;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mContext = RuntimeEnvironment.application;
-
-        when(mDevice.getId()).thenReturn(TEST_ID);
-
-        mMediaManager = new MediaManager(mContext, null) {
-            @Override
-            public void startScan() {
-
-            }
-
-            @Override
-            public void stopScan() {
-
-            }
-        };
-    }
-
-    @Test
-    public void dispatchDeviceListAdded_registerCallback_shouldDispatchCallback() {
-        mMediaManager.registerCallback(mCallback);
-
-        mMediaManager.dispatchDeviceListAdded();
-
-        verify(mCallback).onDeviceListAdded(any());
-    }
-
-    @Test
-    public void dispatchDeviceListRemoved_registerCallback_shouldDispatchCallback() {
-        mMediaManager.registerCallback(mCallback);
-
-        mMediaManager.dispatchDeviceListRemoved(mMediaManager.mMediaDevices);
-
-        verify(mCallback).onDeviceListRemoved(mMediaManager.mMediaDevices);
-    }
-
-    @Test
-    public void dispatchActiveDeviceChanged_registerCallback_shouldDispatchCallback() {
-        mMediaManager.registerCallback(mCallback);
-
-        mMediaManager.dispatchConnectedDeviceChanged(TEST_ID);
-
-        verify(mCallback).onConnectedDeviceChanged(TEST_ID);
-    }
-
-    @Test
-    public void findMediaDevice_idExist_shouldReturnMediaDevice() {
-        mMediaManager.mMediaDevices.add(mDevice);
-
-        final MediaDevice device = mMediaManager.findMediaDevice(TEST_ID);
-
-        assertThat(device.getId()).isEqualTo(mDevice.getId());
-    }
-
-    @Test
-    public void findMediaDevice_idNotExist_shouldReturnNull() {
-        mMediaManager.mMediaDevices.add(mDevice);
-
-        final MediaDevice device = mMediaManager.findMediaDevice("123");
-
-        assertThat(device).isNull();
-    }
-
-    @Test
-    public void dispatchOnRequestFailed_registerCallback_shouldDispatchCallback() {
-        mMediaManager.registerCallback(mCallback);
-
-        mMediaManager.dispatchOnRequestFailed(1);
-
-        verify(mCallback).onRequestFailed(1);
-    }
-
-}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java
new file mode 100644
index 0000000..d630301
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.media;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/**
+ * Tests for {@link NoOpInfoMediaManager} to avoid exceptions in {@link InfoMediaManager}.
+ *
+ * <p>While {@link NoOpInfoMediaManager} should not perform any actions, it should still return
+ * placeholder information in certain cases to not change the behaviour of {@link InfoMediaManager}
+ * and prevent crashes.
+ */
+@RunWith(RobolectricTestRunner.class)
+public class NoOpInfoMediaManagerTest {
+    private InfoMediaManager mInfoMediaManager;
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = ApplicationProvider.getApplicationContext();
+        mInfoMediaManager =
+                new NoOpInfoMediaManager(
+                        mContext,
+                        /* packageName */ "FAKE_PACKAGE_NAME",
+                        /* localBluetoothManager */ null);
+    }
+
+    @Test
+    public void getSessionVolumeMax_returnsNotFound() {
+        assertThat(mInfoMediaManager.getSessionVolumeMax()).isEqualTo(-1);
+    }
+
+    @Test
+    public void getSessionVolume_returnsNotFound() {
+        assertThat(mInfoMediaManager.getSessionVolume()).isEqualTo(-1);
+    }
+
+    @Test
+    public void getSessionName_returnsNull() {
+        assertThat(mInfoMediaManager.getSessionName()).isNull();
+    }
+
+    @Test
+    public void getRoutingSessionForPackage_returnsPlaceholderSession() {
+        // Make sure we return a placeholder routing session so that we avoid OOB exceptions.
+        assertThat(mInfoMediaManager.getRoutingSessionsForPackage()).hasSize(1);
+    }
+
+    @Test
+    public void getSelectedMediaDevices_returnsEmptyList() {
+        assertThat(mInfoMediaManager.getSelectedMediaDevices()).isEmpty();
+    }
+}
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index d5814e3..7ec3d24 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -60,12 +60,15 @@
         // 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",
         "libaconfig_java_proto_lite",
         "mockito-target-minus-junit4",
         "platform-test-annotations",
         "truth",
+        "Nene",
+        "Harrier",
     ],
     libs: [
         "android.test.base",
diff --git a/packages/SettingsProvider/res/xml/bookmarks.xml b/packages/SettingsProvider/res/xml/bookmarks.xml
index 4b97b47..22d0226 100644
--- a/packages/SettingsProvider/res/xml/bookmarks.xml
+++ b/packages/SettingsProvider/res/xml/bookmarks.xml
@@ -23,7 +23,7 @@
        'c': Contacts
        'e': Email
        'g': GMail
-       'l': Calendar
+       'k': Calendar
        'm': Maps
        'p': Music
        's': SMS
@@ -33,7 +33,7 @@
 -->
 <bookmarks>
     <bookmark
-        category="android.intent.category.APP_BROWSER"
+        role="android.app.role.BROWSER"
         shortcut="b" />
     <bookmark
         category="android.intent.category.APP_CONTACTS"
@@ -51,7 +51,7 @@
         category="android.intent.category.APP_MUSIC"
         shortcut="p" />
     <bookmark
-        category="android.intent.category.APP_MESSAGING"
+        role="android.app.role.SMS"
         shortcut="s" />
     <bookmark
         category="android.intent.category.APP_CALCULATOR"
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index add3134..8f8445d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -115,6 +115,10 @@
         Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED,
         Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED,
         Settings.Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED,
+        Settings.Global.Wearable.AUTO_BEDTIME_MODE,
         Settings.Global.FORCE_ENABLE_PSS_PROFILING,
+        Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_ENABLED,
+        Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_TYPE,
+        Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_SPEED,
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 2fa1c6e..30d5d4b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -154,6 +154,7 @@
         Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT,
         Settings.Secure.LOW_POWER_WARNING_ACKNOWLEDGED,
         Settings.Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED,
+        Settings.Secure.EMERGENCY_THERMAL_ALERT_DISABLED,
         Settings.Secure.HUSH_GESTURE_USED,
         Settings.Secure.IN_CALL_NOTIFICATION_ENABLED,
         Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
@@ -267,6 +268,7 @@
         Settings.Secure.EVEN_DIMMER_MIN_NITS,
         Settings.Secure.STYLUS_POINTER_ICON_ENABLED,
         Settings.Secure.CAMERA_EXTENSIONS_FALLBACK,
-        Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED
+        Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED,
+        Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index c0a0760..6def40b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -452,6 +452,7 @@
         VALIDATORS.put(Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
                 Global.Wearable.CONSISTENT_NOTIFICATION_BLOCKING_ENABLED, ANY_INTEGER_VALIDATOR);
+        VALIDATORS.put(Global.Wearable.AUTO_BEDTIME_MODE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.FORCE_ENABLE_PSS_PROFILING, 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 2535fdb..893932f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -235,6 +235,7 @@
         VALIDATORS.put(Secure.MANUAL_RINGER_TOGGLE_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.LOW_POWER_WARNING_ACKNOWLEDGED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.EMERGENCY_THERMAL_ALERT_DISABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.IN_CALL_NOTIFICATION_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, BOOLEAN_VALIDATOR);
@@ -422,5 +423,6 @@
         VALIDATORS.put(Secure.AUTOFILL_SERVICE, AUTOFILL_SERVICE_VALIDATOR);
         VALIDATORS.put(Secure.STYLUS_POINTER_ICON_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.CAMERA_EXTENSIONS_FALLBACK, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.IMMERSIVE_MODE_CONFIRMATIONS, ANY_STRING_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index 6ff36d4..ad3eb92 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -20,8 +20,6 @@
 import static android.provider.Settings.Config.SYNC_DISABLED_MODE_PERSISTENT;
 import static android.provider.Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT;
 
-import static com.android.providers.settings.Flags.supportOverrides;
-
 import android.aconfig.Aconfig.parsed_flag;
 import android.aconfig.Aconfig.parsed_flags;
 import android.annotation.SuppressLint;
@@ -269,9 +267,9 @@
                 verb = CommandVerb.GET;
             } else if ("put".equalsIgnoreCase(cmd)) {
                 verb = CommandVerb.PUT;
-            } else if (supportOverrides() && "override".equalsIgnoreCase(cmd)) {
+            } else if ("override".equalsIgnoreCase(cmd)) {
                 verb = CommandVerb.OVERRIDE;
-            } else if (supportOverrides() && "clear_override".equalsIgnoreCase(cmd)) {
+            } else if ("clear_override".equalsIgnoreCase(cmd)) {
                 verb = CommandVerb.CLEAR_OVERRIDE;
             } else if ("delete".equalsIgnoreCase(cmd)) {
                 verb = CommandVerb.DELETE;
@@ -285,7 +283,7 @@
                 if (peekNextArg() == null) {
                     isValid = true;
                 }
-            } else if (supportOverrides() && "list_local_overrides".equalsIgnoreCase(cmd)) {
+            } else if ("list_local_overrides".equalsIgnoreCase(cmd)) {
                 verb = CommandVerb.LIST_LOCAL_OVERRIDES;
                 if (peekNextArg() == null) {
                     isValid = true;
@@ -427,14 +425,10 @@
                     DeviceConfig.setProperty(namespace, key, value, makeDefault);
                     break;
                 case OVERRIDE:
-                    if (supportOverrides()) {
-                        DeviceConfig.setLocalOverride(namespace, key, value);
-                    }
+                    DeviceConfig.setLocalOverride(namespace, key, value);
                     break;
                 case CLEAR_OVERRIDE:
-                    if (supportOverrides()) {
-                        DeviceConfig.clearLocalOverride(namespace, key);
-                    }
+                    DeviceConfig.clearLocalOverride(namespace, key);
                     break;
                 case DELETE:
                     pout.println(delete(iprovider, namespace, key)
@@ -452,19 +446,15 @@
                         }
                     } else {
                         for (String line : listAll(iprovider)) {
-                            if (supportOverrides()) {
-                                boolean isPrivate = false;
-                                for (String privateNamespace : PRIVATE_NAMESPACES) {
-                                    if (line.startsWith(privateNamespace)) {
-                                        isPrivate = true;
-                                        break;
-                                    }
+                            boolean isPrivate = false;
+                            for (String privateNamespace : PRIVATE_NAMESPACES) {
+                                if (line.startsWith(privateNamespace)) {
+                                    isPrivate = true;
+                                    break;
                                 }
+                            }
 
-                                if (!isPrivate) {
-                                    pout.println(line);
-                                }
-                            } else {
+                            if (!isPrivate) {
                                 pout.println(line);
                             }
                         }
@@ -495,18 +485,16 @@
                     }
                     break;
                 case LIST_LOCAL_OVERRIDES:
-                    if (supportOverrides()) {
-                        Map<String, Map<String, String>> underlyingValues =
-                                DeviceConfig.getUnderlyingValuesForOverriddenFlags();
-                        for (String overrideNamespace : underlyingValues.keySet()) {
-                            Map<String, String> flagToValue =
-                                    underlyingValues.get(overrideNamespace);
-                            for (String flag : flagToValue.keySet()) {
-                                String flagText = overrideNamespace + "/" + flag;
-                                String valueText =
-                                        DeviceConfig.getProperty(overrideNamespace, flag);
-                                pout.println(flagText + "=" + valueText);
-                            }
+                    Map<String, Map<String, String>> underlyingValues =
+                            DeviceConfig.getUnderlyingValuesForOverriddenFlags();
+                    for (String overrideNamespace : underlyingValues.keySet()) {
+                        Map<String, String> flagToValue =
+                                underlyingValues.get(overrideNamespace);
+                        for (String flag : flagToValue.keySet()) {
+                            String flagText = overrideNamespace + "/" + flag;
+                            String valueText =
+                                    DeviceConfig.getProperty(overrideNamespace, flag);
+                            pout.println(flagText + "=" + valueText);
                         }
                     }
                     break;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index febce97..1ead14a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3812,7 +3812,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 225;
+            private static final int SETTINGS_VERSION = 226;
 
             private final int mUserId;
 
@@ -6011,6 +6011,28 @@
                     currentVersion = 225;
                 }
 
+                // Version 225: Set the System#KEYBOARD_VIBRATION_ENABLED based on touch
+                // feedback enabled state.
+                if (currentVersion == 225) {
+                    final SettingsState systemSettings = getSystemSettingsLocked(userId);
+                    final Setting touchFeedbackSettings = systemSettings
+                            .getSettingLocked(Settings.System.HAPTIC_FEEDBACK_ENABLED);
+                    final Setting keyboardVibrationSettings = systemSettings
+                            .getSettingLocked(Settings.System.KEYBOARD_VIBRATION_ENABLED);
+                    if (keyboardVibrationSettings.isNull()) {
+                        if (!touchFeedbackSettings.isNull()) {
+                            // Use touch feedback settings.
+                            systemSettings.insertSettingOverrideableByRestoreLocked(
+                                    Settings.System.KEYBOARD_VIBRATION_ENABLED,
+                                    touchFeedbackSettings.getValue(),
+                                    touchFeedbackSettings.getTag(),
+                                    touchFeedbackSettings.isDefaultFromSystem(),
+                                    SettingsState.SYSTEM_PACKAGE_NAME);
+                        }
+                    }
+                    currentVersion = 226;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 1c9e748..3266c12 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.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -157,6 +158,9 @@
             "/product/etc/aconfig_flags.pb",
             "/vendor/etc/aconfig_flags.pb");
 
+    private static final String APEX_DIR = "/apex";
+    private static final String APEX_ACONFIG_PATH_SUFFIX = "/etc/aconfig_flags.pb";
+
     /**
      * This tag is applied to all aconfig default value-loaded flags.
      */
@@ -238,7 +242,7 @@
     private int mNextHistoricalOpIdx;
 
     @GuardedBy("mLock")
-    @Nullable
+    @NonNull
     private Map<String, Map<String, String>> mNamespaceDefaults;
 
     public static final int SETTINGS_TYPE_GLOBAL = 0;
@@ -332,23 +336,29 @@
         mHistoricalOperations = Build.IS_DEBUGGABLE
                 ? new ArrayList<>(HISTORICAL_OPERATION_COUNT) : null;
 
+        mNamespaceDefaults = new HashMap<>();
+
         synchronized (mLock) {
             readStateSyncLocked();
 
             if (Flags.loadAconfigDefaults()) {
                 if (isConfigSettingsKey(mKey)) {
-                    loadAconfigDefaultValuesLocked();
+                    loadAconfigDefaultValuesLocked(sAconfigTextProtoFilesOnDevice);
                 }
             }
 
+            if (Flags.loadApexAconfigProtobufs()) {
+                if (isConfigSettingsKey(mKey)) {
+                    List<String> apexProtoPaths = listApexProtoPaths();
+                    loadAconfigDefaultValuesLocked(apexProtoPaths);
+                }
+            }
         }
     }
 
     @GuardedBy("mLock")
-    private void loadAconfigDefaultValuesLocked() {
-        mNamespaceDefaults = new HashMap<>();
-
-        for (String fileName : sAconfigTextProtoFilesOnDevice) {
+    private void loadAconfigDefaultValuesLocked(List<String> filePaths) {
+        for (String fileName : filePaths) {
             try (FileInputStream inputStream = new FileInputStream(fileName)) {
                 loadAconfigDefaultValues(inputStream.readAllBytes(), mNamespaceDefaults);
             } catch (IOException e) {
@@ -357,6 +367,43 @@
         }
     }
 
+    private List<String> listApexProtoPaths() {
+        LinkedList<String> paths = new LinkedList();
+
+        File apexDirectory = new File(APEX_DIR);
+        if (!apexDirectory.isDirectory()) {
+            return paths;
+        }
+
+        File[] subdirs = apexDirectory.listFiles();
+        if (subdirs == null) {
+            return paths;
+        }
+
+        for (File prefix : subdirs) {
+            // For each mainline modules, there are two directories, one <modulepackage>/,
+            // and one <modulepackage>@<versioncode>/. Just read the former.
+            if (prefix.getAbsolutePath().contains("@")) {
+                continue;
+            }
+
+            File protoPath = new File(prefix + APEX_ACONFIG_PATH_SUFFIX);
+            if (!protoPath.exists()) {
+                continue;
+            }
+
+            paths.add(protoPath.getAbsolutePath());
+        }
+        return paths;
+    }
+
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    public void addAconfigDefaultValuesFromMap(
+            @NonNull Map<String, Map<String, String>> defaultMap) {
+        mNamespaceDefaults.putAll(defaultMap);
+    }
+
     @VisibleForTesting
     @GuardedBy("mLock")
     public static void loadAconfigDefaultValues(byte[] fileContents,
@@ -438,7 +485,7 @@
         return names;
     }
 
-    @Nullable
+    @NonNull
     public Map<String, Map<String, String>> getAconfigDefaultValues() {
         synchronized (mLock) {
             return mNamespaceDefaults;
@@ -510,6 +557,28 @@
             return false;
         }
 
+        // Aconfig flags are always boot stable, so we anytime we write one, we stage it to be
+        // applied on reboot.
+        if (Flags.stageAllAconfigFlags()) {
+            int slashIndex = name.indexOf("/");
+            boolean stageFlag = isConfigSettingsKey(mKey)
+                    && slashIndex != -1
+                    && slashIndex != 0
+                    && slashIndex != name.length();
+
+            if (stageFlag) {
+                String namespace = name.substring(0, slashIndex);
+                String flag = name.substring(slashIndex + 1);
+
+                boolean isAconfig = mNamespaceDefaults.containsKey(namespace)
+                        && mNamespaceDefaults.get(namespace).containsKey(name);
+
+                if (isAconfig) {
+                    name = "staged/" + namespace + "*" + flag;
+                }
+            }
+        }
+
         final boolean isNameTooLong = name.length() > SettingsState.MAX_LENGTH_PER_STRING;
         final boolean isValueTooLong =
                 value != null && value.length() > SettingsState.MAX_LENGTH_PER_STRING;
@@ -638,23 +707,36 @@
         // Update/add new keys
         for (String key : keyValues.keySet()) {
             String value = keyValues.get(key);
+
+            // Rename key if it's an aconfig flag.
+            String flagName = key;
+            if (Flags.stageAllAconfigFlags() && isConfigSettingsKey(mKey)) {
+                int slashIndex = flagName.indexOf("/");
+                boolean stageFlag = slashIndex > 0 && slashIndex != flagName.length();
+                boolean isAconfig = trunkFlagMap != null && trunkFlagMap.containsKey(flagName);
+                if (stageFlag && isAconfig) {
+                    String flagWithoutNamespace = flagName.substring(slashIndex + 1);
+                    flagName = "staged/" + namespace + "*" + flagWithoutNamespace;
+                }
+            }
+
             String oldValue = null;
-            Setting state = mSettings.get(key);
+            Setting state = mSettings.get(flagName);
             if (state == null) {
-                state = new Setting(key, value, false, packageName, null);
-                mSettings.put(key, state);
-                changedKeys.add(key); // key was added
+                state = new Setting(flagName, value, false, packageName, null);
+                mSettings.put(flagName, state);
+                changedKeys.add(flagName); // key was added
             } else if (state.value != value) {
                 oldValue = state.value;
                 state.update(value, false, packageName, null, true,
                         /* overrideableByRestore */ false);
-                changedKeys.add(key); // key was updated
+                changedKeys.add(flagName); // key was updated
             } else {
                 // this key/value already exists, no change and no logging necessary
                 continue;
             }
 
-            FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, key, value, state.value,
+            FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, flagName, value, state.value,
                     oldValue, /* tag */ null, /* make default */ false,
                     getUserIdFromKey(mKey), FrameworkStatsLog.SETTING_CHANGED__REASON__UPDATED);
             addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, state);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index ecac5ee..2e14e9b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -14,3 +14,22 @@
     bug: "311155098"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "stage_all_aconfig_flags"
+    namespace: "core_experiments_team_internal"
+    description: "Stage _all_ aconfig flags on writes, even local ones."
+    bug: "326598713"
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
+    name: "load_apex_aconfig_protobufs"
+    namespace: "core_experiments_team_internal"
+    description: "When enabled, loads aconfig default values in apex flag protobufs into DeviceConfig on boot."
+    bug: "327383546"
+    is_fixed_read_only: true
+}
diff --git a/packages/SettingsProvider/test/AndroidTest.xml b/packages/SettingsProvider/test/AndroidTest.xml
index 0bf53cc..dccc2d3 100644
--- a/packages/SettingsProvider/test/AndroidTest.xml
+++ b/packages/SettingsProvider/test/AndroidTest.xml
@@ -14,6 +14,8 @@
      limitations under the License.
 -->
 <configuration description="Run Settings Provider Tests.">
+    <option name="config-descriptor:metadata" key="parameter" value="multiuser" />
+
     <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
         <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" />
         <option name="restore-settings" value="true" />
@@ -30,5 +32,7 @@
         <option name="package" value="com.android.providers.setting.test" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="hidden-api-checks" value="false"/>
+        <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
+        <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser" />
     </test>
 </configuration>
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 28cdc6d..46c89900 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -626,9 +626,6 @@
                     Settings.Global.Wearable.BEDTIME_MODE,
                     Settings.Global.Wearable.BEDTIME_HARD_MODE,
                     Settings.Global.Wearable.LOCK_SCREEN_STATE,
-                    Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_ENABLED,
-                    Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_TYPE,
-                    Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_SPEED,
                     Settings.Global.Wearable.DISABLE_AOD_WHILE_PLUGGED,
                     Settings.Global.Wearable.NETWORK_LOCATION_OPT_IN,
                     Settings.Global.Wearable.CUSTOM_COLOR_FOREGROUND,
@@ -705,7 +702,6 @@
                  Settings.Secure.ENABLED_PRINT_SERVICES,
                  Settings.Secure.GLOBAL_ACTIONS_PANEL_AVAILABLE,
                  Settings.Secure.GLOBAL_ACTIONS_PANEL_DEBUG_ENABLED,
-                 Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
                  Settings.Secure.INCALL_BACK_BUTTON_BEHAVIOR,
                  Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY,
                  Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
new file mode 100644
index 0000000..ca1e4c1
--- /dev/null
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.settings;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_ENABLED;
+import static android.provider.Settings.Secure.SYNC_PARENT_SOUNDS;
+import static android.provider.Settings.System.RINGTONE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.pm.PackageManager;
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.bedstead.harrier.BedsteadJUnit4;
+import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureHasNoWorkProfile;
+import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
+import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
+import com.android.bedstead.harrier.annotations.RequireFeature;
+import com.android.bedstead.harrier.annotations.RequireRunOnInitialUser;
+import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.users.UserReference;
+import com.android.bedstead.nene.users.UserType;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+@RunWith(BedsteadJUnit4.class)
+@RequireRunOnPrimaryUser
+public class SettingsProviderMultiUsersTest {
+
+    @ClassRule @Rule
+    public static final DeviceState sDeviceState = new DeviceState();
+
+    private static final String SETTINGS = "some_random_setting";
+
+    private static final String GET_SHELL_COMMAND = "settings get --user ";
+    private static final String SET_SHELL_COMMAND = "settings put --user ";
+    private static final String DELETE_SHELL_COMMAND = "settings delete --user ";
+
+    private static final String SPACE_GLOBAL = "global";
+    private static final String SPACE_SYSTEM = "system";
+    private static final String SPACE_SECURE = "secure";
+
+    private static final String CLONE_TO_MANAGED_PROFILE_SETTING = ACCESSIBILITY_ENABLED;
+    private static final String CLONE_FROM_PARENT_SETTINGS = RINGTONE;
+    private static final String SYNC_FROM_PARENT_SETTINGS = SYNC_PARENT_SOUNDS;
+
+    private UiDevice mUiDevice;
+    private UserReference mPrimaryUser;
+
+    @Before
+    public void setUp() throws Exception {
+        mPrimaryUser = sDeviceState.initialUser();
+
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+    }
+
+    @Test
+    @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+    @EnsureHasWorkProfile
+    public void testSettings_workProfile() throws Exception {
+        UserReference profile = sDeviceState.workProfile();
+
+        // Settings.Global settings are shared between different users
+        assertSettingsShared(SPACE_GLOBAL, mPrimaryUser.id(), profile.id());
+        // Settings.System and Settings.Secure settings can be different on different users
+        assertSettingsDifferent(SPACE_SYSTEM, mPrimaryUser.id(), profile.id());
+        assertSettingsDifferent(SPACE_SECURE, mPrimaryUser.id(), profile.id());
+    }
+
+    @Test
+    @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+    @RequireRunOnInitialUser
+    @EnsureHasSecondaryUser
+    public void testSettings_secondaryUser() throws Exception {
+        UserReference secondaryUser = sDeviceState.secondaryUser();
+
+        // Settings.Global settings are shared between different users
+        assertSettingsShared(SPACE_GLOBAL, mPrimaryUser.id(), secondaryUser.id());
+        // Settings.System and Settings.Secure settings can be different on different users
+        assertSettingsDifferent(SPACE_SYSTEM, mPrimaryUser.id(), secondaryUser.id());
+        assertSettingsDifferent(SPACE_SECURE, mPrimaryUser.id(), secondaryUser.id());
+    }
+
+    private void assertSettingsDifferent(String type, int userId1, int userId2) throws Exception {
+        // reset settings
+        setSetting(type, SETTINGS, "noValue", userId2);
+        waitForIdle();
+
+        // set the value with user 1
+        setSetting(type, SETTINGS, "value1", userId1);
+        waitForIdle();
+
+        // check no value with user 2
+        String value = getSetting(type, SETTINGS, userId2);
+        assertThat(value).startsWith("noValue");
+
+        // set the value with user 2
+        setSetting(type, SETTINGS, "value2", userId2);
+        waitForIdle();
+
+        // check the value with user 1 is not changed
+        value = getSetting(type, SETTINGS, userId1);
+        assertThat(value).startsWith("value1");
+    }
+
+    private void assertSettingsShared(String type, int userId1, int userId2) throws Exception {
+        // reset settings
+        setSetting(type, SETTINGS, "noValue", userId2);
+        waitForIdle();
+
+        // set the value with user 1
+        setSetting(type, SETTINGS, "value1", userId1);
+        waitForIdle();
+
+        // check no value with user 2
+        String value = getSetting(type, SETTINGS, userId2);
+        assertThat(value).startsWith("value1");
+
+        // set the value with user 2
+        setSetting(type, SETTINGS, "value2", userId2);
+        waitForIdle();
+
+        // check the value with user 1 is not changed
+        value = getSetting(type, SETTINGS, userId1);
+        assertThat(value).startsWith("value2");
+    }
+
+    @Test
+    @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+    @EnsureHasNoWorkProfile
+    public void testSettings_profile_cloneToManagedProfile() throws Exception {
+        assertSettingsCloned(SPACE_SECURE, CLONE_TO_MANAGED_PROFILE_SETTING, false);
+    }
+
+    @Test
+    @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+    @EnsureHasNoWorkProfile
+    public void testSettings_profile_cloneFromParent() throws Exception {
+        assertSettingsCloned(SPACE_SYSTEM, CLONE_FROM_PARENT_SETTINGS, true);
+    }
+
+    private void assertSettingsCloned(String type, String name, boolean isSyncSettings)
+            throws Exception {
+        boolean resetSyncValue = false;
+        if (isSyncSettings) {
+            // set to sync settings from parent
+            final String oldSyncValue =
+                    getSetting(SPACE_SECURE, SYNC_FROM_PARENT_SETTINGS, mPrimaryUser.id());
+            resetSyncValue = oldSyncValue.startsWith("0");
+            if (resetSyncValue) {
+                setSetting(SPACE_SECURE, SYNC_FROM_PARENT_SETTINGS, "1", mPrimaryUser.id());
+                waitForIdle();
+            }
+        }
+
+        final String oldValue = getSetting(type, name, mPrimaryUser.id());
+
+        try (UserReference myProfile = TestApis.users().createUser()
+                .parent(mPrimaryUser)
+                .type(TestApis.users().supportedType(UserType.MANAGED_PROFILE_TYPE_NAME))
+                .createAndStart()) {
+            String value = getSetting(type, name, myProfile.id());
+            assertThat(value).isEqualTo(oldValue);
+
+            String newValue;
+            if (isSyncSettings) {
+                newValue = generateNewValue(oldValue);
+            } else {
+                newValue = oldValue.startsWith("0") ? "1" : "0";
+            }
+
+            setSetting(type, name, newValue, mPrimaryUser.id());
+            waitForIdle();
+
+            value = getSetting(type, name, myProfile.id());
+            assertThat(value).startsWith(newValue);
+        } finally {
+            // reset settings
+            setSetting(type, name, oldValue, mPrimaryUser.id());
+            if (resetSyncValue) {
+                setSetting(SPACE_SECURE, SYNC_FROM_PARENT_SETTINGS, "0", mPrimaryUser.id());
+            }
+        }
+    }
+
+    private String generateNewValue(String oldValue) {
+        String newValue = oldValue.replace("\n", "");
+        if (newValue.endsWith("0")) {
+            final int size = newValue.length();
+            newValue = newValue.substring(0, size - 1) + "1";
+        } else {
+            final int size = newValue.length();
+            newValue = newValue.substring(0, size - 1) + "0";
+        }
+        return newValue;
+    }
+
+    @Test
+    @RequireRunOnInitialUser
+    @EnsureHasSecondaryUser
+    public void testSettings_stopAndRestartSecondaryUser() throws Exception {
+        UserReference secondaryUser = sDeviceState.secondaryUser();
+
+        assertSettingsDifferent(SPACE_SECURE, mPrimaryUser.id(), secondaryUser.id());
+
+        secondaryUser.stop();
+
+        assertSettingsDifferent(SPACE_SECURE, mPrimaryUser.id(), secondaryUser.id());
+
+        secondaryUser.start();
+
+        assertSettingsDifferent(SPACE_SECURE, mPrimaryUser.id(), secondaryUser.id());
+    }
+
+    private void waitForIdle() {
+        final UiDevice uiDevice = UiDevice.getInstance(
+                InstrumentationRegistry.getInstrumentation());
+        uiDevice.waitForIdle();
+    }
+
+    private String getSetting(String type, String name, int userId) throws Exception {
+        return executeShellCmd(GET_SHELL_COMMAND + userId + " " +  type + " " + name);
+    }
+
+    private void setSetting(String type, String name, String setting, int userId)
+            throws Exception {
+        setSetting(name, setting, userId + " " + type);
+    }
+
+    private void setSetting(String name, String setting, String type) throws Exception {
+        if (setting == null || setting.equals("null")) {
+            executeShellCmd(DELETE_SHELL_COMMAND + type + " " + name);
+        } else {
+            setting = setting.replace("\n", "");
+            executeShellCmd(SET_SHELL_COMMAND + type + " " + name + " " + setting);
+        }
+    }
+
+    private String executeShellCmd(String command) throws IOException {
+        return mUiDevice.executeShellCommand(command);
+    }
+}
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 9ecbd50..e0e31d7 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -15,13 +15,24 @@
  */
 package com.android.providers.settings;
 
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
 import android.aconfig.Aconfig;
 import android.aconfig.Aconfig.parsed_flag;
 import android.aconfig.Aconfig.parsed_flags;
 import android.os.Looper;
-import android.test.AndroidTestCase;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.util.Xml;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.modules.utils.TypedXmlSerializer;
 
 import com.google.common.base.Strings;
@@ -34,7 +45,18 @@
 import java.util.List;
 import java.util.Map;
 
-public class SettingsStateTest extends AndroidTestCase {
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SettingsStateTest {
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     public static final String CRAZY_STRING =
             "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\n\u000b\u000c\r" +
                     "\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a" +
@@ -76,25 +98,25 @@
 
     private File mSettingsFile;
 
-    @Override
-    protected void setUp() {
-        mSettingsFile = new File(getContext().getCacheDir(), "setting.xml");
+    @Before
+    public void setUp() {
+        mSettingsFile = new File(InstrumentationRegistry.getContext().getCacheDir(), "setting.xml");
         mSettingsFile.delete();
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         if (mSettingsFile != null) {
             mSettingsFile.delete();
         }
-        super.tearDown();
     }
 
+    @Test
     public void testLoadValidAconfigProto() {
         int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
         Object lock = new Object();
         SettingsState settingsState = new SettingsState(
-                getContext(), lock, mSettingsFile, configKey,
+                InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
                 SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
         parsed_flags flags = parsed_flags
                 .newBuilder()
@@ -129,11 +151,12 @@
         }
     }
 
+    @Test
     public void testSkipLoadingAconfigFlagWithMissingFields() {
         int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
         Object lock = new Object();
         SettingsState settingsState = new SettingsState(
-                getContext(), lock, mSettingsFile, configKey,
+                InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
                 SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
 
         parsed_flags flags = parsed_flags
@@ -155,12 +178,65 @@
         }
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_STAGE_ALL_ACONFIG_FLAGS)
+    public void testWritingAconfigFlagStages() {
+        int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+        Object lock = new Object();
+        SettingsState settingsState = new SettingsState(
+                InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
+                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+        parsed_flags flags = parsed_flags
+                .newBuilder()
+                .addParsedFlag(parsed_flag
+                    .newBuilder()
+                        .setPackage("com.android.flags")
+                        .setName("flag5")
+                        .setNamespace("test_namespace")
+                        .setDescription("test flag")
+                        .addBug("12345678")
+                        .setState(Aconfig.flag_state.DISABLED)
+                        .setPermission(Aconfig.flag_permission.READ_WRITE))
+                .build();
+
+        synchronized (lock) {
+            Map<String, Map<String, String>> defaults = new HashMap<>();
+            settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
+            settingsState.addAconfigDefaultValuesFromMap(defaults);
+
+            settingsState.insertSettingLocked("test_namespace/com.android.flags.flag5",
+                    "true", null, false, "com.android.flags");
+            settingsState.insertSettingLocked("test_namespace/com.android.flags.flag6",
+                    "true", null, false, "com.android.flags");
+
+            assertEquals("true",
+                    settingsState
+                        .getSettingLocked("staged/test_namespace*com.android.flags.flag5")
+                        .getValue());
+            assertEquals(null,
+                    settingsState
+                        .getSettingLocked("test_namespace/com.android.flags.flag5")
+                        .getValue());
+
+            assertEquals(null,
+                    settingsState
+                        .getSettingLocked("staged/test_namespace*com.android.flags.flag6")
+                        .getValue());
+            assertEquals("true",
+                    settingsState
+                        .getSettingLocked("test_namespace/com.android.flags.flag6")
+                        .getValue());
+        }
+    }
+
+    @Test
     public void testInvalidAconfigProtoDoesNotCrash() {
         Map<String, Map<String, String>> defaults = new HashMap<>();
         SettingsState settingsState = getSettingStateObject();
         settingsState.loadAconfigDefaultValues("invalid protobuf".getBytes(), defaults);
     }
 
+    @Test
     public void testIsBinary() {
         assertFalse(SettingsState.isBinary(" abc 日本語"));
 
@@ -191,6 +267,7 @@
     }
 
     /** Make sure we won't pass invalid characters to XML serializer. */
+    @Test
     public void testWriteReadNoCrash() throws Exception {
         ByteArrayOutputStream os = new ByteArrayOutputStream();
 
@@ -233,12 +310,15 @@
     /**
      * Make sure settings can be written to a file and also can be read.
      */
+    @Test
     public void testReadWrite() {
         final Object lock = new Object();
 
         assertFalse(mSettingsFile.exists());
-        final SettingsState ssWriter = new SettingsState(getContext(), lock, mSettingsFile, 1,
-                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+        final SettingsState ssWriter =
+                new SettingsState(
+                        InstrumentationRegistry.getContext(), lock, mSettingsFile, 1,
+                        SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
         ssWriter.setVersionLocked(SettingsState.SETTINGS_VERSION_NEW_ENCODING);
 
         ssWriter.insertSettingLocked("k1", "\u0000", null, false, "package");
@@ -250,8 +330,10 @@
         }
         ssWriter.waitForHandler();
         assertTrue(mSettingsFile.exists());
-        final SettingsState ssReader = new SettingsState(getContext(), lock, mSettingsFile, 1,
-                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+        final SettingsState ssReader =
+                new SettingsState(
+                        InstrumentationRegistry.getContext(), lock, mSettingsFile, 1,
+                        SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
 
         synchronized (lock) {
             assertEquals("\u0000", ssReader.getSettingLocked("k1").getValue());
@@ -264,6 +346,7 @@
     /**
      * In version 120, value "null" meant {code NULL}.
      */
+    @Test
     public void testUpgrade() throws Exception {
         final Object lock = new Object();
         final PrintStream os = new PrintStream(new FileOutputStream(mSettingsFile));
@@ -276,8 +359,10 @@
                         "</settings>");
         os.close();
 
-        final SettingsState ss = new SettingsState(getContext(), lock, mSettingsFile, 1,
-                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+        final SettingsState ss =
+                new SettingsState(
+                        InstrumentationRegistry.getContext(), lock, mSettingsFile, 1,
+                        SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
         synchronized (lock) {
             SettingsState.Setting s;
             s = ss.getSettingLocked("k0");
@@ -294,6 +379,7 @@
         }
     }
 
+    @Test
     public void testInitializeSetting_preserveFlagNotSet() {
         SettingsState settingsWriter = getSettingStateObject();
         settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
@@ -304,6 +390,7 @@
         assertFalse(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
     }
 
+    @Test
     public void testModifySetting_preserveFlagSet() {
         SettingsState settingsWriter = getSettingStateObject();
         settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
@@ -315,6 +402,7 @@
         assertTrue(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
     }
 
+    @Test
     public void testModifySettingOverrideableByRestore_preserveFlagNotSet() {
         SettingsState settingsWriter = getSettingStateObject();
         settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
@@ -327,6 +415,7 @@
         assertFalse(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
     }
 
+    @Test
     public void testModifySettingOverrideableByRestore_preserveFlagAlreadySet_flagValueUnchanged() {
         SettingsState settingsWriter = getSettingStateObject();
         // Init the setting.
@@ -344,6 +433,7 @@
         assertTrue(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
     }
 
+    @Test
     public void testResetSetting_preservedFlagIsReset() {
         SettingsState settingsState = getSettingStateObject();
         // Initialize the setting.
@@ -356,6 +446,7 @@
 
     }
 
+    @Test
     public void testModifySettingBySystemPackage_sameValue_preserveFlagNotSet() {
         SettingsState settingsState = getSettingStateObject();
         // Initialize the setting.
@@ -366,6 +457,7 @@
         assertFalse(settingsState.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
     }
 
+    @Test
     public void testModifySettingBySystemPackage_newValue_preserveFlagSet() {
         SettingsState settingsState = getSettingStateObject();
         // Initialize the setting.
@@ -377,12 +469,15 @@
     }
 
     private SettingsState getSettingStateObject() {
-        SettingsState settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
-                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+        SettingsState settingsState =
+                new SettingsState(
+                        InstrumentationRegistry.getContext(), mLock, mSettingsFile, 1,
+                        SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
         settingsState.setVersionLocked(SettingsState.SETTINGS_VERSION_NEW_ENCODING);
         return settingsState;
     }
 
+    @Test
     public void testInsertSetting_memoryUsage() {
         SettingsState settingsState = getSettingStateObject();
         // No exception should be thrown when there is no cap
@@ -390,8 +485,10 @@
                 null, false, "p1");
         settingsState.deleteSettingLocked(SETTING_NAME);
 
-        settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
-                SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
+        settingsState =
+                new SettingsState(
+                        InstrumentationRegistry.getContext(), mLock, mSettingsFile, 1,
+                        SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
         // System package doesn't have memory usage limit
         settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001),
                 null, false, SYSTEM_PACKAGE);
@@ -425,9 +522,12 @@
         }
     }
 
+    @Test
     public void testMemoryUsagePerPackage() {
-        SettingsState settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
-                SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
+        SettingsState settingsState =
+                new SettingsState(
+                        InstrumentationRegistry.getContext(), mLock, mSettingsFile, 1,
+                        SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
 
         // Test inserting one key with default
         final String testKey1 = SETTING_NAME;
@@ -512,9 +612,12 @@
         assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
     }
 
+    @Test
     public void testLargeSettingKey() {
-        SettingsState settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
-                SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
+        SettingsState settingsState =
+                new SettingsState(
+                        InstrumentationRegistry.getContext(), mLock, mSettingsFile, 1,
+                        SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
         final String largeKey = Strings.repeat("A", SettingsState.MAX_LENGTH_PER_STRING + 1);
         final String testValue = "testValue";
         synchronized (mLock) {
@@ -535,9 +638,12 @@
         }
     }
 
+    @Test
     public void testLargeSettingValue() {
-        SettingsState settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
-                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+        SettingsState settingsState =
+                new SettingsState(
+                        InstrumentationRegistry.getContext(), mLock, mSettingsFile, 1,
+                        SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
         final String testKey = "testKey";
         final String largeValue = Strings.repeat("A", SettingsState.MAX_LENGTH_PER_STRING + 1);
         synchronized (mLock) {
@@ -558,11 +664,12 @@
         }
     }
 
+    @Test
     public void testApplyStagedConfigValues() {
         int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
         Object lock = new Object();
         SettingsState settingsState = new SettingsState(
-                getContext(), lock, mSettingsFile, configKey,
+                InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
                 SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
 
         synchronized (lock) {
@@ -578,7 +685,8 @@
             assertEquals(VALUE2, settingsState.getSettingLocked(FLAG_NAME_2).getValue());
         }
 
-        settingsState = new SettingsState(getContext(), lock, mSettingsFile, configKey,
+        settingsState = new SettingsState(
+                InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
                 SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
 
         synchronized (lock) {
@@ -589,6 +697,7 @@
         }
     }
 
+    @Test
     public void testStagingTransformation() {
         assertEquals(INVALID_STAGED_FLAG_1,
                 SettingsState.createRealFlagName(INVALID_STAGED_FLAG_1));
@@ -603,11 +712,12 @@
                 SettingsState.createRealFlagName(VALID_STAGED_FLAG_1));
     }
 
+    @Test
     public void testInvalidStagedFlagsUnaffectedByReboot() {
         int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
         Object lock = new Object();
         SettingsState settingsState = new SettingsState(
-                getContext(), lock, mSettingsFile, configKey,
+                InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
                 SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
 
         synchronized (lock) {
@@ -620,7 +730,8 @@
             assertEquals(VALUE2, settingsState.getSettingLocked(INVALID_STAGED_FLAG_1).getValue());
         }
 
-        settingsState = new SettingsState(getContext(), lock, mSettingsFile, configKey,
+        settingsState = new SettingsState(
+                InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
                 SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
 
         synchronized (lock) {
@@ -628,6 +739,52 @@
         }
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_STAGE_ALL_ACONFIG_FLAGS)
+    public void testSetSettingsLockedStagesAconfigFlags() throws Exception {
+        int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+
+        SettingsState settingsState = new SettingsState(
+                InstrumentationRegistry.getContext(), mLock, mSettingsFile, configKey,
+                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+
+        String prefix = "test_namespace";
+        String packageName = "com.android.flags";
+        Map<String, String> keyValues =
+                Map.of("test_namespace/com.android.flags.flag3", "true");
+
+        parsed_flags flags = parsed_flags
+                .newBuilder()
+                .addParsedFlag(parsed_flag
+                    .newBuilder()
+                        .setPackage(packageName)
+                        .setName("flag3")
+                        .setNamespace(prefix)
+                        .setDescription("test flag")
+                        .addBug("12345678")
+                        .setState(Aconfig.flag_state.DISABLED)
+                        .setPermission(Aconfig.flag_permission.READ_WRITE))
+                .build();
+
+        synchronized (mLock) {
+            settingsState.loadAconfigDefaultValues(
+                    flags.toByteArray(), settingsState.getAconfigDefaultValues());
+            List<String> updates =
+                    settingsState.setSettingsLocked("test_namespace/", keyValues, packageName);
+            assertEquals(1, updates.size());
+            assertEquals(updates.get(0), "staged/test_namespace*com.android.flags.flag3");
+
+            SettingsState.Setting s;
+
+            s = settingsState.getSettingLocked("test_namespace/com.android.flags.flag3");
+            assertNull(s.getValue());
+
+            s = settingsState.getSettingLocked("staged/test_namespace*com.android.flags.flag3");
+            assertEquals("true", s.getValue());
+        }
+    }
+
+    @Test
     public void testsetSettingsLockedKeepTrunkDefault() throws Exception {
         final PrintStream os = new PrintStream(new FileOutputStream(mSettingsFile));
         os.print(
@@ -648,7 +805,7 @@
         int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
 
         SettingsState settingsState = new SettingsState(
-                getContext(), mLock, mSettingsFile, configKey,
+                InstrumentationRegistry.getContext(), mLock, mSettingsFile, configKey,
                 SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
 
         String prefix = "test_namespace";
@@ -705,6 +862,7 @@
         }
     }
 
+    @Test
     public void testsetSettingsLockedNoTrunkDefault() throws Exception {
         final PrintStream os = new PrintStream(new FileOutputStream(mSettingsFile));
         os.print(
@@ -720,7 +878,7 @@
         int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
 
         SettingsState settingsState = new SettingsState(
-                getContext(), mLock, mSettingsFile, configKey,
+                InstrumentationRegistry.getContext(), mLock, mSettingsFile, configKey,
                 SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
 
         Map<String, String> keyValues =
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 53f2caf..eb2d13d 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -210,6 +210,7 @@
     <uses-permission android:name="android.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS" />
     <uses-permission android:name="android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS" />
     <uses-permission android:name="android.permission.WHITELIST_RESTRICTED_PERMISSIONS" />
+    <uses-permission android:name="android.permission.EMERGENCY_INSTALL_PACKAGES" />
     <!-- Permission required for processes that don't own the focused window to switch
          touch mode state -->
     <uses-permission android:name="android.permission.MODIFY_TOUCH_MODE_STATE" />
@@ -565,9 +566,6 @@
     <!-- Permission required for CTS test - android.server.biometrics -->
     <uses-permission android:name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" />
 
-    <!-- Permission required for CTS test - android.server.biometrics -->
-    <uses-permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
-
     <!-- Permissions required for CTS test - NotificationManagerTest -->
     <uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" />
 
diff --git a/packages/Shell/tests/src/com/android/shell/UtilitiesTest.java b/packages/Shell/tests/src/com/android/shell/UtilitiesTest.java
index 51b7ba8..ad9eec7 100644
--- a/packages/Shell/tests/src/com/android/shell/UtilitiesTest.java
+++ b/packages/Shell/tests/src/com/android/shell/UtilitiesTest.java
@@ -15,10 +15,12 @@
  */
 package com.android.shell;
 
-import android.test.suitebuilder.annotation.SmallTest;
-import junit.framework.TestCase;
 import static com.android.shell.BugreportProgressService.isValid;
 
+import androidx.test.filters.SmallTest;
+
+import junit.framework.TestCase;
+
 @SmallTest
 public class UtilitiesTest extends TestCase {
 
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
index ea46c0c..ee81813 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
@@ -300,6 +300,8 @@
                 }
             };
             installTask.execute(data.getData());
+        } else if (requestCode == ADD_FILE_REQUEST_CODE && resultCode == RESULT_CANCELED) {
+            setupAlert();
         }
     }
 
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index bed95a5..617df74 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -32,55 +32,6 @@
     ],
 }
 
-// Opt-in configuration for code depending on Jetpack Compose.
-soong_config_module_type {
-    name: "systemui_compose_java_defaults",
-    module_type: "java_defaults",
-    config_namespace: "ANDROID",
-    bool_variables: ["SYSTEMUI_USE_COMPOSE"],
-    properties: [
-        "srcs",
-        "static_libs",
-    ],
-}
-
-systemui_compose_java_defaults {
-    name: "SystemUI_compose_defaults",
-    soong_config_variables: {
-        SYSTEMUI_USE_COMPOSE: {
-            // Because files in compose/features/ depend on SystemUI
-            // code, we compile those files when compiling SystemUI-core.
-            // We also compile the ComposeFacade in
-            // compose/facade/enabled/.
-            srcs: [
-                "compose/features/src/**/*.kt",
-                "compose/facade/enabled/src/**/*.kt",
-            ],
-
-            // The dependencies needed by SystemUIComposeFeatures,
-            // except for SystemUI-core.
-            // Copied from compose/features/Android.bp.
-            static_libs: [
-                "PlatformComposeCore",
-                "PlatformComposeSceneTransitionLayout",
-
-                "androidx.compose.runtime_runtime",
-                "androidx.compose.material3_material3",
-                "androidx.compose.material_material-icons-extended",
-                "androidx.activity_activity-compose",
-                "androidx.compose.animation_animation-graphics",
-            ],
-
-            // By default, Compose is disabled and we compile the ComposeFacade
-            // in compose/facade/disabled/.
-            conditions_default: {
-                srcs: ["compose/facade/disabled/src/**/*.kt"],
-                static_libs: [],
-            },
-        },
-    },
-}
-
 java_library {
     name: "SystemUI-proto",
 
@@ -138,14 +89,13 @@
 
 android_library {
     name: "SystemUI-core",
-    defaults: [
-        "SystemUI_compose_defaults",
-    ],
     srcs: [
         "src/**/*.kt",
         "src/**/*.java",
         "src/**/I*.aidl",
         ":ReleaseJavaFiles",
+        "compose/features/src/**/*.kt",
+        "compose/facade/enabled/src/**/*.kt",
     ],
     product_variables: {
         debuggable: {
@@ -205,8 +155,16 @@
         "jsr330",
         "lottie",
         "LowLightDreamLib",
+        "TraceurCommon",
         "motion_tool_lib",
         "notification_flags_lib",
+        "PlatformComposeCore",
+        "PlatformComposeSceneTransitionLayout",
+        "androidx.compose.runtime_runtime",
+        "androidx.compose.material3_material3",
+        "androidx.compose.material_material-icons-extended",
+        "androidx.activity_activity-compose",
+        "androidx.compose.animation_animation-graphics",
     ],
     libs: [
         "keepanno-annotations",
@@ -230,6 +188,7 @@
         extra_check_modules: ["SystemUILintChecker"],
         warning_checks: ["MissingApacheLicenseDetector"],
     },
+    skip_jarjar_repackage: true,
 }
 
 filegroup {
@@ -336,16 +295,23 @@
         "ravenwood-junit",
         "platform-test-annotations",
         "notification_flags_lib",
+        "PlatformComposeCore",
+        "PlatformComposeSceneTransitionLayout",
+        "androidx.compose.runtime_runtime",
+        "androidx.compose.material3_material3",
+        "androidx.compose.material_material-icons-extended",
+        "androidx.activity_activity-compose",
+        "androidx.compose.animation_animation-graphics",
+        "TraceurCommon",
     ],
+    skip_jarjar_repackage: true,
 }
 
 android_library {
     name: "SystemUI-tests",
     use_resource_processor: true,
-    defaults: [
-        "SystemUI_compose_defaults",
-    ],
     manifest: "tests/AndroidManifest-base.xml",
+    resource_dirs: [],
     additional_manifests: ["tests/AndroidManifest.xml"],
     srcs: [
         "tests/src/**/*.kt",
@@ -356,6 +322,8 @@
         ":ReleaseJavaFiles",
         ":SystemUI-tests-multivalent",
         ":SystemUI-tests-utils",
+        "compose/features/src/**/*.kt",
+        "compose/facade/enabled/src/**/*.kt",
     ],
     static_libs: [
         "SystemUI-tests-base",
@@ -366,6 +334,7 @@
         "androidx.test.ext.truth",
         "kotlin-test",
         "SystemUICustomizationTestUtils",
+        "androidx.compose.runtime_runtime",
     ],
     libs: [
         "android.test.runner",
@@ -386,6 +355,7 @@
         test: true,
         extra_check_modules: ["SystemUILintChecker"],
     },
+    skip_jarjar_repackage: true,
 }
 
 android_app {
@@ -395,7 +365,6 @@
     defaults: [
         "platform_app_defaults",
         "SystemUI_optimized_defaults",
-        "SystemUI_compose_defaults",
     ],
     manifest: "tests/AndroidManifest-base.xml",
 
@@ -404,9 +373,12 @@
         "src/**/*.java",
         "src/**/I*.aidl",
         ":ReleaseJavaFiles",
+        "compose/features/src/**/*.kt",
+        "compose/facade/enabled/src/**/*.kt",
     ],
     static_libs: [
         "SystemUI-tests-base",
+        "androidx.compose.runtime_runtime",
     ],
     libs: [
         "keepanno-annotations",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 12e8f57..98591e9 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -273,6 +273,9 @@
     <!-- to control accessibility volume -->
     <uses-permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" />
 
+    <!-- to change spatial audio -->
+    <uses-permission android:name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS" />
+
     <!-- to access ResolverRankerServices -->
     <uses-permission android:name="android.permission.BIND_RESOLVER_RANKER_SERVICE" />
 
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
index 085fc29..88181e7 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
@@ -56,10 +56,12 @@
         implements View.OnTouchListener {
 
     public static final String PACKAGE_NAME = AccessibilityMenuService.class.getPackageName();
+    public static final String PACKAGE_TESTS = ".tests";
     public static final String INTENT_TOGGLE_MENU = ".toggle_menu";
     public static final String INTENT_HIDE_MENU = ".hide_menu";
     public static final String INTENT_GLOBAL_ACTION = ".global_action";
     public static final String INTENT_GLOBAL_ACTION_EXTRA = "GLOBAL_ACTION";
+    public static final String INTENT_OPEN_BLOCKED = "OPEN_BLOCKED";
 
     private static final String TAG = "A11yMenuService";
     private static final long BUFFER_MILLISECONDS_TO_PREVENT_UPDATE_FAILURE = 100L;
@@ -192,7 +194,7 @@
 
         IntentFilter hideMenuFilter = new IntentFilter();
         hideMenuFilter.addAction(Intent.ACTION_SCREEN_OFF);
-        hideMenuFilter.addAction(PACKAGE_NAME + INTENT_HIDE_MENU);
+        hideMenuFilter.addAction(INTENT_HIDE_MENU);
 
         // Including WRITE_SECURE_SETTINGS enforces that we only listen to apps
         // with the restricted WRITE_SECURE_SETTINGS permission who broadcast this intent.
@@ -200,7 +202,7 @@
                 Manifest.permission.WRITE_SECURE_SETTINGS, null,
                 Context.RECEIVER_EXPORTED);
         registerReceiver(mToggleMenuReceiver,
-                new IntentFilter(PACKAGE_NAME + INTENT_TOGGLE_MENU),
+                new IntentFilter(INTENT_TOGGLE_MENU),
                 Manifest.permission.WRITE_SECURE_SETTINGS, null,
                 Context.RECEIVER_EXPORTED);
 
@@ -245,8 +247,9 @@
      * @return {@code true} if successful, {@code false} otherwise.
      */
     private boolean performGlobalActionInternal(int globalAction) {
-        Intent intent = new Intent(PACKAGE_NAME + INTENT_GLOBAL_ACTION);
+        Intent intent = new Intent(INTENT_GLOBAL_ACTION);
         intent.putExtra(INTENT_GLOBAL_ACTION_EXTRA, globalAction);
+        intent.setPackage(PACKAGE_NAME + PACKAGE_TESTS);
         sendBroadcast(intent);
         Log.i("A11yMenuService", "Broadcasting global action " + globalAction);
         return performGlobalAction(globalAction);
@@ -410,9 +413,16 @@
 
     private void toggleVisibility() {
         boolean locked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
-        if (!locked && SystemClock.uptimeMillis() - mLastTimeTouchedOutside
-                        > BUTTON_CLICK_TIMEOUT) {
-            mA11yMenuLayout.toggleVisibility();
+        if (!locked) {
+            if (SystemClock.uptimeMillis() - mLastTimeTouchedOutside
+                    > BUTTON_CLICK_TIMEOUT) {
+                mA11yMenuLayout.toggleVisibility();
+            }
+        } else {
+            // Broadcast for testing.
+            Intent intent = new Intent(INTENT_OPEN_BLOCKED);
+            intent.setPackage(PACKAGE_NAME + PACKAGE_TESTS);
+            sendBroadcast(intent);
         }
     }
 }
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index 72c1092..6546b87 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -23,6 +23,7 @@
 import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_RECENTS;
 import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT;
 
+import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_OPEN_BLOCKED;
 import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_GLOBAL_ACTION;
 import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_GLOBAL_ACTION_EXTRA;
 import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_HIDE_MENU;
@@ -65,6 +66,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
 @RunWith(AndroidJUnit4.class)
@@ -75,12 +77,11 @@
     private static final int TIMEOUT_SERVICE_STATUS_CHANGE_S = 5;
     private static final int TIMEOUT_UI_CHANGE_S = 5;
     private static final int NO_GLOBAL_ACTION = -1;
-    private static final String INPUT_KEYEVENT_KEYCODE_BACK = "input keyevent KEYCODE_BACK";
-    private static final String TEST_PIN = "1234";
 
     private static Instrumentation sInstrumentation;
     private static UiAutomation sUiAutomation;
-    private static AtomicInteger sLastGlobalAction;
+    private static final AtomicInteger sLastGlobalAction = new AtomicInteger(NO_GLOBAL_ACTION);
+    private static final AtomicBoolean sOpenBlocked = new AtomicBoolean(false);
 
     private static AccessibilityManager sAccessibilityManager;
     private static PowerManager sPowerManager;
@@ -122,8 +123,6 @@
                 () -> sAccessibilityManager.getEnabledAccessibilityServiceList(
                         AccessibilityServiceInfo.FEEDBACK_ALL_MASK).stream().filter(
                                 info -> info.getId().contains(serviceName)).count() == 1);
-
-        sLastGlobalAction = new AtomicInteger(NO_GLOBAL_ACTION);
         context.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
@@ -131,20 +130,28 @@
                 sLastGlobalAction.set(
                         intent.getIntExtra(INTENT_GLOBAL_ACTION_EXTRA, NO_GLOBAL_ACTION));
             }},
-                new IntentFilter(PACKAGE_NAME + INTENT_GLOBAL_ACTION),
+                new IntentFilter(INTENT_GLOBAL_ACTION),
+                null, null, Context.RECEIVER_EXPORTED);
+
+        context.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                Log.i(TAG, "Received notification that menu cannot be opened.");
+                sOpenBlocked.set(true);
+            }},
+                new IntentFilter(INTENT_OPEN_BLOCKED),
                 null, null, Context.RECEIVER_EXPORTED);
     }
 
     @AfterClass
-    public static void classTeardown() throws Throwable {
-        clearPin();
+    public static void classTeardown() {
         Settings.Secure.putString(sInstrumentation.getTargetContext().getContentResolver(),
                 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "");
     }
 
     @Before
     public void setup() throws Throwable {
-        clearPin();
+        sOpenBlocked.set(false);
         wakeUpScreen();
         sUiAutomation.executeShellCommand("input keyevent KEYCODE_MENU");
         openMenu();
@@ -154,20 +161,8 @@
     public void tearDown() throws Throwable {
         closeMenu();
         sLastGlobalAction.set(NO_GLOBAL_ACTION);
-    }
-
-    private static void clearPin() throws Throwable {
-        sUiAutomation.executeShellCommand("locksettings clear --old " + TEST_PIN);
-        TestUtils.waitUntil("Device did not register as unlocked & insecure.",
-                TIMEOUT_SERVICE_STATUS_CHANGE_S,
-                () -> !sKeyguardManager.isDeviceSecure());
-    }
-
-    private static void setPin() throws Throwable {
-        sUiAutomation.executeShellCommand("locksettings set-pin " + TEST_PIN);
-        TestUtils.waitUntil("Device did not recognize as locked & secure.",
-                TIMEOUT_SERVICE_STATUS_CHANGE_S,
-                () -> sKeyguardManager.isDeviceSecure());
+        // dismisses screenshot popup if present.
+        sUiAutomation.executeShellCommand("input keyevent KEYCODE_BACK");
     }
 
     private static boolean isMenuVisible() {
@@ -184,7 +179,6 @@
 
     private static void closeScreen() throws Throwable {
         Display display = sDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
-        setPin();
         sUiAutomation.performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN);
         TestUtils.waitUntil("Screen did not close.",
                 TIMEOUT_UI_CHANGE_S,
@@ -194,12 +188,20 @@
     }
 
     private static void openMenu() throws Throwable {
-        Intent intent = new Intent(PACKAGE_NAME + INTENT_TOGGLE_MENU);
+        openMenu(false);
+    }
+
+    private static void openMenu(boolean abandonOnBlock) throws Throwable {
+        Intent intent = new Intent(INTENT_TOGGLE_MENU);
+        intent.setPackage(PACKAGE_NAME);
         sInstrumentation.getContext().sendBroadcast(intent);
 
         TestUtils.waitUntil("Timed out before menu could appear.",
                 TIMEOUT_UI_CHANGE_S,
                 () -> {
+                    if (sOpenBlocked.get() && abandonOnBlock) {
+                        throw new IllegalStateException();
+                    }
                     if (isMenuVisible()) {
                         return true;
                     } else {
@@ -213,7 +215,8 @@
         if (!isMenuVisible()) {
             return;
         }
-        Intent intent = new Intent(PACKAGE_NAME + INTENT_HIDE_MENU);
+        Intent intent = new Intent(INTENT_HIDE_MENU);
+        intent.setPackage(PACKAGE_NAME);
         sInstrumentation.getContext().sendBroadcast(intent);
         TestUtils.waitUntil("Timed out before menu could close.",
                 TIMEOUT_UI_CHANGE_S, () -> !isMenuVisible());
@@ -444,13 +447,13 @@
         closeScreen();
         wakeUpScreen();
 
-        boolean timedOut = false;
+        boolean blocked = false;
         try {
-            openMenu();
-        } catch (AssertionError e) {
+            openMenu(true);
+        } catch (IllegalStateException e) {
             // Expected
-            timedOut = true;
+            blocked = true;
         }
-        assertThat(timedOut).isTrue();
+        assertThat(blocked).isTrue();
     }
 }
diff --git a/packages/SystemUI/aconfig/predictive_back.aconfig b/packages/SystemUI/aconfig/predictive_back.aconfig
index d0e6b28..7bbe82c 100644
--- a/packages/SystemUI/aconfig/predictive_back.aconfig
+++ b/packages/SystemUI/aconfig/predictive_back.aconfig
@@ -4,26 +4,26 @@
     name: "predictive_back_sysui"
     namespace: "systemui"
     description: "Predictive Back Dispatching for SysUI"
-    bug: "309545085"
+    bug: "327737297"
 }
 
 flag {
     name: "predictive_back_animate_shade"
     namespace: "systemui"
     description: "Enable Shade Animations"
-    bug: "309545085"
+    bug: "327732946"
 }
 
 flag {
     name: "predictive_back_animate_bouncer"
     namespace: "systemui"
     description: "Enable Predictive Back Animation in Bouncer"
-    bug: "309545085"
+    bug: "327733487"
 }
 
 flag {
     name: "predictive_back_animate_dialogs"
     namespace: "systemui"
     description: "Enable Predictive Back Animation for SysUI dialogs"
-    bug: "309545085"
+    bug: "327721544"
 }
\ No newline at end of file
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index f5c4843..85bdb29 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -25,6 +25,16 @@
 }
 
 flag {
+   name: "notification_view_flipper_pausing"
+   namespace: "systemui"
+   description: "Pause ViewFlippers inside Notification custom layouts when the shade is closed."
+   bug: "309146176"
+   metadata {
+        purpose: PURPOSE_BUGFIX
+   }
+}
+
+flag {
     name: "notification_async_group_header_inflation"
     namespace: "systemui"
     description: "Inflates the notification group summary header views from the background thread."
@@ -76,6 +86,16 @@
 }
 
 flag {
+    name: "notification_content_alpha_optimization"
+    namespace: "systemui"
+    description: "Only reset alpha values of needed content views"
+    bug: "292024656"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "notifications_live_data_store_refactor"
     namespace: "systemui"
     description: "Replaces NotifLiveDataStore with ActiveNotificationListRepository, and updates consumers. "
@@ -84,6 +104,17 @@
 }
 
 flag {
+   name: "pss_app_selector_abrupt_exit_fix"
+   namespace: "systemui"
+   description: "Fixes the app selector abruptly disappearing without an animation, when the"
+        "selected task is the foreground task."
+   bug: "314385883"
+   metadata {
+        purpose: PURPOSE_BUGFIX
+   }
+}
+
+flag {
     name: "notifications_background_media_icons"
     namespace: "systemui"
     description: "Updates icons for media notifications in the background."
@@ -173,14 +204,6 @@
 }
 
 flag {
-    name: "keyguard_shade_migration_nssl"
-    namespace: "systemui"
-    description: "Moves NSSL into a shared element between the notification_panel and "
-        "keyguard_root_view."
-    bug: "278054201"
-}
-
-flag {
     name: "unfold_animation_background_progress"
     namespace: "systemui"
     description: "Moves unfold animation progress calculation to a background thread"
@@ -398,6 +421,13 @@
 }
 
 flag {
+   name: "smartspace_remoteviews_rendering"
+   namespace: "systemui"
+   description: "Indicate Smartspace RemoteViews rendering"
+   bug: "326292691"
+}
+
+flag {
    name: "pin_input_field_styled_focus_state"
    namespace: "systemui"
    description: "Enables styled focus states on pin input field if keyboard is connected"
@@ -447,6 +477,13 @@
 }
 
 flag {
+    name: "hearing_aids_qs_tile_dialog"
+    namespace: "systemui"
+    description: "Show a dialog when clicking on hearing aids quick settings tile."
+    bug: "291423171"
+}
+
+flag {
     name: "notification_row_user_context"
     namespace: "systemui"
     description: "Create a user-specific Context for the ImageResolver in ExpandableNotificationRow"
@@ -548,6 +585,16 @@
 }
 
 flag {
+    name: "delayed_wakelock_release_on_background_thread"
+    namespace: "systemui"
+    description: "Released delayed wakelocks on background threads to avoid janking screen transitions."
+    bug: "316128516"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "notify_power_manager_user_activity_background"
     namespace: "systemui"
     description: "Decide whether to notify the user activity to power manager in the background thread."
@@ -556,3 +603,28 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "media_controls_refactor"
+    namespace: "systemui"
+    description: "Refactors media code to follow the recommended architecture"
+    bug: "326408371"
+}
+
+flag {
+    name: "qs_tile_focus_state"
+    namespace: "systemui"
+    description: "enables new focus outline for qs tiles when focused on with physical keyboard"
+    bug: "312899524"
+}
+
+flag {
+   name: "edgeback_gesture_handler_get_running_tasks_background"
+    namespace: "systemui"
+    description: "Decide whether to get the running tasks from activity manager in EdgebackGestureHandler"
+        " class on the background thread."
+    bug: "325041960"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
index 5d5f12e..3f57f88 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
@@ -337,6 +337,7 @@
         if (ghostedView is LaunchableView) {
             // Restore the ghosted view visibility.
             ghostedView.setShouldBlockVisibilityChanges(false)
+            ghostedView.onActivityLaunchAnimationEnd()
         } else {
             // Make the ghosted view visible. We ensure that the view is considered VISIBLE by
             // accessibility by first making it INVISIBLE then VISIBLE (see b/204944038#comment17
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
index ed8e705..da6ccaa 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
@@ -38,6 +38,9 @@
      * @param block whether we should block/postpone all calls to `setVisibility`.
      */
     fun setShouldBlockVisibilityChanges(block: Boolean)
+
+    /** Perform an action when the activity launch animation ends */
+    fun onActivityLaunchAnimationEnd() {}
 }
 
 /** A delegate that can be used by views to make the implementation of [LaunchableView] easier. */
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index 8dc7495..b124025 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -27,6 +27,7 @@
 import android.text.Layout
 import android.util.LruCache
 import kotlin.math.roundToInt
+import android.util.Log
 
 private const val DEFAULT_ANIMATION_DURATION: Long = 300
 private const val TYPEFACE_CACHE_MAX_ENTRIES = 5
@@ -140,7 +141,6 @@
     }
 
     sealed class PositionedGlyph {
-
         /** Mutable X coordinate of the glyph position relative from drawing offset. */
         var x: Float = 0f
 
@@ -269,41 +269,53 @@
         duration: Long = -1L,
         interpolator: TimeInterpolator? = null,
         delay: Long = 0,
-        onAnimationEnd: Runnable? = null
+        onAnimationEnd: Runnable? = null,
+    ) = setTextStyleInternal(fvar, textSize, color, strokeWidth, animate, duration,
+        interpolator, delay, onAnimationEnd, updateLayoutOnFailure = true)
+
+    private fun setTextStyleInternal(
+        fvar: String?,
+        textSize: Float,
+        color: Int?,
+        strokeWidth: Float,
+        animate: Boolean,
+        duration: Long,
+        interpolator: TimeInterpolator?,
+        delay: Long,
+        onAnimationEnd: Runnable?,
+        updateLayoutOnFailure: Boolean,
     ) {
-        if (animate) {
-            animator.cancel()
-            textInterpolator.rebase()
-        }
+        try {
+            if (animate) {
+                animator.cancel()
+                textInterpolator.rebase()
+            }
 
-        if (textSize >= 0) {
-            textInterpolator.targetPaint.textSize = textSize
-        }
+            if (textSize >= 0) {
+                textInterpolator.targetPaint.textSize = textSize
+            }
+            if (!fvar.isNullOrBlank()) {
+                textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
+            }
+            if (color != null) {
+                textInterpolator.targetPaint.color = color
+            }
+            if (strokeWidth >= 0F) {
+                textInterpolator.targetPaint.strokeWidth = strokeWidth
+            }
+            textInterpolator.onTargetPaintModified()
 
-        if (!fvar.isNullOrBlank()) {
-            textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
-        }
-
-        if (color != null) {
-            textInterpolator.targetPaint.color = color
-        }
-        if (strokeWidth >= 0F) {
-            textInterpolator.targetPaint.strokeWidth = strokeWidth
-        }
-        textInterpolator.onTargetPaintModified()
-
-        if (animate) {
-            animator.startDelay = delay
-            animator.duration =
-                if (duration == -1L) {
-                    DEFAULT_ANIMATION_DURATION
-                } else {
-                    duration
-                }
-            interpolator?.let { animator.interpolator = it }
-            if (onAnimationEnd != null) {
-                val listener =
-                    object : AnimatorListenerAdapter() {
+            if (animate) {
+                animator.startDelay = delay
+                animator.duration =
+                    if (duration == -1L) {
+                        DEFAULT_ANIMATION_DURATION
+                    } else {
+                        duration
+                    }
+                interpolator?.let { animator.interpolator = it }
+                if (onAnimationEnd != null) {
+                    val listener = object : AnimatorListenerAdapter() {
                         override fun onAnimationEnd(animation: Animator) {
                             onAnimationEnd.run()
                             animator.removeListener(this)
@@ -312,14 +324,25 @@
                             animator.removeListener(this)
                         }
                     }
-                animator.addListener(listener)
+                    animator.addListener(listener)
+                }
+                animator.start()
+            } else {
+                // No animation is requested, thus set base and target state to the same state.
+                textInterpolator.progress = 1f
+                textInterpolator.rebase()
+                invalidateCallback()
             }
-            animator.start()
-        } else {
-            // No animation is requested, thus set base and target state to the same state.
-            textInterpolator.progress = 1f
-            textInterpolator.rebase()
-            invalidateCallback()
+        } catch (ex: IllegalArgumentException) {
+            if (updateLayoutOnFailure) {
+                Log.e(TAG, "setTextStyleInternal: Exception caught but retrying. This is usually" +
+                    " due to the layout having changed unexpectedly without being notified.", ex)
+                updateLayout(textInterpolator.layout)
+                setTextStyleInternal(fvar, textSize, color, strokeWidth, animate, duration,
+                    interpolator, delay, onAnimationEnd, updateLayoutOnFailure = false)
+            } else {
+                throw ex
+            }
         }
     }
 
@@ -355,15 +378,13 @@
         interpolator: TimeInterpolator? = null,
         delay: Long = 0,
         onAnimationEnd: Runnable? = null
-    ) {
-        val fvar = fontVariationUtils.updateFontVariation(
-            weight = weight,
-            width = width,
-            opticalSize = opticalSize,
-            roundness = roundness,
-        )
-        setTextStyle(
-            fvar = fvar,
+    ) = setTextStyleInternal(
+            fvar = fontVariationUtils.updateFontVariation(
+                weight = weight,
+                width = width,
+                opticalSize = opticalSize,
+                roundness = roundness,
+            ),
             textSize = textSize,
             color = color,
             strokeWidth = strokeWidth,
@@ -372,6 +393,10 @@
             interpolator = interpolator,
             delay = delay,
             onAnimationEnd = onAnimationEnd,
+            updateLayoutOnFailure = true,
         )
+
+    companion object {
+        private val TAG = TextAnimator::class.simpleName!!
     }
 }
diff --git a/packages/SystemUI/compose/core/TEST_MAPPING b/packages/SystemUI/compose/core/TEST_MAPPING
index ee95f73..b71c5fb 100644
--- a/packages/SystemUI/compose/core/TEST_MAPPING
+++ b/packages/SystemUI/compose/core/TEST_MAPPING
@@ -12,17 +12,6 @@
       ]
     },
     {
-      "name": "SystemUIComposeFeaturesTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
-    },
-    {
       "name": "SystemUIComposeGalleryTests",
       "options": [
         {
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
index 62dd4ac..9a99649 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
@@ -21,22 +21,25 @@
 import androidx.compose.animation.core.animateDpAsState
 import androidx.compose.animation.core.animateFloatAsState
 import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.background
 import androidx.compose.foundation.interaction.DragInteraction
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.aspectRatio
-import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.LocalContentColor
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Slider
 import androidx.compose.material3.SliderState
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -44,17 +47,28 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
 import androidx.compose.ui.geometry.CornerRadius
 import androidx.compose.ui.geometry.RoundRect
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Path
 import androidx.compose.ui.graphics.drawscope.clipPath
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasurePolicy
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.layout.layoutId
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
-import com.android.compose.modifiers.padding
+import androidx.compose.ui.util.fastFirst
+import androidx.compose.ui.util.fastFirstOrNull
 import com.android.compose.theme.LocalAndroidColorScheme
 
 /**
@@ -62,15 +76,16 @@
  *
  * @param onValueChangeFinished is called when the slider settles on a [value]. This callback
  *   shouldn't be used to react to value changes. Use [onValueChange] instead
- * @param interactionSource - the [MutableInteractionSource] representing the stream of Interactions
+ * @param interactionSource the [MutableInteractionSource] representing the stream of Interactions
  *   for this slider. You can create and pass in your own remembered instance to observe
  *   Interactions and customize the appearance / behavior of this slider in different states.
- * @param colors - slider color scheme.
- * @param draggingCornersRadius - radius of the slider indicator when the user drags it
- * @param icon - icon at the start of the slider. Icon is limited to a square space at the start of
- *   the slider
- * @param label - control shown next to the icon.
+ * @param colors determine slider color scheme.
+ * @param draggingCornersRadius is the radius of the slider indicator when the user drags it
+ * @param icon at the start of the slider. Icon is limited to a square space at the start of the
+ *   slider
+ * @param label is shown next to the icon.
  */
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun PlatformSlider(
     value: Float,
@@ -86,7 +101,7 @@
     label: (@Composable (isDragging: Boolean) -> Unit)? = null,
 ) {
     val sliderHeight: Dp = 64.dp
-    val iconWidth: Dp = sliderHeight
+    val thumbSize: Dp = sliderHeight
     var isDragging by remember { mutableStateOf(false) }
     LaunchedEffect(interactionSource) {
         interactionSource.interactions.collect { interaction ->
@@ -101,16 +116,6 @@
             }
         }
     }
-    val paddingStart by
-        animateDpAsState(
-            targetValue =
-                if ((!isDragging && value == valueRange.start) || icon == null) {
-                    16.dp
-                } else {
-                    0.dp
-                },
-            label = "LabelIconSpacingAnimation"
-        )
 
     Box(modifier = modifier.height(sliderHeight)) {
         Slider(
@@ -126,127 +131,275 @@
                     sliderState = it,
                     enabled = enabled,
                     colors = colors,
-                    iconWidth = iconWidth,
                     draggingCornersRadius = draggingCornersRadius,
                     sliderHeight = sliderHeight,
+                    thumbSize = thumbSize,
                     isDragging = isDragging,
-                    modifier = Modifier,
+                    label = label,
+                    icon = icon,
+                    modifier = Modifier.fillMaxSize(),
                 )
             },
-            thumb = { Spacer(Modifier.width(iconWidth).height(sliderHeight)) },
+            thumb = { Spacer(Modifier.size(thumbSize)) },
         )
 
-        if (icon != null || label != null) {
-            Row(modifier = Modifier.fillMaxSize()) {
-                icon?.let { iconComposable ->
-                    Box(
-                        modifier = Modifier.fillMaxHeight().aspectRatio(1f),
-                        contentAlignment = Alignment.Center,
-                    ) {
-                        iconComposable(isDragging)
-                    }
-                }
-
-                label?.let { labelComposable ->
-                    Box(
-                        modifier =
-                            Modifier.fillMaxHeight()
-                                .weight(1f)
-                                .padding(start = { paddingStart.roundToPx() }),
-                        contentAlignment = Alignment.CenterStart,
-                    ) {
-                        labelComposable(isDragging)
-                    }
-                }
-            }
-        }
+        Spacer(
+            Modifier.padding(8.dp)
+                .size(4.dp)
+                .align(Alignment.CenterEnd)
+                .background(color = colors.indicatorColor, shape = CircleShape)
+        )
     }
 }
 
+private enum class TrackComponent(val zIndex: Float) {
+    Background(0f),
+    Icon(1f),
+    Label(1f),
+}
+
 @Composable
 private fun Track(
     sliderState: SliderState,
     enabled: Boolean,
     colors: PlatformSliderColors,
-    iconWidth: Dp,
     draggingCornersRadius: Dp,
     sliderHeight: Dp,
+    thumbSize: Dp,
     isDragging: Boolean,
+    icon: (@Composable (isDragging: Boolean) -> Unit)?,
+    label: (@Composable (isDragging: Boolean) -> Unit)?,
     modifier: Modifier = Modifier,
 ) {
     val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
-    val iconWidthPx: Float
-    val halfIconWidthPx: Float
-    val targetIndicatorRadiusPx: Float
-    val halfSliderHeightPx: Float
-    with(LocalDensity.current) {
-        halfSliderHeightPx = sliderHeight.toPx() / 2
-        iconWidthPx = iconWidth.toPx()
-        halfIconWidthPx = iconWidthPx / 2
-        targetIndicatorRadiusPx =
-            if (isDragging) draggingCornersRadius.toPx() else halfSliderHeightPx
-    }
+    var drawingState: DrawingState by remember { mutableStateOf(DrawingState()) }
+    Layout(
+        modifier = modifier,
+        content = {
+            TrackBackground(
+                modifier = Modifier.layoutId(TrackComponent.Background),
+                drawingState = drawingState,
+                enabled = enabled,
+                colors = colors,
+                draggingCornersRadiusActive = draggingCornersRadius,
+                draggingCornersRadiusIdle = sliderHeight / 2,
+                isDragging = isDragging,
+            )
+            if (icon != null) {
+                Box(
+                    modifier = Modifier.layoutId(TrackComponent.Icon).clip(CircleShape),
+                    contentAlignment = Alignment.Center,
+                ) {
+                    CompositionLocalProvider(
+                        LocalContentColor provides
+                            if (enabled) colors.iconColor else colors.disabledIconColor
+                    ) {
+                        icon(isDragging)
+                    }
+                }
+            }
+            if (label != null) {
+                val offsetX by
+                    animateFloatAsState(
+                        targetValue =
+                            if (enabled) {
+                                if (drawingState.isLabelOnTopOfIndicator) {
+                                    drawingState.iconWidth.coerceAtLeast(
+                                        LocalDensity.current.run { 16.dp.toPx() }
+                                    )
+                                } else {
+                                    val indicatorWidth =
+                                        drawingState.indicatorRight - drawingState.indicatorLeft
+                                    indicatorWidth + LocalDensity.current.run { 16.dp.toPx() }
+                                }
+                            } else {
+                                drawingState.iconWidth
+                            },
+                        label = "LabelIconSpacingAnimation"
+                    )
+                Box(
+                    modifier =
+                        Modifier.layoutId(TrackComponent.Label).offset {
+                            IntOffset(offsetX.toInt(), 0)
+                        },
+                    contentAlignment = Alignment.CenterStart,
+                ) {
+                    CompositionLocalProvider(
+                        LocalContentColor provides
+                            colors.getLabelColor(
+                                isEnabled = enabled,
+                                isLabelOnTopOfTheIndicator = drawingState.isLabelOnTopOfIndicator,
+                            )
+                    ) {
+                        label(isDragging)
+                    }
+                }
+            }
+        },
+        measurePolicy =
+            TrackMeasurePolicy(
+                sliderState = sliderState,
+                thumbSize = LocalDensity.current.run { thumbSize.roundToPx() },
+                isRtl = isRtl,
+                onDrawingStateMeasured = { drawingState = it }
+            )
+    )
+}
 
-    val indicatorRadiusPx: Float by
-        animateFloatAsState(
-            targetValue = targetIndicatorRadiusPx,
+@Composable
+private fun TrackBackground(
+    drawingState: DrawingState,
+    enabled: Boolean,
+    colors: PlatformSliderColors,
+    draggingCornersRadiusActive: Dp,
+    draggingCornersRadiusIdle: Dp,
+    isDragging: Boolean,
+    modifier: Modifier = Modifier,
+) {
+    val indicatorRadiusDp: Dp by
+        animateDpAsState(
+            targetValue =
+                if (isDragging) draggingCornersRadiusActive else draggingCornersRadiusIdle,
             label = "PlatformSliderCornersAnimation",
         )
 
     val trackColor = colors.getTrackColor(enabled)
     val indicatorColor = colors.getIndicatorColor(enabled)
-    val trackCornerRadius = CornerRadius(halfSliderHeightPx, halfSliderHeightPx)
-    val indicatorCornerRadius = CornerRadius(indicatorRadiusPx, indicatorRadiusPx)
     Canvas(modifier.fillMaxSize()) {
+        val trackCornerRadius = CornerRadius(size.height / 2, size.height / 2)
         val trackPath = Path()
         trackPath.addRoundRect(
             RoundRect(
-                left = -halfIconWidthPx,
+                left = 0f,
                 top = 0f,
-                right = size.width + halfIconWidthPx,
-                bottom = size.height,
+                right = drawingState.totalWidth,
+                bottom = drawingState.totalHeight,
                 cornerRadius = trackCornerRadius,
             )
         )
         drawPath(path = trackPath, color = trackColor)
 
+        val indicatorCornerRadius = CornerRadius(indicatorRadiusDp.toPx(), indicatorRadiusDp.toPx())
         clipPath(trackPath) {
             val indicatorPath = Path()
-            if (isRtl) {
-                indicatorPath.addRoundRect(
-                    RoundRect(
-                        left =
-                            size.width -
-                                size.width * sliderState.coercedNormalizedValue -
-                                halfIconWidthPx,
-                        top = 0f,
-                        right = size.width + iconWidthPx,
-                        bottom = size.height,
-                        topLeftCornerRadius = indicatorCornerRadius,
-                        topRightCornerRadius = trackCornerRadius,
-                        bottomRightCornerRadius = trackCornerRadius,
-                        bottomLeftCornerRadius = indicatorCornerRadius,
-                    )
+            indicatorPath.addRoundRect(
+                RoundRect(
+                    left = drawingState.indicatorLeft,
+                    top = drawingState.indicatorTop,
+                    right = drawingState.indicatorRight,
+                    bottom = drawingState.indicatorBottom,
+                    topLeftCornerRadius = trackCornerRadius,
+                    topRightCornerRadius = indicatorCornerRadius,
+                    bottomRightCornerRadius = indicatorCornerRadius,
+                    bottomLeftCornerRadius = trackCornerRadius,
                 )
-            } else {
-                indicatorPath.addRoundRect(
-                    RoundRect(
-                        left = -halfIconWidthPx,
-                        top = 0f,
-                        right = size.width * sliderState.coercedNormalizedValue + halfIconWidthPx,
-                        bottom = size.height,
-                        topLeftCornerRadius = trackCornerRadius,
-                        topRightCornerRadius = indicatorCornerRadius,
-                        bottomRightCornerRadius = indicatorCornerRadius,
-                        bottomLeftCornerRadius = trackCornerRadius,
-                    )
-                )
-            }
+            )
             drawPath(path = indicatorPath, color = indicatorColor)
         }
     }
 }
 
+/** Measures track components sizes and calls [onDrawingStateMeasured] when it's done. */
+private class TrackMeasurePolicy(
+    private val sliderState: SliderState,
+    private val thumbSize: Int,
+    private val isRtl: Boolean,
+    private val onDrawingStateMeasured: (DrawingState) -> Unit,
+) : MeasurePolicy {
+
+    override fun MeasureScope.measure(
+        measurables: List<Measurable>,
+        constraints: Constraints
+    ): MeasureResult {
+        // Slider adds a paddings to the Track to make spase for thumb
+        val desiredWidth = constraints.maxWidth + thumbSize
+        val desiredHeight = constraints.maxHeight
+        val backgroundPlaceable: Placeable =
+            measurables
+                .fastFirst { it.layoutId == TrackComponent.Background }
+                .measure(Constraints(desiredWidth, desiredWidth, desiredHeight, desiredHeight))
+
+        val iconPlaceable: Placeable? =
+            measurables
+                .fastFirstOrNull { it.layoutId == TrackComponent.Icon }
+                ?.measure(
+                    Constraints(
+                        minWidth = desiredHeight,
+                        maxWidth = desiredHeight,
+                        minHeight = desiredHeight,
+                        maxHeight = desiredHeight,
+                    )
+                )
+
+        val iconSize = iconPlaceable?.width ?: 0
+        val labelMaxWidth = (desiredWidth - iconSize) / 2
+        val labelPlaceable: Placeable? =
+            measurables
+                .fastFirstOrNull { it.layoutId == TrackComponent.Label }
+                ?.measure(
+                    Constraints(
+                        minWidth = 0,
+                        maxWidth = labelMaxWidth,
+                        minHeight = desiredHeight,
+                        maxHeight = desiredHeight,
+                    )
+                )
+
+        val drawingState =
+            if (isRtl) {
+                DrawingState(
+                    isRtl = true,
+                    totalWidth = desiredWidth.toFloat(),
+                    totalHeight = desiredHeight.toFloat(),
+                    indicatorLeft =
+                        (desiredWidth - iconSize) * (1 - sliderState.coercedNormalizedValue),
+                    indicatorTop = 0f,
+                    indicatorRight = desiredWidth.toFloat(),
+                    indicatorBottom = desiredHeight.toFloat(),
+                    iconWidth = iconSize.toFloat(),
+                    labelWidth = labelPlaceable?.width?.toFloat() ?: 0f,
+                )
+            } else {
+                DrawingState(
+                    isRtl = false,
+                    totalWidth = desiredWidth.toFloat(),
+                    totalHeight = desiredHeight.toFloat(),
+                    indicatorLeft = 0f,
+                    indicatorTop = 0f,
+                    indicatorRight =
+                        iconSize + (desiredWidth - iconSize) * sliderState.coercedNormalizedValue,
+                    indicatorBottom = desiredHeight.toFloat(),
+                    iconWidth = iconSize.toFloat(),
+                    labelWidth = labelPlaceable?.width?.toFloat() ?: 0f,
+                )
+            }
+
+        onDrawingStateMeasured(drawingState)
+
+        return layout(desiredWidth, desiredHeight) {
+            backgroundPlaceable.placeRelative(0, 0, TrackComponent.Background.zIndex)
+
+            iconPlaceable?.placeRelative(0, 0, TrackComponent.Icon.zIndex)
+            labelPlaceable?.placeRelative(0, 0, TrackComponent.Label.zIndex)
+        }
+    }
+}
+
+private data class DrawingState(
+    val isRtl: Boolean = false,
+    val totalWidth: Float = 0f,
+    val totalHeight: Float = 0f,
+    val indicatorLeft: Float = 0f,
+    val indicatorTop: Float = 0f,
+    val indicatorRight: Float = 0f,
+    val indicatorBottom: Float = 0f,
+    val iconWidth: Float = 0f,
+    val labelWidth: Float = 0f,
+)
+
+private val DrawingState.isLabelOnTopOfIndicator: Boolean
+    get() = labelWidth < indicatorRight - indicatorLeft - iconWidth
+
 /** [SliderState.value] normalized using [SliderState.valueRange]. The result belongs to [0, 1] */
 private val SliderState.coercedNormalizedValue: Float
     get() {
@@ -265,17 +418,19 @@
  * @param trackColor fills the track of the slider. This is a "background" of the slider
  * @param indicatorColor fills the slider from the start to the value
  * @param iconColor is the default icon color
- * @param labelColor is the default icon color
+ * @param labelColorOnIndicator is the label color for when it's shown on top of the indicator
+ * @param labelColorOnTrack is the label color for when it's shown on top of the track
  * @param disabledTrackColor is the [trackColor] when the PlatformSlider#enabled == false
  * @param disabledIndicatorColor is the [indicatorColor] when the PlatformSlider#enabled == false
  * @param disabledIconColor is the [iconColor] when the PlatformSlider#enabled == false
- * @param disabledLabelColor is the [labelColor] when the PlatformSlider#enabled == false
+ * @param disabledLabelColor is the label color when the PlatformSlider#enabled == false
  */
 data class PlatformSliderColors(
     val trackColor: Color,
     val indicatorColor: Color,
     val iconColor: Color,
-    val labelColor: Color,
+    val labelColorOnIndicator: Color,
+    val labelColorOnTrack: Color,
     val disabledTrackColor: Color,
     val disabledIndicatorColor: Color,
     val disabledIconColor: Color,
@@ -297,10 +452,11 @@
 @Composable
 private fun lightThemePlatformSliderColors() =
     PlatformSliderColors(
-        trackColor = MaterialTheme.colorScheme.tertiaryContainer,
-        indicatorColor = LocalAndroidColorScheme.current.tertiaryFixedDim,
-        iconColor = MaterialTheme.colorScheme.onTertiaryContainer,
-        labelColor = MaterialTheme.colorScheme.onTertiaryContainer,
+        trackColor = LocalAndroidColorScheme.current.tertiaryFixedDim,
+        indicatorColor = MaterialTheme.colorScheme.tertiary,
+        iconColor = MaterialTheme.colorScheme.onTertiary,
+        labelColorOnIndicator = MaterialTheme.colorScheme.onTertiary,
+        labelColorOnTrack = LocalAndroidColorScheme.current.onTertiaryFixed,
         disabledTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
         disabledIndicatorColor = MaterialTheme.colorScheme.surfaceContainerHighest,
         disabledIconColor = MaterialTheme.colorScheme.outline,
@@ -311,10 +467,11 @@
 @Composable
 private fun darkThemePlatformSliderColors() =
     PlatformSliderColors(
-        trackColor = MaterialTheme.colorScheme.onTertiary,
-        indicatorColor = LocalAndroidColorScheme.current.onTertiaryFixedVariant,
+        trackColor = MaterialTheme.colorScheme.tertiary,
+        indicatorColor = MaterialTheme.colorScheme.tertiary,
         iconColor = MaterialTheme.colorScheme.onTertiaryContainer,
-        labelColor = MaterialTheme.colorScheme.onTertiaryContainer,
+        labelColorOnIndicator = MaterialTheme.colorScheme.onTertiary,
+        labelColorOnTrack = LocalAndroidColorScheme.current.onTertiaryFixed,
         disabledTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
         disabledIndicatorColor = MaterialTheme.colorScheme.surfaceContainerHighest,
         disabledIconColor = MaterialTheme.colorScheme.outline,
@@ -326,3 +483,14 @@
 
 private fun PlatformSliderColors.getIndicatorColor(isEnabled: Boolean): Color =
     if (isEnabled) indicatorColor else disabledIndicatorColor
+
+private fun PlatformSliderColors.getLabelColor(
+    isEnabled: Boolean,
+    isLabelOnTopOfTheIndicator: Boolean
+): Color {
+    return if (isEnabled) {
+        if (isLabelOnTopOfTheIndicator) labelColorOnIndicator else labelColorOnTrack
+    } else {
+        disabledLabelColor
+    }
+}
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
deleted file mode 100644
index 4398b25..0000000
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ /dev/null
@@ -1,132 +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.compose
-
-import android.content.Context
-import android.view.View
-import android.view.WindowInsets
-import androidx.activity.ComponentActivity
-import androidx.lifecycle.LifecycleOwner
-import com.android.systemui.bouncer.ui.BouncerDialogFactory
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
-import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
-import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
-import com.android.systemui.communal.widgets.WidgetConfigurator
-import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
-import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
-import com.android.systemui.people.ui.viewmodel.PeopleViewModel
-import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
-import com.android.systemui.scene.shared.model.Scene
-import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
-import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.StateFlow
-
-/** The Compose facade, when Compose is *not* available. */
-object ComposeFacade : BaseComposeFacade {
-    override fun isComposeAvailable(): Boolean = false
-
-    override fun composeInitializer(): ComposeInitializer {
-        throwComposeUnavailableError()
-    }
-
-    override fun setPeopleSpaceActivityContent(
-        activity: ComponentActivity,
-        viewModel: PeopleViewModel,
-        onResult: (PeopleViewModel.Result) -> Unit,
-    ) {
-        throwComposeUnavailableError()
-    }
-
-    override fun setCommunalEditWidgetActivityContent(
-        activity: ComponentActivity,
-        viewModel: BaseCommunalViewModel,
-        widgetConfigurator: WidgetConfigurator,
-        onOpenWidgetPicker: () -> Unit,
-        onEditDone: () -> Unit,
-    ) {
-        throwComposeUnavailableError()
-    }
-
-    override fun setVolumePanelActivityContent(
-        activity: ComponentActivity,
-        viewModel: VolumePanelViewModel,
-        onDismiss: () -> Unit,
-    ) {
-        throwComposeUnavailableError()
-    }
-
-    override fun createFooterActionsView(
-        context: Context,
-        viewModel: FooterActionsViewModel,
-        qsVisibilityLifecycleOwner: LifecycleOwner
-    ): View {
-        throwComposeUnavailableError()
-    }
-
-    override fun createSceneContainerView(
-        scope: CoroutineScope,
-        context: Context,
-        viewModel: SceneContainerViewModel,
-        windowInsets: StateFlow<WindowInsets?>,
-        sceneByKey: Map<SceneKey, Scene>,
-        dataSourceDelegator: SceneDataSourceDelegator,
-    ): View {
-        throwComposeUnavailableError()
-    }
-
-    override fun createStickyKeysIndicatorContent(
-        context: Context,
-        viewModel: StickyKeysIndicatorViewModel
-    ): View {
-        throwComposeUnavailableError()
-    }
-
-    override fun createCommunalView(
-        context: Context,
-        viewModel: BaseCommunalViewModel,
-    ): View {
-        throwComposeUnavailableError()
-    }
-
-    override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View {
-        throwComposeUnavailableError()
-    }
-
-    override fun createBouncer(
-        context: Context,
-        viewModel: BouncerViewModel,
-        dialogFactory: BouncerDialogFactory,
-    ): View = throwComposeUnavailableError()
-
-    override fun createLockscreen(
-        context: Context,
-        viewModel: LockscreenContentViewModel,
-        blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>,
-    ): View = throwComposeUnavailableError()
-
-    private fun throwComposeUnavailableError(): Nothing {
-        error(
-            "Compose is not available. Make sure to check isComposeAvailable() before calling any" +
-                " other function on ComposeFacade."
-        )
-    }
-}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/BouncerSceneModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/BouncerSceneModule.kt
deleted file mode 100644
index 1a5e22b..0000000
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/BouncerSceneModule.kt
+++ /dev/null
@@ -1,21 +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.systemui.scene
-
-import dagger.Module
-
-@Module interface BouncerSceneModule
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/CommunalSceneModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/CommunalSceneModule.kt
deleted file mode 100644
index f80a906..0000000
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/CommunalSceneModule.kt
+++ /dev/null
@@ -1,21 +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.systemui.scene
-
-import dagger.Module
-
-@Module interface CommunalSceneModule
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/GoneSceneModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/GoneSceneModule.kt
deleted file mode 100644
index 5cc3b75d..0000000
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/GoneSceneModule.kt
+++ /dev/null
@@ -1,21 +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.systemui.scene
-
-import dagger.Module
-
-@Module interface GoneSceneModule
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/LockscreenSceneModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
deleted file mode 100644
index fc3912e..0000000
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
+++ /dev/null
@@ -1,31 +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.systemui.scene
-
-import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
-import dagger.Module
-import dagger.Provides
-
-@Module
-interface LockscreenSceneModule {
-    companion object {
-        @Provides
-        fun providesLockscreenBlueprints(): Set<LockscreenSceneBlueprint> {
-            return emptySet()
-        }
-    }
-}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/QuickSettingsSceneModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/QuickSettingsSceneModule.kt
deleted file mode 100644
index 387b056..0000000
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/QuickSettingsSceneModule.kt
+++ /dev/null
@@ -1,21 +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.systemui.scene
-
-import dagger.Module
-
-@Module interface QuickSettingsSceneModule
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/anc/AncModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/anc/AncModule.kt
deleted file mode 100644
index a4fb05d..0000000
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/anc/AncModule.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- *  Licensed under the Apache License, Version 2.0 (the "License");
- *  you may not use this file except in compliance with the License.
- *  You may obtain a copy of the License at
- *
- *       http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package com.android.systemui.volume.panel.component.anc
-
-import dagger.Module
-
-@Module interface AncModule
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt
deleted file mode 100644
index c8dae76..0000000
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume.panel.component.bottombar
-
-import dagger.Module
-
-@Module interface BottomBarModule
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
similarity index 85%
rename from packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
rename to packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
index 8ad0a08..a066b38 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel.component.mediaoutput
+package com.android.systemui.volume.panel.component.spatialaudio
 
 import dagger.Module
 
-@Module interface MediaOutputModule
+@Module interface SpatialAudioModule
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/volume/VolumeSlidersModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/volume/VolumeSlidersModule.kt
deleted file mode 100644
index b4cb098..0000000
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/volume/VolumeSlidersModule.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- *  Licensed under the Apache License, Version 2.0 (the "License");
- *  you may not use this file except in compliance with the License.
- *  You may obtain a copy of the License at
- *
- *       http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package com.android.systemui.volume.panel.component.volume
-
-import dagger.Module
-
-@Module interface VolumeSlidersModule
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
deleted file mode 100644
index 76931a2..0000000
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ /dev/null
@@ -1,250 +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.compose
-
-import android.content.Context
-import android.graphics.Point
-import android.view.View
-import android.view.WindowInsets
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.ComposeView
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.lifecycle.LifecycleOwner
-import com.android.compose.theme.LocalAndroidColorScheme
-import com.android.compose.theme.PlatformTheme
-import com.android.compose.ui.platform.DensityAwareComposeView
-import com.android.internal.policy.ScreenDecorationsUtils
-import com.android.systemui.bouncer.ui.BouncerDialogFactory
-import com.android.systemui.bouncer.ui.composable.BouncerContent
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
-import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
-import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
-import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider
-import com.android.systemui.communal.ui.compose.CommunalContainer
-import com.android.systemui.communal.ui.compose.CommunalHub
-import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
-import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
-import com.android.systemui.communal.widgets.WidgetConfigurator
-import com.android.systemui.keyboard.stickykeys.ui.view.createStickyKeyIndicatorView
-import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
-import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
-import com.android.systemui.keyguard.ui.composable.LockscreenContent
-import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
-import com.android.systemui.people.ui.compose.PeopleScreen
-import com.android.systemui.people.ui.viewmodel.PeopleViewModel
-import com.android.systemui.qs.footer.ui.compose.FooterActions
-import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
-import com.android.systemui.scene.shared.model.Scene
-import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.ui.composable.ComposableScene
-import com.android.systemui.scene.ui.composable.SceneContainer
-import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
-import com.android.systemui.volume.panel.ui.composable.VolumePanelRoot
-import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-
-/** The Compose facade, when Compose is available. */
-object ComposeFacade : BaseComposeFacade {
-    override fun isComposeAvailable(): Boolean = true
-
-    override fun composeInitializer(): ComposeInitializer = ComposeInitializerImpl
-
-    override fun setPeopleSpaceActivityContent(
-        activity: ComponentActivity,
-        viewModel: PeopleViewModel,
-        onResult: (PeopleViewModel.Result) -> Unit,
-    ) {
-        activity.setContent { PlatformTheme { PeopleScreen(viewModel, onResult) } }
-    }
-
-    override fun setCommunalEditWidgetActivityContent(
-        activity: ComponentActivity,
-        viewModel: BaseCommunalViewModel,
-        widgetConfigurator: WidgetConfigurator,
-        onOpenWidgetPicker: () -> Unit,
-        onEditDone: () -> Unit,
-    ) {
-        activity.setContent {
-            PlatformTheme {
-                Box(
-                    modifier =
-                        Modifier.fillMaxSize()
-                            .background(LocalAndroidColorScheme.current.outlineVariant),
-                ) {
-                    CommunalHub(
-                        viewModel = viewModel,
-                        onOpenWidgetPicker = onOpenWidgetPicker,
-                        widgetConfigurator = widgetConfigurator,
-                        onEditDone = onEditDone,
-                    )
-                }
-            }
-        }
-    }
-
-    override fun setVolumePanelActivityContent(
-        activity: ComponentActivity,
-        viewModel: VolumePanelViewModel,
-        onDismiss: () -> Unit,
-    ) {
-        activity.setContent {
-            VolumePanelRoot(
-                viewModel = viewModel,
-                onDismiss = onDismiss,
-            )
-        }
-    }
-
-    override fun createFooterActionsView(
-        context: Context,
-        viewModel: FooterActionsViewModel,
-        qsVisibilityLifecycleOwner: LifecycleOwner,
-    ): View {
-        return DensityAwareComposeView(context).apply {
-            setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } }
-        }
-    }
-
-    override fun createSceneContainerView(
-        scope: CoroutineScope,
-        context: Context,
-        viewModel: SceneContainerViewModel,
-        windowInsets: StateFlow<WindowInsets?>,
-        sceneByKey: Map<SceneKey, Scene>,
-        dataSourceDelegator: SceneDataSourceDelegator,
-    ): View {
-        return ComposeView(context).apply {
-            setContent {
-                PlatformTheme {
-                    ScreenDecorProvider(
-                        displayCutout = displayCutoutFromWindowInsets(scope, context, windowInsets),
-                        screenCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
-                    ) {
-                        SceneContainer(
-                            viewModel = viewModel,
-                            sceneByKey =
-                                sceneByKey.mapValues { (_, scene) -> scene as ComposableScene },
-                            dataSourceDelegator = dataSourceDelegator,
-                        )
-                    }
-                }
-            }
-        }
-    }
-
-    override fun createStickyKeysIndicatorContent(
-        context: Context,
-        viewModel: StickyKeysIndicatorViewModel
-    ): View {
-        return createStickyKeyIndicatorView(context, viewModel)
-    }
-
-    override fun createCommunalView(
-        context: Context,
-        viewModel: BaseCommunalViewModel,
-    ): 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,
-        context: Context,
-        windowInsets: StateFlow<WindowInsets?>,
-    ): StateFlow<DisplayCutout> =
-        windowInsets
-            .map {
-                val boundingRect = it?.displayCutout?.boundingRectTop
-                val width = boundingRect?.let { boundingRect.right - boundingRect.left } ?: 0
-                val left = boundingRect?.left?.toDp(context) ?: 0.dp
-                val top = boundingRect?.top?.toDp(context) ?: 0.dp
-                val right = boundingRect?.right?.toDp(context) ?: 0.dp
-                val bottom = boundingRect?.bottom?.toDp(context) ?: 0.dp
-                val location =
-                    when {
-                        width <= 0f -> CutoutLocation.NONE
-                        left <= 0.dp -> CutoutLocation.LEFT
-                        right >= getDisplayWidth(context) -> CutoutLocation.RIGHT
-                        else -> CutoutLocation.CENTER
-                    }
-                DisplayCutout(
-                    left,
-                    top,
-                    right,
-                    bottom,
-                    location,
-                )
-            }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), DisplayCutout())
-
-    // TODO(b/298525212): remove once Compose exposes window inset bounds.
-    private fun getDisplayWidth(context: Context): Dp {
-        val point = Point()
-        checkNotNull(context.display).getRealSize(point)
-        return point.x.dp
-    }
-
-    // TODO(b/298525212): remove once Compose exposes window inset bounds.
-    private fun Int.toDp(context: Context): Dp {
-        return (this.toFloat() / context.resources.displayMetrics.density).dp
-    }
-
-    override fun createBouncer(
-        context: Context,
-        viewModel: BouncerViewModel,
-        dialogFactory: BouncerDialogFactory,
-    ): View {
-        return ComposeView(context).apply {
-            setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } }
-        }
-    }
-
-    override fun createLockscreen(
-        context: Context,
-        viewModel: LockscreenContentViewModel,
-        blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>,
-    ): View {
-        val sceneBlueprints =
-            blueprints.mapNotNull { it as? ComposableLockscreenSceneBlueprint }.toSet()
-        return ComposeView(context).apply {
-            setContent {
-                LockscreenContent(viewModel = viewModel, blueprints = sceneBlueprints)
-                    .Content(modifier = Modifier.fillMaxSize())
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt
deleted file mode 100644
index 1674591..0000000
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.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.compose
-
-import android.view.View
-import androidx.lifecycle.findViewTreeLifecycleOwner
-import androidx.lifecycle.setViewTreeLifecycleOwner
-import androidx.lifecycle.Lifecycle
-import androidx.savedstate.SavedStateRegistryController
-import androidx.savedstate.SavedStateRegistryOwner
-import com.android.compose.animation.ViewTreeSavedStateRegistryOwner
-import com.android.systemui.lifecycle.ViewLifecycleOwner
-
-internal object ComposeInitializerImpl : ComposeInitializer {
-    override fun onAttachedToWindow(root: View) {
-        if (root.findViewTreeLifecycleOwner() != null) {
-            error("root $root already has a LifecycleOwner")
-        }
-
-        val parent = root.parent
-        if (parent is View && parent.id != android.R.id.content) {
-            error(
-                "ComposeInitializer.onAttachedToWindow(View) must be called on the content child." +
-                    "Outside of activities and dialogs, this is usually the top-most View of a " +
-                    "window."
-            )
-        }
-
-        // The lifecycle owner, which is STARTED when [root] is visible and RESUMED when [root] is
-        // both visible and focused.
-        val lifecycleOwner = ViewLifecycleOwner(root)
-
-        // We create a trivial implementation of [SavedStateRegistryOwner] that does not do any save
-        // or restore because SystemUI process is always running and top-level windows using this
-        // initializer are created once, when the process is started.
-        val savedStateRegistryOwner =
-            object : SavedStateRegistryOwner {
-                private val savedStateRegistryController =
-                    SavedStateRegistryController.create(this).apply { performRestore(null) }
-
-                override val savedStateRegistry = savedStateRegistryController.savedStateRegistry
-
-                override val lifecycle: Lifecycle
-                    get() = lifecycleOwner.lifecycle
-            }
-
-        // We must call [ViewLifecycleOwner.onCreate] after creating the [SavedStateRegistryOwner]
-        // because `onCreate` might move the lifecycle state to STARTED which will make
-        // [SavedStateRegistryController.performRestore] throw.
-        lifecycleOwner.onCreate()
-
-        // Set the owners on the root. They will be reused by any ComposeView inside the root
-        // hierarchy.
-        root.setViewTreeLifecycleOwner(lifecycleOwner)
-        ViewTreeSavedStateRegistryOwner.set(root, savedStateRegistryOwner)
-    }
-
-    override fun onDetachedFromWindow(root: View) {
-        (root.findViewTreeLifecycleOwner() as ViewLifecycleOwner).onDestroy()
-        root.setViewTreeLifecycleOwner(null)
-        ViewTreeSavedStateRegistryOwner.set(root, null)
-    }
-}
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
deleted file mode 100644
index c12084d..0000000
--- a/packages/SystemUI/compose/features/Android.bp
+++ /dev/null
@@ -1,47 +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 {
-    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
-    // 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: "SystemUIComposeFeatures",
-    use_resource_processor: true,
-    manifest: "AndroidManifest.xml",
-
-    srcs: [
-        "src/**/*.kt",
-    ],
-
-    static_libs: [
-        "SystemUI-core",
-        "PlatformComposeCore",
-        "PlatformComposeSceneTransitionLayout",
-
-        "androidx.compose.runtime_runtime",
-        "androidx.compose.animation_animation-graphics",
-        "androidx.compose.material3_material3",
-        "androidx.compose.material_material-icons-extended",
-        "androidx.activity_activity-compose",
-    ],
-
-    kotlincflags: ["-Xjvm-default=all"],
-}
diff --git a/packages/SystemUI/compose/features/TEST_MAPPING b/packages/SystemUI/compose/features/TEST_MAPPING
deleted file mode 100644
index 7430acb..0000000
--- a/packages/SystemUI/compose/features/TEST_MAPPING
+++ /dev/null
@@ -1,26 +0,0 @@
-{
-  "presubmit": [
-    {
-      "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/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 0469cbe..3ec5508 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -22,15 +22,17 @@
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
+import com.android.compose.animation.scene.Back
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.bouncer.ui.BouncerDialogFactory
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.scene.shared.model.Direction
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.shared.model.UserAction
-import com.android.systemui.scene.shared.model.UserActionResult
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.composable.ComposableScene
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -52,13 +54,13 @@
     private val viewModel: BouncerViewModel,
     private val dialogFactory: BouncerDialogFactory,
 ) : ComposableScene {
-    override val key = SceneKey.Bouncer
+    override val key = Scenes.Bouncer
 
     override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
         MutableStateFlow(
                 mapOf(
-                    UserAction.Back to UserActionResult(SceneKey.Lockscreen),
-                    UserAction.Swipe(Direction.DOWN) to UserActionResult(SceneKey.Lockscreen),
+                    Back to UserActionResult(Scenes.Lockscreen),
+                    Swipe(SwipeDirection.Down) to UserActionResult(Scenes.Lockscreen),
                 )
             )
             .asStateFlow()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
index bd539a7..2a13d49 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
@@ -51,6 +51,7 @@
 import androidx.compose.ui.unit.dp
 import com.android.compose.PlatformIconButton
 import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
+import com.android.systemui.common.ui.compose.SelectedUserAwareInputConnection
 import com.android.systemui.res.R
 
 /** UI for the input part of a password-requiring version of the bouncer. */
@@ -71,6 +72,7 @@
     val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
     val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
     val isImeSwitcherButtonVisible by viewModel.isImeSwitcherButtonVisible.collectAsState()
+    val selectedUserId by viewModel.selectedUserId.collectAsState()
 
     DisposableEffect(Unit) {
         viewModel.onShown()
@@ -87,47 +89,51 @@
     val color = MaterialTheme.colorScheme.onSurfaceVariant
     val lineWidthPx = with(LocalDensity.current) { 2.dp.toPx() }
 
-    TextField(
-        value = password,
-        onValueChange = viewModel::onPasswordInputChanged,
-        enabled = isInputEnabled,
-        visualTransformation = PasswordVisualTransformation(),
-        singleLine = true,
-        textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
-        keyboardOptions =
-            KeyboardOptions(
-                keyboardType = KeyboardType.Password,
-                imeAction = ImeAction.Done,
-            ),
-        keyboardActions =
-            KeyboardActions(
-                onDone = { viewModel.onAuthenticateKeyPressed() },
-            ),
-        modifier =
-            modifier
-                .focusRequester(focusRequester)
-                .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) }
-                .drawBehind {
-                    drawLine(
-                        color = color,
-                        start = Offset(x = 0f, y = size.height - lineWidthPx),
-                        end = Offset(size.width, y = size.height - lineWidthPx),
-                        strokeWidth = lineWidthPx,
-                    )
-                }
-                .onInterceptKeyBeforeSoftKeyboard { keyEvent ->
-                    if (keyEvent.key == Key.Back) {
-                        viewModel.onImeDismissed()
-                        true
-                    } else {
-                        false
+    SelectedUserAwareInputConnection(selectedUserId) {
+        TextField(
+            value = password,
+            onValueChange = viewModel::onPasswordInputChanged,
+            enabled = isInputEnabled,
+            visualTransformation = PasswordVisualTransformation(),
+            singleLine = true,
+            textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
+            keyboardOptions =
+                KeyboardOptions(
+                    keyboardType = KeyboardType.Password,
+                    imeAction = ImeAction.Done,
+                ),
+            keyboardActions =
+                KeyboardActions(
+                    onDone = { viewModel.onAuthenticateKeyPressed() },
+                ),
+            modifier =
+                modifier
+                    .focusRequester(focusRequester)
+                    .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) }
+                    .drawBehind {
+                        drawLine(
+                            color = color,
+                            start = Offset(x = 0f, y = size.height - lineWidthPx),
+                            end = Offset(size.width, y = size.height - lineWidthPx),
+                            strokeWidth = lineWidthPx,
+                        )
                     }
-                },
-        trailingIcon =
-            if (isImeSwitcherButtonVisible) {
-                { ImeSwitcherButton(viewModel, color) }
-            } else null
-    )
+                    .onInterceptKeyBeforeSoftKeyboard { keyEvent ->
+                        if (keyEvent.key == Key.Back) {
+                            viewModel.onImeDismissed()
+                            true
+                        } else {
+                            false
+                        }
+                    },
+            trailingIcon =
+                if (isImeSwitcherButtonVisible) {
+                    { ImeSwitcherButton(viewModel, color) }
+                } else {
+                    null
+                }
+        )
+    }
 }
 
 /** Button for changing the password input method (IME). */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt
new file mode 100644
index 0000000..c8e1450
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalComposeUiApi::class)
+
+package com.android.systemui.common.ui.compose
+
+import android.annotation.UserIdInt
+import android.os.UserHandle
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InputConnection
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.platform.InterceptPlatformTextInput
+import androidx.compose.ui.platform.PlatformTextInputMethodRequest
+
+/**
+ * Wrapper for input connection composables that need to be aware of the selected user to connect to
+ * the correct instance of on-device services like autofill, autocorrect, etc.
+ *
+ * Usage:
+ * ```
+ * @Composable
+ * fun YourFunction(viewModel: YourViewModel) {
+ *     val selectedUserId by viewModel.selectedUserId.collectAsState()
+ *
+ *     SelectedUserAwareInputConnection(selectedUserId) {
+ *         TextField(...)
+ *     }
+ * }
+ * ```
+ */
+@Composable
+fun SelectedUserAwareInputConnection(
+    @UserIdInt selectedUserId: Int,
+    content: @Composable () -> Unit,
+) {
+    InterceptPlatformTextInput(
+        interceptor = { request, nextHandler ->
+            // Create a new request to wrap the incoming one with some custom logic.
+            val modifiedRequest =
+                object : PlatformTextInputMethodRequest {
+                    override fun createInputConnection(outAttributes: EditorInfo): InputConnection {
+                        val inputConnection = request.createInputConnection(outAttributes)
+                        // After the original request finishes initializing the EditorInfo we can
+                        // customize it. If we needed to we could also wrap the InputConnection
+                        // before
+                        // returning it.
+                        updateEditorInfo(outAttributes)
+                        return inputConnection
+                    }
+
+                    fun updateEditorInfo(outAttributes: EditorInfo) {
+                        outAttributes.targetInputMethodUser = UserHandle.of(selectedUserId)
+                    }
+                }
+
+            // Send our wrapping request to the next handler, which could be the system or another
+            // interceptor up the tree.
+            nextHandler.startInputMethod(modifiedRequest)
+        }
+    ) {
+        content()
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index be5aa8a..d0c4984 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -9,12 +9,11 @@
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
+import androidx.compose.ui.res.dimensionResource
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.FixedSizeEdgeDetector
 import com.android.compose.animation.scene.LowestZIndexScenePicker
-import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.SceneTransitionLayout
@@ -24,13 +23,11 @@
 import com.android.compose.animation.scene.transitions
 import com.android.compose.animation.scene.updateSceneTransitionLayoutState
 import com.android.compose.theme.LocalAndroidColorScheme
-import com.android.systemui.communal.shared.model.CommunalSceneKey
-import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
-import com.android.systemui.communal.ui.compose.extensions.allowGestures
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.transform
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 
 object Communal {
     object Elements {
@@ -40,7 +37,7 @@
 }
 
 val sceneTransitions = transitions {
-    to(TransitionSceneKey.Communal) {
+    to(CommunalScenes.Communal) {
         spec = tween(durationMillis = 1000)
         translate(Communal.Elements.Content, Edge.Right)
         timestampRange(startMillis = 167, endMillis = 334) {
@@ -48,7 +45,7 @@
             fade(Communal.Elements.Content)
         }
     }
-    to(TransitionSceneKey.Blank) {
+    to(CommunalScenes.Blank) {
         spec = tween(durationMillis = 1000)
         translate(Communal.Elements.Content, Edge.Right)
         timestampRange(endMillis = 167) { fade(Communal.Elements.Content) }
@@ -66,15 +63,13 @@
 fun CommunalContainer(
     modifier: Modifier = Modifier,
     viewModel: CommunalViewModel,
+    dialogFactory: SystemUIDialogFactory,
 ) {
-    val currentScene: SceneKey by
-        viewModel.currentScene
-            .transform { value -> emit(value.toTransitionSceneKey()) }
-            .collectAsState(TransitionSceneKey.Blank)
+    val currentScene: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank)
     val sceneTransitionLayoutState =
         updateSceneTransitionLayoutState(
             currentScene,
-            onChangeScene = { viewModel.onSceneChanged(it.toCommunalSceneKey()) },
+            onChangeScene = { viewModel.onSceneChanged(it) },
             transitions = sceneTransitions,
         )
     val touchesAllowed by viewModel.touchesAllowed.collectAsState(initial = false)
@@ -82,37 +77,46 @@
     // This effect exposes the SceneTransitionLayout's observable transition state to the rest of
     // the system, and unsets it when the view is disposed to avoid a memory leak.
     DisposableEffect(viewModel, sceneTransitionLayoutState) {
-        viewModel.setTransitionState(
-            sceneTransitionLayoutState.observableTransitionState().map { it.toModel() }
-        )
+        viewModel.setTransitionState(sceneTransitionLayoutState.observableTransitionState())
         onDispose { viewModel.setTransitionState(null) }
     }
 
     SceneTransitionLayout(
         state = sceneTransitionLayoutState,
-        modifier = modifier.fillMaxSize().allowGestures(allowed = touchesAllowed),
-        swipeSourceDetector = FixedSizeEdgeDetector(ContainerDimensions.EdgeSwipeSize),
+        modifier = modifier.fillMaxSize(),
+        swipeSourceDetector =
+            FixedSizeEdgeDetector(
+                dimensionResource(id = R.dimen.communal_gesture_initiation_width)
+            ),
     ) {
         scene(
-            TransitionSceneKey.Blank,
+            CommunalScenes.Blank,
             userActions =
-                mapOf(
-                    Swipe(SwipeDirection.Left, fromSource = Edge.Right) to
-                        TransitionSceneKey.Communal
-                )
+                if (touchesAllowed) {
+                    mapOf(
+                        Swipe(SwipeDirection.Left, fromSource = Edge.Right) to
+                            CommunalScenes.Communal
+                    )
+                } else {
+                    emptyMap()
+                }
         ) {
             // This scene shows nothing only allowing for transitions to the communal scene.
             Box(modifier = Modifier.fillMaxSize())
         }
 
         scene(
-            TransitionSceneKey.Communal,
+            CommunalScenes.Communal,
             userActions =
-                mapOf(
-                    Swipe(SwipeDirection.Right, fromSource = Edge.Left) to TransitionSceneKey.Blank
-                ),
+                if (touchesAllowed) {
+                    mapOf(
+                        Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank
+                    )
+                } else {
+                    emptyMap()
+                },
         ) {
-            CommunalScene(viewModel, modifier = modifier)
+            CommunalScene(viewModel, dialogFactory, modifier = modifier)
         }
     }
 }
@@ -121,6 +125,7 @@
 @Composable
 private fun SceneScope.CommunalScene(
     viewModel: BaseCommunalViewModel,
+    dialogFactory: SystemUIDialogFactory,
     modifier: Modifier = Modifier,
 ) {
     Box(
@@ -129,45 +134,7 @@
                 .fillMaxSize()
                 .background(LocalAndroidColorScheme.current.outlineVariant),
     )
-    Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) }
-}
-
-// TODO(b/315490861): Remove these conversions once Compose can be used throughout SysUI.
-object TransitionSceneKey {
-    val Blank = CommunalSceneKey.Blank.toTransitionSceneKey()
-    val Communal = CommunalSceneKey.Communal.toTransitionSceneKey()
-}
-
-// TODO(b/315490861): Remove these conversions once Compose can be used throughout SysUI.
-fun SceneKey.toCommunalSceneKey(): CommunalSceneKey {
-    return this.identity as CommunalSceneKey
-}
-
-// TODO(b/315490861): Remove these conversions once Compose can be used throughout SysUI.
-fun CommunalSceneKey.toTransitionSceneKey(): SceneKey {
-    return SceneKey(debugName = toString(), identity = this)
-}
-
-/**
- * Converts between the [SceneTransitionLayout] state class and our forked data class that can be
- * used throughout SysUI.
- */
-// TODO(b/315490861): Remove these conversions once Compose can be used throughout SysUI.
-fun ObservableTransitionState.toModel(): ObservableCommunalTransitionState {
-    return when (this) {
-        is ObservableTransitionState.Idle ->
-            ObservableCommunalTransitionState.Idle(scene.toCommunalSceneKey())
-        is ObservableTransitionState.Transition ->
-            ObservableCommunalTransitionState.Transition(
-                fromScene = fromScene.toCommunalSceneKey(),
-                toScene = toScene.toCommunalSceneKey(),
-                progress = progress,
-                isInitiatedByUserInput = isInitiatedByUserInput,
-                isUserInputOngoing = isUserInputOngoing,
-            )
+    Box(modifier.element(Communal.Elements.Content)) {
+        CommunalHub(viewModel = viewModel, dialogFactory = dialogFactory)
     }
 }
-
-object ContainerDimensions {
-    val EdgeSwipeSize = 40.dp
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 622a4f0..0d6b710 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.communal.ui.compose
 
 import android.appwidget.AppWidgetHostView
+import android.graphics.drawable.Icon
 import android.os.Bundle
 import android.util.SizeF
 import android.widget.FrameLayout
@@ -26,7 +27,9 @@
 import androidx.compose.animation.fadeOut
 import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.Image
 import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
@@ -64,6 +67,7 @@
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.State
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.derivedStateOf
@@ -72,11 +76,14 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.ColorMatrix
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.layout.LayoutCoordinates
@@ -84,9 +91,11 @@
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.layout.positionInWindow
-import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.testTagsAsResourceId
@@ -99,19 +108,24 @@
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.compose.ui.window.Popup
 import androidx.core.view.setPadding
+import androidx.window.layout.WindowMetricsCalculator
 import com.android.compose.modifiers.thenIf
 import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.compose.ui.graphics.painter.rememberDrawablePainter
+import com.android.internal.R.dimen.system_app_widget_background_radius
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.ui.compose.Dimensions.CardOutlineWidth
 import com.android.systemui.communal.ui.compose.extensions.allowGestures
 import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture
 import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
-import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming
+import com.android.systemui.communal.ui.compose.extensions.observeTaps
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import kotlinx.coroutines.launch
 
 @OptIn(ExperimentalComposeUiApi::class)
@@ -119,6 +133,7 @@
 fun CommunalHub(
     modifier: Modifier = Modifier,
     viewModel: BaseCommunalViewModel,
+    dialogFactory: SystemUIDialogFactory? = null,
     widgetConfigurator: WidgetConfigurator? = null,
     onOpenWidgetPicker: (() -> Unit)? = null,
     onEditDone: (() -> Unit)? = null,
@@ -142,6 +157,8 @@
     val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize)
     val contentOffset = beforeContentPadding(contentPadding).toOffset()
 
+    ScrollOnNewSmartspaceEffect(viewModel, gridState)
+
     Box(
         modifier =
             modifier
@@ -151,7 +168,7 @@
                 .pointerInput(gridState, contentOffset, contentListState) {
                     // If not in edit mode, don't allow selecting items.
                     if (!viewModel.isEditMode) return@pointerInput
-                    observeTapsWithoutConsuming { offset ->
+                    observeTaps { offset ->
                         val adjustedOffset = offset - contentOffset
                         val index = firstIndexAtOffset(gridState, adjustedOffset)
                         val key = index?.let { keyAtIndexIfEditable(contentListState.list, index) }
@@ -178,7 +195,7 @@
                             // not display this button.
                             if (
                                 index == null ||
-                                    communalContent[index].isWidget() ||
+                                    communalContent[index].isWidgetContent() ||
                                     communalContent[index] is CommunalContentModel.CtaTileInViewMode
                             ) {
                                 isButtonToEditWidgetsShowing = true
@@ -210,6 +227,17 @@
             widgetConfigurator = widgetConfigurator,
         )
 
+        // TODO(b/326060686): Remove this once keyguard indication area can persist over hub
+        if (viewModel is CommunalViewModel) {
+            val isUnlocked by viewModel.deviceUnlocked.collectAsState(initial = false)
+            LockStateIcon(
+                modifier =
+                    Modifier.align(Alignment.BottomCenter)
+                        .padding(bottom = Dimensions.LockIconBottomPadding),
+                isUnlocked = isUnlocked,
+            )
+        }
+
         if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
             Toolbar(
                 isDraggingToRemove = isDraggingToRemove,
@@ -246,6 +274,31 @@
             )
         }
 
+        if (viewModel is CommunalViewModel && dialogFactory != null) {
+            val isEnableWidgetDialogShowing by
+                viewModel.isEnableWidgetDialogShowing.collectAsState(false)
+            val isEnableWorkProfileDialogShowing by
+                viewModel.isEnableWorkProfileDialogShowing.collectAsState(false)
+
+            EnableWidgetDialog(
+                isEnableWidgetDialogVisible = isEnableWidgetDialogShowing,
+                dialogFactory = dialogFactory,
+                title = stringResource(id = R.string.dialog_title_to_allow_any_widget),
+                positiveButtonText = stringResource(id = R.string.button_text_to_open_settings),
+                onConfirm = viewModel::onEnableWidgetDialogConfirm,
+                onCancel = viewModel::onEnableWidgetDialogCancel
+            )
+
+            EnableWidgetDialog(
+                isEnableWidgetDialogVisible = isEnableWorkProfileDialogShowing,
+                dialogFactory = dialogFactory,
+                title = stringResource(id = R.string.work_mode_off_title),
+                positiveButtonText = stringResource(id = R.string.work_mode_turn_on),
+                onConfirm = viewModel::onEnableWorkProfileDialogConfirm,
+                onCancel = viewModel::onEnableWorkProfileDialogCancel
+            )
+        }
+
         // This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving
         // touches, so that the SceneTransitionLayout can intercept the touches and allow an edge
         // swipe back to the blank scene.
@@ -258,6 +311,34 @@
     }
 }
 
+@Composable
+private fun ScrollOnNewSmartspaceEffect(
+    viewModel: BaseCommunalViewModel,
+    gridState: LazyGridState
+) {
+    val communalContent by viewModel.communalContent.collectAsState(initial = emptyList())
+    var smartspaceCount by remember { mutableStateOf(0) }
+
+    LaunchedEffect(communalContent) {
+        snapshotFlow { gridState.firstVisibleItemIndex }
+            .collect { index ->
+                val existingSmartspaceCount = smartspaceCount
+                smartspaceCount = communalContent.count { it.isSmartspace() }
+                val firstIndex = communalContent.indexOfFirst { it.isSmartspace() }
+
+                // Scroll to the beginning of the smartspace area whenever the number of
+                // smartspace elements grows
+                if (
+                    existingSmartspaceCount < smartspaceCount &&
+                        !viewModel.isEditMode &&
+                        index > firstIndex
+                ) {
+                    gridState.animateScrollToItem(firstIndex)
+                }
+            }
+    }
+}
+
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
 private fun BoxScope.CommunalHubLazyGrid(
@@ -274,7 +355,7 @@
     widgetConfigurator: WidgetConfigurator?,
 ) {
     var gridModifier =
-        Modifier.align(Alignment.CenterStart).onGloballyPositioned { setGridCoordinates(it) }
+        Modifier.align(Alignment.TopStart).onGloballyPositioned { setGridCoordinates(it) }
     var list = communalContent
     var dragDropState: GridDragDropState? = null
     if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) {
@@ -309,8 +390,8 @@
         state = gridState,
         rows = GridCells.Fixed(CommunalContentSize.FULL.span),
         contentPadding = contentPadding,
-        horizontalArrangement = Arrangement.spacedBy(Dimensions.Spacing),
-        verticalArrangement = Arrangement.spacedBy(Dimensions.Spacing),
+        horizontalArrangement = Arrangement.spacedBy(32.dp),
+        verticalArrangement = Arrangement.spacedBy(32.dp),
     ) {
         items(
             count = list.size,
@@ -330,7 +411,7 @@
                 DraggableItem(
                     dragDropState = dragDropState,
                     selected = selected,
-                    enabled = list[index] is CommunalContentModel.Widget,
+                    enabled = list[index].isWidgetContent(),
                     index = index,
                 ) { isDragging ->
                     CommunalContent(
@@ -345,17 +426,37 @@
                 }
             } else {
                 CommunalContent(
+                    modifier = cardModifier.animateItemPlacement(),
                     model = list[index],
                     viewModel = viewModel,
                     size = size,
                     selected = false,
-                    modifier = cardModifier,
                 )
             }
         }
     }
 }
 
+@Composable
+private fun LockStateIcon(
+    isUnlocked: Boolean,
+    modifier: Modifier = Modifier,
+) {
+    val colors = LocalAndroidColorScheme.current
+    val resource =
+        if (isUnlocked) {
+            R.drawable.ic_unlocked
+        } else {
+            R.drawable.ic_lock
+        }
+    Icon(
+        painter = painterResource(id = resource),
+        contentDescription = null,
+        tint = colors.onPrimaryContainer,
+        modifier = modifier.size(Dimensions.LockIconSize),
+    )
+}
+
 /**
  * Toolbar that contains action buttons to
  * 1) open the widget picker
@@ -539,9 +640,11 @@
     widgetConfigurator: WidgetConfigurator? = null,
 ) {
     when (model) {
-        is CommunalContentModel.Widget ->
+        is CommunalContentModel.WidgetContent.Widget ->
             WidgetContent(viewModel, model, size, selected, widgetConfigurator, modifier)
         is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(modifier)
+        is CommunalContentModel.WidgetContent.DisabledWidget ->
+            DisabledWidgetPlaceholder(model, viewModel, modifier)
         is CommunalContentModel.CtaTileInViewMode -> CtaTileInViewModeContent(viewModel, modifier)
         is CommunalContentModel.CtaTileInEditMode ->
             CtaTileInEditModeContent(modifier, onOpenWidgetPicker)
@@ -570,7 +673,7 @@
 ) {
     val colors = LocalAndroidColorScheme.current
     Card(
-        modifier = modifier.padding(CardOutlineWidth),
+        modifier = modifier,
         colors =
             CardDefaults.cardColors(
                 containerColor = colors.primary,
@@ -641,7 +744,7 @@
     }
     val colors = LocalAndroidColorScheme.current
     Card(
-        modifier = modifier.padding(CardOutlineWidth),
+        modifier = modifier,
         colors = CardDefaults.cardColors(containerColor = Color.Transparent),
         border = BorderStroke(1.dp, colors.primary),
         shape = RoundedCornerShape(200.dp),
@@ -672,28 +775,35 @@
 @Composable
 private fun WidgetContent(
     viewModel: BaseCommunalViewModel,
-    model: CommunalContentModel.Widget,
+    model: CommunalContentModel.WidgetContent.Widget,
     size: SizeF,
     selected: Boolean,
     widgetConfigurator: WidgetConfigurator?,
     modifier: Modifier = Modifier,
 ) {
     Box(
-        modifier = modifier,
+        modifier =
+            modifier.thenIf(!viewModel.isEditMode && model.inQuietMode) {
+                Modifier.pointerInput(Unit) {
+                    // consume tap to prevent the child view from triggering interactions with the
+                    // app widget
+                    observeTaps(shouldConsume = true) { _ ->
+                        viewModel.onOpenEnableWorkProfileDialog()
+                    }
+                }
+            }
     ) {
-        val paddingInPx = with(LocalDensity.current) { CardOutlineWidth.toPx().toInt() }
         AndroidView(
             modifier = Modifier.fillMaxSize().allowGestures(allowed = !viewModel.isEditMode),
             factory = { context ->
-                val view =
-                    model.appWidgetHost
-                        .createViewForCommunal(context, model.appWidgetId, model.providerInfo)
-                        .apply { updateAppWidgetSize(Bundle.EMPTY, listOf(size)) }
-                // Remove the extra padding applied to AppWidgetHostView to allow widgets to
-                // occupy the entire box. The added padding is now adjusted to leave only sufficient
-                // space for displaying the outline around the box when the widget is selected.
-                view.setPadding(paddingInPx)
-                view
+                model.appWidgetHost
+                    .createViewForCommunal(context, model.appWidgetId, model.providerInfo)
+                    .apply {
+                        updateAppWidgetSize(Bundle.EMPTY, listOf(size))
+                        // Remove the extra padding applied to AppWidgetHostView to allow widgets to
+                        // occupy the entire box.
+                        setPadding(0)
+                    }
             },
             // For reusing composition in lazy lists.
             onReset = {},
@@ -716,7 +826,7 @@
 @Composable
 fun WidgetConfigureButton(
     visible: Boolean,
-    model: CommunalContentModel.Widget,
+    model: CommunalContentModel.WidgetContent.Widget,
     modifier: Modifier = Modifier,
     widgetConfigurator: WidgetConfigurator,
 ) {
@@ -751,6 +861,46 @@
 }
 
 @Composable
+fun DisabledWidgetPlaceholder(
+    model: CommunalContentModel.WidgetContent.DisabledWidget,
+    viewModel: BaseCommunalViewModel,
+    modifier: Modifier = Modifier,
+) {
+    val context = LocalContext.current
+    val appInfo = model.appInfo
+    val icon: Icon =
+        if (appInfo == null || appInfo.icon == 0) {
+            Icon.createWithResource(context, android.R.drawable.sym_def_app_icon)
+        } else {
+            Icon.createWithResource(appInfo.packageName, appInfo.icon)
+        }
+
+    Column(
+        modifier =
+            modifier
+                .background(
+                    MaterialTheme.colorScheme.surfaceVariant,
+                    RoundedCornerShape(dimensionResource(system_app_widget_background_radius))
+                )
+                .clickable(
+                    enabled = !viewModel.isEditMode,
+                    interactionSource = null,
+                    indication = null,
+                    onClick = viewModel::onOpenEnableWidgetDialog
+                ),
+        verticalArrangement = Arrangement.Center,
+        horizontalAlignment = Alignment.CenterHorizontally,
+    ) {
+        Image(
+            painter = rememberDrawablePainter(icon.loadDrawable(context)),
+            contentDescription = stringResource(R.string.icon_description_for_disabled_widget),
+            modifier = Modifier.size(48.dp),
+            colorFilter = ColorFilter.colorMatrix(Colors.DisabledColorFilter),
+        )
+    }
+}
+
+@Composable
 private fun SmartspaceContent(
     model: CommunalContentModel.Smartspace,
     modifier: Modifier = Modifier,
@@ -795,16 +945,16 @@
 @Composable
 private fun gridContentPadding(isEditMode: Boolean, toolbarSize: IntSize?): PaddingValues {
     if (!isEditMode || toolbarSize == null) {
-        return PaddingValues(horizontal = Dimensions.Spacing)
+        return PaddingValues(start = 48.dp, end = 48.dp, top = Dimensions.GridTopSpacing)
     }
-    val configuration = LocalConfiguration.current
+    val context = LocalContext.current
     val density = LocalDensity.current
-    val screenHeight = configuration.screenHeightDp.dp
+    val windowMetrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
+    val screenHeight = with(density) { windowMetrics.bounds.height().toDp() }
     val toolbarHeight = with(density) { Dimensions.ToolbarPaddingTop + toolbarSize.height.toDp() }
     val verticalPadding =
-        ((screenHeight - toolbarHeight - Dimensions.GridHeight) / 2).coerceAtLeast(
-            Dimensions.Spacing
-        )
+        ((screenHeight - toolbarHeight - Dimensions.GridHeight + Dimensions.GridTopSpacing) / 2)
+            .coerceAtLeast(Dimensions.Spacing)
     return PaddingValues(
         start = Dimensions.ToolbarPaddingHorizontal,
         end = Dimensions.ToolbarPaddingHorizontal,
@@ -851,19 +1001,20 @@
 
 /** Returns the key of item if it's editable at the given index. Only widget is editable. */
 private fun keyAtIndexIfEditable(list: List<CommunalContentModel>, index: Int): String? =
-    if (index in list.indices && list[index].isWidget()) list[index].key else null
+    if (index in list.indices && list[index].isWidgetContent()) list[index].key else null
 
 data class ContentPaddingInPx(val start: Float, val top: Float) {
     fun toOffset(): Offset = Offset(start, top)
 }
 
 object Dimensions {
-    val CardWidth = 464.dp
-    val CardHeightFull = 630.dp
-    val CardHeightHalf = 307.dp
-    val CardHeightThird = 199.dp
+    val CardWidth = 424.dp
+    val CardHeightFull = 596.dp
+    val CardHeightHalf = 282.dp
+    val CardHeightThird = 177.33.dp
     val CardOutlineWidth = 3.dp
-    val GridHeight = CardHeightFull
+    val GridTopSpacing = 72.dp
+    val GridHeight = CardHeightFull + GridTopSpacing
     val Spacing = 16.dp
 
     // The sizing/padding of the toolbar in glanceable hub edit mode
@@ -878,6 +1029,34 @@
             horizontal = ToolbarButtonPaddingHorizontal,
         )
     val IconSize = 48.dp
+
+    val LockIconSize = 52.dp
+    val LockIconBottomPadding = 70.dp
+}
+
+private object Colors {
+    val DisabledColorFilter by lazy { disabledColorMatrix() }
+
+    /** Returns the disabled image filter. Ported over from [DisableImageView]. */
+    private fun disabledColorMatrix(): ColorMatrix {
+        val brightnessMatrix = ColorMatrix()
+        val brightnessAmount = 0.5f
+        val brightnessRgb = (255 * brightnessAmount).toInt().toFloat()
+        // Brightness: C-new = C-old*(1-amount) + amount
+        val scale = 1f - brightnessAmount
+        val mat = brightnessMatrix.values
+        mat[0] = scale
+        mat[6] = scale
+        mat[12] = scale
+        mat[4] = brightnessRgb
+        mat[9] = brightnessRgb
+        mat[14] = brightnessRgb
+
+        return ColorMatrix().apply {
+            setToSaturation(0F)
+            timesAssign(brightnessMatrix)
+        }
+    }
 }
 
 /** The resource id of communal hub accessible from UiAutomator. */
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 11a38f9..9e905ac 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
@@ -19,13 +19,15 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
 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
-import com.android.systemui.scene.shared.model.UserAction
-import com.android.systemui.scene.shared.model.UserActionResult
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -37,19 +39,20 @@
 @Inject
 constructor(
     private val viewModel: CommunalViewModel,
+    private val dialogFactory: SystemUIDialogFactory,
 ) : ComposableScene {
-    override val key = SceneKey.Communal
+    override val key = Scenes.Communal
 
     override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
         MutableStateFlow<Map<UserAction, UserActionResult>>(
                 mapOf(
-                    UserAction.Swipe(Direction.RIGHT) to UserActionResult(SceneKey.Lockscreen),
+                    Swipe(SwipeDirection.Right) to UserActionResult(Scenes.Lockscreen),
                 )
             )
             .asStateFlow()
 
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
-        CommunalHub(modifier, viewModel)
+        CommunalHub(modifier, viewModel, dialogFactory)
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
index 9b8c9d0..c5dab33 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
@@ -71,8 +71,8 @@
 
     /** Remove widget from the list and the database. */
     fun onRemove(indexToRemove: Int) {
-        if (list[indexToRemove] is CommunalContentModel.Widget) {
-            val widget = list[indexToRemove] as CommunalContentModel.Widget
+        if (list[indexToRemove].isWidgetContent()) {
+            val widget = list[indexToRemove] as CommunalContentModel.WidgetContent
             list.apply { removeAt(indexToRemove) }
             onDeleteWidget(widget.appWidgetId)
         }
@@ -100,7 +100,7 @@
         val widgetIdToPriorityMap: Map<Int, Int> =
             list
                 .mapIndexedNotNull { index, item ->
-                    if (item is CommunalContentModel.Widget) {
+                    if (item is CommunalContentModel.WidgetContent) {
                         item.appWidgetId to list.size - index
                     } else {
                         null
@@ -115,5 +115,5 @@
     }
 
     /** Returns true if the item at given index is editable. */
-    fun isItemEditable(index: Int) = list[index] is CommunalContentModel.Widget
+    fun isItemEditable(index: Int) = list[index].isWidgetContent()
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/EnableWidgetDialog.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/EnableWidgetDialog.kt
new file mode 100644
index 0000000..df11206
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/EnableWidgetDialog.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.compose
+
+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.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+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.platform.LocalView
+import androidx.compose.ui.res.stringResource
+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.statusbar.phone.ComponentSystemUIDialog
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import com.android.systemui.statusbar.phone.create
+
+/** Dialog shown upon tapping a disabled widget which allows users to enable the widget. */
+@Composable
+fun EnableWidgetDialog(
+    isEnableWidgetDialogVisible: Boolean,
+    dialogFactory: SystemUIDialogFactory,
+    title: String,
+    positiveButtonText: String,
+    onConfirm: () -> Unit,
+    onCancel: () -> Unit
+) {
+    var dialog: ComponentSystemUIDialog? by remember { mutableStateOf(null) }
+    val context = LocalView.current.context
+
+    DisposableEffect(isEnableWidgetDialogVisible) {
+        if (isEnableWidgetDialogVisible) {
+            dialog =
+                dialogFactory.create(
+                    context = context,
+                ) {
+                    DialogComposable(title, positiveButtonText, onConfirm, onCancel)
+                }
+            dialog?.apply {
+                setCancelable(true)
+                setCanceledOnTouchOutside(true)
+                setOnCancelListener { onCancel() }
+                show()
+            }
+        }
+
+        onDispose {
+            dialog?.dismiss()
+            dialog = null
+        }
+    }
+}
+
+@Composable
+private fun DialogComposable(
+    title: String,
+    positiveButtonText: String,
+    onConfirm: () -> Unit,
+    onCancel: () -> Unit,
+) {
+    Box(
+        Modifier.fillMaxWidth()
+            .padding(top = 18.dp, bottom = 8.dp)
+            .background(LocalAndroidColorScheme.current.surfaceBright, RoundedCornerShape(28.dp))
+    ) {
+        Column(
+            modifier = Modifier.fillMaxWidth(),
+            verticalArrangement = Arrangement.spacedBy(20.dp),
+        ) {
+            Box(
+                modifier = Modifier.padding(horizontal = 24.dp).fillMaxWidth().wrapContentHeight(),
+                contentAlignment = Alignment.TopStart
+            ) {
+                Text(
+                    text = title,
+                    style = MaterialTheme.typography.titleMedium,
+                    color = LocalAndroidColorScheme.current.onSurface,
+                    textAlign = TextAlign.Center,
+                    maxLines = 1,
+                )
+            }
+
+            Box(
+                modifier = Modifier.padding(end = 12.dp).fillMaxWidth().wrapContentHeight(),
+                contentAlignment = Alignment.Center
+            ) {
+                Row(
+                    modifier = Modifier.fillMaxWidth(),
+                    horizontalArrangement = Arrangement.End,
+                ) {
+                    TextButton(
+                        contentPadding = PaddingValues(16.dp),
+                        onClick = onCancel,
+                    ) {
+                        Text(
+                            text = stringResource(R.string.cancel),
+                        )
+                    }
+                    TextButton(
+                        contentPadding = PaddingValues(16.dp),
+                        onClick = onConfirm,
+                    ) {
+                        Text(
+                            text = positiveButtonText,
+                        )
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index beb8ddef..1adb335 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -41,7 +41,6 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.toOffset
 import androidx.compose.ui.unit.toSize
-import androidx.compose.ui.zIndex
 import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
 import com.android.systemui.communal.ui.compose.extensions.plus
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
@@ -247,7 +246,7 @@
     content: @Composable (isDragging: Boolean) -> Unit
 ) {
     if (!enabled) {
-        return Box(modifier = modifier) { content(false) }
+        return content(false)
     }
 
     val dragging = index == dragDropState.draggingItemIndex
@@ -258,7 +257,7 @@
         )
     val draggingModifier =
         if (dragging) {
-            Modifier.zIndex(1f).graphicsLayer {
+            Modifier.graphicsLayer {
                 translationX = dragDropState.draggingItemOffset.x
                 translationY = dragDropState.draggingItemOffset.y
                 alpha = itemAlpha
@@ -268,13 +267,14 @@
         }
 
     Box(modifier) {
+        Box(draggingModifier) { content(dragging) }
         AnimatedVisibility(
+            modifier = Modifier.matchParentSize(),
             visible = (dragging || selected) && !dragDropState.isDraggingToRemove,
             enter = fadeIn(),
             exit = fadeOut()
         ) {
-            HighlightedItem()
+            HighlightedItem(Modifier.matchParentSize())
         }
-        Box(modifier = draggingModifier, propagateMinConstraints = true) { content(dragging) }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
index bc1e429..379c165 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
@@ -30,16 +30,18 @@
 import kotlinx.coroutines.coroutineScope
 
 /**
- * Observe taps without actually consuming them, so child elements can still respond to them. Long
+ * Observe taps without consuming them by default, so child elements can still respond to them. Long
  * presses are excluded.
  */
-suspend fun PointerInputScope.observeTapsWithoutConsuming(
+suspend fun PointerInputScope.observeTaps(
     pass: PointerEventPass = PointerEventPass.Initial,
+    shouldConsume: Boolean = false,
     onTap: ((Offset) -> Unit)? = null,
 ) = coroutineScope {
     if (onTap == null) return@coroutineScope
     awaitEachGesture {
-        awaitFirstDown(pass = pass)
+        val down = awaitFirstDown(pass = pass)
+        if (shouldConsume) down.consume()
         val tapTimeout = viewConfiguration.longPressTimeoutMillis
         val up = withTimeoutOrNull(tapTimeout) { waitForUpOrCancellation(pass = pass) }
         if (up != null) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
index dd86646..a8d801a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
@@ -18,8 +18,11 @@
 
 import android.content.Context
 import android.view.View
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
 import androidx.compose.material3.LocalContentColor
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Surface
@@ -61,22 +64,32 @@
 @Composable
 fun StickyKeysIndicator(stickyKeys: Map<ModifierKey, Locked>, modifier: Modifier = Modifier) {
     Surface(
-        color = MaterialTheme.colorScheme.surface,
+        color = MaterialTheme.colorScheme.inverseSurface,
         shape = MaterialTheme.shapes.medium,
-        modifier = modifier
+        modifier = modifier.heightIn(min = 84.dp).width(96.dp)
     ) {
         Column(
             horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.Center,
             modifier = Modifier.padding(16.dp)
         ) {
-            stickyKeys.forEach { (key, isLocked) ->
-                key(key) {
-                    Text(
-                        text = key.displayedText,
-                        fontWeight = if (isLocked.locked) FontWeight.Bold else FontWeight.Normal
-                    )
-                }
-            }
+            stickyKeys.forEach { (key, isLocked) -> key(key) { StickyKeyText(key, isLocked) } }
         }
     }
 }
+
+@Composable
+private fun StickyKeyText(key: ModifierKey, isLocked: Locked, modifier: Modifier = Modifier) {
+    Text(
+        text = key.displayedText,
+        fontWeight = if (isLocked.locked) FontWeight.Bold else FontWeight.Normal,
+        style = MaterialTheme.typography.bodyMedium,
+        color =
+            if (isLocked.locked) {
+                MaterialTheme.colorScheme.inverseOnSurface
+            } else {
+                MaterialTheme.colorScheme.outlineVariant
+            },
+        modifier = modifier
+    )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
index b5499b7..bc4e555 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
@@ -18,13 +18,16 @@
 
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalView
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneTransitionLayout
 import com.android.compose.animation.scene.transitions
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import javax.inject.Inject
@@ -40,6 +43,7 @@
 constructor(
     private val viewModel: LockscreenContentViewModel,
     private val blueprints: Set<@JvmSuppressWildcards ComposableLockscreenSceneBlueprint>,
+    private val clockInteractor: KeyguardClockInteractor,
 ) {
 
     private val sceneKeyByBlueprint: Map<ComposableLockscreenSceneBlueprint, SceneKey> by lazy {
@@ -55,6 +59,12 @@
     ) {
         val coroutineScope = rememberCoroutineScope()
         val blueprintId by viewModel.blueprintId(coroutineScope).collectAsState()
+        val view = LocalView.current
+        DisposableEffect(view) {
+            clockInteractor.clockEventController.registerListeners(view)
+
+            onDispose { clockInteractor.clockEventController.unregisterListeners() }
+        }
 
         // Switch smoothly between blueprints, any composable tagged with element() will be
         // transition-animated between any two blueprints, if they both display the same element.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index dd043db..7acb4d5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -18,52 +18,32 @@
 
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
 import com.android.compose.animation.scene.animateSceneFloatAsState
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
 import com.android.systemui.qs.ui.composable.QuickSettings
-import com.android.systemui.scene.shared.model.Direction
-import com.android.systemui.scene.shared.model.Edge
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.shared.model.UserAction
-import com.android.systemui.scene.shared.model.UserActionResult
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.composable.ComposableScene
 import dagger.Lazy
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
 
 /** The lock screen scene shows when the device is locked. */
 @SysUISingleton
 class LockscreenScene
 @Inject
 constructor(
-    @Application private val applicationScope: CoroutineScope,
     viewModel: LockscreenSceneViewModel,
     private val lockscreenContent: Lazy<LockscreenContent>,
 ) : ComposableScene {
-    override val key = SceneKey.Lockscreen
+    override val key = Scenes.Lockscreen
 
     override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
-        combine(viewModel.upDestinationSceneKey, viewModel.leftDestinationSceneKey, ::Pair)
-            .map { (upKey, leftKey) -> destinationScenes(up = upKey, left = leftKey) }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.Eagerly,
-                initialValue =
-                    destinationScenes(
-                        up = viewModel.upDestinationSceneKey.value,
-                        left = viewModel.leftDestinationSceneKey.value,
-                    )
-            )
+        viewModel.destinationScenes
 
     @Composable
     override fun SceneScope.Content(
@@ -74,19 +54,6 @@
             modifier = modifier,
         )
     }
-
-    private fun destinationScenes(
-        up: SceneKey?,
-        left: SceneKey?,
-    ): Map<UserAction, UserActionResult> {
-        return buildMap {
-            up?.let { this[UserAction.Swipe(Direction.UP)] = UserActionResult(up) }
-            left?.let { this[UserAction.Swipe(Direction.LEFT)] = UserActionResult(left) }
-            this[UserAction.Swipe(fromEdge = Edge.TOP, direction = Direction.DOWN)] =
-                UserActionResult(SceneKey.QuickSettings)
-            this[UserAction.Swipe(direction = Direction.DOWN)] = UserActionResult(SceneKey.Shade)
-        }
-    }
 }
 
 @Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt
new file mode 100644
index 0000000..c5ff859
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable.blueprint
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.transitions
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.largeClockElementKey
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smallClockElementKey
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smartspaceElementKey
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.ClockFaceInTransition.Companion.CLOCK_IN_MILLIS
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.ClockFaceInTransition.Companion.CLOCK_IN_START_DELAY_MILLIS
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.ClockFaceOutTransition.Companion.CLOCK_OUT_MILLIS
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.SmartspaceMoveTransition.Companion.STATUS_AREA_MOVE_DOWN_MILLIS
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.SmartspaceMoveTransition.Companion.STATUS_AREA_MOVE_UP_MILLIS
+
+object ClockTransition {
+    val defaultClockTransitions = transitions {
+        from(ClockScenes.smallClockScene, to = ClockScenes.largeClockScene) {
+            transitioningToLargeClock()
+        }
+        from(ClockScenes.largeClockScene, to = ClockScenes.smallClockScene) {
+            transitioningToSmallClock()
+        }
+    }
+
+    private fun TransitionBuilder.transitioningToLargeClock() {
+        spec = tween(durationMillis = STATUS_AREA_MOVE_UP_MILLIS.toInt())
+        timestampRange(
+            startMillis = CLOCK_IN_START_DELAY_MILLIS.toInt(),
+            endMillis = (CLOCK_IN_START_DELAY_MILLIS + CLOCK_IN_MILLIS).toInt()
+        ) {
+            fade(largeClockElementKey)
+        }
+
+        timestampRange(endMillis = CLOCK_OUT_MILLIS.toInt()) { fade(smallClockElementKey) }
+        anchoredTranslate(smallClockElementKey, smartspaceElementKey)
+    }
+
+    private fun TransitionBuilder.transitioningToSmallClock() {
+        spec = tween(durationMillis = STATUS_AREA_MOVE_DOWN_MILLIS.toInt())
+        timestampRange(
+            startMillis = CLOCK_IN_START_DELAY_MILLIS.toInt(),
+            endMillis = (CLOCK_IN_START_DELAY_MILLIS + CLOCK_IN_MILLIS).toInt()
+        ) {
+            fade(smallClockElementKey)
+        }
+
+        timestampRange(endMillis = CLOCK_OUT_MILLIS.toInt()) { fade(largeClockElementKey) }
+        anchoredTranslate(smallClockElementKey, smartspaceElementKey)
+    }
+}
+
+object ClockScenes {
+    val smallClockScene = SceneKey("small-clock-scene")
+    val largeClockScene = SceneKey("large-clock-scene")
+}
+
+object ClockElementKeys {
+    val largeClockElementKey = ElementKey("large-clock")
+    val smallClockElementKey = ElementKey("small-clock")
+    val smartspaceElementKey = ElementKey("smart-space")
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 452dc03..9509fd2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -17,30 +17,24 @@
 package com.android.systemui.keyguard.ui.composable.blueprint
 
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.IntRect
 import com.android.compose.animation.scene.SceneScope
-import com.android.compose.modifiers.padding
-import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
 import com.android.systemui.keyguard.ui.composable.section.DefaultClockSection
 import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection
 import com.android.systemui.keyguard.ui.composable.section.NotificationSection
 import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
-import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
 import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
-import com.android.systemui.res.R
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
@@ -57,13 +51,12 @@
     private val viewModel: LockscreenContentViewModel,
     private val statusBarSection: StatusBarSection,
     private val clockSection: DefaultClockSection,
-    private val smartSpaceSection: SmartSpaceSection,
     private val notificationSection: NotificationSection,
     private val lockSection: LockSection,
     private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
     private val bottomAreaSection: BottomAreaSection,
     private val settingsMenuSection: SettingsMenuSection,
-    private val clockInteractor: KeyguardClockInteractor,
+    private val mediaCarouselSection: MediaCarouselSection,
 ) : ComposableLockscreenSceneBlueprint {
 
     override val id: String = "default"
@@ -71,7 +64,6 @@
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
-        val burnIn = rememberBurnIn(clockInteractor)
         val resources = LocalContext.current.resources
 
         LockscreenLongPress(
@@ -85,37 +77,10 @@
                         modifier = Modifier.fillMaxWidth(),
                     ) {
                         with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
-                        with(clockSection) {
-                            SmallClock(
-                                burnInParams = burnIn.parameters,
-                                onTopChanged = burnIn.onSmallClockTopChanged,
-                                modifier = Modifier.fillMaxWidth(),
-                            )
-                        }
-                        with(smartSpaceSection) {
-                            SmartSpace(
-                                burnInParams = burnIn.parameters,
-                                onTopChanged = burnIn.onSmartspaceTopChanged,
-                                modifier =
-                                    Modifier.fillMaxWidth()
-                                        .padding(
-                                            top = { viewModel.getSmartSpacePaddingTop(resources) },
-                                        )
-                                        .padding(
-                                            bottom =
-                                                dimensionResource(
-                                                    R.dimen.keyguard_status_view_bottom_margin
-                                                ),
-                                        ),
-                            )
-                        }
+                        with(clockSection) { DefaultClockLayout() }
+                        with(mediaCarouselSection) { MediaCarousel() }
 
-                        if (viewModel.isLargeClockVisible) {
-                            Spacer(modifier = Modifier.weight(weight = 1f))
-                            with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
-                        }
-
-                        if (viewModel.areNotificationsVisible) {
+                        if (viewModel.areNotificationsVisible(resources = resources)) {
                             with(notificationSection) {
                                 Notifications(
                                     modifier = Modifier.fillMaxWidth().weight(weight = 1f)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index 71c60c7..9abfa42 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -17,30 +17,24 @@
 package com.android.systemui.keyguard.ui.composable.blueprint
 
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.IntRect
 import com.android.compose.animation.scene.SceneScope
-import com.android.compose.modifiers.padding
-import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
 import com.android.systemui.keyguard.ui.composable.section.DefaultClockSection
 import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection
 import com.android.systemui.keyguard.ui.composable.section.NotificationSection
 import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
-import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
 import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
-import com.android.systemui.res.R
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
@@ -57,13 +51,12 @@
     private val viewModel: LockscreenContentViewModel,
     private val statusBarSection: StatusBarSection,
     private val clockSection: DefaultClockSection,
-    private val smartSpaceSection: SmartSpaceSection,
     private val notificationSection: NotificationSection,
     private val lockSection: LockSection,
     private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
     private val bottomAreaSection: BottomAreaSection,
     private val settingsMenuSection: SettingsMenuSection,
-    private val clockInteractor: KeyguardClockInteractor,
+    private val mediaCarouselSection: MediaCarouselSection,
 ) : ComposableLockscreenSceneBlueprint {
 
     override val id: String = "shortcuts-besides-udfps"
@@ -71,7 +64,6 @@
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
-        val burnIn = rememberBurnIn(clockInteractor)
         val resources = LocalContext.current.resources
 
         LockscreenLongPress(
@@ -85,37 +77,10 @@
                         modifier = Modifier.fillMaxWidth(),
                     ) {
                         with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
-                        with(clockSection) {
-                            SmallClock(
-                                onTopChanged = burnIn.onSmallClockTopChanged,
-                                modifier = Modifier.fillMaxWidth(),
-                                burnInParams = burnIn.parameters,
-                            )
-                        }
-                        with(smartSpaceSection) {
-                            SmartSpace(
-                                burnInParams = burnIn.parameters,
-                                onTopChanged = burnIn.onSmartspaceTopChanged,
-                                modifier =
-                                    Modifier.fillMaxWidth()
-                                        .padding(
-                                            top = { viewModel.getSmartSpacePaddingTop(resources) }
-                                        )
-                                        .padding(
-                                            bottom =
-                                                dimensionResource(
-                                                    R.dimen.keyguard_status_view_bottom_margin
-                                                )
-                                        ),
-                            )
-                        }
+                        with(clockSection) { DefaultClockLayout() }
+                        with(mediaCarouselSection) { MediaCarousel() }
 
-                        if (viewModel.isLargeClockVisible) {
-                            Spacer(modifier = Modifier.weight(weight = 1f))
-                            with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
-                        }
-
-                        if (viewModel.areNotificationsVisible) {
+                        if (viewModel.areNotificationsVisible(resources = resources)) {
                             with(notificationSection) {
                                 Notifications(
                                     modifier = Modifier.fillMaxWidth().weight(weight = 1f)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
index af836b6..652412d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
@@ -18,7 +18,6 @@
 
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -27,7 +26,6 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntRect
@@ -35,15 +33,14 @@
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.padding
 import com.android.systemui.Flags
-import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
 import com.android.systemui.keyguard.ui.composable.section.DefaultClockSection
 import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection
 import com.android.systemui.keyguard.ui.composable.section.NotificationSection
 import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
-import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
 import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import com.android.systemui.res.R
@@ -64,13 +61,12 @@
     private val viewModel: LockscreenContentViewModel,
     private val statusBarSection: StatusBarSection,
     private val clockSection: DefaultClockSection,
-    private val smartSpaceSection: SmartSpaceSection,
     private val notificationSection: NotificationSection,
     private val lockSection: LockSection,
     private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
     private val bottomAreaSection: BottomAreaSection,
     private val settingsMenuSection: SettingsMenuSection,
-    private val clockInteractor: KeyguardClockInteractor,
+    private val mediaCarouselSection: MediaCarouselSection,
     private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
 ) : ComposableLockscreenSceneBlueprint {
 
@@ -79,8 +75,6 @@
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
-        val burnIn = rememberBurnIn(clockInteractor)
-        val resources = LocalContext.current.resources
 
         LockscreenLongPress(
             viewModel = viewModel.longPress,
@@ -100,30 +94,8 @@
                                 modifier = Modifier.fillMaxHeight().weight(weight = 1f),
                                 horizontalAlignment = Alignment.CenterHorizontally,
                             ) {
-                                with(smartSpaceSection) {
-                                    SmartSpace(
-                                        burnInParams = burnIn.parameters,
-                                        onTopChanged = burnIn.onSmartspaceTopChanged,
-                                        modifier =
-                                            Modifier.fillMaxWidth()
-                                                .padding(
-                                                    top = {
-                                                        viewModel.getSmartSpacePaddingTop(resources)
-                                                    },
-                                                )
-                                                .padding(
-                                                    bottom =
-                                                        dimensionResource(
-                                                            R.dimen
-                                                                .keyguard_status_view_bottom_margin
-                                                        )
-                                                ),
-                                    )
-                                }
-
-                                Spacer(modifier = Modifier.weight(weight = 1f))
-                                with(clockSection) { LargeClock() }
-                                Spacer(modifier = Modifier.weight(weight = 1f))
+                                with(clockSection) { DefaultClockLayout() }
+                                with(mediaCarouselSection) { MediaCarousel() }
                             }
                             with(notificationSection) {
                                 val splitShadeTopMargin: Dp =
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
index e2e7a95..f86623f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
@@ -41,12 +41,14 @@
 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
 import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection
 import com.android.systemui.keyguard.ui.composable.section.NotificationSection
 import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
 import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
 import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
 import com.android.systemui.keyguard.ui.composable.section.WeatherClockSection
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import com.android.systemui.media.controls.ui.composable.MediaCarousel
 import com.android.systemui.res.R
 import com.android.systemui.shade.LargeScreenHeaderHelper
 import dagger.Binds
@@ -68,6 +70,7 @@
     private val bottomAreaSection: BottomAreaSection,
     private val settingsMenuSection: SettingsMenuSection,
     private val clockInteractor: KeyguardClockInteractor,
+    private val mediaCarouselSection: MediaCarouselSection,
 ) : ComposableLockscreenSceneBlueprint {
 
     override val id: String = WEATHER_CLOCK_BLUEPRINT_ID
@@ -107,7 +110,9 @@
                             )
                         }
 
-                        if (viewModel.areNotificationsVisible) {
+                        with(mediaCarouselSection) { MediaCarousel() }
+
+                        if (viewModel.areNotificationsVisible(resources = resources)) {
                             with(notificationSection) {
                                 Notifications(
                                     modifier = Modifier.fillMaxWidth().weight(weight = 1f)
@@ -228,6 +233,7 @@
     private val clockInteractor: KeyguardClockInteractor,
     private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
     private val weatherClockSection: WeatherClockSection,
+    private val mediaCarouselSection: MediaCarouselSection,
 ) : ComposableLockscreenSceneBlueprint {
     override val id: String = SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID
 
@@ -276,6 +282,8 @@
                                                 ),
                                     )
                                 }
+
+                                with(mediaCarouselSection) { MediaCarousel() }
                             }
                             with(notificationSection) {
                                 val splitShadeTopMargin: Dp =
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
index 0728daf..2a99039 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.keyguard.ui.viewmodel.BurnInScaleViewModel
+import kotlinx.coroutines.flow.map
 
 /**
  * Modifies the composable to account for anti-burn in translation, alpha, and scaling.
@@ -38,9 +39,18 @@
     params: BurnInParameters,
     isClock: Boolean = false,
 ): Modifier {
-    val translationX by viewModel.translationX(params).collectAsState(initial = 0f)
-    val translationY by viewModel.translationY(params).collectAsState(initial = 0f)
-    val scaleViewModel by viewModel.scale(params).collectAsState(initial = BurnInScaleViewModel())
+    val burnIn = viewModel.movement(params)
+    val translationX by burnIn.map { it.translationX.toFloat() }.collectAsState(initial = 0f)
+    val translationY by burnIn.map { it.translationY.toFloat() }.collectAsState(initial = 0f)
+    val scaleViewModel by
+        burnIn
+            .map {
+                BurnInScaleViewModel(
+                    scale = it.scale,
+                    scaleClockOnly = it.scaleClockOnly,
+                )
+            }
+            .collectAsState(initial = BurnInScaleViewModel())
 
     return this.graphicsLayer {
         val scale =
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
index 335c915..1ab2bc76 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
@@ -18,28 +18,37 @@
 
 import android.view.ViewGroup
 import android.widget.FrameLayout
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.viewinterop.AndroidView
-import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.SceneTransitionLayout
 import com.android.compose.modifiers.padding
-import com.android.keyguard.KeyguardClockSwitch
 import com.android.systemui.customization.R as customizationR
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.largeClockElementKey
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smallClockElementKey
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smartspaceElementKey
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.smallClockScene
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockTransition.defaultClockTransitions
+import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn
 import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
 import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged
 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import com.android.systemui.res.R
 import javax.inject.Inject
 
 /** Provides small clock and large clock composables for the default clock face. */
@@ -49,117 +58,152 @@
     private val viewModel: KeyguardClockViewModel,
     private val clockInteractor: KeyguardClockInteractor,
     private val aodBurnInViewModel: AodBurnInViewModel,
-    private val lockscreenSmartspaceController: LockscreenSmartspaceController,
+    private val lockscreenContentViewModel: LockscreenContentViewModel,
+    private val smartSpaceSection: SmartSpaceSection,
 ) {
+    @Composable
+    fun DefaultClockLayout(
+        modifier: Modifier = Modifier,
+    ) {
+        val isLargeClockVisible by viewModel.isLargeClockVisible.collectAsState()
+        val burnIn = rememberBurnIn(clockInteractor)
+        val currentScene =
+            if (isLargeClockVisible) {
+                largeClockScene
+            } else {
+                smallClockScene
+            }
+
+        LaunchedEffect(isLargeClockVisible) {
+            if (isLargeClockVisible) {
+                burnIn.onSmallClockTopChanged(null)
+            }
+        }
+
+        SceneTransitionLayout(
+            modifier = modifier,
+            currentScene = currentScene,
+            onChangeScene = {},
+            transitions = defaultClockTransitions,
+        ) {
+            scene(smallClockScene) {
+                Column {
+                    SmallClock(
+                        burnInParams = burnIn.parameters,
+                        onTopChanged = burnIn.onSmallClockTopChanged,
+                        modifier = Modifier.element(smallClockElementKey).fillMaxWidth()
+                    )
+                    SmartSpaceContent()
+                }
+            }
+
+            scene(largeClockScene) {
+                Column {
+                    SmartSpaceContent()
+                    LargeClock(modifier = Modifier.element(largeClockElementKey).fillMaxWidth())
+                }
+            }
+        }
+    }
 
     @Composable
-    fun SceneScope.SmallClock(
+    private fun SceneScope.SmartSpaceContent(
+        modifier: Modifier = Modifier,
+    ) {
+        val burnIn = rememberBurnIn(clockInteractor)
+        val resources = LocalContext.current.resources
+
+        MovableElement(key = smartspaceElementKey, modifier = modifier) {
+            content {
+                with(smartSpaceSection) {
+                    this@SmartSpaceContent.SmartSpace(
+                        burnInParams = burnIn.parameters,
+                        onTopChanged = burnIn.onSmartspaceTopChanged,
+                        modifier =
+                            Modifier.fillMaxWidth()
+                                .padding(
+                                    top = {
+                                        lockscreenContentViewModel.getSmartSpacePaddingTop(
+                                            resources
+                                        )
+                                    },
+                                    bottom = {
+                                        resources.getDimensionPixelSize(
+                                            R.dimen.keyguard_status_view_bottom_margin
+                                        )
+                                    }
+                                ),
+                    )
+                }
+            }
+        }
+    }
+
+    @Composable
+    private fun SceneScope.SmallClock(
         burnInParams: BurnInParameters,
         onTopChanged: (top: Float?) -> Unit,
         modifier: Modifier = Modifier,
     ) {
-        val clockSize by viewModel.clockSize.collectAsState()
         val currentClock by viewModel.currentClock.collectAsState()
-        viewModel.clock = currentClock
-
-        if (clockSize != KeyguardClockSwitch.SMALL) {
-            onTopChanged(null)
-            return
-        }
-
         if (currentClock?.smallClock?.view == null) {
             return
         }
+        viewModel.clock = currentClock
 
-        val view = LocalView.current
+        val context = LocalContext.current
 
-        DisposableEffect(view) {
-            clockInteractor.clockEventController.registerListeners(view)
-
-            onDispose { clockInteractor.clockEventController.unregisterListeners() }
-        }
-
-        MovableElement(
-            key = ClockElementKey,
-            modifier = modifier,
-        ) {
-            content {
-                AndroidView(
-                    factory = { context ->
-                        FrameLayout(context).apply {
-                            val newClockView = checkNotNull(currentClock).smallClock.view
-                            (newClockView.parent as? ViewGroup)?.removeView(newClockView)
-                            addView(newClockView)
-                        }
-                    },
-                    modifier =
-                        Modifier.padding(
-                                horizontal =
-                                    dimensionResource(customizationR.dimen.clock_padding_start)
-                            )
-                            .padding(top = { viewModel.getSmallClockTopMargin(view.context) })
-                            .onTopPlacementChanged(onTopChanged)
-                            .burnInAware(
-                                viewModel = aodBurnInViewModel,
-                                params = burnInParams,
-                            ),
-                    update = {
-                        val newClockView = checkNotNull(currentClock).smallClock.view
-                        it.removeAllViews()
-                        (newClockView.parent as? ViewGroup)?.removeView(newClockView)
-                        it.addView(newClockView)
-                    },
-                )
-            }
-        }
+        AndroidView(
+            factory = { context ->
+                FrameLayout(context).apply {
+                    val newClockView = checkNotNull(currentClock).smallClock.view
+                    (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+                    addView(newClockView)
+                }
+            },
+            update = {
+                val newClockView = checkNotNull(currentClock).smallClock.view
+                it.removeAllViews()
+                (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+                it.addView(newClockView)
+            },
+            modifier =
+                modifier
+                    .padding(
+                        horizontal = dimensionResource(customizationR.dimen.clock_padding_start)
+                    )
+                    .padding(top = { viewModel.getSmallClockTopMargin(context) })
+                    .onTopPlacementChanged(onTopChanged)
+                    .burnInAware(
+                        viewModel = aodBurnInViewModel,
+                        params = burnInParams,
+                    ),
+        )
     }
 
     @Composable
-    fun SceneScope.LargeClock(modifier: Modifier = Modifier) {
-        val clockSize by viewModel.clockSize.collectAsState()
+    private fun SceneScope.LargeClock(modifier: Modifier = Modifier) {
         val currentClock by viewModel.currentClock.collectAsState()
         viewModel.clock = currentClock
-
-        if (clockSize != KeyguardClockSwitch.LARGE) {
-            return
-        }
-
         if (currentClock?.largeClock?.view == null) {
             return
         }
 
-        val view = LocalView.current
-
-        DisposableEffect(view) {
-            clockInteractor.clockEventController.registerListeners(view)
-
-            onDispose { clockInteractor.clockEventController.unregisterListeners() }
-        }
-
-        MovableElement(
-            key = ClockElementKey,
-            modifier = modifier,
-        ) {
-            content {
-                AndroidView(
-                    factory = { context ->
-                        FrameLayout(context).apply {
-                            val newClockView = checkNotNull(currentClock).largeClock.view
-                            (newClockView.parent as? ViewGroup)?.removeView(newClockView)
-                            addView(newClockView)
-                        }
-                    },
-                    update = {
-                        val newClockView = checkNotNull(currentClock).largeClock.view
-                        it.removeAllViews()
-                        (newClockView.parent as? ViewGroup)?.removeView(newClockView)
-                        it.addView(newClockView)
-                    },
-                    modifier = Modifier.fillMaxSize()
-                )
-            }
-        }
+        AndroidView(
+            factory = { context ->
+                FrameLayout(context).apply {
+                    val newClockView = checkNotNull(currentClock).largeClock.view
+                    (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+                    addView(newClockView)
+                }
+            },
+            update = {
+                val newClockView = checkNotNull(currentClock).largeClock.view
+                it.removeAllViews()
+                (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+                it.addView(newClockView)
+            },
+            modifier = modifier.fillMaxSize()
+        )
     }
 }
-
-private val ClockElementKey = ElementKey("Clock")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
index be6f022..31d3fa0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.util.DisplayMetrics
+import android.view.View
 import android.view.WindowManager
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
@@ -45,6 +46,7 @@
 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.statusbar.VibratorHelper
 import dagger.Lazy
 import javax.inject.Inject
@@ -63,6 +65,7 @@
     private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
     private val falsingManager: Lazy<FalsingManager>,
     private val vibratorHelper: Lazy<VibratorHelper>,
+    private val notificationPanelView: NotificationPanelView,
 ) {
     @Composable
     fun SceneScope.LockIcon(modifier: Modifier = Modifier) {
@@ -70,6 +73,10 @@
             return
         }
 
+        notificationPanelView.findViewById<View?>(R.id.lock_icon_view)?.let {
+            notificationPanelView.removeView(it)
+        }
+
         val context = LocalContext.current
 
         AndroidView(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt
new file mode 100644
index 0000000..dae120c
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable.section
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.dimensionResource
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.viewmodel.MediaCarouselViewModel
+import com.android.systemui.media.controls.ui.composable.MediaCarousel
+import com.android.systemui.media.controls.ui.controller.MediaCarouselController
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.dagger.MediaModule
+import com.android.systemui.res.R
+import com.android.systemui.util.animation.MeasurementInput
+import javax.inject.Inject
+import javax.inject.Named
+
+class MediaCarouselSection
+@Inject
+constructor(
+    private val mediaCarouselController: MediaCarouselController,
+    @param:Named(MediaModule.KEYGUARD) private val mediaHost: MediaHost,
+    private val mediaCarouselViewModel: MediaCarouselViewModel,
+) {
+
+    @Composable
+    fun SceneScope.MediaCarousel(modifier: Modifier = Modifier) {
+        if (!mediaCarouselViewModel.isMediaVisible) {
+            return
+        }
+
+        if (mediaCarouselController.mediaFrame == null) {
+            return
+        }
+
+        val mediaHeight = dimensionResource(R.dimen.qs_media_session_height_expanded)
+        // TODO(b/312714128): MediaPlayer background size is not as expected.
+        MediaCarousel(
+            modifier =
+                modifier.height(mediaHeight).fillMaxWidth().onSizeChanged { size ->
+                    // Notify controller to size the carousel for the
+                    // current space
+                    mediaHost.measurementInput = MeasurementInput(size.width, size.height)
+                    mediaCarouselController.setSceneContainerSize(size.width, size.height)
+                },
+            mediaHost = mediaHost,
+            layoutWidth = 0, // Layout width is not used.
+            layoutHeight = with(LocalDensity.current) { mediaHeight.toPx() }.toInt(),
+            carouselController = mediaCarouselController,
+        )
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
index 9b71844..d1cc53e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
@@ -33,7 +33,6 @@
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
-import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
 import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
@@ -60,7 +59,7 @@
         modifier: Modifier = Modifier,
     ) {
         Column(
-            modifier = modifier.element(SmartSpaceElementKey).onTopPlacementChanged(onTopChanged),
+            modifier = modifier.onTopPlacementChanged(onTopChanged),
         ) {
             if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) {
                 return
@@ -192,5 +191,3 @@
         )
     }
 }
-
-private val SmartSpaceElementKey = ElementKey("SmartSpace")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
index 61b2d4e..d3e4553 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
@@ -16,9 +16,12 @@
 
 package com.android.systemui.media.controls.ui.composable
 
+import android.view.ViewGroup
+import android.widget.FrameLayout
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.view.contains
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.media.controls.ui.controller.MediaCarouselController
@@ -45,6 +48,20 @@
 
     AndroidView(
         modifier = modifier.element(MediaCarousel.Elements.Content),
-        factory = { _ -> carouselController.mediaFrame },
+        factory = { context ->
+            FrameLayout(context).apply {
+                val mediaFrame = carouselController.mediaFrame
+                (mediaFrame.parent as? ViewGroup)?.removeView(mediaFrame)
+                addView(mediaFrame)
+            }
+        },
+        update = {
+            if (it.contains(carouselController.mediaFrame)) {
+                return@AndroidView
+            }
+            val mediaFrame = carouselController.mediaFrame
+            (mediaFrame.parent as? ViewGroup)?.removeView(mediaFrame)
+            it.addView(mediaFrame)
+        },
     )
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index ef6ae2e..791d629 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -71,8 +71,7 @@
 import com.android.systemui.notifications.ui.composable.Notifications.Form
 import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_CORNER_RADIUS
 import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA
-import com.android.systemui.scene.ui.composable.Gone
-import com.android.systemui.scene.ui.composable.Shade
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.ui.composable.ShadeHeader
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationStackAppearanceViewBinder.SCRIM_CORNER_RADIUS
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
@@ -214,7 +213,7 @@
                     // in step with the transition so that it is 0 when it completes.
                     if (
                         scrimOffset.value < 0 &&
-                            layoutState.isTransitioning(from = Shade, to = Gone)
+                            layoutState.isTransitioning(from = Scenes.Shade, to = Scenes.Gone)
                     ) {
                         IntOffset(x = 0, y = (scrimOffset.value * expansionFraction).roundToInt())
                     } else {
@@ -226,7 +225,7 @@
                         calculateCornerRadius(
                                 screenCornerRadius,
                                 { expansionFraction },
-                                layoutState.isTransitioningBetween(Gone, Shade)
+                                layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade)
                             )
                             .let {
                                 RoundedCornerShape(
@@ -250,7 +249,7 @@
                 Modifier.fillMaxSize()
                     .graphicsLayer {
                         alpha =
-                            if (layoutState.isTransitioningBetween(Gone, Shade)) {
+                            if (layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade)) {
                                 (expansionFraction / EXPANSION_FOR_MAX_SCRIM_ALPHA).coerceAtMost(1f)
                             } else 1f
                     }
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 eb71490..5d9b014 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
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.footer.ui.compose
 
+import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.Canvas
 import androidx.compose.foundation.LocalIndication
@@ -76,9 +77,31 @@
 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.qs.ui.composable.QuickSettingsTheme
 import com.android.systemui.res.R
 import kotlinx.coroutines.launch
 
+@Composable
+fun FooterActionsWithAnimatedVisibility(
+    viewModel: FooterActionsViewModel,
+    isCustomizing: Boolean,
+    lifecycleOwner: LifecycleOwner,
+    footerActionsModifier: (Modifier) -> Modifier,
+    modifier: Modifier = Modifier,
+) {
+    AnimatedVisibility(visible = !isCustomizing, modifier = modifier.fillMaxWidth()) {
+        QuickSettingsTheme {
+            // This view has its own horizontal padding
+            // TODO(b/321716470) This should use a lifecycle tied to the scene.
+            FooterActions(
+                viewModel = viewModel,
+                qsVisibilityLifecycleOwner = lifecycleOwner,
+                modifier = footerActionsModifier(Modifier),
+            )
+        }
+    }
+}
+
 /** The Quick Settings footer actions row. */
 @Composable
 fun FooterActions(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index 5d0b9ba..bc48dd1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -37,14 +37,13 @@
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Companion.Collapsing
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Expanding
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Unsquishing
-import com.android.systemui.scene.ui.composable.QuickSettings as QuickSettingsSceneKey
-import com.android.systemui.scene.ui.composable.Shade
+import com.android.systemui.scene.shared.model.Scenes
 
 object QuickSettings {
     private val SCENES =
         setOf(
-            QuickSettingsSceneKey,
-            Shade,
+            Scenes.QuickSettings,
+            Scenes.Shade,
         )
 
     object Elements {
@@ -64,23 +63,28 @@
 }
 
 private fun SceneScope.stateForQuickSettingsContent(
+    isSplitShade: Boolean,
     squishiness: Float = QuickSettings.SharedValues.SquishinessValues.Default
 ): QSSceneAdapter.State {
     return when (val transitionState = layoutState.transitionState) {
         is TransitionState.Idle -> {
             when (transitionState.currentScene) {
-                Shade -> QSSceneAdapter.State.QQS
-                QuickSettingsSceneKey -> QSSceneAdapter.State.QS
+                Scenes.Shade -> QSSceneAdapter.State.QQS.takeUnless { isSplitShade }
+                        ?: QSSceneAdapter.State.QS
+                Scenes.QuickSettings -> QSSceneAdapter.State.QS
                 else -> QSSceneAdapter.State.CLOSED
             }
         }
         is TransitionState.Transition ->
             with(transitionState) {
                 when {
-                    fromScene == Shade && toScene == QuickSettingsSceneKey -> Expanding(progress)
-                    fromScene == QuickSettingsSceneKey && toScene == Shade -> Collapsing(progress)
-                    fromScene == Shade || toScene == Shade -> Unsquishing(squishiness)
-                    fromScene == QuickSettingsSceneKey || toScene == QuickSettingsSceneKey -> {
+                    isSplitShade -> QSSceneAdapter.State.QS
+                    fromScene == Scenes.Shade && toScene == Scenes.QuickSettings ->
+                        Expanding(progress)
+                    fromScene == Scenes.QuickSettings && toScene == Scenes.Shade ->
+                        Collapsing(progress)
+                    fromScene == Scenes.Shade || toScene == Scenes.Shade -> Unsquishing(squishiness)
+                    fromScene == Scenes.QuickSettings || toScene == Scenes.QuickSettings -> {
                         QSSceneAdapter.State.QS
                     }
                     else ->
@@ -110,10 +114,11 @@
 fun SceneScope.QuickSettings(
     qsSceneAdapter: QSSceneAdapter,
     heightProvider: () -> Int,
+    isSplitShade: Boolean,
     modifier: Modifier = Modifier,
     squishiness: Float = QuickSettings.SharedValues.SquishinessValues.Default,
 ) {
-    val contentState = stateForQuickSettingsContent(squishiness)
+    val contentState = stateForQuickSettingsContent(isSplitShade, squishiness)
 
     MovableElement(
         key = QuickSettings.Elements.Content,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 66cef86..6ae1410 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -40,7 +40,6 @@
 import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
@@ -51,6 +50,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.res.colorResource
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.TransitionState
@@ -61,10 +61,11 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.qs.footer.ui.compose.FooterActions
+import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.res.R
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.composable.ComposableScene
-import com.android.systemui.scene.ui.composable.asComposeAware
 import com.android.systemui.shade.ui.composable.CollapsedShadeHeader
 import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
 import com.android.systemui.shade.ui.composable.Shade
@@ -88,7 +89,7 @@
     private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
     private val statusBarIconController: StatusBarIconController,
 ) : ComposableScene {
-    override val key = SceneKey.QuickSettings
+    override val key = Scenes.QuickSettings
 
     override val destinationScenes =
         viewModel.destinationScenes.stateIn(
@@ -139,9 +140,7 @@
         val isScrollable =
             when (val state = layoutState.transitionState) {
                 is TransitionState.Idle -> true
-                is TransitionState.Transition -> {
-                    state.fromScene == SceneKey.QuickSettings.asComposeAware()
-                }
+                is TransitionState.Transition -> state.fromScene == Scenes.QuickSettings
             }
 
         LaunchedEffect(isCustomizing, scrollState) {
@@ -168,7 +167,7 @@
             modifier =
                 Modifier.element(Shade.Elements.BackgroundScrim)
                     .fillMaxSize()
-                    .background(MaterialTheme.colorScheme.scrim)
+                    .background(colorResource(R.color.shade_scrim_background_dark))
         )
         Column(
             horizontalAlignment = Alignment.CenterHorizontally,
@@ -240,24 +239,21 @@
                     QuickSettings(
                         viewModel.qsSceneAdapter,
                         { viewModel.qsSceneAdapter.qsHeight },
+                        isSplitShade = false,
                         modifier = Modifier.sysuiResTag("expanded_qs_scroll_view"),
                     )
                 }
             }
-            AnimatedVisibility(
-                visible = !isCustomizing,
-                modifier = Modifier.align(Alignment.CenterHorizontally).fillMaxWidth()
-            ) {
-                QuickSettingsTheme {
-                    // This view has its own horizontal padding
-                    // TODO(b/321716470) This should use a lifecycle tied to the scene.
-                    FooterActions(
-                        viewModel = footerActionsViewModel,
-                        qsVisibilityLifecycleOwner = lifecycleOwner,
-                        modifier = Modifier.element(QuickSettings.Elements.FooterActions)
-                    )
-                }
-            }
+
+            FooterActionsWithAnimatedVisibility(
+                viewModel = footerActionsViewModel,
+                isCustomizing = isCustomizing,
+                lifecycleOwner = lifecycleOwner,
+                footerActionsModifier = { modifier ->
+                    modifier.element(QuickSettings.Elements.FooterActions)
+                },
+                modifier = Modifier.align(Alignment.CenterHorizontally),
+            )
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeAwareExtensions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeAwareExtensions.kt
deleted file mode 100644
index 0de4650..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeAwareExtensions.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.ui.composable
-
-import com.android.compose.animation.scene.Back
-import com.android.compose.animation.scene.Edge as ComposeAwareEdge
-import com.android.compose.animation.scene.SceneKey as ComposeAwareSceneKey
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
-import com.android.compose.animation.scene.TransitionKey as ComposeAwareTransitionKey
-import com.android.compose.animation.scene.UserAction as ComposeAwareUserAction
-import com.android.compose.animation.scene.UserActionResult as ComposeAwareUserActionResult
-import com.android.systemui.scene.shared.model.Direction
-import com.android.systemui.scene.shared.model.Edge
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.shared.model.TransitionKey
-import com.android.systemui.scene.shared.model.UserAction
-import com.android.systemui.scene.shared.model.UserActionResult
-
-// TODO(b/293899074): remove this file once we can use the types from SceneTransitionLayout.
-
-fun SceneKey.asComposeAware(): ComposeAwareSceneKey {
-    return ComposeAwareSceneKey(
-        debugName = toString(),
-        identity = this,
-    )
-}
-
-fun TransitionKey.asComposeAware(): ComposeAwareTransitionKey {
-    return ComposeAwareTransitionKey(
-        debugName = debugName,
-        identity = this,
-    )
-}
-
-fun UserAction.asComposeAware(): ComposeAwareUserAction {
-    return when (this) {
-        is UserAction.Swipe ->
-            Swipe(
-                pointerCount = pointerCount,
-                fromSource =
-                    when (this.fromEdge) {
-                        null -> null
-                        Edge.LEFT -> ComposeAwareEdge.Left
-                        Edge.TOP -> ComposeAwareEdge.Top
-                        Edge.RIGHT -> ComposeAwareEdge.Right
-                        Edge.BOTTOM -> ComposeAwareEdge.Bottom
-                    },
-                direction =
-                    when (this.direction) {
-                        Direction.LEFT -> SwipeDirection.Left
-                        Direction.UP -> SwipeDirection.Up
-                        Direction.RIGHT -> SwipeDirection.Right
-                        Direction.DOWN -> SwipeDirection.Down
-                    }
-            )
-        is UserAction.Back -> Back
-    }
-}
-
-fun UserActionResult.asComposeAware(): ComposeAwareUserActionResult {
-    val composeUnaware = this
-    return ComposeAwareUserActionResult(
-        toScene = composeUnaware.toScene.asComposeAware(),
-        transitionKey = composeUnaware.transitionKey?.asComposeAware(),
-    )
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeUnawareExtensions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeUnawareExtensions.kt
deleted file mode 100644
index 4c03664..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeUnawareExtensions.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.ui.composable
-
-import com.android.compose.animation.scene.ObservableTransitionState as ComposeAwareObservableTransitionState
-import com.android.compose.animation.scene.SceneKey as ComposeAwareSceneKey
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
-
-fun ComposeAwareSceneKey.asComposeUnaware(): SceneKey {
-    return this.identity as SceneKey
-}
-
-fun ComposeAwareObservableTransitionState.asComposeUnaware(): ObservableTransitionState {
-    return when (this) {
-        is ComposeAwareObservableTransitionState.Idle ->
-            ObservableTransitionState.Idle(scene.asComposeUnaware())
-        is ComposeAwareObservableTransitionState.Transition ->
-            ObservableTransitionState.Transition(
-                fromScene = fromScene.asComposeUnaware(),
-                toScene = toScene.asComposeUnaware(),
-                progress = progress,
-                isInitiatedByUserInput = isInitiatedByUserInput,
-                isUserInputOngoing = isUserInputOngoing,
-            )
-    }
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 9ca751e..975829a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -19,22 +19,17 @@
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
 import com.android.compose.animation.scene.animateSceneFloatAsState
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.qs.ui.composable.QuickSettings
-import com.android.systemui.scene.shared.model.Direction
-import com.android.systemui.scene.shared.model.Edge
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.shared.model.UserAction
-import com.android.systemui.scene.shared.model.UserActionResult
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.ui.viewmodel.GoneSceneViewModel
 import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
 
 /**
  * "Gone" is not a real scene but rather the absence of scenes when we want to skip showing any
@@ -44,23 +39,12 @@
 class GoneScene
 @Inject
 constructor(
-    private val notificationsViewModel: NotificationsPlaceholderViewModel,
+    private val viewModel: GoneSceneViewModel,
 ) : ComposableScene {
-    override val key = SceneKey.Gone
+    override val key = Scenes.Gone
 
     override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
-        MutableStateFlow<Map<UserAction, UserActionResult>>(
-                mapOf(
-                    UserAction.Swipe(
-                        pointerCount = 2,
-                        fromEdge = Edge.TOP,
-                        direction = Direction.DOWN,
-                    ) to UserActionResult(SceneKey.QuickSettings),
-                    UserAction.Swipe(direction = Direction.DOWN) to
-                        UserActionResult(SceneKey.Shade),
-                )
-            )
-            .asStateFlow()
+        viewModel.destinationScenes
 
     @Composable
     override fun SceneScope.Content(
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 9779d71..0fdaabe 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
@@ -35,15 +35,14 @@
 import androidx.compose.ui.input.pointer.motionEventSpy
 import androidx.compose.ui.input.pointer.pointerInput
 import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
 import com.android.compose.animation.scene.observableTransitionState
 import com.android.systemui.ribbon.ui.composable.BottomRightCornerRibbon
 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.shared.model.UserAction
-import com.android.systemui.scene.shared.model.UserActionResult
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
-import kotlinx.coroutines.flow.map
 
 /**
  * Renders a container of a collection of "scenes" that the user can switch between using certain
@@ -77,8 +76,8 @@
         currentScene.destinationScenes.collectAsState()
     val state: MutableSceneTransitionLayoutState = remember {
         MutableSceneTransitionLayoutState(
-            initialScene = currentSceneKey.asComposeAware(),
-            canChangeScene = { toScene -> viewModel.canChangeScene(toScene.asComposeUnaware()) },
+            initialScene = currentSceneKey,
+            canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
             transitions = SceneContainerTransitions,
         )
     }
@@ -90,9 +89,7 @@
     }
 
     DisposableEffect(viewModel, state) {
-        viewModel.setTransitionState(
-            state.observableTransitionState().map { it.asComposeUnaware() }
-        )
+        viewModel.setTransitionState(state.observableTransitionState())
         onDispose { viewModel.setTransitionState(null) }
     }
 
@@ -116,23 +113,17 @@
         ) {
             sceneByKey.forEach { (sceneKey, composableScene) ->
                 scene(
-                    key = sceneKey.asComposeAware(),
+                    key = sceneKey,
                     userActions =
                         if (sceneKey == currentSceneKey) {
-                                currentDestinations
-                            } else {
-                                composableScene.destinationScenes.value
-                            }
-                            .map { (userAction, userActionResult) ->
-                                userAction.asComposeAware() to userActionResult.asComposeAware()
-                            }
-                            .toMap(),
+                            currentDestinations
+                        } else {
+                            composableScene.destinationScenes.value
+                        },
                 ) {
                     with(composableScene) {
                         this@scene.Content(
-                            modifier =
-                                Modifier.element(sceneKey.asComposeAware().rootElementKey)
-                                    .fillMaxSize(),
+                            modifier = Modifier.element(sceneKey.rootElementKey).fillMaxSize(),
                         )
                     }
                 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index 61f8120..dea9485 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -1,6 +1,7 @@
 package com.android.systemui.scene.ui.composable
 
 import com.android.compose.animation.scene.transitions
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.TransitionKeys.CollapseShadeInstantly
 import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse
 import com.android.systemui.scene.ui.composable.transitions.bouncerToGoneTransition
@@ -26,41 +27,41 @@
  * Please keep the list sorted alphabetically.
  */
 val SceneContainerTransitions = transitions {
-    from(Bouncer, to = Gone) { bouncerToGoneTransition() }
-    from(Gone, to = Shade) { goneToShadeTransition() }
+    from(Scenes.Bouncer, to = Scenes.Gone) { bouncerToGoneTransition() }
+    from(Scenes.Gone, to = Scenes.Shade) { goneToShadeTransition() }
     from(
-        Gone,
-        to = Shade,
-        key = CollapseShadeInstantly.asComposeAware(),
+        Scenes.Gone,
+        to = Scenes.Shade,
+        key = CollapseShadeInstantly,
     ) {
         goneToShadeTransition(durationScale = 0.0)
     }
     from(
-        Gone,
-        to = Shade,
-        key = SlightlyFasterShadeCollapse.asComposeAware(),
+        Scenes.Gone,
+        to = Scenes.Shade,
+        key = SlightlyFasterShadeCollapse,
     ) {
         goneToShadeTransition(durationScale = 0.9)
     }
-    from(Gone, to = QuickSettings) { goneToQuickSettingsTransition() }
-    from(Lockscreen, to = Bouncer) { lockscreenToBouncerTransition() }
-    from(Lockscreen, to = Communal) { lockscreenToCommunalTransition() }
-    from(Lockscreen, to = Shade) { lockscreenToShadeTransition() }
+    from(Scenes.Gone, to = Scenes.QuickSettings) { goneToQuickSettingsTransition() }
+    from(Scenes.Lockscreen, to = Scenes.Bouncer) { lockscreenToBouncerTransition() }
+    from(Scenes.Lockscreen, to = Scenes.Communal) { lockscreenToCommunalTransition() }
+    from(Scenes.Lockscreen, to = Scenes.Shade) { lockscreenToShadeTransition() }
     from(
-        Lockscreen,
-        to = Shade,
-        key = CollapseShadeInstantly.asComposeAware(),
+        Scenes.Lockscreen,
+        to = Scenes.Shade,
+        key = CollapseShadeInstantly,
     ) {
         lockscreenToShadeTransition(durationScale = 0.0)
     }
     from(
-        Lockscreen,
-        to = Shade,
-        key = SlightlyFasterShadeCollapse.asComposeAware(),
+        Scenes.Lockscreen,
+        to = Scenes.Shade,
+        key = SlightlyFasterShadeCollapse,
     ) {
         lockscreenToShadeTransition(durationScale = 0.9)
     }
-    from(Lockscreen, to = QuickSettings) { lockscreenToQuickSettingsTransition() }
-    from(Lockscreen, to = Gone) { lockscreenToGoneTransition() }
-    from(Shade, to = QuickSettings) { shadeToQuickSettingsTransition() }
+    from(Scenes.Lockscreen, to = Scenes.QuickSettings) { lockscreenToQuickSettingsTransition() }
+    from(Scenes.Lockscreen, to = Scenes.Gone) { lockscreenToGoneTransition() }
+    from(Scenes.Shade, to = Scenes.QuickSettings) { shadeToQuickSettingsTransition() }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
index 60c0b77..a54994d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
@@ -20,10 +20,10 @@
 
 import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
 import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionKey
 import com.android.compose.animation.scene.observableTransitionState
 import com.android.systemui.scene.shared.model.SceneDataSource
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.shared.model.TransitionKey
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.SharingStarted
@@ -61,11 +61,10 @@
                         }
                 }
             }
-            .map { it.asComposeUnaware() }
             .stateIn(
                 scope = coroutineScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = state.transitionState.currentScene.asComposeUnaware(),
+                initialValue = state.transitionState.currentScene,
             )
 
     override fun changeScene(
@@ -73,8 +72,8 @@
         transitionKey: TransitionKey?,
     ) {
         state.setTargetScene(
-            targetScene = toScene.asComposeAware(),
-            transitionKey = transitionKey?.asComposeAware(),
+            targetScene = toScene,
+            transitionKey = transitionKey,
             coroutineScope = coroutineScope,
         )
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt
deleted file mode 100644
index 5a9add1..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.android.systemui.scene.ui.composable
-
-import com.android.systemui.scene.shared.model.SceneKey
-
-val Lockscreen = SceneKey.Lockscreen.asComposeAware()
-val Bouncer = SceneKey.Bouncer.asComposeAware()
-val Shade = SceneKey.Shade.asComposeAware()
-val QuickSettings = SceneKey.QuickSettings.asComposeAware()
-val Gone = SceneKey.Gone.asComposeAware()
-val Communal = SceneKey.Communal.asComposeAware()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerToGoneTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerToGoneTransition.kt
index 1a9face..5eefe49 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerToGoneTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerToGoneTransition.kt
@@ -2,10 +2,10 @@
 
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.TransitionBuilder
-import com.android.systemui.scene.ui.composable.Bouncer
+import com.android.systemui.scene.shared.model.Scenes
 
 fun TransitionBuilder.bouncerToGoneTransition() {
     spec = tween(durationMillis = 500)
 
-    fade(Bouncer.rootElementKey)
+    fade(Scenes.Bouncer.rootElementKey)
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt
index 291617f..5bd1583 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt
@@ -3,10 +3,10 @@
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
-import com.android.systemui.scene.ui.composable.QuickSettings
+import com.android.systemui.scene.shared.model.Scenes
 
 fun TransitionBuilder.goneToQuickSettingsTransition() {
     spec = tween(durationMillis = 500)
 
-    translate(QuickSettings.rootElementKey, Edge.Top, true)
+    translate(Scenes.QuickSettings.rootElementKey, Edge.Top, true)
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt
index ea8110a..0021bf5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt
@@ -19,15 +19,14 @@
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
-import com.android.systemui.scene.ui.composable.Communal
-import com.android.systemui.scene.ui.composable.Lockscreen
+import com.android.systemui.scene.shared.model.Scenes
 
 fun TransitionBuilder.lockscreenToCommunalTransition() {
     spec = tween(durationMillis = 500)
 
     // Translate lockscreen to the left.
-    translate(Lockscreen.rootElementKey, Edge.Left)
+    translate(Scenes.Lockscreen.rootElementKey, Edge.Left)
 
     // Translate communal from the right.
-    translate(Communal.rootElementKey, Edge.Right)
+    translate(Scenes.Communal.rootElementKey, Edge.Right)
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToGoneTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToGoneTransition.kt
index da6306d..3e576bc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToGoneTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToGoneTransition.kt
@@ -2,10 +2,10 @@
 
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.TransitionBuilder
-import com.android.systemui.scene.ui.composable.Lockscreen
+import com.android.systemui.scene.shared.model.Scenes
 
 fun TransitionBuilder.lockscreenToGoneTransition() {
     spec = tween(durationMillis = 500)
 
-    fade(Lockscreen.rootElementKey)
+    fade(Scenes.Lockscreen.rootElementKey)
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt
index e63bc4e..962d822 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt
@@ -3,10 +3,10 @@
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
-import com.android.systemui.scene.ui.composable.QuickSettings
+import com.android.systemui.scene.shared.model.Scenes
 
 fun TransitionBuilder.lockscreenToQuickSettingsTransition() {
     spec = tween(durationMillis = 500)
 
-    translate(QuickSettings.rootElementKey, Edge.Top, true)
+    translate(Scenes.QuickSettings.rootElementKey, Edge.Top, true)
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index d7911ea..12b07a3 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
@@ -63,8 +63,7 @@
 import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
 import com.android.systemui.privacy.OngoingPrivacyChip
 import com.android.systemui.res.R
-import com.android.systemui.scene.ui.composable.QuickSettings
-import com.android.systemui.scene.ui.composable.Shade as ShadeKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.ui.composable.ShadeHeader.Dimensions.CollapsedHeight
 import com.android.systemui.shade.ui.composable.ShadeHeader.Values.ClockScale
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
@@ -443,7 +442,7 @@
         },
         update = { iconContainer ->
             iconContainer.setQsExpansionTransitioning(
-                layoutState.isTransitioningBetween(ShadeKey, QuickSettings)
+                layoutState.isTransitioningBetween(Scenes.Shade, Scenes.QuickSettings)
             )
             if (isSingleCarrier || !useExpandedFormat) {
                 iconContainer.removeIgnoredSlots(carrierIconSlots)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 2e0ce42..51464d0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -19,15 +19,26 @@
 import android.view.ViewGroup
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
+import androidx.compose.foundation.clipScrollableContainer
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.navigationBars
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.MaterialTheme
+import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -37,16 +48,19 @@
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.layout
 import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.res.colorResource
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.LowestZIndexScenePicker
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
 import com.android.compose.animation.scene.animateSceneFloatAsState
 import com.android.compose.modifiers.thenIf
 import com.android.systemui.battery.BatteryMeterViewController
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.media.controls.ui.composable.MediaCarousel
 import com.android.systemui.media.controls.ui.controller.MediaCarouselController
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
@@ -54,13 +68,12 @@
 import com.android.systemui.media.controls.ui.view.MediaHostState
 import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
 import com.android.systemui.notifications.ui.composable.NotificationScrollingStack
+import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility
 import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.res.R
-import com.android.systemui.scene.shared.model.Direction
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.shared.model.UserAction
-import com.android.systemui.scene.shared.model.UserActionResult
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager
@@ -69,11 +82,7 @@
 import javax.inject.Inject
 import javax.inject.Named
 import kotlin.math.roundToInt
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
 
 object Shade {
     object Elements {
@@ -101,7 +110,6 @@
 class ShadeScene
 @Inject
 constructor(
-    @Application private val applicationScope: CoroutineScope,
     private val viewModel: ShadeSceneViewModel,
     private val tintedIconManagerFactory: TintedIconManager.Factory,
     private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
@@ -109,16 +117,10 @@
     private val mediaCarouselController: MediaCarouselController,
     @Named(QUICK_QS_PANEL) private val mediaHost: MediaHost,
 ) : ComposableScene {
-    override val key = SceneKey.Shade
+    override val key = Scenes.Shade
 
     override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
-        viewModel.upDestinationSceneKey
-            .map { sceneKey -> destinationScenes(up = sceneKey) }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.Eagerly,
-                initialValue = destinationScenes(up = viewModel.upDestinationSceneKey.value),
-            )
+        viewModel.destinationScenes
 
     @Composable
     override fun SceneScope.Content(
@@ -139,15 +141,6 @@
         mediaHost.showsOnlyActiveMedia = true
         mediaHost.init(MediaHierarchyManager.LOCATION_QQS)
     }
-
-    private fun destinationScenes(
-        up: SceneKey,
-    ): Map<UserAction, UserActionResult> {
-        return mapOf(
-            UserAction.Swipe(Direction.UP) to UserActionResult(up),
-            UserAction.Swipe(Direction.DOWN) to UserActionResult(SceneKey.QuickSettings),
-        )
-    }
 }
 
 @Composable
@@ -160,8 +153,42 @@
     mediaHost: MediaHost,
     modifier: Modifier = Modifier,
 ) {
-    val density = LocalDensity.current
-    val layoutWidth = remember { mutableStateOf(0) }
+    val shadeMode by viewModel.shadeMode.collectAsState()
+    when (shadeMode) {
+        is ShadeMode.Single ->
+            SingleShade(
+                viewModel = viewModel,
+                createTintedIconManager = createTintedIconManager,
+                createBatteryMeterViewController = createBatteryMeterViewController,
+                statusBarIconController = statusBarIconController,
+                mediaCarouselController = mediaCarouselController,
+                mediaHost = mediaHost,
+                modifier = modifier,
+            )
+        is ShadeMode.Split ->
+            SplitShade(
+                viewModel = viewModel,
+                createTintedIconManager = createTintedIconManager,
+                createBatteryMeterViewController = createBatteryMeterViewController,
+                statusBarIconController = statusBarIconController,
+                mediaCarouselController = mediaCarouselController,
+                mediaHost = mediaHost,
+                modifier = modifier,
+            )
+        is ShadeMode.Dual -> error("Dual shade is not yet implemented!")
+    }
+}
+
+@Composable
+private fun SceneScope.SingleShade(
+    viewModel: ShadeSceneViewModel,
+    createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
+    createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
+    statusBarIconController: StatusBarIconController,
+    mediaCarouselController: MediaCarouselController,
+    mediaHost: MediaHost,
+    modifier: Modifier = Modifier,
+) {
     val maxNotifScrimTop = remember { mutableStateOf(0f) }
     val tileSquishiness by
         animateSceneFloatAsState(value = 1f, key = QuickSettings.SharedValues.TilesSquishiness)
@@ -171,7 +198,7 @@
         modifier =
             modifier
                 .element(Shade.Elements.BackgroundScrim)
-                .background(MaterialTheme.colorScheme.scrim),
+                .background(colorResource(R.color.shade_scrim_background_dark)),
     )
     Box {
         Layout(
@@ -201,38 +228,15 @@
                                     (viewModel.qsSceneAdapter.qqsHeight * tileSquishiness)
                                         .roundToInt()
                                 },
+                                isSplitShade = false,
                                 squishiness = tileSquishiness,
                             )
 
-                            if (viewModel.isMediaVisible()) {
-                                val mediaHeight =
-                                    dimensionResource(R.dimen.qs_media_session_height_expanded)
-                                MediaCarousel(
-                                    modifier =
-                                        Modifier.height(mediaHeight).fillMaxWidth().layout {
-                                            measurable,
-                                            constraints ->
-                                            val placeable = measurable.measure(constraints)
-
-                                            // Notify controller to size the carousel for the
-                                            // current space
-                                            mediaHost.measurementInput =
-                                                MeasurementInput(placeable.width, placeable.height)
-                                            mediaCarouselController.setSceneContainerSize(
-                                                placeable.width,
-                                                placeable.height
-                                            )
-
-                                            layout(placeable.width, placeable.height) {
-                                                placeable.placeRelative(0, 0)
-                                            }
-                                        },
-                                    mediaHost = mediaHost,
-                                    layoutWidth = layoutWidth.value,
-                                    layoutHeight = with(density) { mediaHeight.toPx() }.toInt(),
-                                    carouselController = mediaCarouselController,
-                                )
-                            }
+                            MediaIfVisible(
+                                viewModel = viewModel,
+                                mediaCarouselController = mediaCarouselController,
+                                mediaHost = mediaHost,
+                            )
 
                             Spacer(modifier = Modifier.height(16.dp))
                         }
@@ -261,3 +265,133 @@
         }
     }
 }
+
+@Composable
+private fun SceneScope.SplitShade(
+    viewModel: ShadeSceneViewModel,
+    createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
+    createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
+    statusBarIconController: StatusBarIconController,
+    mediaCarouselController: MediaCarouselController,
+    mediaHost: MediaHost,
+    modifier: Modifier = Modifier,
+) {
+    val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
+    val lifecycleOwner = LocalLifecycleOwner.current
+    val footerActionsViewModel =
+        remember(lifecycleOwner, viewModel) { viewModel.getFooterActionsViewModel(lifecycleOwner) }
+
+    val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
+    val density = LocalDensity.current
+    LaunchedEffect(navBarBottomHeight, density) {
+        with(density) {
+            viewModel.qsSceneAdapter.applyBottomNavBarPadding(navBarBottomHeight.roundToPx())
+        }
+    }
+
+    val quickSettingsScrollState = rememberScrollState()
+    LaunchedEffect(isCustomizing, quickSettingsScrollState) {
+        if (isCustomizing) {
+            quickSettingsScrollState.scrollTo(0)
+        }
+    }
+
+    Box(
+        modifier =
+            modifier
+                .fillMaxSize()
+                .element(Shade.Elements.BackgroundScrim)
+                .background(colorResource(R.color.shade_scrim_background_dark))
+    ) {
+        Column(
+            modifier = Modifier.fillMaxSize(),
+        ) {
+            CollapsedShadeHeader(
+                viewModel = viewModel.shadeHeaderViewModel,
+                createTintedIconManager = createTintedIconManager,
+                createBatteryMeterViewController = createBatteryMeterViewController,
+                statusBarIconController = statusBarIconController,
+                modifier = Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding)
+            )
+
+            Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
+                Column(
+                    verticalArrangement = Arrangement.Top,
+                    modifier =
+                        Modifier.weight(1f).fillMaxHeight().thenIf(!isCustomizing) {
+                            Modifier.verticalNestedScrollToScene()
+                                .verticalScroll(quickSettingsScrollState)
+                                .clipScrollableContainer(Orientation.Horizontal)
+                                .padding(bottom = navBarBottomHeight)
+                        }
+                ) {
+                    QuickSettings(
+                        qsSceneAdapter = viewModel.qsSceneAdapter,
+                        heightProvider = { viewModel.qsSceneAdapter.qsHeight },
+                        isSplitShade = true,
+                        modifier = Modifier.fillMaxWidth(),
+                    )
+
+                    MediaIfVisible(
+                        viewModel = viewModel,
+                        mediaCarouselController = mediaCarouselController,
+                        mediaHost = mediaHost,
+                        modifier = Modifier.fillMaxWidth(),
+                    )
+
+                    Spacer(
+                        modifier = Modifier.weight(1f),
+                    )
+
+                    FooterActionsWithAnimatedVisibility(
+                        viewModel = footerActionsViewModel,
+                        isCustomizing = isCustomizing,
+                        lifecycleOwner = lifecycleOwner,
+                        footerActionsModifier = { modifier ->
+                            modifier.element(QuickSettings.Elements.FooterActions)
+                        },
+                        modifier = Modifier.align(Alignment.CenterHorizontally),
+                    )
+                }
+
+                NotificationScrollingStack(
+                    viewModel = viewModel.notifications,
+                    maxScrimTop = { 0f },
+                    modifier = Modifier.weight(1f).fillMaxHeight(),
+                )
+            }
+        }
+    }
+}
+
+@Composable
+private fun SceneScope.MediaIfVisible(
+    viewModel: ShadeSceneViewModel,
+    mediaCarouselController: MediaCarouselController,
+    mediaHost: MediaHost,
+    modifier: Modifier = Modifier,
+) {
+    if (viewModel.isMediaVisible()) {
+        val density = LocalDensity.current
+        val layoutWidth = remember { mutableStateOf(0) }
+        val mediaHeight = dimensionResource(R.dimen.qs_media_session_height_expanded)
+
+        MediaCarousel(
+            modifier =
+                modifier.height(mediaHeight).fillMaxWidth().layout { measurable, constraints ->
+                    val placeable = measurable.measure(constraints)
+
+                    // Notify controller to size the carousel for the
+                    // current space
+                    mediaHost.measurementInput = MeasurementInput(placeable.width, placeable.height)
+                    mediaCarouselController.setSceneContainerSize(placeable.width, placeable.height)
+
+                    layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) }
+                },
+            mediaHost = mediaHost,
+            layoutWidth = layoutWidth.value,
+            layoutHeight = with(density) { mediaHeight.toPx() }.toInt(),
+            carouselController = mediaCarouselController,
+        )
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
index 8ac84ff..b1fbe35 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.volume.panel.component.anc.ui.composable
 
 import android.content.Context
+import android.view.ContextThemeWrapper
 import android.view.View
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.material3.MaterialTheme
@@ -73,15 +74,16 @@
         AndroidView<SliceView>(
             modifier = Modifier.fillMaxWidth(),
             factory = { context: Context ->
-                SliceView(context).apply {
-                    mode = SliceView.MODE_LARGE
-                    isScrollable = false
-                    importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
-                    setShowTitleItems(true)
-                    addOnLayoutChangeListener(
-                        OnWidthChangedLayoutListener(viewModel::changeSliceWidth)
-                    )
-                }
+                SliceView(ContextThemeWrapper(context, R.style.Widget_SliceView_VolumePanel))
+                    .apply {
+                        mode = SliceView.MODE_LARGE
+                        isScrollable = false
+                        importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+                        setShowTitleItems(true)
+                        addOnLayoutChangeListener(
+                            OnWidthChangedLayoutListener(viewModel::changeSliceWidth)
+                        )
+                    }
             },
             update = { sliceView: SliceView -> sliceView.slice = slice }
         )
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
index d401261..c08eb94 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
@@ -19,17 +19,17 @@
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.material3.Button
 import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
-import com.android.compose.PlatformButton
-import com.android.compose.PlatformOutlinedButton
 import com.android.systemui.res.R
 import com.android.systemui.volume.panel.component.bottombar.ui.viewmodel.BottomBarViewModel
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
@@ -47,11 +47,11 @@
     @Composable
     override fun VolumePanelComposeScope.Content(modifier: Modifier) {
         Row(
-            modifier = modifier.height(if (isLargeScreen) 54.dp else 48.dp).fillMaxWidth(),
+            modifier = modifier.heightIn(min = if (isLargeScreen) 54.dp else 48.dp).fillMaxWidth(),
             horizontalArrangement = Arrangement.SpaceBetween,
             verticalAlignment = Alignment.CenterVertically,
         ) {
-            PlatformOutlinedButton(
+            OutlinedButton(
                 onClick = viewModel::onSettingsClicked,
                 colors =
                     ButtonDefaults.outlinedButtonColors(
@@ -60,8 +60,8 @@
             ) {
                 Text(text = stringResource(R.string.volume_panel_dialog_settings_button))
             }
-            PlatformButton(onClick = viewModel::onDoneClicked) {
-                Text(text = stringResource(R.string.inline_done_button))
+            Button(onClick = viewModel::onDoneClicked) {
+                Text(stringResource(R.string.inline_done_button))
             }
         }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
index d49fed5..53de5bc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
@@ -27,11 +27,14 @@
 import androidx.compose.animation.slideInVertically
 import androidx.compose.animation.slideOutVertically
 import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.background
+import androidx.compose.foundation.basicMarquee
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.aspectRatio
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
@@ -46,7 +49,6 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.Expandable
 import com.android.systemui.common.ui.compose.Icon
@@ -78,8 +80,8 @@
             color = MaterialTheme.colorScheme.surface,
             shape = RoundedCornerShape(28.dp),
             onClick = { viewModel.onBarClick(it) },
-        ) {
-            Row {
+        ) { _ ->
+            Row(verticalAlignment = Alignment.CenterVertically) {
                 connectedDeviceViewModel?.let { ConnectedDeviceText(it) }
 
                 deviceIconViewModel?.let { ConnectedDeviceIcon(it) }
@@ -90,26 +92,23 @@
     @Composable
     private fun RowScope.ConnectedDeviceText(connectedDeviceViewModel: ConnectedDeviceViewModel) {
         Column(
-            modifier =
-                Modifier.weight(1f)
-                    .padding(start = 24.dp, top = 20.dp, bottom = 20.dp)
-                    .fillMaxHeight(),
+            modifier = Modifier.weight(1f).padding(start = 24.dp),
             verticalArrangement = Arrangement.spacedBy(4.dp),
         ) {
             Text(
-                connectedDeviceViewModel.label.toString(),
+                modifier = Modifier.basicMarquee(),
+                text = connectedDeviceViewModel.label.toString(),
                 style = MaterialTheme.typography.labelMedium,
                 color = MaterialTheme.colorScheme.onSurfaceVariant,
                 maxLines = 1,
-                overflow = TextOverflow.Ellipsis,
             )
             connectedDeviceViewModel.deviceName?.let {
                 Text(
-                    it.toString(),
+                    modifier = Modifier.basicMarquee(),
+                    text = it.toString(),
                     style = MaterialTheme.typography.titleMedium,
                     color = MaterialTheme.colorScheme.onSurface,
                     maxLines = 1,
-                    overflow = TextOverflow.Ellipsis,
                 )
             }
         }
@@ -140,12 +139,14 @@
                     }
                 }
             ) { targetViewModel ->
-                Expandable(
-                    modifier = Modifier.fillMaxSize(),
-                    color = targetViewModel.backgroundColor.toColor(),
-                    shape = RoundedCornerShape(12.dp),
-                    onClick = { viewModel.onDeviceClick(it) },
-                ) {}
+                Spacer(
+                    modifier =
+                        Modifier.fillMaxSize()
+                            .background(
+                                color = targetViewModel.backgroundColor.toColor(),
+                                shape = RoundedCornerShape(12.dp),
+                            ),
+                )
             }
             transition.AnimatedContent(
                 contentKey = { it.icon },
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
new file mode 100644
index 0000000..ae267e2
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.selector.ui.composable
+
+import androidx.compose.animation.core.animateOffsetAsState
+import androidx.compose.foundation.Canvas
+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.IntrinsicSize
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+
+/**
+ * Radio button group for the Volume Panel. It allows selecting a single item
+ *
+ * @param indicatorBackgroundPadding is the distance between the edge of the indicator and the
+ *   indicator background
+ * @param labelIndicatorBackgroundSpacing is the distance between indicator background and labels
+ *   row
+ */
+@Composable
+fun VolumePanelRadioButtonBar(
+    modifier: Modifier = Modifier,
+    indicatorBackgroundPadding: Dp =
+        VolumePanelRadioButtonBarDefaults.DefaultIndicatorBackgroundPadding,
+    spacing: Dp = VolumePanelRadioButtonBarDefaults.DefaultSpacing,
+    labelIndicatorBackgroundSpacing: Dp =
+        VolumePanelRadioButtonBarDefaults.DefaultLabelIndicatorBackgroundSpacing,
+    indicatorCornerRadius: CornerRadius =
+        VolumePanelRadioButtonBarDefaults.defaultIndicatorCornerRadius(),
+    indicatorBackgroundCornerSize: CornerSize =
+        CornerSize(VolumePanelRadioButtonBarDefaults.DefaultIndicatorBackgroundCornerRadius),
+    colors: VolumePanelRadioButtonBarColors = VolumePanelRadioButtonBarDefaults.defaultColors(),
+    content: VolumePanelRadioButtonBarScope.() -> Unit
+) {
+    val scope =
+        VolumePanelRadioButtonBarScopeImpl().apply(content).apply {
+            require(hasSelectedItem) { "At least one item should be selected" }
+        }
+
+    val items = scope.items
+
+    var selectedIndex by remember { mutableIntStateOf(items.indexOfFirst { it.isSelected }) }
+
+    var size by remember { mutableStateOf(IntSize(0, 0)) }
+    val spacingPx = with(LocalDensity.current) { spacing.toPx() }
+    val indicatorWidth = size.width / items.size - (spacingPx * (items.size - 1) / items.size)
+    val offset by
+        animateOffsetAsState(
+            targetValue =
+                Offset(
+                    selectedIndex * indicatorWidth + (spacingPx * selectedIndex),
+                    0f,
+                ),
+            label = "VolumePanelRadioButtonOffsetAnimation",
+            finishedListener = {
+                for (itemIndex in items.indices) {
+                    val item = items[itemIndex]
+                    if (itemIndex == selectedIndex) {
+                        item.onItemSelected()
+                        break
+                    }
+                }
+            }
+        )
+
+    Column(modifier = modifier) {
+        Box(modifier = Modifier.height(IntrinsicSize.Max)) {
+            Canvas(
+                modifier =
+                    Modifier.fillMaxSize()
+                        .background(
+                            colors.indicatorBackgroundColor,
+                            RoundedCornerShape(indicatorBackgroundCornerSize),
+                        )
+                        .padding(indicatorBackgroundPadding)
+                        .onGloballyPositioned { size = it.size }
+            ) {
+                drawRoundRect(
+                    color = colors.indicatorColor,
+                    topLeft = offset,
+                    size = Size(indicatorWidth, size.height.toFloat()),
+                    cornerRadius = indicatorCornerRadius,
+                )
+            }
+            Row(
+                modifier = Modifier.padding(indicatorBackgroundPadding),
+                horizontalArrangement = Arrangement.spacedBy(spacing)
+            ) {
+                for (itemIndex in items.indices) {
+                    TextButton(
+                        modifier = Modifier.weight(1f),
+                        onClick = { selectedIndex = itemIndex },
+                    ) {
+                        val item = items[itemIndex]
+                        if (item.icon !== Empty) {
+                            with(items[itemIndex]) { icon() }
+                        }
+                    }
+                }
+            }
+        }
+
+        Row(
+            modifier =
+                Modifier.padding(
+                    start = indicatorBackgroundPadding,
+                    top = labelIndicatorBackgroundSpacing,
+                    end = indicatorBackgroundPadding
+                ),
+            horizontalArrangement = Arrangement.spacedBy(spacing),
+        ) {
+            for (itemIndex in items.indices) {
+                TextButton(
+                    modifier = Modifier.weight(1f),
+                    onClick = { selectedIndex = itemIndex },
+                ) {
+                    val item = items[itemIndex]
+                    if (item.icon !== Empty) {
+                        with(items[itemIndex]) { label() }
+                    }
+                }
+            }
+        }
+    }
+}
+
+data class VolumePanelRadioButtonBarColors(
+    /** Color of the indicator. */
+    val indicatorColor: Color,
+    /** Color of the indicator background. */
+    val indicatorBackgroundColor: Color,
+)
+
+object VolumePanelRadioButtonBarDefaults {
+
+    val DefaultIndicatorBackgroundPadding = 8.dp
+    val DefaultSpacing = 24.dp
+    val DefaultLabelIndicatorBackgroundSpacing = 12.dp
+    val DefaultIndicatorCornerRadius = 20.dp
+    val DefaultIndicatorBackgroundCornerRadius = 20.dp
+
+    @Composable
+    fun defaultIndicatorCornerRadius(
+        x: Dp = DefaultIndicatorCornerRadius,
+        y: Dp = DefaultIndicatorCornerRadius,
+    ): CornerRadius = with(LocalDensity.current) { CornerRadius(x.toPx(), y.toPx()) }
+
+    /**
+     * Returns the default VolumePanelRadioButtonBar colors.
+     *
+     * @param indicatorColor is the color of the indicator
+     * @param indicatorBackgroundColor is the color of the indicator background
+     */
+    @Composable
+    fun defaultColors(
+        indicatorColor: Color = MaterialTheme.colorScheme.primaryContainer,
+        indicatorBackgroundColor: Color = MaterialTheme.colorScheme.surface,
+    ): VolumePanelRadioButtonBarColors =
+        VolumePanelRadioButtonBarColors(
+            indicatorColor = indicatorColor,
+            indicatorBackgroundColor = indicatorBackgroundColor,
+        )
+}
+
+/** [VolumePanelRadioButtonBar] content scope. Use [item] to add more items. */
+interface VolumePanelRadioButtonBarScope {
+
+    /**
+     * Adds a single item to the radio button group.
+     *
+     * @param isSelected true when the item is selected and false the otherwise
+     * @param onItemSelected is called when the item is selected
+     * @param icon of the to show in the indicator bar
+     * @param label to show below the indicator bar for the corresponding [icon]
+     */
+    fun item(
+        isSelected: Boolean,
+        onItemSelected: () -> Unit,
+        icon: @Composable RowScope.() -> Unit = Empty,
+        label: @Composable RowScope.() -> Unit = Empty,
+    )
+}
+
+private val Empty: @Composable RowScope.() -> Unit = {}
+
+private class VolumePanelRadioButtonBarScopeImpl : VolumePanelRadioButtonBarScope {
+
+    var hasSelectedItem: Boolean = false
+        private set
+
+    private val mutableItems: MutableList<Item> = mutableListOf()
+    val items: List<Item> = mutableItems
+
+    override fun item(
+        isSelected: Boolean,
+        onItemSelected: () -> Unit,
+        icon: @Composable RowScope.() -> Unit,
+        label: @Composable RowScope.() -> Unit,
+    ) {
+        require(!isSelected || !hasSelectedItem) { "Only one item should be selected at a time" }
+        hasSelectedItem = hasSelectedItem || isSelected
+        mutableItems.add(
+            Item(
+                isSelected = isSelected,
+                onItemSelected = onItemSelected,
+                icon = icon,
+                label = label,
+            )
+        )
+    }
+}
+
+private class Item(
+    val isSelected: Boolean,
+    val onItemSelected: () -> Unit,
+    val icon: @Composable RowScope.() -> Unit,
+    val label: @Composable RowScope.() -> Unit,
+)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
new file mode 100644
index 0000000..da29d58
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.spatialaudio
+
+import com.android.systemui.volume.panel.component.button.ui.composable.ButtonComponent
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
+import com.android.systemui.volume.panel.component.spatial.domain.SpatialAudioAvailabilityCriteria
+import com.android.systemui.volume.panel.component.spatial.ui.viewmodel.SpatialAudioViewModel
+import com.android.systemui.volume.panel.component.spatialaudio.ui.composable.SpatialAudioPopup
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+/** Dagger module, that provides Spatial Audio Volume Panel UI functionality. */
+@Module
+interface SpatialAudioModule {
+
+    @Binds
+    @IntoMap
+    @StringKey(VolumePanelComponents.SPATIAL_AUDIO)
+    fun bindComponentAvailabilityCriteria(
+        criteria: SpatialAudioAvailabilityCriteria
+    ): ComponentAvailabilityCriteria
+
+    companion object {
+
+        @Provides
+        @IntoMap
+        @StringKey(VolumePanelComponents.SPATIAL_AUDIO)
+        fun provideVolumePanelUiComponent(
+            viewModel: SpatialAudioViewModel,
+            popup: SpatialAudioPopup,
+        ): VolumePanelUiComponent = ButtonComponent(viewModel.spatialAudioButton, popup::show)
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
new file mode 100644
index 0000000..bed0ae8
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.spatialaudio.ui.composable
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.common.ui.compose.toColor
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup
+import com.android.systemui.volume.panel.component.selector.ui.composable.VolumePanelRadioButtonBar
+import com.android.systemui.volume.panel.component.spatial.ui.viewmodel.SpatialAudioViewModel
+import javax.inject.Inject
+
+class SpatialAudioPopup
+@Inject
+constructor(
+    private val viewModel: SpatialAudioViewModel,
+    private val volumePanelPopup: VolumePanelPopup,
+) {
+
+    /** Shows a popup with the [expandable] animation. */
+    fun show(expandable: Expandable) {
+        volumePanelPopup.show(expandable, { Title() }, { Content(it) })
+    }
+
+    @Composable
+    private fun Title() {
+        Text(
+            text = stringResource(R.string.volume_panel_spatial_audio_title),
+            style = MaterialTheme.typography.titleMedium,
+            textAlign = TextAlign.Center,
+            maxLines = 1,
+        )
+    }
+
+    @Composable
+    private fun Content(dialog: SystemUIDialog) {
+        val isAvailable by viewModel.isAvailable.collectAsState()
+
+        if (!isAvailable) {
+            SideEffect { dialog.dismiss() }
+            return
+        }
+
+        val enabledModelStates by viewModel.spatialAudioButtonByEnabled.collectAsState()
+        if (enabledModelStates.isEmpty()) {
+            return
+        }
+        VolumePanelRadioButtonBar {
+            for (buttonViewModel in enabledModelStates) {
+                item(
+                    isSelected = buttonViewModel.button.isChecked,
+                    onItemSelected = { viewModel.setEnabled(buttonViewModel.model) },
+                    icon = {
+                        Icon(
+                            icon = buttonViewModel.button.icon,
+                            tint = buttonViewModel.iconColor.toColor(),
+                        )
+                    },
+                    label = {
+                        Text(
+                            text = buttonViewModel.button.label.toString(),
+                            style = MaterialTheme.typography.labelMedium,
+                            color = buttonViewModel.labelColor.toColor(),
+                        )
+                    }
+                )
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index ddc9252..81d2da0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -36,17 +36,12 @@
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.IconButtonDefaults
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.painterResource
@@ -63,14 +58,15 @@
 @Composable
 fun ColumnVolumeSliders(
     viewModels: List<SliderViewModel>,
+    isExpanded: Boolean,
+    onExpandedChanged: (Boolean) -> Unit,
     sliderColors: PlatformSliderColors,
     isExpandable: Boolean,
     modifier: Modifier = Modifier,
 ) {
     require(viewModels.isNotEmpty())
-    var isExpanded: Boolean by remember(isExpandable) { mutableStateOf(!isExpandable) }
     val transition = updateTransition(isExpanded, label = "CollapsableSliders")
-    Column(modifier = modifier.verticalScroll(rememberScrollState())) {
+    Column(modifier = modifier) {
         Row(
             modifier = Modifier.fillMaxWidth(),
             horizontalArrangement = Arrangement.spacedBy(8.dp),
@@ -87,7 +83,7 @@
             if (isExpandable) {
                 ExpandButton(
                     isExpanded = isExpanded,
-                    onExpandedChanged = { isExpanded = it },
+                    onExpandedChanged = onExpandedChanged,
                     sliderColors,
                     Modifier,
                 )
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index 5925b14..3e0aee5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -20,7 +20,9 @@
 import androidx.compose.animation.animateContentSize
 import androidx.compose.animation.expandVertically
 import androidx.compose.animation.shrinkVertically
+import androidx.compose.foundation.basicMarquee
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
@@ -29,6 +31,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
 import com.android.compose.PlatformSlider
 import com.android.compose.PlatformSliderColors
 import com.android.systemui.common.ui.compose.Icon
@@ -41,7 +44,7 @@
     modifier: Modifier = Modifier,
     sliderColors: PlatformSliderColors,
 ) {
-    var value by remember { mutableFloatStateOf(state.value) }
+    var value by remember(state.value) { mutableFloatStateOf(state.value) }
     PlatformSlider(
         modifier = modifier,
         value = value,
@@ -53,13 +56,18 @@
             if (isDragging) {
                 Text(text = value.toInt().toString())
             } else {
-                state.icon?.let { Icon(icon = it) }
+                state.icon?.let { Icon(modifier = Modifier.size(24.dp), icon = it) }
             }
         },
         colors = sliderColors,
         label = {
             Column(modifier = Modifier.animateContentSize()) {
-                Text(state.label, style = MaterialTheme.typography.titleMedium)
+                Text(
+                    modifier = Modifier.basicMarquee(),
+                    text = state.label,
+                    style = MaterialTheme.typography.titleMedium,
+                    maxLines = 1,
+                )
 
                 state.disabledMessage?.let { message ->
                     AnimatedVisibility(
@@ -67,7 +75,12 @@
                         enter = expandVertically { it },
                         exit = shrinkVertically { it },
                     ) {
-                        Text(text = message, style = MaterialTheme.typography.bodySmall)
+                        Text(
+                            modifier = Modifier.basicMarquee(),
+                            text = message,
+                            style = MaterialTheme.typography.bodySmall,
+                            maxLines = 1,
+                        )
                     }
                 }
             }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
index 1ca18de..fdf8ee8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
@@ -48,8 +48,11 @@
                 modifier = modifier.fillMaxWidth(),
             )
         } else {
+            val isExpanded by viewModel.isExpanded.collectAsState()
             ColumnVolumeSliders(
                 viewModels = sliderViewModels,
+                isExpanded = isExpanded,
+                onExpandedChanged = viewModel::onExpandedChanged,
                 sliderColors = PlatformSliderDefaults.defaultPlatformSliderColors(),
                 isExpandable = isPortrait,
                 modifier = modifier.fillMaxWidth(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt
index a838a99..ac5004e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt
@@ -21,6 +21,8 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -35,7 +37,7 @@
     val spacing = 20.dp
     Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(space = spacing)) {
         Column(
-            modifier = Modifier.weight(1f),
+            modifier = Modifier.weight(1f).verticalScroll(rememberScrollState()),
             verticalArrangement = Arrangement.spacedBy(spacing)
         ) {
             for (component in layout.contentComponents) {
@@ -46,7 +48,7 @@
         }
 
         Column(
-            modifier = Modifier.weight(1f),
+            modifier = Modifier.weight(1f).verticalScroll(rememberScrollState()),
             verticalArrangement = Arrangement.spacedBy(space = spacing, alignment = Alignment.Top)
         ) {
             for (component in layout.headerComponents) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
index 4d07379..dd76781 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
@@ -22,6 +22,8 @@
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
@@ -33,7 +35,7 @@
     modifier: Modifier = Modifier,
 ) {
     Column(
-        modifier = modifier,
+        modifier = modifier.verticalScroll(rememberScrollState()),
         verticalArrangement = Arrangement.spacedBy(20.dp),
     ) {
         for (component in layout.headerComponents) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
index 8a9ebc9..910cd5e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
@@ -21,6 +21,8 @@
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.displayCutout
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.heightIn
@@ -36,13 +38,17 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.max
 import com.android.compose.theme.PlatformTheme
 import com.android.systemui.res.R
 import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
+import kotlin.math.max
 
 private val padding = 24.dp
 
@@ -65,12 +71,12 @@
         val components by viewModel.componentsLayout.collectAsState(null)
 
         with(VolumePanelComposeScope(state)) {
-            var boxModifier = modifier.fillMaxSize().clickable(onClick = onDismiss)
-            if (!isPortrait) {
-                boxModifier = boxModifier.padding(horizontal = 48.dp)
-            }
             Box(
-                modifier = boxModifier,
+                modifier =
+                    modifier
+                        .fillMaxSize()
+                        .clickable(onClick = onDismiss)
+                        .volumePanelPaddings(isPortrait = isPortrait),
                 contentAlignment = Alignment.BottomCenter,
             ) {
                 val radius = dimensionResource(R.dimen.volume_panel_corner_radius)
@@ -80,8 +86,8 @@
                             interactionSource = null,
                             indication = null,
                             onClick = {
-                                // prevent windowCloseOnTouchOutside from dismissing when tapped on
-                                // the panel itself.
+                                // prevent windowCloseOnTouchOutside from dismissing when tapped
+                                // on the panel itself.
                             },
                         ),
                     shape = RoundedCornerShape(topStart = radius, topEnd = radius),
@@ -110,7 +116,7 @@
     layout: ComponentsLayout,
     modifier: Modifier = Modifier
 ) {
-    val arrangement =
+    val arrangement: Arrangement.Vertical =
         if (isLargeScreen) {
             Arrangement.spacedBy(20.dp)
         } else {
@@ -120,16 +126,21 @@
         modifier = modifier.widthIn(max = 800.dp),
         verticalArrangement = arrangement,
     ) {
-        val contentModifier = Modifier
         if (isPortrait || isLargeScreen) {
-            VerticalVolumePanelContent(modifier = contentModifier, layout = layout)
-        } else {
-            HorizontalVolumePanelContent(
-                modifier = contentModifier.heightIn(max = 212.dp),
+            VerticalVolumePanelContent(
+                modifier = Modifier.weight(weight = 1f, fill = false),
                 layout = layout
             )
+        } else {
+            HorizontalVolumePanelContent(
+                modifier = Modifier.weight(weight = 1f, fill = false).heightIn(max = 212.dp),
+                layout = layout,
+            )
         }
-        BottomBar(layout = layout, modifier = Modifier)
+        BottomBar(
+            modifier = Modifier,
+            layout = layout,
+        )
     }
 }
 
@@ -149,3 +160,28 @@
         }
     }
 }
+
+/**
+ * Makes sure volume panel stays symmetrically in the middle of the screen while still avoiding
+ * being under the cutouts.
+ */
+@Composable
+private fun Modifier.volumePanelPaddings(isPortrait: Boolean): Modifier {
+    val cutout = WindowInsets.displayCutout
+    return with(LocalDensity.current) {
+        val horizontalCutout =
+            max(
+                cutout.getLeft(density = this, layoutDirection = LocalLayoutDirection.current),
+                cutout.getRight(density = this, layoutDirection = LocalLayoutDirection.current)
+            )
+        val minHorizontalPadding = if (isPortrait) 0.dp else 48.dp
+        val horizontalPadding = max(horizontalCutout.toDp(), minHorizontalPadding)
+
+        padding(
+            start = horizontalPadding,
+            top = cutout.getTop(this).toDp(),
+            end = horizontalPadding,
+            bottom = cutout.getBottom(this).toDp(),
+        )
+    }
+}
diff --git a/packages/SystemUI/compose/features/tests/Android.bp b/packages/SystemUI/compose/features/tests/Android.bp
deleted file mode 100644
index 69b18c4..0000000
--- a/packages/SystemUI/compose/features/tests/Android.bp
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (C) 2022 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
-    // 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"],
-}
-
-// TODO(b/230606318): Make those host tests instead of device tests.
-android_test {
-    name: "SystemUIComposeFeaturesTests",
-    use_resource_processor: true,
-    manifest: "AndroidManifest.xml",
-    test_suites: ["device-tests"],
-    sdk_version: "current",
-    certificate: "platform",
-
-    srcs: [
-        "src/**/*.kt",
-    ],
-
-    static_libs: [
-        "SystemUIComposeFeatures",
-
-        "androidx.test.runner",
-        "androidx.test.ext.junit",
-
-        "androidx.compose.runtime_runtime",
-        "androidx.compose.ui_ui-test-junit4",
-        "androidx.compose.ui_ui-test-manifest",
-    ],
-
-    kotlincflags: ["-Xjvm-default=all"],
-}
diff --git a/packages/SystemUI/compose/features/tests/AndroidManifest.xml b/packages/SystemUI/compose/features/tests/AndroidManifest.xml
deleted file mode 100644
index fc337fb..0000000
--- a/packages/SystemUI/compose/features/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2022 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    package="com.android.systemui.compose.features.tests" >
-
-    <application
-        android:name="android.app.Application"
-        android:appComponentFactory="androidx.core.app.AppComponentFactory"
-        tools:replace="android:name,android:appComponentFactory">
-        <uses-library android:name="android.test.runner" />
-
-        <!-- Disable providers from SystemUI -->
-        <provider android:name="com.android.systemui.keyguard.KeyguardSliceProvider"
-            android:authorities="com.android.systemui.test.keyguard.disabled"
-            android:enabled="false"
-            tools:replace="android:authorities"
-            tools:node="remove" />
-        <provider android:name="com.android.systemui.keyguard.CustomizationProvider"
-            android:authorities="com.android.systemui.test.keyguard.quickaffordance.disabled"
-            android:enabled="false"
-            tools:replace="android:authorities"
-            tools:node="remove" />
-        <provider android:name="com.android.systemui.people.PeopleProvider"
-            android:authorities="com.android.systemui.test.people.disabled"
-            android:enabled="false"
-            tools:replace="android:authorities"
-            tools:node="remove" />
-        <provider android:name="androidx.core.content.FileProvider"
-            android:authorities="com.android.systemui.test.fileprovider.disabled"
-            android:enabled="false"
-            tools:replace="android:authorities"
-            tools:node="remove"/>
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.systemui.compose.features.tests"
-                     android:label="Tests for SystemUIComposeFeatures"/>
-
-</manifest>
\ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/TEST_MAPPING b/packages/SystemUI/compose/scene/TEST_MAPPING
index f644a23..f9424ed 100644
--- a/packages/SystemUI/compose/scene/TEST_MAPPING
+++ b/packages/SystemUI/compose/scene/TEST_MAPPING
@@ -23,17 +23,6 @@
       ]
     },
     {
-      "name": "SystemUIComposeFeaturesTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
-    },
-    {
       "name": "SystemUIComposeGalleryTests",
       "options": [
         {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index b9b472f..6cff30c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -21,6 +21,7 @@
 import androidx.compose.animation.core.SpringSpec
 import kotlin.math.absoluteValue
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.launch
 
 /**
@@ -147,13 +148,16 @@
         }
 
     // Animate the progress to its target value.
-    launch { animatable.animateTo(targetProgress, animationSpec) }
-        .invokeOnCompletion {
-            // Settle the state to Idle(target). Note that this will do nothing if this transition
-            // was replaced/interrupted by another one, and this also runs if this coroutine is
-            // cancelled, i.e. if [this] coroutine scope is cancelled.
-            layoutState.finishTransition(transition, target)
-        }
+    transition.job =
+        launch { animatable.animateTo(targetProgress, animationSpec) }
+            .apply {
+                invokeOnCompletion {
+                    // Settle the state to Idle(target). Note that this will do nothing if this
+                    // transition was replaced/interrupted by another one, and this also runs if
+                    // this coroutine is cancelled, i.e. if [this] coroutine scope is cancelled.
+                    layoutState.finishTransition(transition, target)
+                }
+            }
 
     return transition
 }
@@ -174,8 +178,13 @@
      */
     lateinit var animatable: Animatable<Float, AnimationVector1D>
 
+    /** The job that is animating [animatable]. */
+    lateinit var job: Job
+
     override val progress: Float
         get() = animatable.value
+
+    override fun finish(): Job = job
 }
 
 // TODO(b/290184746): Compute a good default visibility threshold that depends on the layout size
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
similarity index 69%
rename from packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 187d82a..63ec54f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -20,6 +20,7 @@
 
 import android.util.Log
 import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
 import androidx.compose.animation.core.SpringSpec
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.runtime.getValue
@@ -30,50 +31,45 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.round
+import com.android.compose.animation.scene.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
 import com.android.compose.nestedscroll.PriorityNestedScrollConnection
 import kotlin.math.absoluteValue
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.launch
 
-internal class SceneGestureHandler(
+interface DraggableHandler {
+    /**
+     * Start a drag in the given [startedPosition], with the given [overSlop] and number of
+     * [pointersDown].
+     *
+     * The returned [DragController] should be used to continue or stop the drag.
+     */
+    fun onDragStarted(startedPosition: Offset?, overSlop: Float, pointersDown: Int): DragController
+}
+
+/**
+ * The [DragController] provides control over the transition between two scenes through the [onDrag]
+ * and [onStop] methods.
+ */
+interface DragController {
+    /** Drag the current scene by [delta] pixels. */
+    fun onDrag(delta: Float)
+
+    /** Starts a transition to a target scene. */
+    fun onStop(velocity: Float, canChangeScene: Boolean)
+}
+
+internal class DraggableHandlerImpl(
     internal val layoutImpl: SceneTransitionLayoutImpl,
     internal val orientation: Orientation,
-    private val coroutineScope: CoroutineScope,
-) {
-    private val layoutState = layoutImpl.state
-    val draggable: DraggableHandler = SceneDraggableHandler(this)
+    internal val coroutineScope: CoroutineScope,
+) : DraggableHandler {
+    /** The [DraggableHandler] can only have one active [DragController] at a time. */
+    private var dragController: DragControllerImpl? = null
 
-    private var _swipeTransition: SwipeTransition? = null
-    private var swipeTransition: SwipeTransition
-        get() = _swipeTransition ?: error("SwipeTransition needs to be initialized")
-        set(value) {
-            _swipeTransition = value
-        }
-
-    private fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) {
-        if (isDrivingTransition || force) {
-            layoutState.startTransition(newTransition, newTransition.key)
-
-            // Initialize SwipeTransition.transformationSpec and .swipeSpec. Note that this must be
-            // called right after layoutState.startTransition() is called, because it computes the
-            // current layoutState.transformationSpec().
-            val transformationSpec = layoutState.transformationSpec
-            newTransition.transformationSpec = transformationSpec
-            newTransition.swipeSpec =
-                transformationSpec.swipeSpec ?: layoutState.transitions.defaultSwipeSpec
-        } else {
-            // We were not driving the transition and we don't force the update, so the specs won't
-            // be used and it doesn't matter which ones we set here.
-            newTransition.transformationSpec = TransformationSpec.Empty
-            newTransition.swipeSpec = SceneTransitions.DefaultSwipeSpec
-        }
-
-        swipeTransition = newTransition
-    }
-
-    internal val isDrivingTransition
-        get() = layoutState.transitionState == _swipeTransition
+    internal val isDrivingTransition: Boolean
+        get() = dragController?.isDrivingTransition == true
 
     /**
      * The velocity threshold at which the intent of the user is to swipe up or down. It is the same
@@ -86,14 +82,9 @@
      * 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
+    internal val positionalThreshold
         get() = with(layoutImpl.density) { 56.dp.toPx() }
 
-    internal var currentSource: Any? = null
-
-    /** The [Swipes] associated to the current gesture. */
-    private var swipes: Swipes? = null
-
     /**
      * Whether we should immediately intercept a gesture.
      *
@@ -102,7 +93,15 @@
      */
     internal fun shouldImmediatelyIntercept(startedPosition: Offset?): Boolean {
         // We don't intercept the touch if we are not currently driving the transition.
-        if (!isDrivingTransition) {
+        val dragController = dragController
+        if (dragController?.isDrivingTransition != true) {
+            return false
+        }
+
+        val swipeTransition = dragController.swipeTransition
+
+        // Don't intercept a transition that is finishing.
+        if (swipeTransition.isFinishing) {
             return false
         }
 
@@ -110,27 +109,42 @@
         // between the same pair of scenes.
         val fromScene = swipeTransition._currentScene
         val swipes = computeSwipes(fromScene, startedPosition, pointersDown = 1)
-        val (upOrLeft, downOrRight) = computeSwipesResults(fromScene, swipes)
+        val (upOrLeft, downOrRight) = swipes.computeSwipesResults(fromScene)
         return (upOrLeft != null &&
             swipeTransition.isTransitioningBetween(fromScene.key, upOrLeft.toScene)) ||
             (downOrRight != null &&
                 swipeTransition.isTransitioningBetween(fromScene.key, downOrRight.toScene))
     }
 
-    internal fun onDragStarted(pointersDown: Int, startedPosition: Offset?, overSlop: Float) {
+    override fun onDragStarted(
+        startedPosition: Offset?,
+        overSlop: Float,
+        pointersDown: Int,
+    ): DragController {
         if (overSlop == 0f) {
-            check(isDrivingTransition) {
-                "onDragStarted() called while isDrivingTransition=false overSlop=0f"
+            val oldDragController = dragController
+            check(oldDragController != null && oldDragController.isDrivingTransition) {
+                val isActive = oldDragController?.isDrivingTransition
+                "onDragStarted(overSlop=0f) requires an active dragController, but was $isActive"
             }
 
             // This [transition] was already driving the animation: simply take over it.
             // Stop animating and start from where the current offset.
-            swipeTransition.cancelOffsetAnimation()
-            swipes!!.updateSwipesResults(swipeTransition._fromScene)
-            return
+            oldDragController.swipeTransition.cancelOffsetAnimation()
+
+            // We need to recompute the swipe results since this is a new gesture, and the
+            // fromScene.userActions may have changed.
+            val swipes = oldDragController.swipes
+            swipes.updateSwipesResults(oldDragController.swipeTransition._fromScene)
+
+            // A new gesture should always create a new SwipeTransition. This way there cannot be
+            // different gestures controlling the same transition.
+            val swipeTransition = SwipeTransition(oldDragController.swipeTransition)
+            swipes.updateSwipesResults(fromScene = swipeTransition._fromScene)
+            return updateDragController(swipes, swipeTransition)
         }
 
-        val transitionState = layoutState.transitionState
+        val transitionState = layoutImpl.state.transitionState
         if (transitionState is TransitionState.Transition) {
             // TODO(b/290184746): Better handle interruptions here if state != idle.
             Log.w(
@@ -142,24 +156,36 @@
         }
 
         val fromScene = layoutImpl.scene(transitionState.currentScene)
-        val newSwipes = computeSwipes(fromScene, startedPosition, pointersDown)
-        swipes = newSwipes
-        val result = newSwipes.findUserActionResult(fromScene, overSlop, true)
+        val swipes = computeSwipes(fromScene, startedPosition, pointersDown)
+        val result =
+            swipes.findUserActionResult(fromScene, overSlop, true)
+            // As we were unable to locate a valid target scene, the initial SwipeTransition
+            // cannot be defined. Consequently, a simple NoOp Controller will be returned.
+            ?: return NoOpDragController
 
-        // As we were unable to locate a valid target scene, the initial SwipeTransition cannot be
-        // defined.
-        if (result == null) return
+        return updateDragController(
+            swipes = swipes,
+            swipeTransition =
+                SwipeTransition(
+                    layoutImpl.state,
+                    coroutineScope,
+                    fromScene,
+                    result,
+                    swipes,
+                    layoutImpl,
+                    orientation,
+                )
+        )
+    }
 
-        val newSwipeTransition =
-            SwipeTransition(
-                fromScene = fromScene,
-                result = result,
-                swipes = newSwipes,
-                layoutImpl = layoutImpl,
-                orientation = orientation
-            )
-
-        updateTransition(newSwipeTransition, force = true)
+    private fun updateDragController(
+        swipes: Swipes,
+        swipeTransition: SwipeTransition
+    ): DragController {
+        val newDragController = DragControllerImpl(this, swipes, swipeTransition)
+        newDragController.updateTransition(swipeTransition, force = true)
+        dragController = newDragController
+        return newDragController
     }
 
     private fun computeSwipes(
@@ -216,8 +242,59 @@
         }
     }
 
-    internal fun onDrag(delta: Float) {
-        if (delta == 0f || !isDrivingTransition) return
+    companion object {
+        private const val TAG = "DraggableHandlerImpl"
+    }
+}
+
+/** @param swipes The [Swipes] associated to the current gesture. */
+private class DragControllerImpl(
+    private val draggableHandler: DraggableHandlerImpl,
+    val swipes: Swipes,
+    var swipeTransition: SwipeTransition,
+) : DragController {
+    val layoutState = draggableHandler.layoutImpl.state
+
+    /**
+     * Whether this handle is active. If this returns false, calling [onDrag] and [onStop] will do
+     * nothing. We should have only one active controller at a time
+     */
+    val isDrivingTransition: Boolean
+        get() = layoutState.transitionState == swipeTransition
+
+    init {
+        check(!isDrivingTransition) { "Multiple controllers with the same SwipeTransition" }
+    }
+
+    fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) {
+        if (isDrivingTransition || force) {
+            layoutState.startTransition(newTransition, newTransition.key)
+
+            // Initialize SwipeTransition.transformationSpec and .swipeSpec. Note that this must be
+            // called right after layoutState.startTransition() is called, because it computes the
+            // current layoutState.transformationSpec().
+            val transformationSpec = layoutState.transformationSpec
+            newTransition.transformationSpec = transformationSpec
+            newTransition.swipeSpec =
+                transformationSpec.swipeSpec ?: layoutState.transitions.defaultSwipeSpec
+        } else {
+            // We were not driving the transition and we don't force the update, so the specs won't
+            // be used and it doesn't matter which ones we set here.
+            newTransition.transformationSpec = TransformationSpec.Empty
+            newTransition.swipeSpec = SceneTransitions.DefaultSwipeSpec
+        }
+
+        swipeTransition = newTransition
+    }
+
+    /**
+     * We receive a [delta] that can be consumed to change the offset of the current
+     * [SwipeTransition].
+     *
+     * @return the consumed delta
+     */
+    override fun onDrag(delta: Float) {
+        if (delta == 0f || !isDrivingTransition || swipeTransition.isFinishing) return
         swipeTransition.dragOffset += delta
 
         val (fromScene, acceleratedOffset) =
@@ -225,14 +302,14 @@
 
         val isNewFromScene = fromScene.key != swipeTransition.fromScene
         val result =
-            swipes!!.findUserActionResult(
+            swipes.findUserActionResult(
                 fromScene = fromScene,
                 directionOffset = swipeTransition.dragOffset,
                 updateSwipesResults = isNewFromScene
             )
 
         if (result == null) {
-            onDragStopped(velocity = delta, canChangeScene = true)
+            onStop(velocity = delta, canChangeScene = true)
             return
         }
 
@@ -243,36 +320,22 @@
                 result.toScene != swipeTransition.toScene ||
                 result.transitionKey != swipeTransition.key
         ) {
-            val newSwipeTransition =
+            val swipeTransition =
                 SwipeTransition(
+                        layoutState = layoutState,
+                        coroutineScope = draggableHandler.coroutineScope,
                         fromScene = fromScene,
                         result = result,
-                        swipes = swipes!!,
-                        layoutImpl = layoutImpl,
-                        orientation = orientation
+                        swipes = swipes,
+                        layoutImpl = draggableHandler.layoutImpl,
+                        orientation = draggableHandler.orientation,
                     )
                     .apply { dragOffset = swipeTransition.dragOffset }
 
-            updateTransition(newSwipeTransition)
+            updateTransition(swipeTransition)
         }
     }
 
-    private fun computeSwipesResults(
-        fromScene: Scene,
-        swipes: Swipes
-    ): Pair<UserActionResult?, UserActionResult?> {
-        val userActions = fromScene.userActions
-        fun sceneToSwipePair(swipe: Swipe?): UserActionResult? {
-            return userActions[swipe ?: return null]
-        }
-
-        val upOrLeftResult =
-            sceneToSwipePair(swipes.upOrLeft) ?: sceneToSwipePair(swipes.upOrLeftNoSource)
-        val downOrRightResult =
-            sceneToSwipePair(swipes.downOrRight) ?: sceneToSwipePair(swipes.downOrRightNoSource)
-        return Pair(upOrLeftResult, downOrRightResult)
-    }
-
     /**
      * 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.
@@ -291,10 +354,7 @@
 
         // If the swipe was not committed or if the swipe distance is not computed yet, don't do
         // anything.
-        if (
-            swipeTransition._currentScene != toScene ||
-                distance == SwipeTransition.DistanceUnspecified
-        ) {
+        if (swipeTransition._currentScene != toScene || distance == DistanceUnspecified) {
             return fromScene to 0f
         }
 
@@ -302,20 +362,18 @@
         // to the next screen or go back to the previous one.
         val offset = swipeTransition.dragOffset
         val absoluteDistance = distance.absoluteValue
-        return if (offset <= -absoluteDistance && swipes!!.upOrLeftResult?.toScene == toScene.key) {
+        return if (offset <= -absoluteDistance && swipes.upOrLeftResult?.toScene == toScene.key) {
             toScene to absoluteDistance
-        } else if (
-            offset >= absoluteDistance && swipes!!.downOrRightResult?.toScene == toScene.key
-        ) {
+        } else if (offset >= absoluteDistance && swipes.downOrRightResult?.toScene == toScene.key) {
             toScene to -absoluteDistance
         } else {
             fromScene to 0f
         }
     }
 
-    internal fun onDragStopped(velocity: Float, canChangeScene: Boolean) {
+    override fun onStop(velocity: Float, canChangeScene: Boolean) {
         // The state was changed since the drag started; don't do anything.
-        if (!isDrivingTransition) {
+        if (!isDrivingTransition || swipeTransition.isFinishing) {
             return
         }
 
@@ -332,16 +390,16 @@
             // immediately go back B => A.
             if (targetScene != swipeTransition._currentScene) {
                 swipeTransition._currentScene = targetScene
-                with(layoutImpl.state) { coroutineScope.onChangeScene(targetScene.key) }
+                with(draggableHandler.layoutImpl.state) {
+                    draggableHandler.coroutineScope.onChangeScene(targetScene.key)
+                }
             }
 
             swipeTransition.animateOffset(
-                coroutineScope = coroutineScope,
+                coroutineScope = draggableHandler.coroutineScope,
                 initialVelocity = velocity,
                 targetOffset = targetOffset,
-                onAnimationCompleted = {
-                    layoutState.finishTransition(swipeTransition, idleScene = targetScene.key)
-                }
+                targetScene = targetScene.key,
             )
         }
 
@@ -358,7 +416,7 @@
             var targetScene: Scene
             var targetOffset: Float
             if (
-                distance != SwipeTransition.DistanceUnspecified &&
+                distance != DistanceUnspecified &&
                     shouldCommitSwipe(
                         offset,
                         distance,
@@ -384,8 +442,8 @@
                     if (targetScene == fromScene) {
                         0f
                     } else {
-                        check(distance != SwipeTransition.DistanceUnspecified) {
-                            "distance is equal to ${SwipeTransition.DistanceUnspecified}"
+                        check(distance != DistanceUnspecified) {
+                            "distance is equal to $DistanceUnspecified"
                         }
                         distance
                     }
@@ -400,20 +458,22 @@
 
             if (startFromIdlePosition) {
                 // If there is a target scene, we start the overscroll animation.
-                val result = swipes!!.findUserActionResultStrict(velocity)
+                val result = swipes.findUserActionResultStrict(velocity)
                 if (result == null) {
                     // We will not animate
-                    layoutState.finishTransition(swipeTransition, idleScene = fromScene.key)
+                    swipeTransition.snapToScene(fromScene.key)
                     return
                 }
 
                 val newSwipeTransition =
                     SwipeTransition(
+                            layoutState = layoutState,
+                            coroutineScope = draggableHandler.coroutineScope,
                             fromScene = fromScene,
                             result = result,
-                            swipes = swipes!!,
-                            layoutImpl = layoutImpl,
-                            orientation = orientation
+                            swipes = swipes,
+                            layoutImpl = draggableHandler.layoutImpl,
+                            orientation = draggableHandler.orientation,
                         )
                         .apply { _currentScene = swipeTransition._currentScene }
 
@@ -440,6 +500,9 @@
             return (offset - distance).absoluteValue < offset.absoluteValue
         }
 
+        val velocityThreshold = draggableHandler.velocityThreshold
+        val positionalThreshold = draggableHandler.positionalThreshold
+
         // Swiping up or left.
         if (distance < 0f) {
             return if (offset > 0f || velocity >= velocityThreshold) {
@@ -460,13 +523,11 @@
                 isCloserToTarget()
         }
     }
-
-    companion object {
-        private const val TAG = "SceneGestureHandler"
-    }
 }
 
 private fun SwipeTransition(
+    layoutState: BaseSceneTransitionLayoutState,
+    coroutineScope: CoroutineScope,
     fromScene: Scene,
     result: UserActionResult,
     swipes: Swipes,
@@ -483,6 +544,8 @@
         }
 
     return SwipeTransition(
+        layoutState = layoutState,
+        coroutineScope = coroutineScope,
         key = result.transitionKey,
         _fromScene = fromScene,
         _toScene = layoutImpl.scene(result.toScene),
@@ -492,11 +555,30 @@
     )
 }
 
+private fun SwipeTransition(old: SwipeTransition): SwipeTransition {
+    return SwipeTransition(
+            layoutState = old.layoutState,
+            coroutineScope = old.coroutineScope,
+            key = old.key,
+            _fromScene = old._fromScene,
+            _toScene = old._toScene,
+            userActionDistanceScope = old.userActionDistanceScope,
+            orientation = old.orientation,
+            isUpOrLeft = old.isUpOrLeft
+        )
+        .apply {
+            _currentScene = old._currentScene
+            dragOffset = old.dragOffset
+        }
+}
+
 private class SwipeTransition(
+    val layoutState: BaseSceneTransitionLayoutState,
+    val coroutineScope: CoroutineScope,
     val key: TransitionKey?,
     val _fromScene: Scene,
     val _toScene: Scene,
-    private val userActionDistanceScope: UserActionDistanceScope,
+    val userActionDistanceScope: UserActionDistanceScope,
     override val orientation: Orientation,
     override val isUpOrLeft: Boolean,
 ) :
@@ -511,7 +593,7 @@
             // Important: If we are going to return early because distance is equal to 0, we should
             // still make sure we read the offset before returning so that the calling code still
             // subscribes to the offset value.
-            val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
+            val offset = offsetAnimation?.animatable?.value ?: dragOffset
 
             val distance = distance()
             if (distance == DistanceUnspecified) {
@@ -526,20 +608,11 @@
     /** 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 offset animation that animates the offset once the user lifts their finger. */
+    private var offsetAnimation: OffsetAnimation? by mutableStateOf(null)
 
-    // If we are not animating offset, it means the offset is being driven by the user's finger.
     override val isUserInputOngoing: Boolean
-        get() = !isAnimatingOffset
-
-    /** 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
+        get() = offsetAnimation == null
 
     /**
      * The [TransformationSpecImpl] associated to this transition.
@@ -553,8 +626,18 @@
     /** The spec to use when animating this transition to either [fromScene] or [toScene]. */
     lateinit var swipeSpec: SpringSpec<Float>
 
+    override val overscrollScope: OverscrollScope =
+        object : OverscrollScope {
+            override val absoluteDistance: Float
+                get() = distance().absoluteValue
+        }
+
     private var lastDistance = DistanceUnspecified
 
+    /** Whether [TransitionState.Transition.finish] was called on this transition. */
+    var isFinishing = false
+        private set
+
     /**
      * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is above
      * or to the left of [toScene].
@@ -585,25 +668,21 @@
         return distance
     }
 
-    /** Ends any previous [offsetAnimationJob] and runs the new [job]. */
-    private fun startOffsetAnimation(job: () -> Job) {
+    /** Ends any previous [offsetAnimation] and runs the new [animation]. */
+    private fun startOffsetAnimation(animation: () -> OffsetAnimation): OffsetAnimation {
         cancelOffsetAnimation()
-        offsetAnimationJob = job()
+        return animation().also { offsetAnimation = it }
     }
 
     /** Cancel any ongoing offset animation. */
     // TODO(b/317063114) This should be a suspended function to avoid multiple jobs running at
     // the same time.
     fun cancelOffsetAnimation() {
-        offsetAnimationJob?.cancel()
-        finishOffsetAnimation()
-    }
+        val animation = offsetAnimation ?: return
+        offsetAnimation = null
 
-    fun finishOffsetAnimation() {
-        if (isAnimatingOffset) {
-            isAnimatingOffset = false
-            dragOffset = offsetAnimatable.value
-        }
+        dragOffset = animation.animatable.value
+        animation.job.cancel()
     }
 
     fun animateOffset(
@@ -611,35 +690,73 @@
         coroutineScope: CoroutineScope,
         initialVelocity: Float,
         targetOffset: Float,
-        onAnimationCompleted: () -> Unit,
-    ) {
-        startOffsetAnimation {
-            coroutineScope.launch {
-                animateOffset(targetOffset, initialVelocity)
-                onAnimationCompleted()
+        targetScene: SceneKey,
+    ): OffsetAnimation {
+        return startOffsetAnimation {
+            val animatable = Animatable(dragOffset, OffsetVisibilityThreshold)
+            val job =
+                coroutineScope
+                    .launch {
+                        animatable.animateTo(
+                            targetValue = targetOffset,
+                            animationSpec = swipeSpec,
+                            initialVelocity = initialVelocity,
+                        )
+                    }
+                    // Make sure that we settle to target scene at the end of the animation or if
+                    // the animation is cancelled.
+                    .apply { invokeOnCompletion { snapToScene(targetScene) } }
+
+            OffsetAnimation(animatable, job)
+        }
+    }
+
+    fun snapToScene(scene: SceneKey) {
+        if (layoutState.transitionState != this) return
+        cancelOffsetAnimation()
+        layoutState.finishTransition(this, idleScene = scene)
+    }
+
+    override fun finish(): Job {
+        if (isFinishing) return requireNotNull(offsetAnimation).job
+        isFinishing = true
+
+        // If we were already animating the offset, simply return the job.
+        offsetAnimation?.let {
+            return it.job
+        }
+
+        // Animate to the current scene.
+        val targetScene = currentScene
+        val targetOffset =
+            if (targetScene == fromScene) {
+                0f
+            } else {
+                val distance = distance()
+                check(distance != DistanceUnspecified) {
+                    "targetScene != fromScene but distance is unspecified"
+                }
+                distance
             }
-        }
+
+        val animation =
+            animateOffset(
+                coroutineScope = coroutineScope,
+                initialVelocity = 0f,
+                targetOffset = targetOffset,
+                targetScene = currentScene,
+            )
+        check(offsetAnimation == animation)
+        return animation.job
     }
 
-    private suspend fun animateOffset(targetOffset: Float, initialVelocity: Float) {
-        if (!isAnimatingOffset) {
-            offsetAnimatable.snapTo(dragOffset)
-        }
-        isAnimatingOffset = true
+    internal class OffsetAnimation(
+        /** The animatable used to animate the offset. */
+        val animatable: Animatable<Float, AnimationVector1D>,
 
-        val animationSpec = transformationSpec
-        offsetAnimatable.animateTo(
-            targetValue = targetOffset,
-            animationSpec = swipeSpec,
-            initialVelocity = initialVelocity,
-        )
-
-        finishOffsetAnimation()
-    }
-
-    companion object {
-        const val DistanceUnspecified = 0f
-    }
+        /** The job in which [animatable] is animated. */
+        val job: Job,
+    )
 }
 
 private object DefaultSwipeDistance : UserActionDistance {
@@ -730,40 +847,16 @@
     }
 }
 
-private class SceneDraggableHandler(
-    private val gestureHandler: SceneGestureHandler,
-) : DraggableHandler {
-    private val source = this
-
-    override fun onDragStarted(startedPosition: Offset, overSlop: Float, pointersDown: Int) {
-        gestureHandler.currentSource = source
-        gestureHandler.onDragStarted(pointersDown, startedPosition, overSlop)
-    }
-
-    override fun onDelta(pixels: Float) {
-        if (gestureHandler.currentSource == source) {
-            gestureHandler.onDrag(delta = pixels)
-        }
-    }
-
-    override fun onDragStopped(velocity: Float) {
-        if (gestureHandler.currentSource == source) {
-            gestureHandler.currentSource = null
-            gestureHandler.onDragStopped(velocity = velocity, canChangeScene = true)
-        }
-    }
-}
-
-internal class SceneNestedScrollHandler(
+internal class NestedScrollHandlerImpl(
     private val layoutImpl: SceneTransitionLayoutImpl,
     private val orientation: Orientation,
     private val topOrLeftBehavior: NestedScrollBehavior,
     private val bottomOrRightBehavior: NestedScrollBehavior,
-) : NestedScrollHandler {
+) {
     private val layoutState = layoutImpl.state
-    private val gestureHandler = layoutImpl.gestureHandler(orientation)
+    private val draggableHandler = layoutImpl.draggableHandler(orientation)
 
-    override val connection: PriorityNestedScrollConnection = nestedScrollConnection()
+    val connection: PriorityNestedScrollConnection = nestedScrollConnection()
 
     private fun nestedScrollConnection(): PriorityNestedScrollConnection {
         // If we performed a long gesture before entering priority mode, we would have to avoid
@@ -808,7 +901,7 @@
             return overscrollSpec != null
         }
 
-        val source = this
+        var dragController: DragController? = null
         var isIntercepting = false
 
         return PriorityNestedScrollConnection(
@@ -819,7 +912,7 @@
                 val canInterceptSwipeTransition =
                     canChangeScene &&
                         offsetAvailable != 0f &&
-                        gestureHandler.shouldImmediatelyIntercept(startedPosition = null)
+                        draggableHandler.shouldImmediatelyIntercept(startedPosition = null)
                 if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false
 
                 val threshold = layoutImpl.transitionInterceptionThreshold
@@ -893,34 +986,28 @@
             canContinueScroll = { true },
             canScrollOnFling = false,
             onStart = { offsetAvailable ->
-                gestureHandler.currentSource = source
-                gestureHandler.onDragStarted(
-                    pointersDown = 1,
-                    startedPosition = null,
-                    overSlop = if (isIntercepting) 0f else offsetAvailable,
-                )
+                dragController =
+                    draggableHandler.onDragStarted(
+                        pointersDown = 1,
+                        startedPosition = null,
+                        overSlop = if (isIntercepting) 0f else offsetAvailable,
+                    )
             },
             onScroll = { offsetAvailable ->
-                if (gestureHandler.currentSource != source) {
-                    return@PriorityNestedScrollConnection 0f
-                }
+                val controller = dragController ?: error("Should be called after onStart")
 
                 // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture is
                 // initiated in a nested child.
-                gestureHandler.onDrag(offsetAvailable)
+                controller.onDrag(delta = offsetAvailable)
 
                 offsetAvailable
             },
             onStop = { velocityAvailable ->
-                if (gestureHandler.currentSource != source) {
-                    return@PriorityNestedScrollConnection 0f
-                }
+                val controller = dragController ?: error("Should be called after onStart")
 
-                gestureHandler.onDragStopped(
-                    velocity = velocityAvailable,
-                    canChangeScene = canChangeScene
-                )
+                controller.onStop(velocity = velocityAvailable, canChangeScene = canChangeScene)
 
+                dragController = null
                 // The onDragStopped animation consumes any remaining velocity.
                 velocityAvailable
             },
@@ -935,3 +1022,9 @@
 // TODO(b/290184746): Have a better default visibility threshold which takes the swipe distance into
 // account instead.
 internal const val OffsetVisibilityThreshold = 0.5f
+
+private object NoOpDragController : DragController {
+    override fun onDrag(delta: Float) {}
+
+    override fun onStop(velocity: Float, canChangeScene: Boolean) {}
+}
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
deleted file mode 100644
index 58052cd..0000000
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.android.compose.animation.scene
-
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
-
-interface DraggableHandler {
-    fun onDragStarted(startedPosition: Offset, overSlop: Float, pointersDown: Int = 1)
-    fun onDelta(pixels: Float)
-    fun onDragStopped(velocity: Float)
-}
-
-interface NestedScrollHandler {
-    val connection: NestedScrollConnection
-}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index cdc4778..be066fd 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -133,18 +133,14 @@
         if (shouldComposeMovableElement) {
             val movableContent: MovableElementContent =
                 layoutImpl.movableContents[element]
-                    ?: movableContentOf {
-                            contentScope: MovableElementContentScope,
-                            content: @Composable MovableElementContentScope.() -> Unit ->
-                            contentScope.content()
-                        }
+                    ?: movableContentOf { content: @Composable () -> Unit -> content() }
                         .also { layoutImpl.movableContents[element] = it }
 
             // Important: Don't introduce any parent Box or other layout here, because contentScope
             // delegates its BoxScope implementation to the Box where this content() function is
             // called, so it's important that this movableContent is composed directly under that
             // Box.
-            movableContent(contentScope, content)
+            movableContent { contentScope.content() }
         } else {
             // If we are not composed, we still need to lay out an empty space with the same *target
             // size* as its movable content, i.e. the same *size when idle*. During transitions,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 3ff869b..05dd5cc 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -24,7 +24,6 @@
 import androidx.compose.foundation.gestures.horizontalDrag
 import androidx.compose.foundation.gestures.verticalDrag
 import androidx.compose.runtime.Stable
-import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.pointer.PointerEvent
@@ -33,7 +32,6 @@
 import androidx.compose.ui.input.pointer.PointerInputChange
 import androidx.compose.ui.input.pointer.PointerInputScope
 import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
-import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.input.pointer.positionChange
 import androidx.compose.ui.input.pointer.util.VelocityTracker
 import androidx.compose.ui.input.pointer.util.addPointerInputChange
@@ -69,9 +67,7 @@
     orientation: Orientation,
     enabled: () -> Boolean,
     startDragImmediately: (startedPosition: Offset) -> Boolean,
-    onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
-    onDragDelta: (delta: Float) -> Unit,
-    onDragStopped: (velocity: Float) -> Unit,
+    onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
 ): Modifier =
     this.then(
         MultiPointerDraggableElement(
@@ -79,8 +75,6 @@
             enabled,
             startDragImmediately,
             onDragStarted,
-            onDragDelta,
-            onDragStopped,
         )
     )
 
@@ -89,9 +83,7 @@
     private val enabled: () -> Boolean,
     private val startDragImmediately: (startedPosition: Offset) -> Boolean,
     private val onDragStarted:
-        (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
-    private val onDragDelta: (Float) -> Unit,
-    private val onDragStopped: (velocity: Float) -> Unit,
+        (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
 ) : ModifierNodeElement<MultiPointerDraggableNode>() {
     override fun create(): MultiPointerDraggableNode =
         MultiPointerDraggableNode(
@@ -99,8 +91,6 @@
             enabled = enabled,
             startDragImmediately = startDragImmediately,
             onDragStarted = onDragStarted,
-            onDragDelta = onDragDelta,
-            onDragStopped = onDragStopped,
         )
 
     override fun update(node: MultiPointerDraggableNode) {
@@ -108,8 +98,6 @@
         node.enabled = enabled
         node.startDragImmediately = startDragImmediately
         node.onDragStarted = onDragStarted
-        node.onDragDelta = onDragDelta
-        node.onDragStopped = onDragStopped
     }
 }
 
@@ -117,9 +105,8 @@
     orientation: Orientation,
     enabled: () -> Boolean,
     var startDragImmediately: (startedPosition: Offset) -> Boolean,
-    var onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
-    var onDragDelta: (Float) -> Unit,
-    var onDragStopped: (velocity: Float) -> Unit,
+    var onDragStarted:
+        (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
 ) :
     PointerInputModifierNode,
     DelegatingNode(),
@@ -176,40 +163,33 @@
             return
         }
 
-        val onDragStart: (Offset, Float, Int) -> Unit = { startedPosition, overSlop, pointersDown ->
-            velocityTracker.resetTracking()
-            onDragStarted(startedPosition, overSlop, pointersDown)
-        }
-
-        val onDragCancel: () -> Unit = { onDragStopped(/* velocity= */ 0f) }
-
-        val onDragEnd: () -> Unit = {
-            val maxFlingVelocity =
-                currentValueOf(LocalViewConfiguration).maximumFlingVelocity.let { max ->
-                    Velocity(max, max)
-                }
-
-            val velocity = velocityTracker.calculateVelocity(maxFlingVelocity)
-            onDragStopped(
-                when (orientation) {
-                    Orientation.Horizontal -> velocity.x
-                    Orientation.Vertical -> velocity.y
-                }
-            )
-        }
-
-        val onDrag: (change: PointerInputChange, dragAmount: Float) -> Unit = { change, amount ->
-            velocityTracker.addPointerInputChange(change)
-            onDragDelta(amount)
-        }
-
         detectDragGestures(
             orientation = orientation,
             startDragImmediately = startDragImmediately,
-            onDragStart = onDragStart,
-            onDragEnd = onDragEnd,
-            onDragCancel = onDragCancel,
-            onDrag = onDrag,
+            onDragStart = { startedPosition, overSlop, pointersDown ->
+                velocityTracker.resetTracking()
+                onDragStarted(startedPosition, overSlop, pointersDown)
+            },
+            onDrag = { controller, change, amount ->
+                velocityTracker.addPointerInputChange(change)
+                controller.onDrag(amount)
+            },
+            onDragEnd = { controller ->
+                val viewConfiguration = currentValueOf(LocalViewConfiguration)
+                val maxVelocity = viewConfiguration.maximumFlingVelocity.let { Velocity(it, it) }
+                val velocity = velocityTracker.calculateVelocity(maxVelocity)
+                controller.onStop(
+                    velocity =
+                        when (orientation) {
+                            Orientation.Horizontal -> velocity.x
+                            Orientation.Vertical -> velocity.y
+                        },
+                    canChangeScene = true,
+                )
+            },
+            onDragCancel = { controller ->
+                controller.onStop(velocity = 0f, canChangeScene = true)
+            },
         )
     }
 }
@@ -225,10 +205,10 @@
 private suspend fun PointerInputScope.detectDragGestures(
     orientation: Orientation,
     startDragImmediately: (startedPosition: Offset) -> Boolean,
-    onDragStart: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
-    onDragEnd: () -> Unit,
-    onDragCancel: () -> Unit,
-    onDrag: (change: PointerInputChange, dragAmount: Float) -> Unit,
+    onDragStart: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+    onDragEnd: (controller: DragController) -> Unit,
+    onDragCancel: (controller: DragController) -> Unit,
+    onDrag: (controller: DragController, change: PointerInputChange, dragAmount: Float) -> Unit,
 ) {
     awaitEachGesture {
         val initialDown = awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Initial)
@@ -282,34 +262,34 @@
                 }
             }
 
-            onDragStart(drag.position, overSlop, pressed.size)
+            val controller = onDragStart(drag.position, overSlop, pressed.size)
 
             val successful: Boolean
             try {
-                onDrag(drag, overSlop)
+                onDrag(controller, drag, overSlop)
 
                 successful =
                     when (orientation) {
                         Orientation.Horizontal ->
                             horizontalDrag(drag.id) {
-                                onDrag(it, it.positionChange().x)
+                                onDrag(controller, it, it.positionChange().x)
                                 it.consume()
                             }
                         Orientation.Vertical ->
                             verticalDrag(drag.id) {
-                                onDrag(it, it.positionChange().y)
+                                onDrag(controller, it, it.positionChange().y)
                                 it.consume()
                             }
                     }
             } catch (t: Throwable) {
-                onDragCancel()
+                onDragCancel(controller)
                 throw t
             }
 
             if (successful) {
-                onDragEnd()
+                onDragEnd(controller)
             } else {
-                onDragCancel()
+                onDragCancel(controller)
             }
         }
     }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
index e78f326..5a2f85a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
@@ -178,7 +178,7 @@
     topOrLeftBehavior: NestedScrollBehavior,
     bottomOrRightBehavior: NestedScrollBehavior,
 ) =
-    SceneNestedScrollHandler(
+    NestedScrollHandlerImpl(
             layoutImpl = layoutImpl,
             orientation = orientation,
             topOrLeftBehavior = topOrLeftBehavior,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 1e3842a..b7e2dd1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -391,7 +391,7 @@
 }
 
 /** The result of performing a [UserAction]. */
-class UserActionResult(
+data class UserActionResult(
     /** The scene we should be transitioning to during the [UserAction]. */
     val toScene: SceneKey,
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 3093d47..25b0895 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -38,15 +38,8 @@
 import com.android.compose.ui.util.lerp
 import kotlinx.coroutines.CoroutineScope
 
-/**
- * The type for the content of movable elements.
- *
- * TODO(b/317972419): Revert back to make this movable content have a single @Composable lambda
- *   parameter.
- */
-internal typealias MovableElementContent =
-    @Composable
-    (MovableElementContentScope, @Composable MovableElementContentScope.() -> Unit) -> Unit
+/** The type for the content of movable elements. */
+internal typealias MovableElementContent = @Composable (@Composable () -> Unit) -> Unit
 
 @Stable
 internal class SceneTransitionLayoutImpl(
@@ -102,8 +95,8 @@
                     .also { _sharedValues = it }
 
     // TODO(b/317958526): Lazily allocate scene gesture handlers the first time they are needed.
-    private val horizontalGestureHandler: SceneGestureHandler
-    private val verticalGestureHandler: SceneGestureHandler
+    private val horizontalDraggableHandler: DraggableHandlerImpl
+    private val verticalDraggableHandler: DraggableHandlerImpl
 
     private var _userActionDistanceScope: UserActionDistanceScope? = null
     internal val userActionDistanceScope: UserActionDistanceScope
@@ -116,27 +109,27 @@
     init {
         updateScenes(builder)
 
-        // SceneGestureHandler must wait for the scenes to be initialized, in order to access the
+        // DraggableHandlerImpl must wait for the scenes to be initialized, in order to access the
         // current scene (required for SwipeTransition).
-        horizontalGestureHandler =
-            SceneGestureHandler(
+        horizontalDraggableHandler =
+            DraggableHandlerImpl(
                 layoutImpl = this,
                 orientation = Orientation.Horizontal,
                 coroutineScope = coroutineScope,
             )
 
-        verticalGestureHandler =
-            SceneGestureHandler(
+        verticalDraggableHandler =
+            DraggableHandlerImpl(
                 layoutImpl = this,
                 orientation = Orientation.Vertical,
                 coroutineScope = coroutineScope,
             )
     }
 
-    internal fun gestureHandler(orientation: Orientation): SceneGestureHandler =
+    internal fun draggableHandler(orientation: Orientation): DraggableHandlerImpl =
         when (orientation) {
-            Orientation.Vertical -> verticalGestureHandler
-            Orientation.Horizontal -> horizontalGestureHandler
+            Orientation.Vertical -> verticalDraggableHandler
+            Orientation.Horizontal -> horizontalDraggableHandler
         }
 
     internal fun scene(key: SceneKey): Scene {
@@ -192,8 +185,8 @@
                 // Handle horizontal and vertical swipes on this layout.
                 // Note: order here is important and will give a slight priority to the vertical
                 // swipes.
-                .swipeToScene(horizontalGestureHandler)
-                .swipeToScene(verticalGestureHandler)
+                .swipeToScene(horizontalDraggableHandler)
+                .swipeToScene(verticalDraggableHandler)
                 .then(LayoutElement(layoutImpl = this))
         ) {
             LookaheadScope {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 0fa19bb..e6f5d58 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -31,6 +31,7 @@
 import com.android.compose.animation.scene.transition.link.StateLink
 import kotlin.math.absoluteValue
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.channels.Channel
 
 /**
@@ -188,13 +189,8 @@
         val fromScene: SceneKey,
 
         /** The scene this transition is going to. Can't be the same as fromScene */
-        val toScene: SceneKey
+        val toScene: SceneKey,
     ) : TransitionState {
-
-        init {
-            check(fromScene != toScene)
-        }
-
         /**
          * The progress of the transition. This is usually in the `[0; 1]` range, but it can also be
          * less than `0` or greater than `1` when using transitions with a spring AnimationSpec or
@@ -208,6 +204,21 @@
         /** Whether user input is currently driving the transition. */
         abstract val isUserInputOngoing: Boolean
 
+        init {
+            check(fromScene != toScene)
+        }
+
+        /**
+         * Force this transition to finish and animate to [currentScene], so that this transition
+         * progress will settle to either 0% (if [currentScene] == [fromScene]) or 100% (if
+         * [currentScene] == [toScene]) in a finite amount of time.
+         *
+         * @return the [Job] that animates the progress to [currentScene]. It can be used to wait
+         *   until the animation is complete or cancel it to snap to [currentScene]. Calling
+         *   [finish] multiple times will return the same [Job].
+         */
+        abstract fun finish(): Job
+
         /**
          * Whether we are transitioning. If [from] or [to] is empty, we will also check that they
          * match the scenes we are animating from and/or to.
@@ -225,19 +236,28 @@
 
     interface HasOverscrollProperties {
         /**
-         * The position of the [TransitionState.Transition.toScene].
+         * The position of the [Transition.toScene].
          *
          * Used to understand the direction of the overscroll.
          */
         val isUpOrLeft: Boolean
 
         /**
-         * The relative orientation between [TransitionState.Transition.fromScene] and
-         * [TransitionState.Transition.toScene].
+         * The relative orientation between [Transition.fromScene] and [Transition.toScene].
          *
          * Used to understand the orientation of the overscroll.
          */
         val orientation: Orientation
+
+        /**
+         * Scope which can be used in the Overscroll DSL to define a transformation based on the
+         * distance between [Transition.fromScene] and [Transition.toScene].
+         */
+        val overscrollScope: OverscrollScope
+
+        companion object {
+            const val DistanceUnspecified = 0f
+        }
     }
 }
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index 2dd41cd..b466143 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -30,6 +30,7 @@
 import com.android.compose.animation.scene.transformation.DrawScale
 import com.android.compose.animation.scene.transformation.EdgeTranslate
 import com.android.compose.animation.scene.transformation.Fade
+import com.android.compose.animation.scene.transformation.OverscrollTranslate
 import com.android.compose.animation.scene.transformation.PropertyTransformation
 import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
 import com.android.compose.animation.scene.transformation.ScaleSize
@@ -124,7 +125,7 @@
         overscrollSpecs.fastForEach { spec ->
             if (spec.orientation == orientation && filter(spec)) {
                 if (match != null) {
-                    error("Found multiple transition specs for transition $scene")
+                    error("Found multiple overscroll specs for overscroll $scene")
                 }
                 match = spec
             }
@@ -297,6 +298,7 @@
         ) {
             when (current) {
                 is Translate,
+                is OverscrollTranslate,
                 is EdgeTranslate,
                 is AnchoredTranslate -> {
                     throwIfNotNull(offset, element, name = "offset")
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 61f4978..b618369 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -31,39 +31,39 @@
  * Configures the swipeable behavior of a [SceneTransitionLayout] depending on the current state.
  */
 @Stable
-internal fun Modifier.swipeToScene(gestureHandler: SceneGestureHandler): Modifier {
-    return this.then(SwipeToSceneElement(gestureHandler))
+internal fun Modifier.swipeToScene(draggableHandler: DraggableHandlerImpl): Modifier {
+    return this.then(SwipeToSceneElement(draggableHandler))
 }
 
 private data class SwipeToSceneElement(
-    val gestureHandler: SceneGestureHandler,
+    val draggableHandler: DraggableHandlerImpl,
 ) : ModifierNodeElement<SwipeToSceneNode>() {
-    override fun create(): SwipeToSceneNode = SwipeToSceneNode(gestureHandler)
+    override fun create(): SwipeToSceneNode = SwipeToSceneNode(draggableHandler)
 
     override fun update(node: SwipeToSceneNode) {
-        node.gestureHandler = gestureHandler
+        node.draggableHandler = draggableHandler
     }
 }
 
 private class SwipeToSceneNode(
-    gestureHandler: SceneGestureHandler,
+    draggableHandler: DraggableHandlerImpl,
 ) : DelegatingNode(), PointerInputModifierNode {
     private val delegate =
         delegate(
             MultiPointerDraggableNode(
-                orientation = gestureHandler.orientation,
+                orientation = draggableHandler.orientation,
                 enabled = ::enabled,
                 startDragImmediately = ::startDragImmediately,
-                onDragStarted = gestureHandler.draggable::onDragStarted,
-                onDragDelta = gestureHandler.draggable::onDelta,
-                onDragStopped = gestureHandler.draggable::onDragStopped,
+                onDragStarted = draggableHandler::onDragStarted,
             )
         )
 
-    var gestureHandler: SceneGestureHandler = gestureHandler
+    private var _draggableHandler = draggableHandler
+    var draggableHandler: DraggableHandlerImpl
+        get() = _draggableHandler
         set(value) {
-            if (value != field) {
-                field = value
+            if (_draggableHandler != value) {
+                _draggableHandler = value
 
                 // Make sure to update the delegate orientation. Note that this will automatically
                 // reset the underlying pointer input handler, so previous gestures will be
@@ -81,12 +81,12 @@
     override fun onCancelPointerInput() = delegate.onCancelPointerInput()
 
     private fun enabled(): Boolean {
-        return gestureHandler.isDrivingTransition ||
-            currentScene().shouldEnableSwipes(gestureHandler.orientation)
+        return draggableHandler.isDrivingTransition ||
+            currentScene().shouldEnableSwipes(delegate.orientation)
     }
 
     private fun currentScene(): Scene {
-        val layoutImpl = gestureHandler.layoutImpl
+        val layoutImpl = draggableHandler.layoutImpl
         return layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
     }
 
@@ -98,12 +98,12 @@
     private fun startDragImmediately(startedPosition: Offset): Boolean {
         // Immediately start the drag if the user can't swipe in the other direction and the gesture
         // handler can intercept it.
-        return !canOppositeSwipe() && gestureHandler.shouldImmediatelyIntercept(startedPosition)
+        return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(startedPosition)
     }
 
     private fun canOppositeSwipe(): Boolean {
         val oppositeOrientation =
-            when (gestureHandler.orientation) {
+            when (draggableHandler.orientation) {
                 Orientation.Vertical -> Orientation.Horizontal
                 Orientation.Horizontal -> Orientation.Vertical
             }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index bc52a28..2c109a3 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -22,6 +22,7 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
 
 /** Define the [transitions][SceneTransitions] to be used with a [SceneTransitionLayout]. */
 fun transitions(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions {
@@ -88,8 +89,7 @@
     ): OverscrollSpec
 }
 
-@TransitionDsl
-interface OverscrollBuilder : PropertyTransformationBuilder {
+interface BaseTransitionBuilder : PropertyTransformationBuilder {
     /**
      * The distance it takes for this transition to animate from 0% to 100% when it is driven by a
      * [UserAction].
@@ -120,7 +120,7 @@
 }
 
 @TransitionDsl
-interface TransitionBuilder : OverscrollBuilder, PropertyTransformationBuilder {
+interface TransitionBuilder : BaseTransitionBuilder {
     /**
      * The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
      * the transition is triggered (i.e. it is not gesture-based).
@@ -176,6 +176,24 @@
     fun reversed(builder: TransitionBuilder.() -> Unit)
 }
 
+@TransitionDsl
+interface OverscrollBuilder : BaseTransitionBuilder {
+    /** Translate the element(s) matching [matcher] by ([x], [y]) pixels. */
+    fun translate(
+        matcher: ElementMatcher,
+        x: OverscrollScope.() -> Float = { 0f },
+        y: OverscrollScope.() -> Float = { 0f },
+    )
+}
+
+interface OverscrollScope {
+    /**
+     * Return the absolute distance between fromScene and toScene, if available, otherwise
+     * [DistanceUnspecified].
+     */
+    val absoluteDistance: Float
+}
+
 /**
  * An interface to decide where we should draw shared Elements or compose MovableElements.
  *
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 65e8ea5..1c9080f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -31,6 +31,7 @@
 import com.android.compose.animation.scene.transformation.DrawScale
 import com.android.compose.animation.scene.transformation.EdgeTranslate
 import com.android.compose.animation.scene.transformation.Fade
+import com.android.compose.animation.scene.transformation.OverscrollTranslate
 import com.android.compose.animation.scene.transformation.PropertyTransformation
 import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
 import com.android.compose.animation.scene.transformation.ScaleSize
@@ -114,7 +115,7 @@
     }
 }
 
-internal open class OverscrollBuilderImpl : OverscrollBuilder {
+internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder {
     val transformations = mutableListOf<Transformation>()
     private var range: TransformationRange? = null
     protected var reversed = false
@@ -130,7 +131,7 @@
         range = null
     }
 
-    private fun transformation(transformation: PropertyTransformation<*>) {
+    protected fun transformation(transformation: PropertyTransformation<*>) {
         val transformation =
             if (range != null) {
                 RangedPropertyTransformation(transformation, range!!)
@@ -185,7 +186,7 @@
     }
 }
 
-internal class TransitionBuilderImpl : OverscrollBuilderImpl(), TransitionBuilder {
+internal class TransitionBuilderImpl : BaseTransitionBuilderImpl(), TransitionBuilder {
     override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
     override var swipeSpec: SpringSpec<Float>? = null
     override var distance: UserActionDistance? = null
@@ -226,3 +227,13 @@
         fractionRange(start, end, builder)
     }
 }
+
+internal open class OverscrollBuilderImpl : BaseTransitionBuilderImpl(), OverscrollBuilder {
+    override fun translate(
+        matcher: ElementMatcher,
+        x: OverscrollScope.() -> Float,
+        y: OverscrollScope.() -> Float
+    ) {
+        transformation(OverscrollTranslate(matcher, x, y))
+    }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
index 04d5914..849c9d7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
@@ -21,11 +21,11 @@
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.Element
 import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.OverscrollScope
 import com.android.compose.animation.scene.Scene
 import com.android.compose.animation.scene.SceneTransitionLayoutImpl
 import com.android.compose.animation.scene.TransitionState
 
-/** Translate an element by a fixed amount of density-independent pixels. */
 internal class Translate(
     override val matcher: ElementMatcher,
     private val x: Dp = 0.dp,
@@ -47,3 +47,28 @@
         }
     }
 }
+
+internal class OverscrollTranslate(
+    override val matcher: ElementMatcher,
+    val x: OverscrollScope.() -> Float = { 0f },
+    val y: OverscrollScope.() -> Float = { 0f },
+) : PropertyTransformation<Offset> {
+    override fun transform(
+        layoutImpl: SceneTransitionLayoutImpl,
+        scene: Scene,
+        element: Element,
+        sceneState: Element.SceneState,
+        transition: TransitionState.Transition,
+        value: Offset,
+    ): Offset {
+        // As this object is created by OverscrollBuilderImpl and we retrieve the current
+        // OverscrollSpec only when the transition implements HasOverscrollProperties, we can assume
+        // that this method was invoked after performing this check.
+        val overscrollProperties = transition as TransitionState.HasOverscrollProperties
+
+        return Offset(
+            x = value.x + overscrollProperties.overscrollScope.x(),
+            y = value.y + overscrollProperties.overscrollScope.y(),
+        )
+    }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
index 33b57b2..73393a1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
@@ -18,6 +18,7 @@
 
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.TransitionState
+import kotlinx.coroutines.Job
 
 /** A linked transition which is driven by a [originalTransition]. */
 internal class LinkedTransition(
@@ -43,4 +44,6 @@
 
     override val progress: Float
         get() = originalTransition.progress
+
+    override fun finish(): Job = originalTransition.finish()
 }
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/DraggableHandlerTest.kt
similarity index 79%
rename from packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index d28ac6a..1e9a7e2 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -39,6 +39,7 @@
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancelAndJoin
 import kotlinx.coroutines.launch
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -47,7 +48,7 @@
 private val LAYOUT_SIZE = IntSize(SCREEN_SIZE.toInt(), SCREEN_SIZE.toInt())
 
 @RunWith(AndroidJUnit4::class)
-class SceneGestureHandlerTest {
+class DraggableHandlerTest {
     private class TestGestureScope(
         private val testScope: MonotonicClockTestScope,
     ) {
@@ -99,19 +100,19 @@
                 )
                 .apply { setScenesTargetSizeForTest(LAYOUT_SIZE) }
 
-        val sceneGestureHandler = layoutImpl.gestureHandler(Orientation.Vertical)
-        val horizontalSceneGestureHandler = layoutImpl.gestureHandler(Orientation.Horizontal)
+        val draggableHandler = layoutImpl.draggableHandler(Orientation.Vertical)
+        val horizontalDraggableHandler = layoutImpl.draggableHandler(Orientation.Horizontal)
 
         fun nestedScrollConnection(nestedScrollBehavior: NestedScrollBehavior) =
-            SceneNestedScrollHandler(
+            NestedScrollHandlerImpl(
                     layoutImpl = layoutImpl,
-                    orientation = sceneGestureHandler.orientation,
+                    orientation = draggableHandler.orientation,
                     topOrLeftBehavior = nestedScrollBehavior,
                     bottomOrRightBehavior = nestedScrollBehavior,
                 )
                 .connection
 
-        val velocityThreshold = sceneGestureHandler.velocityThreshold
+        val velocityThreshold = draggableHandler.velocityThreshold
 
         fun down(fractionOfScreen: Float) =
             if (fractionOfScreen < 0f) error("use up()") else SCREEN_SIZE * fractionOfScreen
@@ -190,20 +191,18 @@
         fun onDragStarted(
             startedPosition: Offset = Offset.Zero,
             overSlop: Float,
-            pointersDown: Int = 1
-        ) {
+            pointersDown: Int = 1,
+        ): DragController {
             // overSlop should be 0f only if the drag gesture starts with startDragImmediately
             if (overSlop == 0f) error("Consider using onDragStartedImmediately()")
-            onDragStarted(sceneGestureHandler.draggable, startedPosition, overSlop, pointersDown)
+            return onDragStarted(draggableHandler, startedPosition, overSlop, pointersDown)
         }
 
-        fun onDragStartedImmediately(startedPosition: Offset = Offset.Zero, pointersDown: Int = 1) {
-            onDragStarted(
-                sceneGestureHandler.draggable,
-                startedPosition,
-                overSlop = 0f,
-                pointersDown
-            )
+        fun onDragStartedImmediately(
+            startedPosition: Offset = Offset.Zero,
+            pointersDown: Int = 1,
+        ): DragController {
+            return onDragStarted(draggableHandler, startedPosition, overSlop = 0f, pointersDown)
         }
 
         fun onDragStarted(
@@ -211,24 +210,26 @@
             startedPosition: Offset = Offset.Zero,
             overSlop: Float = 0f,
             pointersDown: Int = 1
-        ) {
-            draggableHandler.onDragStarted(
-                startedPosition = startedPosition,
-                overSlop = overSlop,
-                pointersDown = pointersDown,
-            )
+        ): DragController {
+            val dragController =
+                draggableHandler.onDragStarted(
+                    startedPosition = startedPosition,
+                    overSlop = overSlop,
+                    pointersDown = pointersDown,
+                )
 
             // MultiPointerDraggable will always call onDelta with the initial overSlop right after
-            onDelta(pixels = overSlop)
+            dragController.onDragDelta(pixels = overSlop)
+
+            return dragController
         }
 
-        fun onDelta(pixels: Float) {
-            sceneGestureHandler.draggable.onDelta(pixels = pixels)
+        fun DragController.onDragDelta(pixels: Float) {
+            onDrag(delta = pixels)
         }
 
-        fun onDragStopped(velocity: Float) {
-            sceneGestureHandler.draggable.onDragStopped(velocity = velocity)
-            runCurrent()
+        fun DragController.onDragStopped(velocity: Float, canChangeScene: Boolean = true) {
+            onStop(velocity, canChangeScene)
         }
 
         fun NestedScrollConnection.scroll(
@@ -281,20 +282,20 @@
 
     @Test
     fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest {
-        onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
+        val dragController = onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
         assertTransition(currentScene = SceneA)
         assertThat(progress).isEqualTo(0.1f)
 
-        onDelta(pixels = down(fractionOfScreen = 0.1f))
+        dragController.onDragDelta(pixels = down(fractionOfScreen = 0.1f))
         assertThat(progress).isEqualTo(0.2f)
     }
 
     @Test
     fun onDragStoppedAfterDrag_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
-        onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
+        val dragController = onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
         assertTransition(currentScene = SceneA)
 
-        onDragStopped(velocity = velocityThreshold - 0.01f)
+        dragController.onDragStopped(velocity = velocityThreshold - 0.01f)
         assertTransition(currentScene = SceneA)
 
         // wait for the stop animation
@@ -304,10 +305,10 @@
 
     @Test
     fun onDragStoppedAfterDrag_velocityAtLeastThreshold_goToNextScene() = runGestureTest {
-        onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
+        val dragController = onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
         assertTransition(currentScene = SceneA)
 
-        onDragStopped(velocity = velocityThreshold)
+        dragController.onDragStopped(velocity = velocityThreshold)
         assertTransition(currentScene = SceneC)
 
         // wait for the stop animation
@@ -317,10 +318,10 @@
 
     @Test
     fun onDragStoppedAfterStarted_returnToIdle() = runGestureTest {
-        onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
+        val dragController = onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
         assertTransition(currentScene = SceneA)
 
-        onDragStopped(velocity = 0f)
+        dragController.onDragStopped(velocity = 0f)
         advanceUntilIdle()
         assertIdle(currentScene = SceneA)
     }
@@ -328,7 +329,7 @@
     @Test
     fun onDragReversedDirection_changeToScene() = runGestureTest {
         // Drag A -> B with progress 0.6
-        onDragStarted(overSlop = -60f)
+        val dragController = onDragStarted(overSlop = -60f)
         assertTransition(
             currentScene = SceneA,
             fromScene = SceneA,
@@ -337,7 +338,7 @@
         )
 
         // Reverse direction such that A -> C now with 0.4
-        onDelta(pixels = 100f)
+        dragController.onDragDelta(pixels = 100f)
         assertTransition(
             currentScene = SceneA,
             fromScene = SceneA,
@@ -346,7 +347,7 @@
         )
 
         // After the drag stopped scene C should be committed
-        onDragStopped(velocity = velocityThreshold)
+        dragController.onDragStopped(velocity = velocityThreshold)
         assertTransition(currentScene = SceneC, fromScene = SceneA, toScene = SceneC)
 
         // wait for the stop animation
@@ -356,8 +357,6 @@
 
     @Test
     fun onDragStartedWithoutActionsInBothDirections_stayIdle() = runGestureTest {
-        val horizontalDraggableHandler = horizontalSceneGestureHandler.draggable
-
         onDragStarted(horizontalDraggableHandler, overSlop = up(fractionOfScreen = 0.3f))
         assertIdle(currentScene = SceneA)
 
@@ -370,7 +369,7 @@
         navigateToSceneC()
 
         // We are on SceneC which has no action in Down direction
-        onDragStarted(overSlop = 10f)
+        val dragController = onDragStarted(overSlop = 10f)
         assertTransition(
             currentScene = SceneC,
             fromScene = SceneC,
@@ -379,7 +378,7 @@
         )
 
         // Reverse drag direction, it will consume the previous drag
-        onDelta(pixels = -10f)
+        dragController.onDragDelta(pixels = -10f)
         assertTransition(
             currentScene = SceneC,
             fromScene = SceneC,
@@ -388,7 +387,7 @@
         )
 
         // Continue reverse drag direction, it should record progress to Scene B
-        onDelta(pixels = -10f)
+        dragController.onDragDelta(pixels = -10f)
         assertTransition(
             currentScene = SceneC,
             fromScene = SceneC,
@@ -416,14 +415,14 @@
 
     @Test
     fun onDragToExactlyZero_toSceneIsSet() = runGestureTest {
-        onDragStarted(overSlop = down(fractionOfScreen = 0.3f))
+        val dragController = onDragStarted(overSlop = down(fractionOfScreen = 0.3f))
         assertTransition(
             currentScene = SceneA,
             fromScene = SceneA,
             toScene = SceneC,
             progress = 0.3f
         )
-        onDelta(pixels = up(fractionOfScreen = 0.3f))
+        dragController.onDragDelta(pixels = up(fractionOfScreen = 0.3f))
         assertTransition(
             currentScene = SceneA,
             fromScene = SceneA,
@@ -434,8 +433,8 @@
 
     private fun TestGestureScope.navigateToSceneC() {
         assertIdle(currentScene = SceneA)
-        onDragStarted(overSlop = down(fractionOfScreen = 1f))
-        onDragStopped(velocity = 0f)
+        val dragController = onDragStarted(overSlop = down(fractionOfScreen = 1f))
+        dragController.onDragStopped(velocity = 0f)
         advanceUntilIdle()
         assertIdle(currentScene = SceneC)
     }
@@ -443,7 +442,7 @@
     @Test
     fun onAccelaratedScroll_scrollToThirdScene() = runGestureTest {
         // Drag A -> B with progress 0.2
-        onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
+        val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
         assertTransition(
             currentScene = SceneA,
             fromScene = SceneA,
@@ -452,13 +451,13 @@
         )
 
         // Start animation A -> B with progress 0.2 -> 1.0
-        onDragStopped(velocity = -velocityThreshold)
+        dragController1.onDragStopped(velocity = -velocityThreshold)
         assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
 
         // While at A -> B do a 100% screen drag (progress 1.2). This should go past B and change
         // the transition to B -> C with progress 0.2
-        onDragStartedImmediately()
-        onDelta(pixels = up(fractionOfScreen = 1f))
+        val dragController2 = onDragStartedImmediately()
+        dragController2.onDragDelta(pixels = up(fractionOfScreen = 1f))
         assertTransition(
             currentScene = SceneB,
             fromScene = SceneB,
@@ -467,7 +466,7 @@
         )
 
         // After the drag stopped scene C should be committed
-        onDragStopped(velocity = -velocityThreshold)
+        dragController2.onDragStopped(velocity = -velocityThreshold)
         assertTransition(currentScene = SceneC, fromScene = SceneB, toScene = SceneC)
 
         // wait for the stop animation
@@ -477,9 +476,9 @@
 
     @Test
     fun onAccelaratedScrollBothTargetsBecomeNull_settlesToIdle() = runGestureTest {
-        onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
-        onDelta(pixels = up(fractionOfScreen = 0.2f))
-        onDragStopped(velocity = -velocityThreshold)
+        val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
+        dragController1.onDragDelta(pixels = up(fractionOfScreen = 0.2f))
+        dragController1.onDragStopped(velocity = -velocityThreshold)
         assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
 
         mutableUserActionsA.remove(Swipe.Up)
@@ -488,34 +487,34 @@
         mutableUserActionsB.remove(Swipe.Down)
 
         // start accelaratedScroll and scroll over to B -> null
-        onDragStartedImmediately()
-        onDelta(pixels = up(fractionOfScreen = 0.5f))
-        onDelta(pixels = up(fractionOfScreen = 0.5f))
+        val dragController2 = onDragStartedImmediately()
+        dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f))
+        dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f))
 
         // here onDragStopped is already triggered, but subsequent onDelta/onDragStopped calls may
         // still be called. Make sure that they don't crash or change the scene
-        onDelta(pixels = up(fractionOfScreen = 0.5f))
-        onDragStopped(velocity = 0f)
+        dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f))
+        dragController2.onDragStopped(velocity = 0f)
 
         advanceUntilIdle()
         assertIdle(SceneB)
 
         // These events can still come in after the animation has settled
-        onDelta(pixels = up(fractionOfScreen = 0.5f))
-        onDragStopped(velocity = 0f)
+        dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f))
+        dragController2.onDragStopped(velocity = 0f)
         assertIdle(SceneB)
     }
 
     @Test
     fun onDragTargetsChanged_targetStaysTheSame() = runGestureTest {
-        onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
+        val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
         assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
 
         mutableUserActionsA[Swipe.Up] = UserActionResult(SceneC)
-        onDelta(pixels = up(fractionOfScreen = 0.1f))
+        dragController1.onDragDelta(pixels = up(fractionOfScreen = 0.1f))
         // target stays B even though UserActions changed
         assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.2f)
-        onDragStopped(velocity = down(fractionOfScreen = 0.1f))
+        dragController1.onDragStopped(velocity = down(fractionOfScreen = 0.1f))
         advanceUntilIdle()
 
         // now target changed to C for new drag
@@ -525,25 +524,26 @@
 
     @Test
     fun onDragTargetsChanged_targetsChangeWhenStartingNewDrag() = runGestureTest {
-        onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
+        val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
         assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
 
         mutableUserActionsA[Swipe.Up] = UserActionResult(SceneC)
-        onDelta(pixels = up(fractionOfScreen = 0.1f))
-        onDragStopped(velocity = down(fractionOfScreen = 0.1f))
+        dragController1.onDragDelta(pixels = up(fractionOfScreen = 0.1f))
+        dragController1.onDragStopped(velocity = down(fractionOfScreen = 0.1f))
 
         // now target changed to C for new drag that started before previous drag settled to Idle
-        onDragStartedImmediately()
-        onDelta(pixels = up(fractionOfScreen = 0.1f))
+        val dragController2 = onDragStartedImmediately()
+        dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.1f))
         assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.3f)
     }
 
     @Test
     fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest {
-        onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
+        val dragController = onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
         assertTransition(currentScene = SceneA)
 
-        onDragStopped(velocity = velocityThreshold)
+        dragController.onDragStopped(velocity = velocityThreshold)
+        runCurrent()
 
         assertTransition(currentScene = SceneC)
         assertThat(isUserInputOngoing).isFalse()
@@ -632,7 +632,7 @@
         // stop scene transition (start the "stop animation")
         nestedScroll.preFling(available = Velocity.Zero)
 
-        // a pre scroll event, that could be intercepted by SceneGestureHandler
+        // a pre scroll event, that could be intercepted by DraggableHandlerImpl
         nestedScroll.onPreScroll(
             available = Offset(0f, secondScroll),
             source = NestedScrollSource.Drag
@@ -801,18 +801,6 @@
     }
 
     @Test
-    fun beforeDraggableStart_drag_shouldBeIgnored() = runGestureTest {
-        onDelta(pixels = down(fractionOfScreen = 0.1f))
-        assertIdle(currentScene = SceneA)
-    }
-
-    @Test
-    fun beforeDraggableStart_stop_shouldBeIgnored() = runGestureTest {
-        onDragStopped(velocity = velocityThreshold)
-        assertIdle(currentScene = SceneA)
-    }
-
-    @Test
     fun beforeNestedScrollStart_stop_shouldBeIgnored() = runGestureTest {
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
         nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
@@ -826,7 +814,7 @@
         val offsetY10 = downOffset(fractionOfScreen = 0.1f)
 
         // Start a drag and then stop it, given that
-        onDragStarted(overSlop = up(0.1f))
+        val dragController = onDragStarted(overSlop = up(0.1f))
 
         assertTransition(currentScene = SceneA)
         assertThat(progress).isEqualTo(0.1f)
@@ -836,7 +824,7 @@
         assertThat(progress).isEqualTo(0.2f)
 
         // this should be ignored, we are scrolling now!
-        onDragStopped(-velocityThreshold)
+        dragController.onDragStopped(-velocityThreshold)
         assertTransition(currentScene = SceneA)
 
         nestedScroll.scroll(available = -offsetY10)
@@ -865,6 +853,7 @@
             currentScene = SceneC,
             fromScene = SceneC,
             toScene = SceneB,
+            progress = 0.1f,
             isUserInputOngoing = true,
         )
 
@@ -873,18 +862,25 @@
         // During the current gesture, start a new gesture, still in the middle of the screen. We
         // should intercept it. Because it is intercepted, the overSlop passed to onDragStarted()
         // should be 0f.
-        assertThat(sceneGestureHandler.shouldImmediatelyIntercept(middle)).isTrue()
+        assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isTrue()
         onDragStartedImmediately(startedPosition = middle)
 
         // We should have intercepted the transition, so the transition should be the same object.
-        assertTransition(currentScene = SceneC, fromScene = SceneC, toScene = SceneB)
-        assertThat(transitionState).isSameInstanceAs(firstTransition)
+        assertTransition(
+            currentScene = SceneC,
+            fromScene = SceneC,
+            toScene = SceneB,
+            progress = 0.1f,
+            isUserInputOngoing = true,
+        )
+        // We should have a new transition
+        assertThat(transitionState).isNotSameInstanceAs(firstTransition)
 
         // Start a new gesture from the bottom of the screen. Because swiping up from the bottom of
         // C leads to scene A (and not B), the previous transitions is *not* intercepted and we
         // instead animate from C to A.
         val bottom = Offset(SCREEN_SIZE / 2, SCREEN_SIZE)
-        assertThat(sceneGestureHandler.shouldImmediatelyIntercept(bottom)).isFalse()
+        assertThat(draggableHandler.shouldImmediatelyIntercept(bottom)).isFalse()
         onDragStarted(startedPosition = bottom, overSlop = up(0.1f))
 
         assertTransition(
@@ -897,16 +893,60 @@
     }
 
     @Test
+    fun finish() = runGestureTest {
+        // Start at scene C.
+        navigateToSceneC()
+
+        // Swipe up from the middle to transition to scene B.
+        val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+        onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+        assertTransition(fromScene = SceneC, toScene = SceneB, isUserInputOngoing = true)
+
+        // The current transition can be intercepted.
+        assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isTrue()
+
+        // Finish the transition.
+        val transition = transitionState as Transition
+        val job = transition.finish()
+        assertTransition(isUserInputOngoing = false)
+
+        // The current transition can not be intercepted anymore.
+        assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isFalse()
+
+        // Calling finish() multiple times returns the same Job.
+        assertThat(transition.finish()).isSameInstanceAs(job)
+        assertThat(transition.finish()).isSameInstanceAs(job)
+        assertThat(transition.finish()).isSameInstanceAs(job)
+
+        // We can join the job to wait for the animation to end.
+        assertTransition()
+        job.join()
+        assertIdle(SceneC)
+    }
+
+    @Test
+    fun finish_cancelled() = runGestureTest {
+        // Swipe up from the middle to transition to scene B.
+        val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+        onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+        assertTransition(fromScene = SceneA, toScene = SceneB)
+
+        // Finish the transition and cancel the returned job.
+        (transitionState as Transition).finish().cancelAndJoin()
+        assertIdle(SceneA)
+    }
+
+    @Test
     fun blockTransition() = runGestureTest {
         assertIdle(SceneA)
 
         // Swipe up to scene B.
-        onDragStarted(overSlop = up(0.1f))
+        val dragController = onDragStarted(overSlop = up(0.1f))
         assertTransition(currentScene = SceneA, fromScene = SceneA, toScene = SceneB)
 
         // Block the transition when the user release their finger.
         canChangeScene = { false }
-        onDragStopped(velocity = -velocityThreshold)
+        dragController.onDragStopped(velocity = -velocityThreshold)
         advanceUntilIdle()
         assertIdle(SceneA)
     }
@@ -916,18 +956,18 @@
         assertIdle(SceneA)
 
         // Swipe up to B.
-        onDragStarted(overSlop = up(0.1f))
+        val dragController1 = onDragStarted(overSlop = up(0.1f))
         assertTransition(currentScene = SceneA, fromScene = SceneA, toScene = SceneB)
-        onDragStopped(velocity = -velocityThreshold)
+        dragController1.onDragStopped(velocity = -velocityThreshold)
         assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
 
         // Intercept the transition and swipe down back to scene A.
-        assertThat(sceneGestureHandler.shouldImmediatelyIntercept(startedPosition = null)).isTrue()
-        onDragStartedImmediately()
+        assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isTrue()
+        val dragController2 = onDragStartedImmediately()
 
         // Block the transition when the user release their finger.
         canChangeScene = { false }
-        onDragStopped(velocity = velocityThreshold)
+        dragController2.onDragStopped(velocity = velocityThreshold)
 
         advanceUntilIdle()
         assertIdle(SceneB)
@@ -956,4 +996,15 @@
         assertThat(transition).isNotNull()
         assertThat(transition!!.progress).isEqualTo(-0.1f)
     }
+
+    @Test
+    fun transitionIsImmediatelyUpdatedWhenReleasingFinger() = runGestureTest {
+        // Swipe up from the middle to transition to scene B.
+        val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+        val dragController = onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+        assertTransition(fromScene = SceneA, toScene = SceneB, isUserInputOngoing = true)
+
+        dragController.onDragStopped(velocity = 0f)
+        assertTransition(isUserInputOngoing = false)
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 059a10e..26e01fe 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -539,24 +539,20 @@
         }
     }
 
-    @Test
-    fun elementTransitionDuringOverscroll() {
+    private fun setupOverscrollScenario(
+        layoutWidth: Dp,
+        layoutHeight: Dp,
+        sceneTransitions: SceneTransitionsBuilder.() -> Unit,
+        firstScroll: Float
+    ): MutableSceneTransitionLayoutStateImpl {
         // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
         // detected as a drag event.
         var touchSlop = 0f
-        val overscrollTranslateY = 10.dp
-        val layoutWidth = 200.dp
-        val layoutHeight = 400.dp
 
         val state =
             MutableSceneTransitionLayoutState(
                 initialScene = TestScenes.SceneA,
-                transitions =
-                    transitions {
-                        overscroll(TestScenes.SceneB, Orientation.Vertical) {
-                            translate(TestElements.Foo, y = overscrollTranslateY)
-                        }
-                    }
+                transitions = transitions(sceneTransitions),
             )
                 as MutableSceneTransitionLayoutStateImpl
 
@@ -585,9 +581,30 @@
         rule.onRoot().performTouchInput {
             val middleTop = Offset((layoutWidth / 2).toPx(), 0f)
             down(middleTop)
-            // Scroll 50%
-            moveBy(Offset(0f, touchSlop + layoutHeight.toPx() * 0.5f), delayMillis = 1_000)
+            val firstScrollHeight = layoutHeight.toPx() * firstScroll
+            moveBy(Offset(0f, touchSlop + firstScrollHeight), delayMillis = 1_000)
         }
+        return state
+    }
+
+    @Test
+    fun elementTransitionDuringOverscroll() {
+        val layoutWidth = 200.dp
+        val layoutHeight = 400.dp
+        val overscrollTranslateY = 10.dp
+
+        val state =
+            setupOverscrollScenario(
+                layoutWidth = layoutWidth,
+                layoutHeight = layoutHeight,
+                sceneTransitions = {
+                    overscroll(TestScenes.SceneB, Orientation.Vertical) {
+                        // On overscroll 100% -> Foo should translate by overscrollTranslateY
+                        translate(TestElements.Foo, y = overscrollTranslateY)
+                    }
+                },
+                firstScroll = 0.5f, // Scroll 50%
+            )
 
         val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true)
         fooElement.assertTopPositionInRootIsEqualTo(0.dp)
@@ -691,4 +708,48 @@
         assertThat(state.currentOverscrollSpec).isNotNull()
         fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 1.5f)
     }
+
+    @Test
+    fun elementTransitionWithDistanceDuringOverscroll() {
+        val layoutWidth = 200.dp
+        val layoutHeight = 400.dp
+        val state =
+            setupOverscrollScenario(
+                layoutWidth = layoutWidth,
+                layoutHeight = layoutHeight,
+                sceneTransitions = {
+                    overscroll(TestScenes.SceneB, Orientation.Vertical) {
+                        // On overscroll 100% -> Foo should translate by layoutHeight
+                        translate(TestElements.Foo, y = { absoluteDistance })
+                    }
+                },
+                firstScroll = 1f, // 100% scroll
+            )
+
+        val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true)
+        fooElement.assertTopPositionInRootIsEqualTo(0.dp)
+
+        rule.onRoot().performTouchInput {
+            // Scroll another 50%
+            moveBy(Offset(0f, layoutHeight.toPx() * 0.5f), delayMillis = 1_000)
+        }
+
+        val transition = state.currentTransition
+        assertThat(transition).isNotNull()
+
+        // Scroll 150% (100% scroll + 50% overscroll)
+        assertThat(transition!!.progress).isEqualTo(1.5f)
+        assertThat(state.currentOverscrollSpec).isNotNull()
+        fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 0.5f)
+
+        rule.onRoot().performTouchInput {
+            // Scroll another 100%
+            moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000)
+        }
+
+        // Scroll 250% (100% scroll + 150% overscroll)
+        assertThat(transition.progress).isEqualTo(2.5f)
+        assertThat(state.currentOverscrollSpec).isNotNull()
+        fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 1.5f)
+    }
 }
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
index 35cb691..224ffe2 100644
--- 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
@@ -45,7 +45,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.compose.test.assertSizeIsEqualTo
 import com.google.common.truth.Truth.assertThat
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -262,7 +261,6 @@
     }
 
     @Test
-    @Ignore("b/317972419#comment2")
     fun movableElementContentIsRecomposedIfContentParametersChange() {
         @Composable
         fun SceneScope.MovableFoo(text: String, modifier: Modifier = Modifier) {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index cd99d05..d8cf1c1 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -59,9 +59,18 @@
                         orientation = Orientation.Vertical,
                         enabled = { enabled },
                         startDragImmediately = { false },
-                        onDragStarted = { _, _, _ -> started = true },
-                        onDragDelta = { _ -> dragged = true },
-                        onDragStopped = { stopped = true },
+                        onDragStarted = { _, _, _ ->
+                            started = true
+                            object : DragController {
+                                override fun onDrag(delta: Float) {
+                                    dragged = true
+                                }
+
+                                override fun onStop(velocity: Float, canChangeScene: Boolean) {
+                                    stopped = true
+                                }
+                            }
+                        },
                     )
             )
         }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 3cbcd73..9baabc3 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -28,6 +28,8 @@
 import com.android.compose.test.runMonotonicClockTest
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.job
 import kotlinx.coroutines.launch
 import org.junit.Rule
 import org.junit.Test
@@ -37,16 +39,6 @@
 class SceneTransitionLayoutStateTest {
     @get:Rule val rule = createComposeRule()
 
-    class TestableTransition(
-        fromScene: SceneKey,
-        toScene: SceneKey,
-    ) : TransitionState.Transition(fromScene, toScene) {
-        override var currentScene: SceneKey = fromScene
-        override var progress: Float = 0.0f
-        override var isInitiatedByUserInput: Boolean = false
-        override var isUserInputOngoing: Boolean = false
-    }
-
     @Test
     fun isTransitioningTo_idle() {
         val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
@@ -83,25 +75,31 @@
         assertThat(transition).isNotNull()
         assertThat(state.transitionState).isEqualTo(transition)
 
-        testScheduler.advanceUntilIdle()
+        transition!!.finish().join()
         assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
     }
 
     @Test
     fun setTargetScene_transitionToSameScene() = runMonotonicClockTest {
         val state = MutableSceneTransitionLayoutState(SceneA)
-        assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
+
+        val transition = state.setTargetScene(SceneB, coroutineScope = this)
+        assertThat(transition).isNotNull()
         assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNull()
-        testScheduler.advanceUntilIdle()
+
+        transition!!.finish().join()
         assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
     }
 
     @Test
     fun setTargetScene_transitionToDifferentScene() = runMonotonicClockTest {
         val state = MutableSceneTransitionLayoutState(SceneA)
+
         assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
-        assertThat(state.setTargetScene(SceneC, coroutineScope = this)).isNotNull()
-        testScheduler.advanceUntilIdle()
+        val transition = state.setTargetScene(SceneC, coroutineScope = this)
+        assertThat(transition).isNotNull()
+
+        transition!!.finish().join()
         assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneC))
     }
 
@@ -127,11 +125,22 @@
         assertThat(state.transitionState).isEqualTo(transition)
 
         // Cancelling the scope/job still sets the state to Idle(targetScene).
-        job.cancel()
-        testScheduler.advanceUntilIdle()
+        job.cancelAndJoin()
         assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
     }
 
+    @Test
+    fun transition_finishReturnsTheSameJobWhenCalledMultipleTimes() = runMonotonicClockTest {
+        val state = MutableSceneTransitionLayoutState(SceneA)
+        val transition = state.setTargetScene(SceneB, coroutineScope = this)
+        assertThat(transition).isNotNull()
+
+        val job = transition!!.finish()
+        assertThat(transition.finish()).isSameInstanceAs(job)
+        assertThat(transition.finish()).isSameInstanceAs(job)
+        assertThat(transition.finish()).isSameInstanceAs(job)
+    }
+
     private fun setupLinkedStates(
         parentInitialScene: SceneKey = SceneC,
         childInitialScene: SceneKey = SceneA,
@@ -159,7 +168,7 @@
     fun linkedTransition_startsLinkAndFinishesLinkInToState() {
         val (parentState, childState) = setupLinkedStates()
 
-        val childTransition = TestableTransition(SceneA, SceneB)
+        val childTransition = transition(SceneA, SceneB)
 
         childState.startTransition(childTransition, null)
         assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
@@ -195,7 +204,7 @@
             MutableSceneTransitionLayoutState(SceneA, stateLinks = link)
                 as BaseSceneTransitionLayoutState
 
-        val childTransition = TestableTransition(SceneA, SceneB)
+        val childTransition = transition(SceneA, SceneB)
 
         childState.startTransition(childTransition, null)
         assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
@@ -212,12 +221,13 @@
     fun linkedTransition_linkProgressIsEqual() {
         val (parentState, childState) = setupLinkedStates()
 
-        val childTransition = TestableTransition(SceneA, SceneB)
+        var progress = 0f
+        val childTransition = transition(SceneA, SceneB, progress = { progress })
 
         childState.startTransition(childTransition, null)
         assertThat(parentState.currentTransition?.progress).isEqualTo(0f)
 
-        childTransition.progress = .5f
+        progress = .5f
         assertThat(parentState.currentTransition?.progress).isEqualTo(.5f)
     }
 
@@ -225,7 +235,7 @@
     fun linkedTransition_reverseTransitionIsNotLinked() {
         val (parentState, childState) = setupLinkedStates()
 
-        val childTransition = TestableTransition(SceneB, SceneA)
+        val childTransition = transition(SceneB, SceneA)
 
         childState.startTransition(childTransition, null)
         assertThat(childState.isTransitioning(SceneB, SceneA)).isTrue()
@@ -240,7 +250,7 @@
     fun linkedTransition_startsLinkAndFinishesLinkInFromState() {
         val (parentState, childState) = setupLinkedStates()
 
-        val childTransition = TestableTransition(SceneA, SceneB)
+        val childTransition = transition(SceneA, SceneB)
         childState.startTransition(childTransition, null)
 
         childState.finishTransition(childTransition, SceneA)
@@ -252,7 +262,7 @@
     fun linkedTransition_startsLinkAndFinishesLinkInUnknownState() {
         val (parentState, childState) = setupLinkedStates()
 
-        val childTransition = TestableTransition(SceneA, SceneB)
+        val childTransition = transition(SceneA, SceneB)
         childState.startTransition(childTransition, null)
 
         childState.finishTransition(childTransition, SceneD)
@@ -264,8 +274,8 @@
     fun linkedTransition_startsLinkButLinkedStateIsTakenOver() {
         val (parentState, childState) = setupLinkedStates()
 
-        val childTransition = TestableTransition(SceneA, SceneB)
-        val parentTransition = TestableTransition(SceneC, SceneA)
+        val childTransition = transition(SceneA, SceneB)
+        val parentTransition = transition(SceneC, SceneA)
         childState.startTransition(childTransition, null)
         parentState.startTransition(parentTransition, null)
 
@@ -315,9 +325,9 @@
 
     @Test
     fun snapToIdleIfClose_snapToStart() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
         state.startTransition(
-            transition(from = TestScenes.SceneA, to = TestScenes.SceneB, progress = { 0.2f }),
+            transition(from = SceneA, to = TestScenes.SceneB, progress = { 0.2f }),
             transitionKey = null
         )
         assertThat(state.isTransitioning()).isTrue()
@@ -329,14 +339,14 @@
         // Go to the initial scene if it is close to 0.
         assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
         assertThat(state.isTransitioning()).isFalse()
-        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneA))
+        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA))
     }
 
     @Test
     fun snapToIdleIfClose_snapToEnd() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
         state.startTransition(
-            transition(from = TestScenes.SceneA, to = TestScenes.SceneB, progress = { 0.8f }),
+            transition(from = SceneA, to = TestScenes.SceneB, progress = { 0.8f }),
             transitionKey = null
         )
         assertThat(state.isTransitioning()).isTrue()
@@ -354,7 +364,7 @@
     @Test
     fun linkedTransition_fuzzyLinksAreMatchedAndStarted() {
         val (parentState, childState) = setupLinkedStates(SceneC, SceneA, null, null, null, SceneD)
-        val childTransition = TestableTransition(SceneA, SceneB)
+        val childTransition = transition(SceneA, SceneB)
 
         childState.startTransition(childTransition, null)
         assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
@@ -370,7 +380,7 @@
         val (parentState, childState) =
             setupLinkedStates(SceneC, SceneA, SceneA, null, null, SceneD)
 
-        val childTransition = TestableTransition(SceneA, SceneB)
+        val childTransition = transition(SceneA, SceneB)
 
         childState.startTransition(childTransition, null)
         assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
@@ -385,7 +395,7 @@
     fun linkedTransition_fuzzyLinksAreNotMatched() {
         val (parentState, childState) =
             setupLinkedStates(SceneC, SceneA, SceneB, null, SceneC, SceneD)
-        val childTransition = TestableTransition(SceneA, SceneB)
+        val childTransition = transition(SceneA, SceneB)
 
         childState.startTransition(childTransition, null)
         assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
@@ -402,18 +412,12 @@
                 sceneTransitions,
             )
         state.startTransition(
-            object :
-                TransitionState.Transition(SceneA, SceneB),
-                TransitionState.HasOverscrollProperties {
-                override val currentScene: SceneKey = SceneA
-                override val progress: Float
-                    get() = progress()
-
-                override val isInitiatedByUserInput: Boolean = false
-                override val isUserInputOngoing: Boolean = false
-                override val isUpOrLeft: Boolean = false
-                override val orientation: Orientation = Orientation.Vertical
-            },
+            transition(
+                from = SceneA,
+                to = SceneB,
+                progress = progress,
+                orientation = Orientation.Vertical,
+            ),
             transitionKey = null
         )
         assertThat(state.isTransitioning()).isTrue()
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index c9c3ecc..825fe13 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -22,9 +22,9 @@
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.gestures.Orientation
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.transformation.OverscrollTranslate
 import com.android.compose.animation.scene.transformation.Transformation
 import com.android.compose.animation.scene.transformation.TransformationRange
-import com.android.compose.animation.scene.transformation.Translate
 import com.google.common.truth.Correspondence
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
@@ -228,12 +228,14 @@
     @Test
     fun overscrollSpec() {
         val transitions = transitions {
-            overscroll(TestScenes.SceneA, Orientation.Vertical) { translate(TestElements.Bar) }
+            overscroll(TestScenes.SceneA, Orientation.Vertical) {
+                translate(TestElements.Bar, x = { 1f }, y = { 2f })
+            }
         }
 
         val overscrollSpec = transitions.overscrollSpecs.single()
         val transformation = overscrollSpec.transformationSpec.transformations.single()
-        assertThat(transformation).isInstanceOf(Translate::class.java)
+        assertThat(transformation).isInstanceOf(OverscrollTranslate::class.java)
     }
 
     companion object {
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
index 238b21e1..73a66c6 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
@@ -16,6 +16,9 @@
 
 package com.android.compose.animation.scene
 
+import androidx.compose.foundation.gestures.Orientation
+import kotlinx.coroutines.Job
+
 /** A utility to easily create a [TransitionState.Transition] in tests. */
 fun transition(
     from: SceneKey,
@@ -23,11 +26,25 @@
     progress: () -> Float = { 0f },
     isInitiatedByUserInput: Boolean = false,
     isUserInputOngoing: Boolean = false,
+    isUpOrLeft: Boolean = false,
+    orientation: Orientation = Orientation.Horizontal,
 ): TransitionState.Transition {
-    return object : TransitionState.Transition(from, to) {
+    return object : TransitionState.Transition(from, to), TransitionState.HasOverscrollProperties {
         override val currentScene: SceneKey = from
-        override val progress: Float = progress()
+        override val progress: Float
+            get() = progress()
+
         override val isInitiatedByUserInput: Boolean = isInitiatedByUserInput
         override val isUserInputOngoing: Boolean = isUserInputOngoing
+        override val isUpOrLeft: Boolean = isUpOrLeft
+        override val orientation: Orientation = orientation
+        override val overscrollScope: OverscrollScope =
+            object : OverscrollScope {
+                override val absoluteDistance = 0f
+            }
+
+        override fun finish(): Job {
+            error("finish() is not supported in test transitions")
+        }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index c385788..7707a60 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -17,6 +17,8 @@
 package com.android.keyguard
 
 import android.testing.TestableLooper
+import android.view.KeyEvent
+import android.view.View
 import android.view.ViewGroup
 import android.view.inputmethod.InputMethodManager
 import android.widget.EditText
@@ -25,8 +27,8 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockscreenCredential
 import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor
-import com.android.systemui.Flags as AconfigFlags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FakeFeatureFlags
@@ -36,12 +38,17 @@
 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.mock
 import com.android.systemui.util.mockito.whenever
+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.ArgumentCaptor
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.mock
@@ -49,6 +56,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
+import com.android.systemui.Flags as AconfigFlags
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -77,6 +85,7 @@
     private lateinit var mKeyguardMessageAreaController:
         KeyguardMessageAreaController<BouncerKeyguardMessageArea>
     @Mock private lateinit var postureController: DevicePostureController
+    @Captor private lateinit var keyListenerArgumentCaptor: ArgumentCaptor<View.OnKeyListener>
 
     private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
 
@@ -171,4 +180,34 @@
         keyguardPasswordViewController.resetState()
         verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password)
     }
+
+    @Test
+    fun testSpaceKeyDoesNotSubmitPassword() {
+        keyguardPasswordViewController.onViewAttached()
+        verify(passwordEntry).setOnKeyListener(keyListenerArgumentCaptor.capture())
+
+        val eventHandled =
+            keyListenerArgumentCaptor.value.onKey(keyguardPasswordView,
+                KeyEvent.KEYCODE_SPACE,
+                KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE))
+
+        assertFalse("Unlock attempted.", eventHandled)
+    }
+
+    @Test
+    fun testEnterKeySubmitsPassword() {
+        val password = mock<LockscreenCredential>()
+        `when`(keyguardPasswordView.enteredCredential).thenReturn(password)
+        `when`(password.size()).thenReturn(4)
+        `when`(password.duplicate()).thenReturn(password)
+        keyguardPasswordViewController.onViewAttached()
+        verify(passwordEntry).setOnKeyListener(keyListenerArgumentCaptor.capture())
+
+        val eventHandled =
+            keyListenerArgumentCaptor.value.onKey(keyguardPasswordView,
+                KeyEvent.KEYCODE_ENTER,
+                KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER))
+
+        assertTrue("Unlock not attempted.", eventHandled)
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 38dc24e..9dbeeda 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -31,6 +31,7 @@
 import android.widget.FrameLayout
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.logging.UiEventLogger
 import com.android.internal.widget.LockPatternUtils
@@ -65,8 +66,7 @@
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.FakeSceneDataSource
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -244,7 +244,7 @@
         sceneInteractor = kosmos.sceneInteractor
         keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
         sceneTransitionStateFlow =
-            MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
+            MutableStateFlow(ObservableTransitionState.Idle(Scenes.Lockscreen))
         sceneInteractor.setTransitionState(sceneTransitionStateFlow)
         deviceEntryInteractor = kosmos.deviceEntryInteractor
 
@@ -815,18 +815,18 @@
             // not enough to trigger a dismissal of the keyguard.
             underTest.onViewAttached()
             fakeSceneDataSource.pause()
-            sceneInteractor.changeScene(SceneKey.Bouncer, "reason")
+            sceneInteractor.changeScene(Scenes.Bouncer, "reason")
             sceneTransitionStateFlow.value =
                 ObservableTransitionState.Transition(
-                    SceneKey.Lockscreen,
-                    SceneKey.Bouncer,
+                    Scenes.Lockscreen,
+                    Scenes.Bouncer,
                     flowOf(.5f),
                     false,
                     isUserInputOngoing = flowOf(false),
                 )
             runCurrent()
-            fakeSceneDataSource.unpause(expectedScene = SceneKey.Bouncer)
-            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer)
+            fakeSceneDataSource.unpause(expectedScene = Scenes.Bouncer)
+            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Bouncer)
             runCurrent()
             verify(viewMediatorCallback, never()).keyguardDone(anyInt())
 
@@ -835,18 +835,18 @@
             kosmos.fakeDeviceEntryRepository.setUnlocked(true)
             runCurrent()
             fakeSceneDataSource.pause()
-            sceneInteractor.changeScene(SceneKey.Gone, "reason")
+            sceneInteractor.changeScene(Scenes.Gone, "reason")
             sceneTransitionStateFlow.value =
                 ObservableTransitionState.Transition(
-                    SceneKey.Bouncer,
-                    SceneKey.Gone,
+                    Scenes.Bouncer,
+                    Scenes.Gone,
                     flowOf(.5f),
                     false,
                     isUserInputOngoing = flowOf(false),
                 )
             runCurrent()
-            fakeSceneDataSource.unpause(expectedScene = SceneKey.Gone)
-            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
+            fakeSceneDataSource.unpause(expectedScene = Scenes.Gone)
+            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone)
             runCurrent()
             verify(viewMediatorCallback).keyguardDone(anyInt())
 
@@ -854,18 +854,18 @@
             // again.
             clearInvocations(viewMediatorCallback)
             fakeSceneDataSource.pause()
-            sceneInteractor.changeScene(SceneKey.Bouncer, "reason")
+            sceneInteractor.changeScene(Scenes.Bouncer, "reason")
             sceneTransitionStateFlow.value =
                 ObservableTransitionState.Transition(
-                    SceneKey.Gone,
-                    SceneKey.Bouncer,
+                    Scenes.Gone,
+                    Scenes.Bouncer,
                     flowOf(.5f),
                     false,
                     isUserInputOngoing = flowOf(false),
                 )
             runCurrent()
-            fakeSceneDataSource.unpause(expectedScene = SceneKey.Bouncer)
-            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer)
+            fakeSceneDataSource.unpause(expectedScene = Scenes.Bouncer)
+            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Bouncer)
             runCurrent()
             verify(viewMediatorCallback, never()).keyguardDone(anyInt())
 
@@ -874,35 +874,35 @@
             // does not dismiss the keyguard while we're not listening.
             underTest.onViewDetached()
             fakeSceneDataSource.pause()
-            sceneInteractor.changeScene(SceneKey.Gone, "reason")
+            sceneInteractor.changeScene(Scenes.Gone, "reason")
             sceneTransitionStateFlow.value =
                 ObservableTransitionState.Transition(
-                    SceneKey.Bouncer,
-                    SceneKey.Gone,
+                    Scenes.Bouncer,
+                    Scenes.Gone,
                     flowOf(.5f),
                     false,
                     isUserInputOngoing = flowOf(false),
                 )
             runCurrent()
-            fakeSceneDataSource.unpause(expectedScene = SceneKey.Gone)
-            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
+            fakeSceneDataSource.unpause(expectedScene = Scenes.Gone)
+            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone)
             runCurrent()
             verify(viewMediatorCallback, never()).keyguardDone(anyInt())
 
             // While not listening, moving to the lockscreen does not dismiss the keyguard.
             fakeSceneDataSource.pause()
-            sceneInteractor.changeScene(SceneKey.Lockscreen, "reason")
+            sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
             sceneTransitionStateFlow.value =
                 ObservableTransitionState.Transition(
-                    SceneKey.Gone,
-                    SceneKey.Lockscreen,
+                    Scenes.Gone,
+                    Scenes.Lockscreen,
                     flowOf(.5f),
                     false,
                     isUserInputOngoing = flowOf(false),
                 )
             runCurrent()
-            fakeSceneDataSource.unpause(expectedScene = SceneKey.Lockscreen)
-            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Lockscreen)
+            fakeSceneDataSource.unpause(expectedScene = Scenes.Lockscreen)
+            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
             runCurrent()
             verify(viewMediatorCallback, never()).keyguardDone(anyInt())
 
@@ -910,18 +910,18 @@
             // gone scene now does dismiss the keyguard again, this time from lockscreen.
             underTest.onViewAttached()
             fakeSceneDataSource.pause()
-            sceneInteractor.changeScene(SceneKey.Gone, "reason")
+            sceneInteractor.changeScene(Scenes.Gone, "reason")
             sceneTransitionStateFlow.value =
                 ObservableTransitionState.Transition(
-                    SceneKey.Lockscreen,
-                    SceneKey.Gone,
+                    Scenes.Lockscreen,
+                    Scenes.Gone,
                     flowOf(.5f),
                     false,
                     isUserInputOngoing = flowOf(false),
                 )
             runCurrent()
-            fakeSceneDataSource.unpause(expectedScene = SceneKey.Gone)
-            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
+            fakeSceneDataSource.unpause(expectedScene = Scenes.Gone)
+            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone)
             runCurrent()
             verify(viewMediatorCallback).keyguardDone(anyInt())
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 5034631..4950b96 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -30,6 +30,7 @@
 import android.view.Surface
 import android.view.Surface.Rotation
 import android.view.View
+import android.view.ViewGroup
 import android.view.WindowManager
 import android.view.accessibility.AccessibilityManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -44,27 +45,32 @@
 import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.data.repository.fakePowerRepository
 import com.android.systemui.power.domain.interactor.PowerInteractor
+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.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 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.testKosmos
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 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
@@ -96,6 +102,7 @@
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper(setAsMainLooper = true)
 class UdfpsControllerOverlayTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
 
     @JvmField @Rule var rule = MockitoJUnit.rule()
 
@@ -127,7 +134,8 @@
     @Mock private lateinit var defaultUdfpsTouchOverlayViewModel: DefaultUdfpsTouchOverlayViewModel
     @Mock
     private lateinit var udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate
-    @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+    private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Mock private lateinit var shadeInteractor: ShadeInteractor
     @Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
     @Mock private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
@@ -141,15 +149,11 @@
 
     @Before
     fun setup() {
-        testScope = TestScope(StandardTestDispatcher())
-        powerRepository = FakePowerRepository()
-        powerInteractor =
-            PowerInteractor(
-                powerRepository,
-                mock(FalsingCollector::class.java),
-                mock(ScreenOffAnimationController::class.java),
-                statusBarStateController,
-            )
+        testScope = kosmos.testScope
+        powerRepository = kosmos.fakePowerRepository
+        powerInteractor = kosmos.powerInteractor
+        keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+        keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
         whenever(inflater.inflate(R.layout.udfps_view, null, false)).thenReturn(udfpsView)
         whenever(inflater.inflate(R.layout.udfps_bp_view, null))
             .thenReturn(mock(UdfpsBpView::class.java))
@@ -159,11 +163,25 @@
             .thenReturn(mock(UdfpsFpmEmptyView::class.java))
     }
 
+    private suspend fun withReasonSuspend(
+        @RequestReason reason: Int,
+        isDebuggable: Boolean = false,
+        enableDeviceEntryUdfpsRefactor: Boolean = false,
+        block: suspend () -> Unit,
+    ) {
+        withReason(
+            reason,
+            isDebuggable,
+            enableDeviceEntryUdfpsRefactor,
+        )
+        block()
+    }
+
     private fun withReason(
         @RequestReason reason: Int,
         isDebuggable: Boolean = false,
         enableDeviceEntryUdfpsRefactor: Boolean = false,
-        block: () -> Unit,
+        block: () -> Unit = {},
     ) {
         if (enableDeviceEntryUdfpsRefactor) {
             mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
@@ -312,6 +330,7 @@
                     lastWakeReason = WakeSleepReason.POWER_BUTTON,
                     lastSleepReason = WakeSleepReason.OTHER,
                 )
+                runCurrent()
                 controllerOverlay.show(udfpsController, overlayParams)
                 runCurrent()
                 verify(windowManager).addView(any(), any())
@@ -321,15 +340,25 @@
     @Test
     fun showUdfpsOverlay_whileGoingToSleep() =
         testScope.runTest {
-            withReason(REASON_AUTH_KEYGUARD) {
+            withReasonSuspend(REASON_AUTH_KEYGUARD) {
                 mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE)
+                keyguardTransitionRepository.sendTransitionSteps(
+                    from = KeyguardState.OFF,
+                    to = KeyguardState.GONE,
+                    testScope = this,
+                )
                 powerRepository.updateWakefulness(
                     rawState = WakefulnessState.STARTING_TO_SLEEP,
                     lastWakeReason = WakeSleepReason.POWER_BUTTON,
                     lastSleepReason = WakeSleepReason.OTHER,
                 )
+                runCurrent()
+
+                // WHEN a request comes to show the view
                 controllerOverlay.show(udfpsController, overlayParams)
                 runCurrent()
+
+                // THEN the view does not get added immediately
                 verify(windowManager, never()).addView(any(), any())
 
                 // we hide to end the job that listens for the finishedGoingToSleep signal
@@ -338,25 +367,82 @@
         }
 
     @Test
-    fun showUdfpsOverlay_afterFinishedGoingToSleep() =
+    fun showUdfpsOverlay_whileAsleep() =
         testScope.runTest {
-            withReason(REASON_AUTH_KEYGUARD) {
+            withReasonSuspend(REASON_AUTH_KEYGUARD) {
                 mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE)
-                powerRepository.updateWakefulness(
-                    rawState = WakefulnessState.STARTING_TO_SLEEP,
-                    lastWakeReason = WakeSleepReason.POWER_BUTTON,
-                    lastSleepReason = WakeSleepReason.OTHER,
+                keyguardTransitionRepository.sendTransitionSteps(
+                    from = KeyguardState.OFF,
+                    to = KeyguardState.GONE,
+                    testScope = this,
                 )
-                controllerOverlay.show(udfpsController, overlayParams)
-                runCurrent()
-                verify(windowManager, never()).addView(any(), any())
-
                 powerRepository.updateWakefulness(
                     rawState = WakefulnessState.ASLEEP,
                     lastWakeReason = WakeSleepReason.POWER_BUTTON,
                     lastSleepReason = WakeSleepReason.OTHER,
                 )
                 runCurrent()
+
+                // WHEN a request comes to show the view
+                controllerOverlay.show(udfpsController, overlayParams)
+                runCurrent()
+
+                // THEN view isn't added yet
+                verify(windowManager, never()).addView(any(), any())
+
+                // we hide to end the job that listens for the finishedGoingToSleep signal
+                controllerOverlay.hide()
+            }
+        }
+
+    @Test
+    fun neverRemoveViewThatHasNotBeenAdded() =
+        testScope.runTest {
+            withReasonSuspend(REASON_AUTH_KEYGUARD) {
+                mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE)
+                controllerOverlay.show(udfpsController, overlayParams)
+                val view = controllerOverlay.getTouchOverlay()
+                view?.let {
+                    // parent is null, signalling that the view was never added
+                    whenever(view.parent).thenReturn(null)
+                }
+                verify(windowManager, never()).removeView(eq(view))
+            }
+        }
+
+    @Test
+    fun showUdfpsOverlay_afterFinishedTransitioningToAOD() =
+        testScope.runTest {
+            withReasonSuspend(REASON_AUTH_KEYGUARD) {
+                mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE)
+                keyguardTransitionRepository.sendTransitionSteps(
+                    from = KeyguardState.OFF,
+                    to = KeyguardState.GONE,
+                    testScope = this,
+                )
+                powerRepository.updateWakefulness(
+                    rawState = WakefulnessState.STARTING_TO_SLEEP,
+                    lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                    lastSleepReason = WakeSleepReason.OTHER,
+                )
+                runCurrent()
+
+                // WHEN a request comes to show the view
+                controllerOverlay.show(udfpsController, overlayParams)
+                runCurrent()
+
+                // THEN the view does not get added immediately
+                verify(windowManager, never()).addView(any(), any())
+
+                // WHEN the device finishes transitioning to AOD
+                keyguardTransitionRepository.sendTransitionSteps(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    testScope = this,
+                )
+                runCurrent()
+
+                // THEN the view gets added
                 verify(windowManager)
                     .addView(eq(controllerOverlay.getTouchOverlay()), layoutParamsCaptor.capture())
             }
@@ -387,6 +473,7 @@
     private fun hideUdfpsOverlay() {
         val didShow = controllerOverlay.show(udfpsController, overlayParams)
         val view = controllerOverlay.getTouchOverlay()
+        view?.let { whenever(view.parent).thenReturn(mock(ViewGroup::class.java)) }
         val didHide = controllerOverlay.hide()
 
         verify(windowManager).removeView(eq(view))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 561cdbb..9b0b5de 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -63,6 +63,7 @@
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.ViewRootImpl;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
@@ -234,6 +235,8 @@
     private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
     private IUdfpsOverlayController mOverlayController;
     @Captor
+    private ArgumentCaptor<View> mViewCaptor;
+    @Captor
     private ArgumentCaptor<UdfpsView.OnTouchListener> mTouchListenerCaptor;
     @Captor
     private ArgumentCaptor<View.OnHoverListener> mHoverListenerCaptor;
@@ -550,8 +553,11 @@
                                     mOpticalProps.sensorId,
                                     BiometricRequestConstants.REASON_ENROLL_ENROLLING,
                                     mUdfpsOverlayControllerCallback);
+
                             mFgExecutor.runAllReady();
-                            verify(mWindowManager).addView(any(), any());
+                            verify(mWindowManager).addView(mViewCaptor.capture(), any());
+                            when(mViewCaptor.getValue().getParent())
+                                    .thenReturn(mock(ViewGroup.class));
 
                             // Update overlay parameters.
                             reset(mWindowManager);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt
index 97c407c..970ce1f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt
@@ -47,6 +47,17 @@
     private val displayRepository by lazy { kosmos.displayRepository }
 
     @Test
+    fun propertiesInitialized() =
+        testScope.runTest {
+            val propertiesInitialized by collectLastValue(underTest.propertiesInitialized)
+            assertThat(propertiesInitialized).isFalse()
+
+            repository.supportsUdfps()
+            runCurrent()
+            assertThat(propertiesInitialized).isTrue()
+        }
+
+    @Test
     fun sensorLocation_resolution1f() =
         testScope.runTest {
             val currSensorLocation by collectLastValue(underTest.sensorLocation)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index ad29e68..df50eb6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -19,6 +19,7 @@
 import android.content.pm.UserInfo
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.domain.interactor.authenticationInteractor
@@ -33,7 +34,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.model.SelectedUserModel
 import com.android.systemui.user.data.model.SelectionStatus
@@ -93,7 +94,7 @@
 
             assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD)
             assertThat(password).isEmpty()
-            assertThat(currentScene).isEqualTo(SceneKey.Bouncer)
+            assertThat(currentScene).isEqualTo(Scenes.Bouncer)
             assertThat(underTest.authenticationMethod).isEqualTo(AuthenticationMethodModel.Password)
         }
 
@@ -125,7 +126,7 @@
 
             assertThat(message?.text).isEmpty()
             assertThat(password).isEqualTo("password")
-            assertThat(currentScene).isEqualTo(SceneKey.Bouncer)
+            assertThat(currentScene).isEqualTo(Scenes.Bouncer)
         }
 
     @Test
@@ -163,7 +164,7 @@
                 AuthenticationMethodModel.Password
             )
             kosmos.fakeDeviceEntryRepository.setUnlocked(false)
-            switchToScene(SceneKey.Bouncer)
+            switchToScene(Scenes.Bouncer)
 
             // No input entered.
 
@@ -209,14 +210,14 @@
             assertThat(password).isEqualTo("password")
 
             // The user doesn't confirm the password, but navigates back to the lockscreen instead.
-            switchToScene(SceneKey.Lockscreen)
+            switchToScene(Scenes.Lockscreen)
 
             // The user navigates to the bouncer again.
-            switchToScene(SceneKey.Bouncer)
+            switchToScene(Scenes.Bouncer)
 
             // Ensure the previously-entered password is not shown.
             assertThat(password).isEmpty()
-            assertThat(currentScene).isEqualTo(SceneKey.Bouncer)
+            assertThat(currentScene).isEqualTo(Scenes.Bouncer)
         }
 
     @Test
@@ -330,8 +331,8 @@
 
     private fun TestScope.switchToScene(toScene: SceneKey) {
         val currentScene by collectLastValue(sceneInteractor.currentScene)
-        val bouncerShown = currentScene != SceneKey.Bouncer && toScene == SceneKey.Bouncer
-        val bouncerHidden = currentScene == SceneKey.Bouncer && toScene != SceneKey.Bouncer
+        val bouncerShown = currentScene != Scenes.Bouncer && toScene == Scenes.Bouncer
+        val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer
         sceneInteractor.changeScene(toScene, "reason")
         if (bouncerShown) underTest.onShown()
         if (bouncerHidden) underTest.onHidden()
@@ -345,7 +346,7 @@
             AuthenticationMethodModel.Password
         )
         kosmos.fakeDeviceEntryRepository.setUnlocked(false)
-        switchToScene(SceneKey.Bouncer)
+        switchToScene(Scenes.Bouncer)
     }
 
     private suspend fun TestScope.setLockout(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 32de1f2..91a056d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.data.repository.authenticationRepository
@@ -31,7 +32,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
@@ -86,7 +87,7 @@
             assertThat(message?.text).isEqualTo(ENTER_YOUR_PATTERN)
             assertThat(selectedDots).isEmpty()
             assertThat(currentDot).isNull()
-            assertThat(currentScene).isEqualTo(SceneKey.Bouncer)
+            assertThat(currentScene).isEqualTo(Scenes.Bouncer)
             assertThat(underTest.authenticationMethod).isEqualTo(AuthenticationMethodModel.Pattern)
         }
 
@@ -104,7 +105,7 @@
             assertThat(message?.text).isEmpty()
             assertThat(selectedDots).isEmpty()
             assertThat(currentDot).isNull()
-            assertThat(currentScene).isEqualTo(SceneKey.Bouncer)
+            assertThat(currentScene).isEqualTo(Scenes.Bouncer)
         }
 
     @Test
@@ -159,7 +160,7 @@
             assertThat(selectedDots).isEmpty()
             assertThat(currentDot).isNull()
             assertThat(message?.text).isEqualTo(WRONG_PATTERN)
-            assertThat(currentScene).isEqualTo(SceneKey.Bouncer)
+            assertThat(currentScene).isEqualTo(Scenes.Bouncer)
         }
 
     @Test
@@ -369,8 +370,8 @@
 
     private fun TestScope.switchToScene(toScene: SceneKey) {
         val currentScene by collectLastValue(sceneInteractor.currentScene)
-        val bouncerShown = currentScene != SceneKey.Bouncer && toScene == SceneKey.Bouncer
-        val bouncerHidden = currentScene == SceneKey.Bouncer && toScene != SceneKey.Bouncer
+        val bouncerShown = currentScene != Scenes.Bouncer && toScene == Scenes.Bouncer
+        val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer
         sceneInteractor.changeScene(toScene, "reason")
         if (bouncerShown) underTest.onShown()
         if (bouncerHidden) underTest.onHidden()
@@ -384,7 +385,7 @@
             AuthenticationMethodModel.Pattern
         )
         kosmos.fakeDeviceEntryRepository.setUnlocked(false)
-        switchToScene(SceneKey.Bouncer)
+        switchToScene(Scenes.Bouncer)
     }
 
     companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index ccf7094..7b75a37 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -31,7 +32,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -196,7 +197,7 @@
 
             assertThat(message?.text).isEmpty()
             assertThat(pin).isEmpty()
-            assertThat(currentScene).isEqualTo(SceneKey.Bouncer)
+            assertThat(currentScene).isEqualTo(Scenes.Bouncer)
         }
 
     @Test
@@ -230,7 +231,7 @@
 
             assertThat(pin).isEmpty()
             assertThat(message?.text).ignoringCase().isEqualTo(WRONG_PIN)
-            assertThat(currentScene).isEqualTo(SceneKey.Bouncer)
+            assertThat(currentScene).isEqualTo(Scenes.Bouncer)
         }
 
     @Test
@@ -290,7 +291,7 @@
 
             assertThat(pin).isEmpty()
             assertThat(message?.text).ignoringCase().isEqualTo(WRONG_PIN)
-            assertThat(currentScene).isEqualTo(SceneKey.Bouncer)
+            assertThat(currentScene).isEqualTo(Scenes.Bouncer)
         }
 
     @Test
@@ -304,10 +305,10 @@
             assertThat(pin).isNotEmpty()
 
             // The user doesn't confirm the PIN, but navigates back to the lockscreen instead.
-            switchToScene(SceneKey.Lockscreen)
+            switchToScene(Scenes.Lockscreen)
 
             // The user navigates to the bouncer again.
-            switchToScene(SceneKey.Bouncer)
+            switchToScene(Scenes.Bouncer)
 
             // Ensure the previously-entered PIN is not shown.
             assertThat(pin).isEmpty()
@@ -389,8 +390,8 @@
 
     private fun TestScope.switchToScene(toScene: SceneKey) {
         val currentScene by collectLastValue(sceneInteractor.currentScene)
-        val bouncerShown = currentScene != SceneKey.Bouncer && toScene == SceneKey.Bouncer
-        val bouncerHidden = currentScene == SceneKey.Bouncer && toScene != SceneKey.Bouncer
+        val bouncerShown = currentScene != Scenes.Bouncer && toScene == Scenes.Bouncer
+        val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer
         sceneInteractor.changeScene(toScene, "reason")
         if (bouncerShown) underTest.onShown()
         if (bouncerHidden) underTest.onHidden()
@@ -402,7 +403,7 @@
     private fun TestScope.lockDeviceAndOpenPinBouncer() {
         kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
         kosmos.fakeDeviceEntryRepository.setUnlocked(false)
-        switchToScene(SceneKey.Bouncer)
+        switchToScene(Scenes.Bouncer)
     }
 
     companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt
new file mode 100644
index 0000000..9cfa572
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.camera.data.repository
+
+import android.os.UserHandle
+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.collectValues
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.util.settings.fakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
+class CameraAutoRotateRepositoryImplTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+    private val settings = kosmos.fakeSettings
+    private val testUser = UserHandle.of(1)
+
+    private val underTest =
+        CameraAutoRotateRepositoryImpl(settings, testScope.testScheduler, testScope.backgroundScope)
+
+    /** 3 changes => 3 change signals + 1 signal emitted at start => 4 signals */
+    @Test
+    fun isCameraAutoRotateSettingEnabled_3times() =
+        testScope.runTest {
+            settings.putIntForUser(SETTING_NAME, DISABLE, testUser.identifier)
+            val isCameraAutoRotateSettingEnabled by
+                collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser))
+            runCurrent()
+            assertThat(isCameraAutoRotateSettingEnabled.last()).isFalse()
+
+            settings.putIntForUser(SETTING_NAME, ENABLE, testUser.identifier)
+            runCurrent()
+            assertThat(isCameraAutoRotateSettingEnabled.last()).isTrue()
+
+            settings.putIntForUser(SETTING_NAME, DISABLE, testUser.identifier)
+            runCurrent()
+            assertThat(isCameraAutoRotateSettingEnabled.last()).isFalse()
+
+            settings.putIntForUser(SETTING_NAME, ENABLE, testUser.identifier)
+            runCurrent()
+            assertThat(isCameraAutoRotateSettingEnabled.last()).isTrue()
+
+            assertThat(isCameraAutoRotateSettingEnabled).hasSize(4)
+        }
+
+    @Test
+    fun isCameraAutoRotateSettingEnabled_emitsOnStart() =
+        testScope.runTest {
+            val isCameraAutoRotateSettingEnabled: List<Boolean> by
+                collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser))
+
+            runCurrent()
+
+            assertThat(isCameraAutoRotateSettingEnabled).hasSize(1)
+        }
+
+    /** 0 for 0 changes + 1 signal emitted on start => 1 signal */
+    @Test
+    fun isCameraAutoRotateSettingEnabled_0Times() =
+        testScope.runTest {
+            settings.putIntForUser(SETTING_NAME, DISABLE, testUser.identifier)
+            val isCameraAutoRotateSettingEnabled: List<Boolean> by
+                collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser))
+            runCurrent()
+
+            settings.putIntForUser(SETTING_NAME, DISABLE, testUser.identifier)
+            runCurrent()
+
+            assertThat(isCameraAutoRotateSettingEnabled).hasSize(1)
+            assertThat(isCameraAutoRotateSettingEnabled[0]).isFalse()
+        }
+
+    /** Maintain that flows are cached by user */
+    @Test
+    fun sameUserCallsIsCameraAutoRotateSettingEnabledTwice_getsSameFlow() =
+        testScope.runTest {
+            val flow1 = underTest.isCameraAutoRotateSettingEnabled(testUser)
+            val flow2 = underTest.isCameraAutoRotateSettingEnabled(testUser)
+
+            assertThat(flow1).isEqualTo(flow2)
+        }
+
+    @Test
+    fun differentUsersCallIsCameraAutoRotateSettingEnabled_getDifferentFlow() =
+        testScope.runTest {
+            val user2 = UserHandle.of(2)
+            val flow1 = underTest.isCameraAutoRotateSettingEnabled(testUser)
+            val flow2 = underTest.isCameraAutoRotateSettingEnabled(user2)
+
+            assertThat(flow1).isNotEqualTo(flow2)
+        }
+
+    private companion object {
+        private const val SETTING_NAME = Settings.Secure.CAMERA_AUTOROTATE
+        private const val DISABLE = 0
+        private const val ENABLE = 1
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepositoryImplTest.kt
new file mode 100644
index 0000000..29de58e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepositoryImplTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.camera.data.repository
+
+import android.hardware.SensorPrivacyManager
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
+class CameraSensorPrivacyRepositoryImplTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+    private val testUser = UserHandle.of(1)
+    private val privacyManager = mock<SensorPrivacyManager>()
+    private val underTest =
+        CameraSensorPrivacyRepositoryImpl(
+            testScope.testScheduler,
+            testScope.backgroundScope,
+            privacyManager
+        )
+
+    @Test
+    fun isEnabled_2TimesForSameUserReturnsCachedFlow() =
+        testScope.runTest {
+            val flow1 = underTest.isEnabled(testUser)
+            val flow2 = underTest.isEnabled(testUser)
+            runCurrent()
+
+            assertThat(flow1).isEqualTo(flow2)
+        }
+
+    @Test
+    fun isEnabled_2TimesForDifferentUsersReturnsTwoDifferentFlows() =
+        testScope.runTest {
+            val user2 = UserHandle.of(2)
+
+            val flow1 = underTest.isEnabled(testUser)
+            val flow2 = underTest.isEnabled(user2)
+            runCurrent()
+
+            assertThat(flow1).isNotEqualTo(flow2)
+        }
+
+    @Test
+    fun isEnabled_dataMatchesSensorPrivacyManager() =
+        testScope.runTest {
+            val isEnabled = collectLastValue(underTest.isEnabled(testUser))
+
+            val captor =
+                ArgumentCaptor.forClass(
+                    SensorPrivacyManager.OnSensorPrivacyChangedListener::class.java
+                )
+            runCurrent()
+            assertThat(isEnabled()).isEqualTo(false)
+
+            Mockito.verify(privacyManager)
+                .addSensorPrivacyListener(
+                    ArgumentMatchers.eq(SensorPrivacyManager.Sensors.CAMERA),
+                    ArgumentMatchers.eq(testUser.identifier),
+                    captor.capture()
+                )
+            val sensorPrivacyCallback = captor.value!!
+
+            sensorPrivacyCallback.onSensorPrivacyChanged(SensorPrivacyManager.Sensors.CAMERA, true)
+            runCurrent()
+            assertThat(isEnabled()).isEqualTo(true)
+
+            sensorPrivacyCallback.onSensorPrivacyChanged(SensorPrivacyManager.Sensors.CAMERA, false)
+            runCurrent()
+            assertThat(isEnabled()).isEqualTo(false)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryTest.kt
new file mode 100644
index 0000000..f75e036
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.camera.data.repository
+
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.Kosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
+class FakeCameraAutoRotateRepositoryTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val underTest = kosmos.fakeCameraAutoRotateRepository
+    private val testUser = UserHandle.of(1)
+
+    @Test
+    fun isCameraAutoRotateSettingEnabled_emitsFalseOnStart() = runTest {
+        val isCameraAutoRotateSettingEnabled by
+            collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser))
+
+        assertThat(isCameraAutoRotateSettingEnabled).hasSize(1)
+        assertThat(isCameraAutoRotateSettingEnabled.first()).isFalse()
+    }
+
+    /**
+     * The value explicitly set in this test is not distinct, therefore only 1 value is collected.
+     */
+    @Test
+    fun isCameraAutoRotateSettingEnabled_emitsDistinctValueOnly() = runTest {
+        val isCameraAutoRotateSettingEnabled by
+            collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser))
+        underTest.setEnabled(testUser, false)
+        runCurrent()
+
+        assertThat(isCameraAutoRotateSettingEnabled).hasSize(1)
+        assertThat(isCameraAutoRotateSettingEnabled.first()).isFalse()
+    }
+
+    @Test
+    fun isCameraAutoRotateSettingEnabled_canSetValue3Times() = runTest {
+        val isCameraAutoRotateSettingEnabled by
+            collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser))
+        runCurrent()
+        underTest.setEnabled(testUser, true)
+        runCurrent()
+        underTest.setEnabled(testUser, false)
+        runCurrent()
+        underTest.setEnabled(testUser, true)
+        runCurrent()
+        assertThat(isCameraAutoRotateSettingEnabled).hasSize(4)
+        assertThat(isCameraAutoRotateSettingEnabled.last()).isTrue()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryTest.kt
new file mode 100644
index 0000000..7fa1be3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.camera.data.repository
+
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.Kosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
+class FakeCameraSensorPrivacyRepositoryTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val underTest = kosmos.fakeCameraSensorPrivacyRepository
+    private val testUser = UserHandle.of(1)
+
+    @Test
+    fun isCameraSensorPrivacyEnabled_emitsFalseOnStart() = runTest {
+        val isCameraSensorPrivacySettingEnabled by collectValues(underTest.isEnabled(testUser))
+
+        assertThat(isCameraSensorPrivacySettingEnabled).hasSize(1)
+        assertThat(isCameraSensorPrivacySettingEnabled.first()).isFalse()
+    }
+
+    /**
+     * The value explicitly set in this test is not distinct, therefore only 1 value is collected.
+     */
+    @Test
+    fun isCameraSensorPrivacyEnabled_emitsDistinctValueOnly() = runTest {
+        val isCameraSensorPrivacySettingEnabled by collectValues(underTest.isEnabled(testUser))
+        underTest.setEnabled(testUser, false)
+        runCurrent()
+
+        assertThat(isCameraSensorPrivacySettingEnabled).hasSize(1)
+        assertThat(isCameraSensorPrivacySettingEnabled.first()).isFalse()
+    }
+
+    @Test
+    fun isCameraSensorPrivacyEnabled_canSetValue3Times() = runTest {
+        val isCameraSensorPrivacySettingEnabled by collectValues(underTest.isEnabled(testUser))
+        runCurrent()
+        underTest.setEnabled(testUser, true)
+        runCurrent()
+        underTest.setEnabled(testUser, false)
+        runCurrent()
+        underTest.setEnabled(testUser, true)
+        runCurrent()
+        assertThat(isCameraSensorPrivacySettingEnabled).hasSize(4)
+        assertThat(isCameraSensorPrivacySettingEnabled.last()).isTrue()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
new file mode 100644
index 0000000..def63ec
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal
+
+import android.service.dream.dreamManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.power.shared.model.ScreenPowerState
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalDreamStartableTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private lateinit var underTest: CommunalDreamStartable
+
+    private val dreamManager by lazy { kosmos.dreamManager }
+    private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+    private val powerRepository by lazy { kosmos.fakePowerRepository }
+
+    @Before
+    fun setUp() {
+        underTest =
+            CommunalDreamStartable(
+                    powerInteractor = kosmos.powerInteractor,
+                    keyguardInteractor = kosmos.keyguardInteractor,
+                    keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+                    dreamManager = dreamManager,
+                    bgScope = kosmos.applicationCoroutineScope,
+                )
+                .apply { start() }
+    }
+
+    @Test
+    fun startDreamWhenTransitioningToHub() =
+        testScope.runTest {
+            keyguardRepository.setDreaming(false)
+            powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+            whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true)
+            runCurrent()
+
+            verify(dreamManager, never()).startDream()
+
+            transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB)
+
+            verify(dreamManager).startDream()
+        }
+
+    @Test
+    fun shouldNotStartDreamWhenIneligibleToDream() =
+        testScope.runTest {
+            keyguardRepository.setDreaming(false)
+            powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+            // Not eligible to dream
+            whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(false)
+            transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB)
+
+            verify(dreamManager, never()).startDream()
+        }
+
+    @Test
+    fun shouldNotStartDreamIfAlreadyDreaming() =
+        testScope.runTest {
+            keyguardRepository.setDreaming(true)
+            powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+            whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true)
+            transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB)
+
+            verify(dreamManager, never()).startDream()
+        }
+
+    @Test
+    fun shouldNotStartDreamForInvalidTransition() =
+        testScope.runTest {
+            keyguardRepository.setDreaming(true)
+            powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+            whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true)
+
+            // Verify we do not trigger dreaming for any other state besides glanceable hub
+            for (state in KeyguardState.entries) {
+                if (state == KeyguardState.GLANCEABLE_HUB) continue
+                transition(from = KeyguardState.GLANCEABLE_HUB, to = state)
+                verify(dreamManager, never()).startDream()
+            }
+        }
+
+    private suspend fun TestScope.transition(from: KeyguardState, to: KeyguardState) {
+        kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+            from = from,
+            to = to,
+            testScope = this
+        )
+        runCurrent()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index 92396e0..ce6445b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -21,9 +21,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.domain.interactor.setCommunalAvailable
-import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.dock.DockManager
 import com.android.systemui.dock.dockManager
 import com.android.systemui.dock.fakeDockManager
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -40,6 +39,7 @@
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -79,8 +79,8 @@
             testScope.runTest {
                 val scene by collectLastValue(communalInteractor.desiredScene)
 
-                communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
-                assertThat(scene).isEqualTo(CommunalSceneKey.Communal)
+                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
                     from = KeyguardState.PRIMARY_BOUNCER,
@@ -88,16 +88,17 @@
                     testScope = this
                 )
 
-                assertThat(scene).isEqualTo(CommunalSceneKey.Blank)
+                assertThat(scene).isEqualTo(CommunalScenes.Blank)
             }
         }
 
+    @Ignore("Ignored until custom animations are implemented in b/322787129")
     @Test
     fun deviceDocked_forceCommunalScene() =
         with(kosmos) {
             testScope.runTest {
                 val scene by collectLastValue(communalInteractor.desiredScene)
-                assertThat(scene).isEqualTo(CommunalSceneKey.Blank)
+                assertThat(scene).isEqualTo(CommunalScenes.Blank)
 
                 updateDocked(true)
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
@@ -105,7 +106,24 @@
                     to = KeyguardState.LOCKSCREEN,
                     testScope = this
                 )
-                assertThat(scene).isEqualTo(CommunalSceneKey.Communal)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+            }
+        }
+
+    @Test
+    fun exitingDream_forceCommunalScene() =
+        with(kosmos) {
+            testScope.runTest {
+                val scene by collectLastValue(communalInteractor.desiredScene)
+                assertThat(scene).isEqualTo(CommunalScenes.Blank)
+
+                updateDocked(true)
+                fakeKeyguardTransitionRepository.sendTransitionSteps(
+                    from = KeyguardState.DREAMING,
+                    to = KeyguardState.LOCKSCREEN,
+                    testScope = this
+                )
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
             }
         }
 
@@ -114,7 +132,7 @@
         with(kosmos) {
             testScope.runTest {
                 val scene by collectLastValue(communalInteractor.desiredScene)
-                assertThat(scene).isEqualTo(CommunalSceneKey.Blank)
+                assertThat(scene).isEqualTo(CommunalScenes.Blank)
 
                 updateDocked(true)
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
@@ -122,7 +140,7 @@
                     to = KeyguardState.LOCKSCREEN,
                     testScope = this
                 )
-                assertThat(scene).isEqualTo(CommunalSceneKey.Blank)
+                assertThat(scene).isEqualTo(CommunalScenes.Blank)
             }
         }
 
@@ -131,19 +149,19 @@
         with(kosmos) {
             testScope.runTest {
                 val scene by collectLastValue(communalInteractor.desiredScene)
-                communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
-                assertThat(scene).isEqualTo(CommunalSceneKey.Communal)
+                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
                     from = KeyguardState.GLANCEABLE_HUB,
                     to = KeyguardState.OFF,
                     testScope = this
                 )
-                assertThat(scene).isEqualTo(CommunalSceneKey.Communal)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 advanceTimeBy(CommunalSceneStartable.AWAKE_DEBOUNCE_DELAY)
 
-                assertThat(scene).isEqualTo(CommunalSceneKey.Blank)
+                assertThat(scene).isEqualTo(CommunalScenes.Blank)
             }
         }
 
@@ -152,17 +170,17 @@
         with(kosmos) {
             testScope.runTest {
                 val scene by collectLastValue(communalInteractor.desiredScene)
-                communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
-                assertThat(scene).isEqualTo(CommunalSceneKey.Communal)
+                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
                     from = KeyguardState.GLANCEABLE_HUB,
                     to = KeyguardState.OFF,
                     testScope = this
                 )
-                assertThat(scene).isEqualTo(CommunalSceneKey.Communal)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
                 advanceTimeBy(CommunalSceneStartable.AWAKE_DEBOUNCE_DELAY / 2)
-                assertThat(scene).isEqualTo(CommunalSceneKey.Communal)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
                     from = KeyguardState.OFF,
@@ -171,15 +189,16 @@
                 )
 
                 advanceTimeBy(CommunalSceneStartable.AWAKE_DEBOUNCE_DELAY)
-                assertThat(scene).isEqualTo(CommunalSceneKey.Communal)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
             }
         }
 
+    @Ignore("Ignored until custom animations are implemented in b/322787129")
     @Test
     fun dockingOnLockscreen_forcesCommunal() =
         with(kosmos) {
             testScope.runTest {
-                communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
+                communalInteractor.onSceneChanged(CommunalScenes.Blank)
                 val scene by collectLastValue(communalInteractor.desiredScene)
 
                 // device is docked while on the lockscreen
@@ -190,17 +209,18 @@
                 )
                 updateDocked(true)
 
-                assertThat(scene).isEqualTo(CommunalSceneKey.Blank)
+                assertThat(scene).isEqualTo(CommunalScenes.Blank)
                 advanceTimeBy(CommunalSceneStartable.DOCK_DEBOUNCE_DELAY)
-                assertThat(scene).isEqualTo(CommunalSceneKey.Communal)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
             }
         }
 
+    @Ignore("Ignored until custom animations are implemented in b/322787129")
     @Test
     fun dockingOnLockscreen_doesNotForceCommunalIfDreamStarts() =
         with(kosmos) {
             testScope.runTest {
-                communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
+                communalInteractor.onSceneChanged(CommunalScenes.Blank)
                 val scene by collectLastValue(communalInteractor.desiredScene)
 
                 // device is docked while on the lockscreen
@@ -211,9 +231,9 @@
                 )
                 updateDocked(true)
 
-                assertThat(scene).isEqualTo(CommunalSceneKey.Blank)
+                assertThat(scene).isEqualTo(CommunalScenes.Blank)
                 advanceTimeBy(CommunalSceneStartable.DOCK_DEBOUNCE_DELAY / 2)
-                assertThat(scene).isEqualTo(CommunalSceneKey.Blank)
+                assertThat(scene).isEqualTo(CommunalScenes.Blank)
 
                 // dream starts shortly after docking
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
@@ -222,7 +242,7 @@
                     testScope = this
                 )
                 advanceTimeBy(CommunalSceneStartable.DOCK_DEBOUNCE_DELAY)
-                assertThat(scene).isEqualTo(CommunalSceneKey.Blank)
+                assertThat(scene).isEqualTo(CommunalScenes.Blank)
             }
         }
 
@@ -230,7 +250,8 @@
         with(kosmos) {
             runCurrent()
             fakeDockManager.setIsDocked(docked)
-            fakeDockManager.setDockEvent(DockManager.STATE_DOCKED)
+            // TODO(b/322787129): uncomment once custom animations are in place
+            // fakeDockManager.setDockEvent(DockManager.STATE_DOCKED)
             runCurrent()
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
index 06b3806..43acf31 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
@@ -18,9 +18,9 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.shared.model.CommunalSceneKey
-import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.data.repository.sceneContainerRepository
@@ -60,20 +60,17 @@
         testScope.runTest {
             val transitionState by collectLastValue(underTest.transitionState)
             assertThat(transitionState)
-                .isEqualTo(ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT))
+                .isEqualTo(ObservableTransitionState.Idle(CommunalScenes.Default))
         }
 
     @Test
     fun transitionState_setTransitionState_returnsNewValue() =
         testScope.runTest {
-            val expectedSceneKey = CommunalSceneKey.Communal
-            underTest.setTransitionState(
-                flowOf(ObservableCommunalTransitionState.Idle(expectedSceneKey))
-            )
+            val expectedSceneKey = CommunalScenes.Communal
+            underTest.setTransitionState(flowOf(ObservableTransitionState.Idle(expectedSceneKey)))
 
             val transitionState by collectLastValue(underTest.transitionState)
-            assertThat(transitionState)
-                .isEqualTo(ObservableCommunalTransitionState.Idle(expectedSceneKey))
+            assertThat(transitionState).isEqualTo(ObservableTransitionState.Idle(expectedSceneKey))
         }
 
     @Test
@@ -81,7 +78,7 @@
         testScope.runTest {
             // Set a value for the transition state flow.
             underTest.setTransitionState(
-                flowOf(ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal))
+                flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
             )
 
             // Set the transition state flow back to null.
@@ -90,6 +87,6 @@
             // Flow returns default scene key.
             val transitionState by collectLastValue(underTest.transitionState)
             assertThat(transitionState)
-                .isEqualTo(ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT))
+                .isEqualTo(ObservableTransitionState.Idle(CommunalScenes.Default))
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index cd29652..a5707e1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -19,14 +19,20 @@
 
 import android.app.smartspace.SmartspaceTarget
 import android.appwidget.AppWidgetProviderInfo
+import android.content.Intent
 import android.content.pm.UserInfo
 import android.os.UserHandle
+import android.os.UserManager
+import android.os.userManager
+import android.provider.Settings
 import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
 import android.widget.RemoteViews
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl
 import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
 import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
 import com.android.systemui.communal.data.repository.FakeCommunalRepository
@@ -39,9 +45,8 @@
 import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalContentSize
-import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
-import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
@@ -49,10 +54,12 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.activityStarter
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.settings.fakeUserTracker
 import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
@@ -60,8 +67,12 @@
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -71,6 +82,8 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
@@ -101,6 +114,8 @@
     private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter
     private lateinit var sceneInteractor: SceneInteractor
     private lateinit var userTracker: FakeUserTracker
+    private lateinit var activityStarter: ActivityStarter
+    private lateinit var userManager: UserManager
 
     private lateinit var underTest: CommunalInteractor
 
@@ -119,9 +134,13 @@
         communalPrefsRepository = kosmos.fakeCommunalPrefsRepository
         sceneInteractor = kosmos.sceneInteractor
         userTracker = kosmos.fakeUserTracker
+        activityStarter = kosmos.activityStarter
+        userManager = kosmos.userManager
 
         whenever(mainUser.isMain).thenReturn(true)
         whenever(secondaryUser.isMain).thenReturn(false)
+        whenever(userManager.isQuietModeEnabled(any<UserHandle>())).thenReturn(false)
+        whenever(userManager.isManagedProfile(anyInt())).thenReturn(false)
         userRepository.setUserInfos(listOf(mainUser, secondaryUser))
 
         kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
@@ -460,9 +479,9 @@
 
             var desiredScene = collectLastValue(underTest.desiredScene)
             runCurrent()
-            assertThat(desiredScene()).isEqualTo(CommunalSceneKey.Blank)
+            assertThat(desiredScene()).isEqualTo(CommunalScenes.Blank)
 
-            val targetScene = CommunalSceneKey.Communal
+            val targetScene = CommunalScenes.Communal
             communalRepository.setDesiredScene(targetScene)
             desiredScene = collectLastValue(underTest.desiredScene)
             runCurrent()
@@ -472,7 +491,7 @@
     @Test
     fun updatesScene() =
         testScope.runTest {
-            val targetScene = CommunalSceneKey.Communal
+            val targetScene = CommunalScenes.Communal
 
             underTest.onSceneChanged(targetScene)
 
@@ -489,32 +508,32 @@
 
             val desiredScene by collectLastValue(underTest.desiredScene)
 
-            underTest.onSceneChanged(CommunalSceneKey.Communal)
-            assertThat(desiredScene).isEqualTo(CommunalSceneKey.Communal)
+            underTest.onSceneChanged(CommunalScenes.Communal)
+            assertThat(desiredScene).isEqualTo(CommunalScenes.Communal)
 
             kosmos.setCommunalAvailable(false)
             runCurrent()
 
             // Scene returns blank when communal is not available.
-            assertThat(desiredScene).isEqualTo(CommunalSceneKey.Blank)
+            assertThat(desiredScene).isEqualTo(CommunalScenes.Blank)
 
             kosmos.setCommunalAvailable(true)
             runCurrent()
 
             // After re-enabling, scene goes back to Communal.
-            assertThat(desiredScene).isEqualTo(CommunalSceneKey.Communal)
+            assertThat(desiredScene).isEqualTo(CommunalScenes.Communal)
         }
 
     @Test
     fun transitionProgress_onTargetScene_fullProgress() =
         testScope.runTest {
-            val targetScene = CommunalSceneKey.Blank
+            val targetScene = CommunalScenes.Blank
             val transitionProgressFlow = underTest.transitionProgressToScene(targetScene)
             val transitionProgress by collectLastValue(transitionProgressFlow)
 
             val transitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(
-                    ObservableCommunalTransitionState.Idle(targetScene)
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(targetScene)
                 )
             underTest.setTransitionState(transitionState)
 
@@ -525,14 +544,14 @@
     @Test
     fun transitionProgress_notOnTargetScene_noProgress() =
         testScope.runTest {
-            val targetScene = CommunalSceneKey.Blank
-            val currentScene = CommunalSceneKey.Communal
+            val targetScene = CommunalScenes.Blank
+            val currentScene = CommunalScenes.Communal
             val transitionProgressFlow = underTest.transitionProgressToScene(targetScene)
             val transitionProgress by collectLastValue(transitionProgressFlow)
 
             val transitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(
-                    ObservableCommunalTransitionState.Idle(currentScene)
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(currentScene)
                 )
             underTest.setTransitionState(transitionState)
 
@@ -543,14 +562,14 @@
     @Test
     fun transitionProgress_transitioningToTrackedScene() =
         testScope.runTest {
-            val currentScene = CommunalSceneKey.Communal
-            val targetScene = CommunalSceneKey.Blank
+            val currentScene = CommunalScenes.Communal
+            val targetScene = CommunalScenes.Blank
             val transitionProgressFlow = underTest.transitionProgressToScene(targetScene)
             val transitionProgress by collectLastValue(transitionProgressFlow)
 
             var transitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(
-                    ObservableCommunalTransitionState.Idle(currentScene)
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(currentScene)
                 )
             underTest.setTransitionState(transitionState)
 
@@ -560,7 +579,7 @@
             val progress = MutableStateFlow(0f)
             transitionState =
                 MutableStateFlow(
-                    ObservableCommunalTransitionState.Transition(
+                    ObservableTransitionState.Transition(
                         fromScene = currentScene,
                         toScene = targetScene,
                         progress = progress,
@@ -579,7 +598,7 @@
             assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Transition(1f))
 
             // Transition finishes.
-            transitionState = MutableStateFlow(ObservableCommunalTransitionState.Idle(targetScene))
+            transitionState = MutableStateFlow(ObservableTransitionState.Idle(targetScene))
             underTest.setTransitionState(transitionState)
             assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(targetScene))
         }
@@ -587,14 +606,14 @@
     @Test
     fun transitionProgress_transitioningAwayFromTrackedScene() =
         testScope.runTest {
-            val currentScene = CommunalSceneKey.Blank
-            val targetScene = CommunalSceneKey.Communal
+            val currentScene = CommunalScenes.Blank
+            val targetScene = CommunalScenes.Communal
             val transitionProgressFlow = underTest.transitionProgressToScene(currentScene)
             val transitionProgress by collectLastValue(transitionProgressFlow)
 
             var transitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(
-                    ObservableCommunalTransitionState.Idle(currentScene)
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(currentScene)
                 )
             underTest.setTransitionState(transitionState)
 
@@ -604,7 +623,7 @@
             val progress = MutableStateFlow(0f)
             transitionState =
                 MutableStateFlow(
-                    ObservableCommunalTransitionState.Transition(
+                    ObservableTransitionState.Transition(
                         fromScene = currentScene,
                         toScene = targetScene,
                         progress = progress,
@@ -625,7 +644,7 @@
             assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.OtherTransition)
 
             // Transition finishes.
-            transitionState = MutableStateFlow(ObservableCommunalTransitionState.Idle(targetScene))
+            transitionState = MutableStateFlow(ObservableTransitionState.Idle(targetScene))
             underTest.setTransitionState(transitionState)
             assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(targetScene))
         }
@@ -640,7 +659,7 @@
             runCurrent()
             assertThat(isCommunalShowing()).isEqualTo(false)
 
-            underTest.onSceneChanged(CommunalSceneKey.Communal)
+            underTest.onSceneChanged(CommunalScenes.Communal)
 
             isCommunalShowing = collectLastValue(underTest.isCommunalShowing)
             runCurrent()
@@ -659,17 +678,17 @@
             assertThat(isCommunalShowing).isFalse()
 
             // Verify scene changes with the flag doesn't have any impact
-            sceneInteractor.changeScene(SceneKey.Communal, loggingReason = "")
+            sceneInteractor.changeScene(Scenes.Communal, loggingReason = "")
             runCurrent()
             assertThat(isCommunalShowing).isFalse()
 
             // Verify scene changes (without the flag) to communal sets the value to true
-            underTest.onSceneChanged(CommunalSceneKey.Communal)
+            underTest.onSceneChanged(CommunalScenes.Communal)
             runCurrent()
             assertThat(isCommunalShowing).isTrue()
 
             // Verify scene changes (without the flag) to blank sets the value back to false
-            underTest.onSceneChanged(CommunalSceneKey.Blank)
+            underTest.onSceneChanged(CommunalScenes.Blank)
             runCurrent()
             assertThat(isCommunalShowing).isFalse()
         }
@@ -685,17 +704,17 @@
             assertThat(isCommunalShowing).isFalse()
 
             // Verify scene changes without the flag doesn't have any impact
-            underTest.onSceneChanged(CommunalSceneKey.Communal)
+            underTest.onSceneChanged(CommunalScenes.Communal)
             runCurrent()
             assertThat(isCommunalShowing).isFalse()
 
             // Verify scene changes (with the flag) to communal sets the value to true
-            sceneInteractor.changeScene(SceneKey.Communal, loggingReason = "")
+            sceneInteractor.changeScene(Scenes.Communal, loggingReason = "")
             runCurrent()
             assertThat(isCommunalShowing).isTrue()
 
             // Verify scene changes (with the flag) to lockscreen sets the value to false
-            sceneInteractor.changeScene(SceneKey.Lockscreen, loggingReason = "")
+            sceneInteractor.changeScene(Scenes.Lockscreen, loggingReason = "")
             runCurrent()
             assertThat(isCommunalShowing).isFalse()
         }
@@ -704,8 +723,8 @@
     fun isIdleOnCommunal() =
         testScope.runTest {
             val transitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(
-                    ObservableCommunalTransitionState.Idle(CommunalSceneKey.Blank)
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(CommunalScenes.Blank)
                 )
             communalRepository.setTransitionState(transitionState)
 
@@ -715,8 +734,7 @@
             assertThat(isIdleOnCommunal).isEqualTo(false)
 
             // Transition to communal.
-            transitionState.value =
-                ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+            transitionState.value = ObservableTransitionState.Idle(CommunalScenes.Communal)
             runCurrent()
 
             // isIdleOnCommunal is now true since we're on communal.
@@ -724,9 +742,9 @@
 
             // Start transition away from communal.
             transitionState.value =
-                ObservableCommunalTransitionState.Transition(
-                    fromScene = CommunalSceneKey.Communal,
-                    toScene = CommunalSceneKey.Blank,
+                ObservableTransitionState.Transition(
+                    fromScene = CommunalScenes.Communal,
+                    toScene = CommunalScenes.Blank,
                     progress = flowOf(0f),
                     isInitiatedByUserInput = false,
                     isUserInputOngoing = flowOf(false),
@@ -741,8 +759,8 @@
     fun isCommunalVisible() =
         testScope.runTest {
             val transitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(
-                    ObservableCommunalTransitionState.Idle(CommunalSceneKey.Blank)
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(CommunalScenes.Blank)
                 )
             communalRepository.setTransitionState(transitionState)
 
@@ -752,9 +770,9 @@
 
             // Start transition to communal.
             transitionState.value =
-                ObservableCommunalTransitionState.Transition(
-                    fromScene = CommunalSceneKey.Blank,
-                    toScene = CommunalSceneKey.Communal,
+                ObservableTransitionState.Transition(
+                    fromScene = CommunalScenes.Blank,
+                    toScene = CommunalScenes.Communal,
                     progress = flowOf(0f),
                     isInitiatedByUserInput = false,
                     isUserInputOngoing = flowOf(false),
@@ -764,17 +782,16 @@
             assertThat(isCommunalVisible).isEqualTo(true)
 
             // Finish transition to communal
-            transitionState.value =
-                ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+            transitionState.value = ObservableTransitionState.Idle(CommunalScenes.Communal)
 
             // isCommunalVisible is true since we're on communal.
             assertThat(isCommunalVisible).isEqualTo(true)
 
             // Start transition away from communal.
             transitionState.value =
-                ObservableCommunalTransitionState.Transition(
-                    fromScene = CommunalSceneKey.Communal,
-                    toScene = CommunalSceneKey.Blank,
+                ObservableTransitionState.Transition(
+                    fromScene = CommunalScenes.Communal,
+                    toScene = CommunalScenes.Blank,
                     progress = flowOf(1.0f),
                     isInitiatedByUserInput = false,
                     isUserInputOngoing = flowOf(false),
@@ -800,6 +817,16 @@
         }
 
     @Test
+    fun navigateToCommunalWidgetSettings_startsActivity() =
+        testScope.runTest {
+            underTest.navigateToCommunalWidgetSettings()
+            val intentCaptor = argumentCaptor<Intent>()
+            verify(activityStarter)
+                .postStartActivityDismissingKeyguard(capture(intentCaptor), eq(0))
+            assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_COMMUNAL_SETTING)
+        }
+
+    @Test
     fun filterWidgets_whenUserProfileRemoved() =
         testScope.runTest {
             // Keyguard showing, and tutorial completed.
@@ -831,6 +858,152 @@
             }
         }
 
+    @Test
+    fun widgetContent_inQuietMode() =
+        testScope.runTest {
+            // Keyguard showing, and tutorial completed.
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setKeyguardOccluded(false)
+            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+
+            // Work profile is set up.
+            val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
+            userRepository.setUserInfos(userInfos)
+            userTracker.set(
+                userInfos = userInfos,
+                selectedUserIndex = 0,
+            )
+            runCurrent()
+
+            // Keyguard widgets are allowed.
+            kosmos.fakeSettings.putIntForUser(
+                CommunalSettingsRepositoryImpl.GLANCEABLE_HUB_CONTENT_SETTING,
+                AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
+                mainUser.id
+            )
+            runCurrent()
+
+            // When work profile is paused.
+            whenever(userManager.isQuietModeEnabled(eq(UserHandle.of(USER_INFO_WORK.id))))
+                .thenReturn(true)
+            whenever(userManager.isManagedProfile(eq(USER_INFO_WORK.id))).thenReturn(true)
+
+            val widgetContent by collectLastValue(underTest.widgetContent)
+            val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
+            val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
+            val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
+            val widgets = listOf(widget1, widget2, widget3)
+            widgetRepository.setCommunalWidgets(widgets)
+
+            // The work profile widget is in quiet mode, while other widgets are not.
+            assertThat(widgetContent).hasSize(3)
+            widgetContent!!.forEach { model ->
+                assertThat(model)
+                    .isInstanceOf(CommunalContentModel.WidgetContent.Widget::class.java)
+            }
+            assertThat(
+                    (widgetContent!![0] as CommunalContentModel.WidgetContent.Widget).inQuietMode
+                )
+                .isTrue()
+            assertThat(
+                    (widgetContent!![1] as CommunalContentModel.WidgetContent.Widget).inQuietMode
+                )
+                .isFalse()
+            assertThat(
+                    (widgetContent!![2] as CommunalContentModel.WidgetContent.Widget).inQuietMode
+                )
+                .isFalse()
+        }
+
+    @Test
+    fun widgetContent_containsDisabledWidgets_whenCategoryNotAllowed() =
+        testScope.runTest {
+            // Communal available, and tutorial completed.
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setKeyguardOccluded(false)
+            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+            userRepository.setSelectedUserInfo(mainUser)
+
+            val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
+            userRepository.setUserInfos(userInfos)
+            userTracker.set(
+                userInfos = userInfos,
+                selectedUserIndex = 0,
+            )
+            runCurrent()
+
+            // Widgets available.
+            val widget1 =
+                createWidgetWithCategory(1, AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN)
+            val widget2 =
+                createWidgetWithCategory(2, AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD)
+            val widget3 =
+                createWidgetWithCategory(3, AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX)
+            val widgets = listOf(widget1, widget2, widget3)
+            widgetRepository.setCommunalWidgets(widgets)
+
+            val widgetContent by collectLastValue(underTest.widgetContent)
+            kosmos.fakeSettings.putIntForUser(
+                CommunalSettingsRepositoryImpl.GLANCEABLE_HUB_CONTENT_SETTING,
+                AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
+                mainUser.id
+            )
+            runCurrent()
+
+            // Only the keyguard widget is enabled.
+            assertThat(widgetContent).hasSize(3)
+            assertThat(widgetContent!!.get(0))
+                .isInstanceOf(CommunalContentModel.WidgetContent.DisabledWidget::class.java)
+            assertThat(widgetContent!!.get(1))
+                .isInstanceOf(CommunalContentModel.WidgetContent.Widget::class.java)
+            assertThat(widgetContent!!.get(2))
+                .isInstanceOf(CommunalContentModel.WidgetContent.DisabledWidget::class.java)
+        }
+
+    @Test
+    fun widgetContent_allEnabled_whenCategoryAllowed() =
+        testScope.runTest {
+            // Communal available, and tutorial completed.
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setKeyguardOccluded(false)
+            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+            userRepository.setSelectedUserInfo(mainUser)
+
+            val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
+            userRepository.setUserInfos(userInfos)
+            userTracker.set(
+                userInfos = userInfos,
+                selectedUserIndex = 0,
+            )
+            runCurrent()
+
+            // Widgets available.
+            val widget1 =
+                createWidgetWithCategory(1, AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN)
+            val widget2 =
+                createWidgetWithCategory(2, AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD)
+            val widget3 =
+                createWidgetWithCategory(3, AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD)
+            val widgets = listOf(widget1, widget2, widget3)
+            widgetRepository.setCommunalWidgets(widgets)
+
+            val widgetContent by collectLastValue(underTest.widgetContent)
+            kosmos.fakeSettings.putIntForUser(
+                CommunalSettingsRepositoryImpl.GLANCEABLE_HUB_CONTENT_SETTING,
+                AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD or
+                    AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN,
+                mainUser.id
+            )
+            runCurrent()
+
+            // All widgets are enabled.
+            assertThat(widgetContent).hasSize(3)
+            widgetContent!!.forEach { model ->
+                assertThat(model)
+                    .isInstanceOf(CommunalContentModel.WidgetContent.Widget::class.java)
+            }
+        }
+
     private fun smartspaceTimer(id: String, timestamp: Long = 0L): SmartspaceTarget {
         val timer = mock(SmartspaceTarget::class.java)
         whenever(timer.smartspaceTargetId).thenReturn(id)
@@ -843,11 +1016,25 @@
     private fun createWidgetForUser(appWidgetId: Int, userId: Int): CommunalWidgetContentModel =
         mock<CommunalWidgetContentModel> {
             whenever(this.appWidgetId).thenReturn(appWidgetId)
-            val providerInfo = mock<AppWidgetProviderInfo>()
+            val providerInfo =
+                mock<AppWidgetProviderInfo>().apply {
+                    widgetCategory = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD
+                }
             whenever(providerInfo.profile).thenReturn(UserHandle(userId))
             whenever(this.providerInfo).thenReturn(providerInfo)
         }
 
+    private fun createWidgetWithCategory(
+        appWidgetId: Int,
+        category: Int
+    ): CommunalWidgetContentModel =
+        mock<CommunalWidgetContentModel> {
+            whenever(this.appWidgetId).thenReturn(appWidgetId)
+            val providerInfo = mock<AppWidgetProviderInfo>().apply { widgetCategory = category }
+            whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id))
+            whenever(this.providerInfo).thenReturn(providerInfo)
+        }
+
     private companion object {
         val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
         val USER_INFO_WORK = UserInfo(10, "work", UserInfo.FLAG_PROFILE)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
index 8b78592..50b8da6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
 import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
-import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -158,7 +158,7 @@
             kosmos.setCommunalAvailable(true)
             communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
 
-            communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
+            communalInteractor.onSceneChanged(CommunalScenes.Blank)
 
             assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_NOT_STARTED)
         }
@@ -171,7 +171,7 @@
             goToCommunal()
             communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
 
-            communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
+            communalInteractor.onSceneChanged(CommunalScenes.Blank)
 
             assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
         }
@@ -184,13 +184,13 @@
             goToCommunal()
             communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
 
-            communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
+            communalInteractor.onSceneChanged(CommunalScenes.Blank)
 
             assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
         }
 
     private suspend fun goToCommunal() {
         kosmos.setCommunalAvailable(true)
-        communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+        communalInteractor.onSceneChanged(CommunalScenes.Communal)
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt
index 6b1b937..a51315b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt
@@ -18,13 +18,14 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.shared.log.CommunalUiEvent
-import com.android.systemui.communal.shared.model.CommunalSceneKey
-import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -73,7 +74,7 @@
         testScope.runTest {
             // Transition state is default (non-communal)
             val transitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(idle(CommunalSceneKey.DEFAULT))
+                MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Default))
             communalInteractor.setTransitionState(transitionState)
             runCurrent()
 
@@ -81,14 +82,14 @@
             verify(uiEventLogger, never()).log(any())
 
             // Start transition to communal
-            transitionState.value = transition(to = CommunalSceneKey.Communal)
+            transitionState.value = transition(to = CommunalScenes.Communal)
             runCurrent()
 
             // Verify UiEvent logged
             verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_START)
 
             // Finish transition to communal
-            transitionState.value = idle(CommunalSceneKey.Communal)
+            transitionState.value = idle(CommunalScenes.Communal)
             runCurrent()
 
             // Verify UiEvent logged
@@ -101,7 +102,7 @@
         testScope.runTest {
             // Transition state is default (non-communal)
             val transitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(idle(CommunalSceneKey.DEFAULT))
+                MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Default))
             communalInteractor.setTransitionState(transitionState)
             runCurrent()
 
@@ -109,14 +110,14 @@
             verify(uiEventLogger, never()).log(any())
 
             // Start transition to communal
-            transitionState.value = transition(to = CommunalSceneKey.Communal)
+            transitionState.value = transition(to = CommunalScenes.Communal)
             runCurrent()
 
             // Verify UiEvent logged
             verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_START)
 
             // Cancel the transition
-            transitionState.value = idle(CommunalSceneKey.DEFAULT)
+            transitionState.value = idle(CommunalScenes.Default)
             runCurrent()
 
             // Verify UiEvent logged
@@ -132,7 +133,7 @@
         testScope.runTest {
             // Transition state is communal
             val transitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(idle(CommunalSceneKey.Communal))
+                MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Communal))
             communalInteractor.setTransitionState(transitionState)
             runCurrent()
 
@@ -140,14 +141,14 @@
             verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
 
             // Start transition from communal
-            transitionState.value = transition(from = CommunalSceneKey.Communal)
+            transitionState.value = transition(from = CommunalScenes.Communal)
             runCurrent()
 
             // Verify UiEvent logged
             verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_START)
 
             // Finish transition to communal
-            transitionState.value = idle(CommunalSceneKey.DEFAULT)
+            transitionState.value = idle(CommunalScenes.Default)
             runCurrent()
 
             // Verify UiEvent logged
@@ -160,7 +161,7 @@
         testScope.runTest {
             // Transition state is communal
             val transitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(idle(CommunalSceneKey.Communal))
+                MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Communal))
             communalInteractor.setTransitionState(transitionState)
             runCurrent()
 
@@ -168,14 +169,14 @@
             clearInvocations(uiEventLogger)
 
             // Start transition from communal
-            transitionState.value = transition(from = CommunalSceneKey.Communal)
+            transitionState.value = transition(from = CommunalScenes.Communal)
             runCurrent()
 
             // Verify UiEvent logged
             verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_START)
 
             // Cancel the transition
-            transitionState.value = idle(CommunalSceneKey.Communal)
+            transitionState.value = idle(CommunalScenes.Communal)
             runCurrent()
 
             // Verify UiEvent logged
@@ -187,10 +188,10 @@
         }
 
     private fun transition(
-        from: CommunalSceneKey = CommunalSceneKey.DEFAULT,
-        to: CommunalSceneKey = CommunalSceneKey.DEFAULT,
-    ): ObservableCommunalTransitionState.Transition {
-        return ObservableCommunalTransitionState.Transition(
+        from: SceneKey = CommunalScenes.Default,
+        to: SceneKey = CommunalScenes.Default,
+    ): ObservableTransitionState.Transition {
+        return ObservableTransitionState.Transition(
             fromScene = from,
             toScene = to,
             progress = emptyFlow(),
@@ -199,7 +200,7 @@
         )
     }
 
-    private fun idle(sceneKey: CommunalSceneKey): ObservableCommunalTransitionState.Idle {
-        return ObservableCommunalTransitionState.Idle(sceneKey)
+    private fun idle(sceneKey: SceneKey): ObservableTransitionState.Idle {
+        return ObservableTransitionState.Idle(sceneKey)
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 5ee88cb..8e2e947 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -135,9 +135,9 @@
             // Only Widgets and CTA tile are shown.
             assertThat(communalContent?.size).isEqualTo(3)
             assertThat(communalContent?.get(0))
-                .isInstanceOf(CommunalContentModel.Widget::class.java)
+                .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
             assertThat(communalContent?.get(1))
-                .isInstanceOf(CommunalContentModel.Widget::class.java)
+                .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
             assertThat(communalContent?.get(2))
                 .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
         }
@@ -181,9 +181,9 @@
             // Widgets and CTA tile are shown.
             assertThat(communalContent?.size).isEqualTo(3)
             assertThat(communalContent?.get(0))
-                .isInstanceOf(CommunalContentModel.Widget::class.java)
+                .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
             assertThat(communalContent?.get(1))
-                .isInstanceOf(CommunalContentModel.Widget::class.java)
+                .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
             assertThat(communalContent?.get(2))
                 .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
 
@@ -192,7 +192,8 @@
             // Only one widget and CTA tile remain.
             assertThat(communalContent?.size).isEqualTo(2)
             val item = communalContent?.get(0)
-            val appWidgetId = if (item is CommunalContentModel.Widget) item.appWidgetId else null
+            val appWidgetId =
+                if (item is CommunalContentModel.WidgetContent) item.appWidgetId else null
             assertThat(appWidgetId).isEqualTo(widgets.get(1).appWidgetId)
             assertThat(communalContent?.get(1))
                 .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 1e523dd..8f802b8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -113,6 +114,7 @@
                 kosmos.communalInteractor,
                 kosmos.communalTutorialInteractor,
                 kosmos.shadeInteractor,
+                kosmos.deviceEntryInteractor,
                 mediaHost,
                 logcatLogBuffer("CommunalViewModelTest"),
             )
@@ -184,9 +186,9 @@
                 .isInstanceOf(CommunalContentModel.Smartspace::class.java)
             assertThat(communalContent?.get(1)).isInstanceOf(CommunalContentModel.Umo::class.java)
             assertThat(communalContent?.get(2))
-                .isInstanceOf(CommunalContentModel.Widget::class.java)
+                .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
             assertThat(communalContent?.get(3))
-                .isInstanceOf(CommunalContentModel.Widget::class.java)
+                .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
             assertThat(communalContent?.get(4))
                 .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt
index 12611cb..88f5e1b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt
@@ -17,8 +17,10 @@
 package com.android.systemui.communal.widgets
 
 import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProviderInfo
 import android.content.ComponentName
 import android.content.pm.UserInfo
+import android.os.Bundle
 import android.os.UserHandle
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -33,8 +35,8 @@
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.user.domain.interactor.selectedUserInteractor
 import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -43,6 +45,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -91,7 +94,7 @@
                         any<Int>(),
                         any<UserHandle>(),
                         any<ComponentName>(),
-                        nullable()
+                        any<Bundle>(),
                     )
                 )
                 .thenReturn(true)
@@ -100,8 +103,14 @@
             val result = underTest.allocateIdAndBindWidget(provider)
 
             verify(appWidgetHost).allocateAppWidgetId()
-            verify(appWidgetManager).bindAppWidgetIdIfAllowed(widgetId, user, provider, null)
+            val bundle =
+                withArgCaptor<Bundle> {
+                    verify(appWidgetManager)
+                        .bindAppWidgetIdIfAllowed(eq(widgetId), eq(user), eq(provider), capture())
+                }
             assertThat(result).isEqualTo(widgetId)
+            assertThat(bundle.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY))
+                .isEqualTo(AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD)
         }
 
     @Test
@@ -117,7 +126,7 @@
                         any<Int>(),
                         any<UserHandle>(),
                         any<ComponentName>(),
-                        nullable()
+                        any<Bundle>()
                     )
                 )
                 .thenReturn(true)
@@ -126,8 +135,14 @@
             val result = underTest.allocateIdAndBindWidget(provider, user)
 
             verify(appWidgetHost).allocateAppWidgetId()
-            verify(appWidgetManager).bindAppWidgetIdIfAllowed(widgetId, user, provider, null)
+            val bundle =
+                withArgCaptor<Bundle> {
+                    verify(appWidgetManager)
+                        .bindAppWidgetIdIfAllowed(eq(widgetId), eq(user), eq(provider), capture())
+                }
             assertThat(result).isEqualTo(widgetId)
+            assertThat(bundle.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY))
+                .isEqualTo(AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD)
         }
 
     @Test
@@ -144,14 +159,15 @@
                         any<Int>(),
                         any<UserHandle>(),
                         any<ComponentName>(),
-                        nullable()
+                        any<Bundle>()
                     )
                 )
                 .thenReturn(false)
             val result = underTest.allocateIdAndBindWidget(provider, user)
 
             verify(appWidgetHost).allocateAppWidgetId()
-            verify(appWidgetManager).bindAppWidgetIdIfAllowed(widgetId, user, provider, null)
+            verify(appWidgetManager)
+                .bindAppWidgetIdIfAllowed(eq(widgetId), eq(user), eq(provider), any())
             verify(appWidgetHost).deleteAppWidgetId(widgetId)
             assertThat(result).isNull()
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index b611e0a..36919d0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -1052,32 +1052,6 @@
             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/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 98719dd3..4f44705 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -31,7 +32,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -120,7 +121,7 @@
         testScope.runTest {
             val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
             setupSwipeDeviceEntryMethod()
-            switchToScene(SceneKey.Lockscreen)
+            switchToScene(Scenes.Lockscreen)
 
             assertThat(isDeviceEntered).isFalse()
         }
@@ -130,9 +131,9 @@
         testScope.runTest {
             val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
             setupSwipeDeviceEntryMethod()
-            switchToScene(SceneKey.Lockscreen)
+            switchToScene(Scenes.Lockscreen)
             runCurrent()
-            switchToScene(SceneKey.Shade)
+            switchToScene(Scenes.Shade)
 
             assertThat(isDeviceEntered).isFalse()
         }
@@ -142,9 +143,9 @@
         testScope.runTest {
             val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
             setupSwipeDeviceEntryMethod()
-            switchToScene(SceneKey.Lockscreen)
+            switchToScene(Scenes.Lockscreen)
             runCurrent()
-            switchToScene(SceneKey.Gone)
+            switchToScene(Scenes.Gone)
 
             assertThat(isDeviceEntered).isTrue()
         }
@@ -154,11 +155,11 @@
         testScope.runTest {
             val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
             setupSwipeDeviceEntryMethod()
-            switchToScene(SceneKey.Lockscreen)
+            switchToScene(Scenes.Lockscreen)
             runCurrent()
-            switchToScene(SceneKey.Gone)
+            switchToScene(Scenes.Gone)
             runCurrent()
-            switchToScene(SceneKey.Shade)
+            switchToScene(Scenes.Shade)
 
             assertThat(isDeviceEntered).isTrue()
         }
@@ -170,9 +171,9 @@
                 AuthenticationMethodModel.Pattern
             )
             kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
-            switchToScene(SceneKey.Lockscreen)
+            switchToScene(Scenes.Lockscreen)
             runCurrent()
-            switchToScene(SceneKey.Bouncer)
+            switchToScene(Scenes.Bouncer)
 
             val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
             assertThat(isDeviceEntered).isFalse()
@@ -182,7 +183,7 @@
     fun canSwipeToEnter_onLockscreenWithSwipe_isTrue() =
         testScope.runTest {
             setupSwipeDeviceEntryMethod()
-            switchToScene(SceneKey.Lockscreen)
+            switchToScene(Scenes.Lockscreen)
 
             val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
             assertThat(canSwipeToEnter).isTrue()
@@ -195,7 +196,7 @@
                 AuthenticationMethodModel.Pin
             )
             kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
-            switchToScene(SceneKey.Lockscreen)
+            switchToScene(Scenes.Lockscreen)
 
             val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
             assertThat(canSwipeToEnter).isFalse()
@@ -205,9 +206,9 @@
     fun canSwipeToEnter_afterLockscreenDismissedInSwipeMode_isFalse() =
         testScope.runTest {
             setupSwipeDeviceEntryMethod()
-            switchToScene(SceneKey.Lockscreen)
+            switchToScene(Scenes.Lockscreen)
             runCurrent()
-            switchToScene(SceneKey.Gone)
+            switchToScene(Scenes.Gone)
 
             val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
             assertThat(canSwipeToEnter).isFalse()
@@ -225,7 +226,7 @@
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
-            switchToScene(SceneKey.Lockscreen)
+            switchToScene(Scenes.Lockscreen)
             assertThat(canSwipeToEnter).isFalse()
 
             trustRepository.setCurrentUserTrusted(true)
@@ -242,7 +243,7 @@
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
-            switchToScene(SceneKey.Lockscreen)
+            switchToScene(Scenes.Lockscreen)
             assertThat(canSwipeToEnter).isFalse()
 
             faceAuthRepository.isAuthenticated.value = true
@@ -311,8 +312,8 @@
     fun showOrUnlockDevice_notLocked_switchesToGoneScene() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene)
-            switchToScene(SceneKey.Lockscreen)
-            assertThat(currentScene).isEqualTo(SceneKey.Lockscreen)
+            switchToScene(Scenes.Lockscreen)
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
 
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin
@@ -322,15 +323,15 @@
 
             underTest.attemptDeviceEntry()
 
-            assertThat(currentScene).isEqualTo(SceneKey.Gone)
+            assertThat(currentScene).isEqualTo(Scenes.Gone)
         }
 
     @Test
     fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene)
-            switchToScene(SceneKey.Lockscreen)
-            assertThat(currentScene).isEqualTo(SceneKey.Lockscreen)
+            switchToScene(Scenes.Lockscreen)
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
 
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.None
@@ -339,15 +340,15 @@
 
             underTest.attemptDeviceEntry()
 
-            assertThat(currentScene).isEqualTo(SceneKey.Gone)
+            assertThat(currentScene).isEqualTo(Scenes.Gone)
         }
 
     @Test
     fun showOrUnlockDevice_authMethodSwipe_switchesToGoneScene() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene)
-            switchToScene(SceneKey.Lockscreen)
-            assertThat(currentScene).isEqualTo(SceneKey.Lockscreen)
+            switchToScene(Scenes.Lockscreen)
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
 
             kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
@@ -357,7 +358,7 @@
 
             underTest.attemptDeviceEntry()
 
-            assertThat(currentScene).isEqualTo(SceneKey.Gone)
+            assertThat(currentScene).isEqualTo(Scenes.Gone)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index c670506..86fdaa5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -8,7 +8,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.complication.ComplicationHostViewController
-import com.android.systemui.dreams.ui.viewmodel.DreamOverlayViewModel
+import com.android.systemui.dreams.ui.viewmodel.DreamViewModel
 import com.android.systemui.log.core.FakeLogBuffer
 import com.android.systemui.statusbar.BlurUtils
 import com.android.systemui.util.mockito.argumentCaptor
@@ -45,7 +45,7 @@
     @Mock private lateinit var hostViewController: ComplicationHostViewController
     @Mock private lateinit var statusBarViewController: DreamOverlayStatusBarViewController
     @Mock private lateinit var stateController: DreamOverlayStateController
-    @Mock private lateinit var transitionViewModel: DreamOverlayViewModel
+    @Mock private lateinit var transitionViewModel: DreamViewModel
     private val logBuffer = FakeLogBuffer.Factory.create()
     private lateinit var controller: DreamOverlayAnimationsController
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
index 0a3aea7..723f6a2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
@@ -27,6 +27,8 @@
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.wakelock.WakeLockFake
+import com.google.common.truth.Truth.assertThat
 import java.util.Optional
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -44,6 +46,9 @@
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
 
+    private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
+    private lateinit var fakeWakeLock: WakeLockFake
+
     @Mock private lateinit var taskFragmentComponentFactory: TaskFragmentComponent.Factory
     @Mock private lateinit var taskFragmentComponent: TaskFragmentComponent
     @Mock private lateinit var activity: Activity
@@ -57,6 +62,10 @@
             whenever(taskFragmentComponentFactory.create(any(), any(), any(), any()))
                 .thenReturn(taskFragmentComponent)
 
+            fakeWakeLock = WakeLockFake()
+            fakeWakeLockBuilder = WakeLockFake.Builder(context)
+            fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
+
             whenever(controlsComponent.getControlsListingController())
                 .thenReturn(Optional.of(controlsListingController))
 
@@ -87,12 +96,29 @@
             verify(taskFragmentComponentFactory, never()).create(any(), any(), any(), any())
         }
 
+    @Test
+    fun testAttachWindow_wakeLockAcquired() =
+        testScope.runTest {
+            underTest.onAttachedToWindow()
+            assertThat(fakeWakeLock.isHeld).isTrue()
+        }
+    @Test
+    fun testDetachWindow_wakeLockCanBeReleased() =
+        testScope.runTest {
+            underTest.onAttachedToWindow()
+            assertThat(fakeWakeLock.isHeld).isTrue()
+
+            underTest.onDetachedFromWindow()
+            assertThat(fakeWakeLock.isHeld).isFalse()
+        }
+
     private fun buildService(activityProvider: DreamActivityProvider): HomeControlsDreamService =
         with(kosmos) {
             return HomeControlsDreamService(
                 controlsSettingsRepository = FakeControlsSettingsRepository(),
                 taskFragmentFactory = taskFragmentComponentFactory,
                 homeControlsComponentInteractor = homeControlsComponentInteractor,
+                fakeWakeLockBuilder,
                 dreamActivityProvider = activityProvider,
                 bgDispatcher = testDispatcher,
                 logBuffer = logcatLogBuffer("HomeControlsDreamServiceTest")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
new file mode 100644
index 0000000..8f03717
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.qs
+
+import android.os.VibrationEffect
+import android.testing.TestableLooper.RunWithLooper
+import android.view.MotionEvent
+import android.view.View
+import androidx.test.core.view.MotionEventBuilder
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWithLooper(setAsMainLooper = true)
+class QSLongPressEffectTest : SysuiTestCase() {
+
+    @Rule @JvmField val mMockitoRule: MockitoRule = MockitoJUnit.rule()
+    @Mock private lateinit var vibratorHelper: VibratorHelper
+    @Mock private lateinit var testView: View
+    @get:Rule val animatorTestRule = AnimatorTestRule(this)
+    private val kosmos = testKosmos()
+
+    private val effectDuration = 400
+    private val lowTickDuration = 12
+    private val spinDuration = 133
+
+    private lateinit var longPressEffect: QSLongPressEffect
+
+    @Before
+    fun setup() {
+        whenever(
+                vibratorHelper.getPrimitiveDurations(
+                    VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+                    VibrationEffect.Composition.PRIMITIVE_SPIN,
+                )
+            )
+            .thenReturn(intArrayOf(lowTickDuration, spinDuration))
+
+        longPressEffect =
+            QSLongPressEffect(
+                vibratorHelper,
+                effectDuration,
+            )
+    }
+
+    @Test
+    fun onActionDown_whileIdle_startsWait() = testWithScope {
+        // GIVEN an action down event occurs
+        val downEvent = buildMotionEvent(MotionEvent.ACTION_DOWN)
+        longPressEffect.onTouch(testView, downEvent)
+
+        // THEN the effect moves to the TIMEOUT_WAIT state
+        assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT)
+    }
+
+    @Test
+    fun onActionCancel_whileWaiting_goesIdle() = testWhileWaiting {
+        // GIVEN an action cancel occurs
+        val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+        longPressEffect.onTouch(testView, cancelEvent)
+
+        // THEN the effect goes back to idle and does not start
+        assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+        assertEffectDidNotStart()
+    }
+
+    @Test
+    fun onActionUp_whileWaiting_performsClick() = testWhileWaiting {
+        // GIVEN an action is being collected
+        val action by collectLastValue(longPressEffect.actionType)
+
+        // GIVEN an action up occurs
+        val upEvent = buildMotionEvent(MotionEvent.ACTION_UP)
+        longPressEffect.onTouch(testView, upEvent)
+
+        // THEN the action to invoke is the click action and the effect does not start
+        assertThat(action).isEqualTo(QSLongPressEffect.ActionType.CLICK)
+        assertEffectDidNotStart()
+    }
+
+    @Test
+    fun onWaitComplete_whileWaiting_beginsEffect() = testWhileWaiting {
+        // GIVEN the pressed timeout is complete
+        advanceTimeBy(QSLongPressEffect.PRESSED_TIMEOUT + 10L)
+
+        // THEN the effect starts
+        assertEffectStarted()
+    }
+
+    @Test
+    fun onActionUp_whileEffectHasBegun_reversesEffect() = testWhileRunning {
+        // GIVEN that the effect is at the middle of its completion (progress of 50%)
+        animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+        // WHEN an action up occurs
+        val upEvent = buildMotionEvent(MotionEvent.ACTION_UP)
+        longPressEffect.onTouch(testView, upEvent)
+
+        // THEN the effect gets reversed at 50% progress
+        assertEffectReverses(0.5f)
+    }
+
+    @Test
+    fun onActionCancel_whileEffectHasBegun_reversesEffect() = testWhileRunning {
+        // GIVEN that the effect is at the middle of its completion (progress of 50%)
+        animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+        // WHEN an action cancel occurs
+        val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+        longPressEffect.onTouch(testView, cancelEvent)
+
+        // THEN the effect gets reversed at 50% progress
+        assertEffectReverses(0.5f)
+    }
+
+    @Test
+    fun onAnimationComplete_effectEnds() = testWhileRunning {
+        // GIVEN that the animation completes
+        animatorTestRule.advanceTimeBy(effectDuration + 10L)
+
+        // THEN the long-press effect completes
+        assertEffectCompleted()
+    }
+
+    @Test
+    fun onActionDown_whileRunningBackwards_resets() = testWhileRunning {
+        // GIVEN that the effect is at the middle of its completion (progress of 50%)
+        animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+        // GIVEN an action cancel occurs and the effect gets reversed
+        val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+        longPressEffect.onTouch(testView, cancelEvent)
+
+        // GIVEN an action down occurs
+        val downEvent = buildMotionEvent(MotionEvent.ACTION_DOWN)
+        longPressEffect.onTouch(testView, downEvent)
+
+        // THEN the effect resets
+        assertEffectResets()
+    }
+
+    @Test
+    fun onAnimationComplete_whileRunningBackwards_goesToIdle() = testWhileRunning {
+        // GIVEN that the effect is at the middle of its completion (progress of 50%)
+        animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+        // GIVEN an action cancel occurs and the effect gets reversed
+        val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+        longPressEffect.onTouch(testView, cancelEvent)
+
+        // GIVEN that the animation completes after a sufficient amount of time
+        animatorTestRule.advanceTimeBy(effectDuration.toLong())
+
+        // THEN the state goes to [QSLongPressEffect.State.IDLE]
+        assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+    }
+
+    private fun buildMotionEvent(action: Int): MotionEvent =
+        MotionEventBuilder.newBuilder().setAction(action).build()
+
+    private fun testWithScope(test: suspend TestScope.() -> Unit) =
+        with(kosmos) {
+            testScope.runTest {
+                // GIVEN an effect with a testing scope
+                longPressEffect.scope = CoroutineScope(UnconfinedTestDispatcher(testScheduler))
+
+                // THEN run the test
+                test()
+            }
+        }
+
+    private fun testWhileWaiting(test: suspend TestScope.() -> Unit) =
+        with(kosmos) {
+            testScope.runTest {
+                // GIVEN an effect with a testing scope
+                longPressEffect.scope = CoroutineScope(UnconfinedTestDispatcher(testScheduler))
+
+                // GIVEN the TIMEOUT_WAIT state is entered
+                val downEvent =
+                    MotionEventBuilder.newBuilder().setAction(MotionEvent.ACTION_DOWN).build()
+                longPressEffect.onTouch(testView, downEvent)
+
+                // THEN run the test
+                test()
+            }
+        }
+
+    private fun testWhileRunning(test: suspend TestScope.() -> Unit) =
+        with(kosmos) {
+            testScope.runTest {
+                // GIVEN an effect with a testing scope
+                longPressEffect.scope = CoroutineScope(UnconfinedTestDispatcher(testScheduler))
+
+                // GIVEN the down event that enters the TIMEOUT_WAIT state
+                val downEvent =
+                    MotionEventBuilder.newBuilder().setAction(MotionEvent.ACTION_DOWN).build()
+                longPressEffect.onTouch(testView, downEvent)
+
+                // GIVEN that the timeout completes and the effect starts
+                advanceTimeBy(QSLongPressEffect.PRESSED_TIMEOUT + 10L)
+
+                // THEN run the test
+                test()
+            }
+        }
+
+    /**
+     * Asserts that the effect started by checking that:
+     * 1. The effect progress is 0f
+     * 2. Initial hint haptics are played
+     * 3. The internal state is [QSLongPressEffect.State.RUNNING_FORWARD]
+     */
+    private fun TestScope.assertEffectStarted() {
+        val effectProgress by collectLastValue(longPressEffect.effectProgress)
+        val longPressHint =
+            LongPressHapticBuilder.createLongPressHint(
+                lowTickDuration,
+                spinDuration,
+                effectDuration,
+            )
+
+        assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.RUNNING_FORWARD)
+        assertThat(effectProgress).isEqualTo(0f)
+        assertThat(longPressHint).isNotNull()
+        verify(vibratorHelper).vibrate(longPressHint!!)
+    }
+
+    /**
+     * Asserts that the effect did not start by checking that:
+     * 1. No effect progress is emitted
+     * 2. No haptics are played
+     * 3. The internal state is not [QSLongPressEffect.State.RUNNING_BACKWARDS] or
+     *    [QSLongPressEffect.State.RUNNING_FORWARD]
+     */
+    private fun TestScope.assertEffectDidNotStart() {
+        val effectProgress by collectLastValue(longPressEffect.effectProgress)
+
+        assertThat(longPressEffect.state).isNotEqualTo(QSLongPressEffect.State.RUNNING_FORWARD)
+        assertThat(longPressEffect.state).isNotEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS)
+        assertThat(effectProgress).isNull()
+        verify(vibratorHelper, never()).vibrate(any(/* type= */ VibrationEffect::class.java))
+    }
+
+    /**
+     * Asserts that the effect completes by checking that:
+     * 1. The progress is null
+     * 2. The final snap haptics are played
+     * 3. The internal state goes back to [QSLongPressEffect.State.IDLE]
+     * 4. The action to perform on the tile is the long-press action
+     */
+    private fun TestScope.assertEffectCompleted() {
+        val action by collectLastValue(longPressEffect.actionType)
+        val effectProgress by collectLastValue(longPressEffect.effectProgress)
+        val snapEffect = LongPressHapticBuilder.createSnapEffect()
+
+        assertThat(effectProgress).isNull()
+        assertThat(snapEffect).isNotNull()
+        verify(vibratorHelper).vibrate(snapEffect!!)
+        assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+        assertThat(action).isEqualTo(QSLongPressEffect.ActionType.LONG_PRESS)
+    }
+
+    /**
+     * Assert that the effect gets reverted by checking that:
+     * 1. The internal state is [QSLongPressEffect.State.RUNNING_BACKWARDS]
+     * 2. The reverse haptics plays at the point where the animation was paused
+     */
+    private fun assertEffectReverses(pausedProgress: Float) {
+        val reverseHaptics =
+            LongPressHapticBuilder.createReversedEffect(
+                pausedProgress,
+                lowTickDuration,
+                effectDuration,
+            )
+
+        assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS)
+        assertThat(reverseHaptics).isNotNull()
+        verify(vibratorHelper).vibrate(reverseHaptics!!)
+    }
+
+    /**
+     * Asserts that the effect resets by checking that:
+     * 1. The effect progress resets to 0
+     * 2. The internal state goes back to [QSLongPressEffect.State.TIMEOUT_WAIT]
+     */
+    private fun TestScope.assertEffectResets() {
+        val effectProgress by collectLastValue(longPressEffect.effectProgress)
+        assertThat(effectProgress).isEqualTo(0f)
+
+        assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 128b465..19b80da 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
-import com.android.systemui.common.shared.model.Position
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.doze.DozeMachine
 import com.android.systemui.doze.DozeTransitionCallback
@@ -152,24 +151,6 @@
         }
 
     @Test
-    fun clockPosition() =
-        testScope.runTest {
-            assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
-
-            underTest.setClockPosition(0, 1)
-            assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 1))
-
-            underTest.setClockPosition(1, 9)
-            assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 9))
-
-            underTest.setClockPosition(1, 0)
-            assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 0))
-
-            underTest.setClockPosition(3, 1)
-            assertThat(underTest.clockPosition.value).isEqualTo(Position(3, 1))
-        }
-
-    @Test
     fun dozeTimeTick() =
         testScope.runTest {
             val lastDozeTimeTick by collectLastValue(underTest.dozeTimeTick)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
index fb46ed9d..69a1a0f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.data.repository
 
 import android.graphics.Point
+import android.os.PowerManager.WAKE_REASON_UNKNOWN
 import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -25,18 +26,18 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.powerRepository
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
 import com.android.systemui.statusbar.CircleReveal
 import com.android.systemui.statusbar.LightRevealEffect
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -49,9 +50,10 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class LightRevealScrimRepositoryTest : SysuiTestCase() {
-    private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
-    private lateinit var powerRepository: FakePowerRepository
-    private lateinit var powerInteractor: PowerInteractor
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
+    private val powerRepository = kosmos.powerRepository
     private lateinit var underTest: LightRevealScrimRepositoryImpl
 
     @get:Rule val animatorTestRule = AnimatorTestRule(this)
@@ -59,13 +61,13 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        fakeKeyguardRepository = FakeKeyguardRepository()
-        powerRepository = FakePowerRepository()
-        powerInteractor =
-            PowerInteractorFactory.create(repository = powerRepository).powerInteractor
-
         underTest =
-            LightRevealScrimRepositoryImpl(fakeKeyguardRepository, context, powerInteractor, mock())
+            LightRevealScrimRepositoryImpl(
+                kosmos.fakeKeyguardRepository,
+                context,
+                powerRepository,
+                mock()
+            )
     }
 
     @Test
@@ -73,7 +75,14 @@
         val values = mutableListOf<LightRevealEffect>()
         val job = launch { underTest.revealEffect.collect { values.add(it) } }
 
-        powerInteractor.setAwakeForTest()
+        powerRepository.updateWakefulness(
+            rawState = WakefulnessState.STARTING_TO_WAKE,
+            lastWakeReason = WakeSleepReason.fromPowerManagerWakeReason(WAKE_REASON_UNKNOWN),
+            powerButtonLaunchGestureTriggered =
+                powerRepository.wakefulness.value.powerButtonLaunchGestureTriggered,
+        )
+        powerRepository.updateWakefulness(rawState = WakefulnessState.AWAKE)
+
         // We should initially emit the default reveal effect.
         runCurrent()
         values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT })
@@ -168,9 +177,10 @@
     @Test
     @TestableLooper.RunWithLooper(setAsMainLooper = true)
     fun revealAmount_emitsTo1AfterAnimationStarted() =
-        runTest(UnconfinedTestDispatcher()) {
+        testScope.runTest {
             val value by collectLastValue(underTest.revealAmount)
-            underTest.startRevealAmountAnimator(true)
+            runCurrent()
+            underTest.startRevealAmountAnimator(true, 500L)
             assertEquals(0.0f, value)
             animatorTestRule.advanceTimeBy(500L)
             assertEquals(1.0f, value)
@@ -179,13 +189,14 @@
     @Test
     @TestableLooper.RunWithLooper(setAsMainLooper = true)
     fun revealAmount_startingRevealTwiceWontRerunAnimator() =
-        runTest(UnconfinedTestDispatcher()) {
+        testScope.runTest {
             val value by collectLastValue(underTest.revealAmount)
-            underTest.startRevealAmountAnimator(true)
+            runCurrent()
+            underTest.startRevealAmountAnimator(true, 500L)
             assertEquals(0.0f, value)
             animatorTestRule.advanceTimeBy(250L)
             assertEquals(0.5f, value)
-            underTest.startRevealAmountAnimator(true)
+            underTest.startRevealAmountAnimator(true, 500L)
             animatorTestRule.advanceTimeBy(250L)
             assertEquals(1.0f, value)
         }
@@ -193,12 +204,14 @@
     @Test
     @TestableLooper.RunWithLooper(setAsMainLooper = true)
     fun revealAmount_emitsTo0AfterAnimationStartedReversed() =
-        runTest(UnconfinedTestDispatcher()) {
-            val value by collectLastValue(underTest.revealAmount)
-            underTest.startRevealAmountAnimator(false)
-            assertEquals(1.0f, value)
+        testScope.runTest {
+            val lastValue by collectLastValue(underTest.revealAmount)
+            runCurrent()
+            underTest.startRevealAmountAnimator(true, 500L)
             animatorTestRule.advanceTimeBy(500L)
-            assertEquals(0.0f, value)
+            underTest.startRevealAmountAnimator(false, 500L)
+            animatorTestRule.advanceTimeBy(500L)
+            assertEquals(0.0f, lastValue)
         }
 
     /**
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index ef2b6f0..24c651f3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -18,8 +18,11 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.app.StatusBarManager
+import android.platform.test.annotations.DisableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
@@ -35,8 +38,7 @@
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -64,7 +66,7 @@
     private val shadeRepository = FakeShadeRepository()
     private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
     private val transitionState: MutableStateFlow<ObservableTransitionState> =
-        MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Gone))
+        MutableStateFlow(ObservableTransitionState.Idle(Scenes.Gone))
 
     private val underTest by lazy {
         KeyguardInteractor(
@@ -120,6 +122,7 @@
         }
 
     @Test
+    @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun testKeyguardGuardVisibilityStopsSecureCamera() =
         testScope.runTest {
             val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
@@ -144,6 +147,7 @@
         }
 
     @Test
+    @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun testBouncerShowingResetsSecureCameraState() =
         testScope.runTest {
             val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
@@ -166,6 +170,7 @@
         }
 
     @Test
+    @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun keyguardVisibilityIsDefinedAsKeyguardShowingButNotOccluded() = runTest {
         val isVisible = collectLastValue(underTest.isKeyguardVisible)
         repository.setKeyguardShowing(true)
@@ -250,8 +255,8 @@
             underTest.setAnimateDozingTransitions(true)
             transitionState.value =
                 ObservableTransitionState.Transition(
-                    fromScene = SceneKey.Gone,
-                    toScene = SceneKey.Lockscreen,
+                    fromScene = Scenes.Gone,
+                    toScene = Scenes.Lockscreen,
                     progress = flowOf(0f),
                     isInitiatedByUserInput = false,
                     isUserInputOngoing = flowOf(false),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 63abc8f..0ebcf56 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.dock.DockManager
 import com.android.systemui.dock.DockManagerFake
 import com.android.systemui.flags.FakeFeatureFlags
@@ -49,6 +50,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.FakeSharedPreferences
@@ -57,6 +59,7 @@
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
@@ -80,6 +83,7 @@
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var launchAnimator: DialogTransitionAnimator
     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+    @Mock private lateinit var shadeInteractor: ShadeInteractor
     @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
 
     private lateinit var underTest: KeyguardQuickAffordanceInteractor
@@ -179,6 +183,7 @@
         underTest =
             KeyguardQuickAffordanceInteractor(
                 keyguardInteractor = withDeps.keyguardInteractor,
+                shadeInteractor = shadeInteractor,
                 lockPatternUtils = lockPatternUtils,
                 keyguardStateController = keyguardStateController,
                 userTracker = userTracker,
@@ -193,6 +198,8 @@
                 backgroundDispatcher = testDispatcher,
                 appContext = context,
             )
+
+        whenever(shadeInteractor.anyExpansion).thenReturn(MutableStateFlow(0f))
     }
 
     @Test
@@ -339,6 +346,25 @@
         }
 
     @Test
+    fun quickAffordance_updateOncePerShadeExpansion() =
+        testScope.runTest {
+            val shadeExpansion = MutableStateFlow(0f)
+            whenever(shadeInteractor.anyExpansion).thenReturn(shadeExpansion)
+
+            val collectedValue by
+                collectValues(
+                    underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+                )
+
+            val initialSize = collectedValue.size
+            for (i in 0..10) {
+                shadeExpansion.value = i / 10f
+            }
+
+            assertThat(collectedValue.size).isEqualTo(initialSize + 1)
+        }
+
+    @Test
     fun quickAffordanceAlwaysVisible_notVisible_restrictedByPolicyManager() =
         testScope.runTest {
             whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 3484025..cd4db2f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -79,34 +79,66 @@
     }
 
     @Test
-    fun dozeAmountTransitionTest() = runTest {
-        val dozeAmountSteps by collectValues(underTest.dozeAmountTransition)
+    fun dozeAmountTransitionTest_AodToFromLockscreen() =
+        testScope.runTest {
+            val dozeAmountSteps by collectValues(underTest.dozeAmountTransition)
 
-        val steps = mutableListOf<TransitionStep>()
+            val steps = mutableListOf<TransitionStep>()
 
-        steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
-        steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
-        steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
-        steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
-        steps.add(TransitionStep(LOCKSCREEN, AOD, 0.8f, RUNNING))
-        steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
-        steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0.8f, RUNNING))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
 
-        steps.forEach {
-            repository.sendTransitionStep(it)
-            runCurrent()
+            steps.forEach {
+                repository.sendTransitionStep(it)
+                runCurrent()
+            }
+
+            assertThat(dozeAmountSteps.subList(0, 3))
+                .isEqualTo(
+                    listOf(
+                        steps[0].copy(value = 1f - steps[0].value),
+                        steps[1].copy(value = 1f - steps[1].value),
+                        steps[2].copy(value = 1f - steps[2].value),
+                    )
+                )
+            assertThat(dozeAmountSteps.subList(3, 7)).isEqualTo(steps.subList(3, 7))
         }
 
-        assertThat(dozeAmountSteps.subList(0, 3))
-            .isEqualTo(
-                listOf(
-                    steps[0].copy(value = 1f - steps[0].value),
-                    steps[1].copy(value = 1f - steps[1].value),
-                    steps[2].copy(value = 1f - steps[2].value),
+    @Test
+    fun dozeAmountTransitionTest_AodToFromGone() =
+        testScope.runTest {
+            val dozeAmountSteps by collectValues(underTest.dozeAmountTransition)
+
+            val steps = mutableListOf<TransitionStep>()
+
+            steps.add(TransitionStep(AOD, GONE, 0f, STARTED))
+            steps.add(TransitionStep(AOD, GONE, 0.3f, RUNNING))
+            steps.add(TransitionStep(AOD, GONE, 1f, FINISHED))
+            steps.add(TransitionStep(GONE, AOD, 0f, STARTED))
+            steps.add(TransitionStep(GONE, AOD, 0.1f, RUNNING))
+            steps.add(TransitionStep(GONE, AOD, 0.3f, RUNNING))
+            steps.add(TransitionStep(GONE, AOD, 1f, FINISHED))
+
+            steps.forEach {
+                repository.sendTransitionStep(it)
+                runCurrent()
+            }
+
+            assertThat(dozeAmountSteps.subList(0, 3))
+                .isEqualTo(
+                    listOf(
+                        steps[0].copy(value = 1f - steps[0].value),
+                        steps[1].copy(value = 1f - steps[1].value),
+                        steps[2].copy(value = 1f - steps[2].value),
+                    )
                 )
-            )
-        assertThat(dozeAmountSteps.subList(3, 7)).isEqualTo(steps.subList(3, 7))
-    }
+            assertThat(dozeAmountSteps.subList(3, 7)).isEqualTo(steps.subList(3, 7))
+        }
 
     @Test
     fun finishedKeyguardStateTests() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
index 2b6e6c7..3e0a1f3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -24,7 +24,6 @@
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.kosmos.testScope
 import com.android.systemui.statusbar.LightRevealEffect
 import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.testKosmos
@@ -50,7 +49,6 @@
     private val fakeLightRevealScrimRepository = kosmos.fakeLightRevealScrimRepository
 
     private val fakeKeyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
-    private val testScope = kosmos.testScope
 
     private val underTest = kosmos.lightRevealScrimInteractor
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelTest.kt
new file mode 100644
index 0000000..87d1cd5
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelTest.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.hardware.fingerprint.FingerprintManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.shared.model.FaceFailureMessage
+import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FingerprintFailureMessage
+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.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AlternateBouncerMessageAreaViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val fingerprintAuthRepository by lazy {
+        kosmos.fakeDeviceEntryFingerprintAuthRepository
+    }
+    private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository }
+    private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
+    private val biometricSettingsRepository by lazy { kosmos.fakeBiometricSettingsRepository }
+    private val underTest: AlternateBouncerMessageAreaViewModel =
+        kosmos.alternateBouncerMessageAreaViewModel
+
+    @Before
+    fun setUp() {
+        biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+        biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
+    }
+
+    @Test
+    fun noInitialValue() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+            bouncerRepository.setAlternateVisible(true)
+            assertThat(message).isNull()
+        }
+
+    @Test
+    fun fingerprintMessage() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+            bouncerRepository.setAlternateVisible(true)
+            runCurrent()
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+            assertThat(message).isInstanceOf(FingerprintFailureMessage::class.java)
+        }
+
+    @Test
+    fun fingerprintLockoutMessage_notShown() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+            bouncerRepository.setAlternateVisible(true)
+            runCurrent()
+            fingerprintAuthRepository.setAuthenticationStatus(
+                ErrorFingerprintAuthenticationStatus(
+                    msgId = FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
+                    msg = "test lockout",
+                )
+            )
+            assertThat(message).isNull()
+        }
+
+    @Test
+    fun alternateBouncerNotVisible_messagesNeverShow() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+            bouncerRepository.setAlternateVisible(false)
+            runCurrent()
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+            assertThat(message).isNull()
+
+            faceAuthRepository.setAuthenticationStatus(FailedFaceAuthenticationStatus())
+            assertThat(message).isNull()
+        }
+
+    @Test
+    fun faceFailMessage() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+            bouncerRepository.setAlternateVisible(true)
+            runCurrent()
+            faceAuthRepository.setAuthenticationStatus(FailedFaceAuthenticationStatus())
+            assertThat(message).isInstanceOf(FaceFailureMessage::class.java)
+        }
+
+    @Test
+    fun faceThenFingerprintMessage() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+            bouncerRepository.setAlternateVisible(true)
+            runCurrent()
+            faceAuthRepository.setAuthenticationStatus(FailedFaceAuthenticationStatus())
+            assertThat(message).isInstanceOf(FaceFailureMessage::class.java)
+
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+            assertThat(message).isInstanceOf(FingerprintFailureMessage::class.java)
+        }
+
+    @Test
+    fun fingerprintMessagePreventsFaceMessageFromShowing() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+            bouncerRepository.setAlternateVisible(true)
+            runCurrent()
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+            assertThat(message).isInstanceOf(FingerprintFailureMessage::class.java)
+
+            faceAuthRepository.setAuthenticationStatus(FailedFaceAuthenticationStatus())
+            assertThat(message).isInstanceOf(FingerprintFailureMessage::class.java)
+        }
+
+    @Test
+    fun fingerprintMessageAllowsFaceMessageAfter4000ms() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+            bouncerRepository.setAlternateVisible(true)
+            runCurrent()
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+            assertThat(message).isInstanceOf(FingerprintFailureMessage::class.java)
+
+            advanceTimeBy(4000)
+
+            faceAuthRepository.setAuthenticationStatus(FailedFaceAuthenticationStatus())
+            assertThat(message).isInstanceOf(FaceFailureMessage::class.java)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
index b0f59fe..de659cf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -71,7 +71,7 @@
         mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
 
         MockitoAnnotations.initMocks(this)
-        whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow)
+        whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow)
         kosmos.burnInInteractor = burnInInteractor
         whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt()))
             .thenReturn(emptyFlow())
@@ -81,18 +81,18 @@
     }
 
     @Test
-    fun translationY_initializedToZero() =
+    fun movement_initializedToZero() =
         testScope.runTest {
-            val translationY by collectLastValue(underTest.translationY(burnInParameters))
-            assertThat(translationY).isEqualTo(0)
+            val movement by collectLastValue(underTest.movement(burnInParameters))
+            assertThat(movement?.translationY).isEqualTo(0)
+            assertThat(movement?.translationX).isEqualTo(0)
+            assertThat(movement?.scale).isEqualTo(0f)
         }
 
     @Test
     fun translationAndScale_whenNotDozing() =
         testScope.runTest {
-            val translationX by collectLastValue(underTest.translationX(burnInParameters))
-            val translationY by collectLastValue(underTest.translationY(burnInParameters))
-            val scale by collectLastValue(underTest.scale(burnInParameters))
+            val movement by collectLastValue(underTest.movement(burnInParameters))
 
             // Set to not dozing (on lockscreen)
             keyguardTransitionRepository.sendTransitionStep(
@@ -113,24 +113,17 @@
                     scale = 0.5f,
                 )
 
-            assertThat(translationX).isEqualTo(0)
-            assertThat(translationY).isEqualTo(0)
-            assertThat(scale)
-                .isEqualTo(
-                    BurnInScaleViewModel(
-                        scale = 1f,
-                        scaleClockOnly = true,
-                    )
-                )
+            assertThat(movement?.translationX).isEqualTo(0)
+            assertThat(movement?.translationY).isEqualTo(0)
+            assertThat(movement?.scale).isEqualTo(1f)
+            assertThat(movement?.scaleClockOnly).isEqualTo(true)
         }
 
     @Test
     fun translationAndScale_whenFullyDozing() =
         testScope.runTest {
             burnInParameters = burnInParameters.copy(minViewY = 100)
-            val translationX by collectLastValue(underTest.translationX(burnInParameters))
-            val translationY by collectLastValue(underTest.translationY(burnInParameters))
-            val scale by collectLastValue(underTest.scale(burnInParameters))
+            val movement by collectLastValue(underTest.movement(burnInParameters))
 
             // Set to dozing (on AOD)
             keyguardTransitionRepository.sendTransitionStep(
@@ -150,15 +143,10 @@
                     scale = 0.5f,
                 )
 
-            assertThat(translationX).isEqualTo(20)
-            assertThat(translationY).isEqualTo(30)
-            assertThat(scale)
-                .isEqualTo(
-                    BurnInScaleViewModel(
-                        scale = 0.5f,
-                        scaleClockOnly = true,
-                    )
-                )
+            assertThat(movement?.translationX).isEqualTo(20)
+            assertThat(movement?.translationY).isEqualTo(30)
+            assertThat(movement?.scale).isEqualTo(0.5f)
+            assertThat(movement?.scaleClockOnly).isEqualTo(true)
 
             // Set to the beginning of GONE->AOD transition
             keyguardTransitionRepository.sendTransitionStep(
@@ -170,15 +158,10 @@
                 ),
                 validateStep = false,
             )
-            assertThat(translationX).isEqualTo(0)
-            assertThat(translationY).isEqualTo(0)
-            assertThat(scale)
-                .isEqualTo(
-                    BurnInScaleViewModel(
-                        scale = 1f,
-                        scaleClockOnly = true,
-                    )
-                )
+            assertThat(movement?.translationX).isEqualTo(0)
+            assertThat(movement?.translationY).isEqualTo(0)
+            assertThat(movement?.scale).isEqualTo(1f)
+            assertThat(movement?.scaleClockOnly).isEqualTo(true)
         }
 
     @Test
@@ -191,9 +174,7 @@
                     minViewY = 100,
                     topInset = 80,
                 )
-            val translationX by collectLastValue(underTest.translationX(burnInParameters))
-            val translationY by collectLastValue(underTest.translationY(burnInParameters))
-            val scale by collectLastValue(underTest.scale(burnInParameters))
+            val movement by collectLastValue(underTest.movement(burnInParameters))
 
             // Set to dozing (on AOD)
             keyguardTransitionRepository.sendTransitionStep(
@@ -213,16 +194,11 @@
                     translationY = -30,
                     scale = 0.5f,
                 )
-            assertThat(translationX).isEqualTo(20)
+            assertThat(movement?.translationX).isEqualTo(20)
             // -20 instead of -30, due to inset of 80
-            assertThat(translationY).isEqualTo(-20)
-            assertThat(scale)
-                .isEqualTo(
-                    BurnInScaleViewModel(
-                        scale = 0.5f,
-                        scaleClockOnly = true,
-                    )
-                )
+            assertThat(movement?.translationY).isEqualTo(-20)
+            assertThat(movement?.scale).isEqualTo(0.5f)
+            assertThat(movement?.scaleClockOnly).isEqualTo(true)
 
             // Set to the beginning of GONE->AOD transition
             keyguardTransitionRepository.sendTransitionStep(
@@ -234,15 +210,10 @@
                 ),
                 validateStep = false,
             )
-            assertThat(translationX).isEqualTo(0)
-            assertThat(translationY).isEqualTo(0)
-            assertThat(scale)
-                .isEqualTo(
-                    BurnInScaleViewModel(
-                        scale = 1f,
-                        scaleClockOnly = true,
-                    )
-                )
+            assertThat(movement?.translationX).isEqualTo(0)
+            assertThat(movement?.translationY).isEqualTo(0)
+            assertThat(movement?.scale).isEqualTo(1f)
+            assertThat(movement?.scaleClockOnly).isEqualTo(true)
         }
 
     @Test
@@ -255,9 +226,7 @@
                     minViewY = 100,
                     topInset = 80,
                 )
-            val translationX by collectLastValue(underTest.translationX(burnInParameters))
-            val translationY by collectLastValue(underTest.translationY(burnInParameters))
-            val scale by collectLastValue(underTest.scale(burnInParameters))
+            val movement by collectLastValue(underTest.movement(burnInParameters))
 
             // Set to dozing (on AOD)
             keyguardTransitionRepository.sendTransitionStep(
@@ -277,16 +246,11 @@
                     translationY = -30,
                     scale = 0.5f,
                 )
-            assertThat(translationX).isEqualTo(20)
+            assertThat(movement?.translationX).isEqualTo(20)
             // -20 instead of -30, due to inset of 80
-            assertThat(translationY).isEqualTo(-20)
-            assertThat(scale)
-                .isEqualTo(
-                    BurnInScaleViewModel(
-                        scale = 0.5f,
-                        scaleClockOnly = true,
-                    )
-                )
+            assertThat(movement?.translationY).isEqualTo(-20)
+            assertThat(movement?.scale).isEqualTo(0.5f)
+            assertThat(movement?.scaleClockOnly).isEqualTo(true)
 
             // Set to the beginning of GONE->AOD transition
             keyguardTransitionRepository.sendTransitionStep(
@@ -298,15 +262,10 @@
                 ),
                 validateStep = false,
             )
-            assertThat(translationX).isEqualTo(0)
-            assertThat(translationY).isEqualTo(0)
-            assertThat(scale)
-                .isEqualTo(
-                    BurnInScaleViewModel(
-                        scale = 1f,
-                        scaleClockOnly = true,
-                    )
-                )
+            assertThat(movement?.translationX).isEqualTo(0)
+            assertThat(movement?.translationY).isEqualTo(0)
+            assertThat(movement?.scale).isEqualTo(1f)
+            assertThat(movement?.scaleClockOnly).isEqualTo(true)
         }
 
     @Test
@@ -314,9 +273,7 @@
         testScope.runTest {
             whenever(clockController.config.useAlternateSmartspaceAODTransition).thenReturn(true)
 
-            val translationX by collectLastValue(underTest.translationX(burnInParameters))
-            val translationY by collectLastValue(underTest.translationY(burnInParameters))
-            val scale by collectLastValue(underTest.scale(burnInParameters))
+            val movement by collectLastValue(underTest.movement(burnInParameters))
 
             // Set to dozing (on AOD)
             keyguardTransitionRepository.sendTransitionStep(
@@ -337,8 +294,9 @@
                     scale = 0.5f,
                 )
 
-            assertThat(translationX).isEqualTo(0)
-            assertThat(translationY).isEqualTo(0)
-            assertThat(scale).isEqualTo(BurnInScaleViewModel(scale = 0.5f, scaleClockOnly = false))
+            assertThat(movement?.translationX).isEqualTo(0)
+            assertThat(movement?.translationY).isEqualTo(0)
+            assertThat(movement?.scale).isEqualTo(0.5f)
+            assertThat(movement?.scaleClockOnly).isEqualTo(false)
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt
similarity index 82%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt
index 4c972e9..a3371d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt
@@ -42,6 +42,25 @@
     val underTest = kosmos.aodToGoneTransitionViewModel
 
     @Test
+    fun lockscreenAlpha() =
+        testScope.runTest {
+            val viewState = ViewStateAccessor(alpha = { 0.5f })
+            val alpha by collectValues(underTest.lockscreenAlpha(viewState))
+
+            repository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.GONE,
+                testScope
+            )
+
+            assertThat(alpha[0]).isEqualTo(0.5f)
+            // Fades out just prior to halfway
+            assertThat(alpha[1]).isEqualTo(0f)
+            // Must finish at 0
+            assertThat(alpha[2]).isEqualTo(0f)
+        }
+
+    @Test
     fun deviceEntryParentViewHides() =
         testScope.runTest {
             val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelTest.kt
index 7e937db..79671b8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelTest.kt
@@ -51,6 +51,25 @@
     }
 
     @Test
+    fun lockscreenAlpha() =
+        testScope.runTest {
+            val viewState = ViewStateAccessor(alpha = { 0.6f })
+            val alpha by collectValues(underTest.lockscreenAlpha(viewState))
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.DOZING,
+                to = KeyguardState.GONE,
+                testScope
+            )
+
+            assertThat(alpha[0]).isEqualTo(0.6f)
+            // Fades out just prior to halfway
+            assertThat(alpha[1]).isEqualTo(0f)
+            // Must finish at 0
+            assertThat(alpha[2]).isEqualTo(0f)
+        }
+
+    @Test
     fun deviceEntryParentViewDisappear() =
         testScope.runTest {
             val values by collectValues(underTest.deviceEntryParentViewAlpha)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index 9da34da..e34edb4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -26,14 +26,9 @@
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
-import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
-import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
-import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -219,37 +214,6 @@
             values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
         }
 
-    @Test
-    fun transitionEnded() =
-        testScope.runTest {
-            val values by collectValues(underTest.transitionEnded)
-
-            keyguardTransitionRepository.sendTransitionSteps(
-                listOf(
-                    TransitionStep(DOZING, DREAMING, 0.0f, STARTED),
-                    TransitionStep(DOZING, DREAMING, 1.0f, FINISHED),
-                    TransitionStep(DREAMING, LOCKSCREEN, 0.0f, STARTED),
-                    TransitionStep(DREAMING, LOCKSCREEN, 0.1f, RUNNING),
-                    TransitionStep(DREAMING, LOCKSCREEN, 1.0f, FINISHED),
-                    TransitionStep(LOCKSCREEN, DREAMING, 0.0f, STARTED),
-                    TransitionStep(LOCKSCREEN, DREAMING, 0.5f, RUNNING),
-                    TransitionStep(LOCKSCREEN, DREAMING, 1.0f, FINISHED),
-                    TransitionStep(DREAMING, GONE, 0.0f, STARTED),
-                    TransitionStep(DREAMING, GONE, 0.5f, RUNNING),
-                    TransitionStep(DREAMING, GONE, 1.0f, CANCELED),
-                    TransitionStep(DREAMING, AOD, 0.0f, STARTED),
-                    TransitionStep(DREAMING, AOD, 1.0f, FINISHED),
-                ),
-                testScope,
-            )
-
-            assertThat(values.size).isEqualTo(3)
-            values.forEach {
-                assertThat(it.transitionState == FINISHED || it.transitionState == CANCELED)
-                    .isTrue()
-            }
-        }
-
     private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
         return TransitionStep(
             from = DREAMING,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
new file mode 100644
index 0000000..04c270d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags as AConfigFlags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardIndicationAreaViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
+    @Mock private lateinit var shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel
+
+    @Mock private lateinit var burnInInteractor: BurnInInteractor
+    private val burnInFlow = MutableStateFlow(BurnInModel())
+
+    private lateinit var bottomAreaInteractor: KeyguardBottomAreaInteractor
+    private lateinit var underTest: KeyguardIndicationAreaViewModel
+    private lateinit var repository: FakeKeyguardRepository
+
+    private val startButtonFlow =
+        MutableStateFlow<KeyguardQuickAffordanceViewModel>(
+            KeyguardQuickAffordanceViewModel(
+                slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()
+            )
+        )
+    private val endButtonFlow =
+        MutableStateFlow<KeyguardQuickAffordanceViewModel>(
+            KeyguardQuickAffordanceViewModel(
+                slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId()
+            )
+        )
+    private val alphaFlow = MutableStateFlow<Float>(1f)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
+        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+
+        whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
+            .thenReturn(RETURNED_BURN_IN_OFFSET)
+        whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow)
+
+        val withDeps = KeyguardInteractorFactory.create()
+        val keyguardInteractor = withDeps.keyguardInteractor
+        repository = withDeps.repository
+
+        val bottomAreaViewModel: KeyguardBottomAreaViewModel = mock()
+        whenever(bottomAreaViewModel.startButton).thenReturn(startButtonFlow)
+        whenever(bottomAreaViewModel.endButton).thenReturn(endButtonFlow)
+        whenever(bottomAreaViewModel.alpha).thenReturn(alphaFlow)
+        bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository)
+        underTest =
+            KeyguardIndicationAreaViewModel(
+                keyguardInteractor = keyguardInteractor,
+                bottomAreaInteractor = bottomAreaInteractor,
+                keyguardBottomAreaViewModel = bottomAreaViewModel,
+                burnInHelperWrapper = burnInHelperWrapper,
+                burnInInteractor = burnInInteractor,
+                shortcutsCombinedViewModel = shortcutsCombinedViewModel,
+                configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
+            )
+    }
+
+    @Test
+    fun alpha() =
+        testScope.runTest {
+            val value = collectLastValue(underTest.alpha)
+
+            assertThat(value()).isEqualTo(1f)
+            alphaFlow.value = 0.1f
+            assertThat(value()).isEqualTo(0.1f)
+            alphaFlow.value = 0.5f
+            assertThat(value()).isEqualTo(0.5f)
+            alphaFlow.value = 0.2f
+            assertThat(value()).isEqualTo(0.2f)
+            alphaFlow.value = 0f
+            assertThat(value()).isEqualTo(0f)
+        }
+
+    @Test
+    fun isIndicationAreaPadded() =
+        testScope.runTest {
+            repository.setKeyguardShowing(true)
+            val value = collectLastValue(underTest.isIndicationAreaPadded)
+
+            assertThat(value()).isFalse()
+            startButtonFlow.value = startButtonFlow.value.copy(isVisible = true)
+            assertThat(value()).isTrue()
+            endButtonFlow.value = endButtonFlow.value.copy(isVisible = true)
+            assertThat(value()).isTrue()
+            startButtonFlow.value = startButtonFlow.value.copy(isVisible = false)
+            assertThat(value()).isTrue()
+            endButtonFlow.value = endButtonFlow.value.copy(isVisible = false)
+            assertThat(value()).isFalse()
+        }
+
+    @Test
+    fun indicationAreaTranslationX() =
+        testScope.runTest {
+            val value = collectLastValue(underTest.indicationAreaTranslationX)
+
+            assertThat(value()).isEqualTo(0f)
+            bottomAreaInteractor.setClockPosition(100, 100)
+            assertThat(value()).isEqualTo(100f)
+            bottomAreaInteractor.setClockPosition(200, 100)
+            assertThat(value()).isEqualTo(200f)
+            bottomAreaInteractor.setClockPosition(200, 200)
+            assertThat(value()).isEqualTo(200f)
+            bottomAreaInteractor.setClockPosition(300, 100)
+            assertThat(value()).isEqualTo(300f)
+        }
+
+    @Test
+    fun indicationAreaTranslationY() =
+        testScope.runTest {
+            val value =
+                collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET))
+
+            // Negative 0 - apparently there's a difference in floating point arithmetic - FML
+            assertThat(value()).isEqualTo(-0f)
+            val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f)
+            assertThat(value()).isEqualTo(expected1)
+            val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f)
+            assertThat(value()).isEqualTo(expected2)
+            val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f)
+            assertThat(value()).isEqualTo(expected3)
+            val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f)
+            assertThat(value()).isEqualTo(expected4)
+        }
+
+    private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
+        repository.setDozeAmount(dozeAmount)
+        return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
+    }
+
+    companion object {
+        private const val DEFAULT_BURN_IN_OFFSET = 5
+        private const val RETURNED_BURN_IN_OFFSET = 3
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 503fd34..979d504 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -22,12 +22,12 @@
 import android.view.View
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.communalRepository
-import com.android.systemui.communal.shared.model.CommunalSceneKey
-import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
 import com.android.systemui.flags.Flags
@@ -262,7 +262,7 @@
 
             // Hub transition state is idle with hub open.
             communalRepository.setTransitionState(
-                flowOf(ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal))
+                flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
             )
             runCurrent()
 
@@ -328,4 +328,42 @@
             shadeRepository.setQsExpansion(0.5f)
             assertThat(alpha).isEqualTo(0f)
         }
+
+    @Test
+    fun alpha_idleOnOccluded_isZero() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.alpha(viewState))
+            assertThat(alpha).isEqualTo(1f)
+
+            // Go to OCCLUDED state
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.OCCLUDED,
+                testScope = testScope,
+            )
+            assertThat(alpha).isEqualTo(0f)
+
+            // Try pulling down shade and ensure the value doesn't change
+            shadeRepository.setQsExpansion(0.5f)
+            assertThat(alpha).isEqualTo(0f)
+        }
+
+    @Test
+    fun alpha_idleOnGone_isZero() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.alpha(viewState))
+            assertThat(alpha).isEqualTo(1f)
+
+            // Go to GONE state
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                testScope = testScope,
+            )
+            assertThat(alpha).isEqualTo(0f)
+
+            // Try pulling down shade and ensure the value doesn't change
+            shadeRepository.setQsExpansion(0.5f)
+            assertThat(alpha).isEqualTo(0f)
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index 0b80ff8..ad1cef1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -36,7 +37,6 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
 class LockscreenContentViewModelTest : SysuiTestCase() {
 
     private val kosmos: Kosmos = testKosmos()
@@ -47,6 +47,7 @@
     fun setup() {
         with(kosmos) {
             fakeFeatureFlagsClassic.set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, true)
+            overrideResource(R.bool.config_use_split_notification_shade, false)
             underTest = lockscreenContentViewModel
         }
     }
@@ -88,11 +89,21 @@
         }
 
     @Test
+    fun areNotificationsVisible_splitShadeTrue_true() =
+        with(kosmos) {
+            testScope.runTest {
+                overrideResource(R.bool.config_use_split_notification_shade, true)
+                kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
+
+                assertThat(underTest.areNotificationsVisible(context.resources)).isTrue()
+            }
+        }
+    @Test
     fun areNotificationsVisible_withSmallClock_true() =
         with(kosmos) {
             testScope.runTest {
                 kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL)
-                assertThat(underTest.areNotificationsVisible).isTrue()
+                assertThat(underTest.areNotificationsVisible(context.resources)).isTrue()
             }
         }
 
@@ -101,7 +112,7 @@
         with(kosmos) {
             testScope.runTest {
                 kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
-                assertThat(underTest.areNotificationsVisible).isFalse()
+                assertThat(underTest.areNotificationsVisible(context.resources)).isFalse()
             }
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 3104842..2fd2ef1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -19,9 +19,12 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import android.platform.test.annotations.EnableFlags
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -32,64 +35,128 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.BeforeClass
 import org.junit.Test
 import org.junit.runner.RunWith
+import platform.test.runner.parameterized.Parameter
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 class LockscreenSceneViewModelTest : SysuiTestCase() {
 
+    companion object {
+        @Parameters(
+            name =
+                "canSwipeToEnter={0}, downWithTwoPointers={1}, downFromEdge={2}," +
+                    " isSingleShade={3}, isCommunalAvailable={4}"
+        )
+        @JvmStatic
+        fun combinations() = buildList {
+            repeat(32) { combination ->
+                add(
+                    arrayOf(
+                        /* canSwipeToEnter= */ combination and 1 != 0,
+                        /* downWithTwoPointers= */ combination and 2 != 0,
+                        /* downFromEdge= */ combination and 4 != 0,
+                        /* isSingleShade= */ combination and 8 != 0,
+                        /* isCommunalAvailable= */ combination and 16 != 0,
+                    )
+                )
+            }
+        }
+
+        @JvmStatic
+        @BeforeClass
+        fun setUp() {
+            val combinationStrings =
+                combinations().map { array ->
+                    check(array.size == 5)
+                    "${array[4]},${array[3]},${array[2]},${array[1]},${array[0]}"
+                }
+            val uniqueCombinations = combinationStrings.toSet()
+            assertThat(combinationStrings).hasSize(uniqueCombinations.size)
+        }
+
+        private fun expectedDownDestination(
+            downFromEdge: Boolean,
+            isSingleShade: Boolean,
+        ): SceneKey {
+            return if (downFromEdge && isSingleShade) Scenes.QuickSettings else Scenes.Shade
+        }
+    }
+
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val sceneInteractor by lazy { kosmos.sceneInteractor }
 
+    @JvmField @Parameter(0) var canSwipeToEnter: Boolean = false
+    @JvmField @Parameter(1) var downWithTwoPointers: Boolean = false
+    @JvmField @Parameter(2) var downFromEdge: Boolean = false
+    @JvmField @Parameter(3) var isSingleShade: Boolean = true
+    @JvmField @Parameter(4) var isCommunalAvailable: Boolean = false
+
     private val underTest by lazy { createLockscreenSceneViewModel() }
 
     @Test
-    fun upTransitionSceneKey_canSwipeToUnlock_gone() =
+    @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
+    fun destinationScenes() =
         testScope.runTest {
-            val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.None
-            )
             kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
-            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
-            sceneInteractor.changeScene(SceneKey.Lockscreen, "reason")
-
-            assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
-        }
-
-    @Test
-    fun upTransitionSceneKey_cannotSwipeToUnlock_bouncer() =
-        testScope.runTest {
-            val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Pin
+                if (canSwipeToEnter) {
+                    AuthenticationMethodModel.None
+                } else {
+                    AuthenticationMethodModel.Pin
+                }
             )
-            kosmos.fakeDeviceEntryRepository.setUnlocked(false)
-            sceneInteractor.changeScene(SceneKey.Lockscreen, "reason")
+            kosmos.fakeDeviceEntryRepository.setUnlocked(canSwipeToEnter)
+            sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+            kosmos.shadeRepository.setShadeMode(
+                if (isSingleShade) {
+                    ShadeMode.Single
+                } else {
+                    ShadeMode.Split
+                }
+            )
+            kosmos.setCommunalAvailable(isCommunalAvailable)
 
-            assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
-        }
+            val destinationScenes by collectLastValue(underTest.destinationScenes)
 
-    @EnableFlags(FLAG_COMMUNAL_HUB)
-    @Test
-    fun leftTransitionSceneKey_communalIsAvailable_communal() =
-        testScope.runTest {
-            val leftDestinationSceneKey by collectLastValue(underTest.leftDestinationSceneKey)
-            assertThat(leftDestinationSceneKey).isNull()
+            assertThat(
+                    destinationScenes
+                        ?.get(
+                            Swipe(
+                                SwipeDirection.Down,
+                                fromSource = Edge.Top.takeIf { downFromEdge },
+                                pointerCount = if (downWithTwoPointers) 2 else 1,
+                            )
+                        )
+                        ?.toScene
+                )
+                .isEqualTo(
+                    expectedDownDestination(
+                        downFromEdge = downFromEdge,
+                        isSingleShade = isSingleShade,
+                    )
+                )
 
-            kosmos.setCommunalAvailable(true)
-            runCurrent()
-            assertThat(leftDestinationSceneKey).isEqualTo(SceneKey.Communal)
+            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
+                .isEqualTo(if (canSwipeToEnter) Scenes.Gone else Scenes.Bouncer)
+
+            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Left))?.toScene)
+                .isEqualTo(Scenes.Communal.takeIf { isCommunalAvailable })
         }
 
     private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
@@ -102,6 +169,7 @@
                     interactor = mock(),
                 ),
             notifications = kosmos.notificationsPlaceholderViewModel,
+            shadeInteractor = kosmos.shadeInteractor,
         )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
index e139466..bef9515 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
@@ -36,10 +36,10 @@
 import com.android.systemui.testKosmos
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
-import kotlin.test.Test
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Test
 import org.junit.runner.RunWith
 
 @ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModelTest.kt
index 28f5eba..86b3f33 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModelTest.kt
@@ -102,6 +102,24 @@
             values.forEach { assertThat(it).isEqualTo(0f) }
         }
 
+
+    @Test
+    fun lockscreenAlphaFadesOutAndFinishesVisible() =
+        testScope.runTest {
+            val alpha by collectValues(underTest.lockscreenAlpha)
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.DOZING,
+                testScope,
+            )
+
+            assertThat(alpha[0]).isEqualTo(1f)
+            // Halfway through, it will have faded out
+            assertThat(alpha[1]).isEqualTo(0f)
+            // FINISHED alpha should be visible, to support pulsing
+            assertThat(alpha[2]).isEqualTo(1f)
+        }
+
     @Test
     fun deviceEntryBackgroundViewDisappear() =
         testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
index 7a564ac..43ab93a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
@@ -33,10 +33,10 @@
 import com.android.systemui.testKosmos
 import com.google.common.collect.Range
 import com.google.common.truth.Truth
-import kotlin.test.Test
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Test
 import org.junit.runner.RunWith
 
 @ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 47e1ee9..db1d5d9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -149,6 +149,42 @@
             values.forEach { assertThat(it).isEqualTo(1f) }
         }
 
+    @Test
+    fun notificationAlpha() =
+        testScope.runTest {
+            val values by collectValues(underTest.notificationAlpha)
+            runCurrent()
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.PRIMARY_BOUNCER,
+                to = KeyguardState.GONE,
+                testScope,
+            )
+
+            assertThat(values[0]).isEqualTo(1f)
+            // Should fade to zero between here
+            assertThat(values[1]).isEqualTo(0f)
+        }
+
+    @Test
+    fun notificationAlpha_leaveShadeOpen() =
+        testScope.runTest {
+            val values by collectValues(underTest.notificationAlpha)
+            runCurrent()
+
+            sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(true)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.PRIMARY_BOUNCER,
+                to = KeyguardState.GONE,
+                testScope,
+            )
+
+            assertThat(values.size).isEqualTo(2)
+            // Shade stays open, and alpha should remain visible
+            values.forEach { assertThat(it).isEqualTo(1f) }
+        }
+
     private fun step(
         value: Float,
         state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/domain/interactor/SpatializerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/domain/interactor/SpatializerInteractorTest.kt
new file mode 100644
index 0000000..a932dd6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/domain/interactor/SpatializerInteractorTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.domain.interactor
+
+import android.media.AudioDeviceAttributes
+import android.media.AudioDeviceInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.media.domain.interactor.SpatializerInteractor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.spatializerRepository
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SpatializerInteractorTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val underTest = SpatializerInteractor(kosmos.spatializerRepository)
+
+    @Test
+    fun setSpatialAudioEnabledFalse_isEnabled_false() {
+        with(kosmos) {
+            testScope.runTest {
+                underTest.setSpatialAudioEnabled(deviceAttributes, false)
+
+                assertThat(underTest.isSpatialAudioEnabled(deviceAttributes)).isFalse()
+            }
+        }
+    }
+
+    @Test
+    fun setSpatialAudioEnabledTrue_isEnabled_true() {
+        with(kosmos) {
+            testScope.runTest {
+                underTest.setSpatialAudioEnabled(deviceAttributes, true)
+
+                assertThat(underTest.isSpatialAudioEnabled(deviceAttributes)).isTrue()
+            }
+        }
+    }
+
+    @Test
+    fun setHeadTrackingEnabledFalse_isEnabled_false() {
+        with(kosmos) {
+            testScope.runTest {
+                underTest.setHeadTrackingEnabled(deviceAttributes, false)
+
+                assertThat(underTest.isHeadTrackingEnabled(deviceAttributes)).isFalse()
+            }
+        }
+    }
+
+    @Test
+    fun setHeadTrackingEnabledTrue_isEnabled_true() {
+        with(kosmos) {
+            testScope.runTest {
+                underTest.setHeadTrackingEnabled(deviceAttributes, true)
+
+                assertThat(underTest.isHeadTrackingEnabled(deviceAttributes)).isTrue()
+            }
+        }
+    }
+
+    private companion object {
+        val deviceAttributes =
+            AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                AudioDeviceInfo.TYPE_BLE_HEADSET,
+                "test_address",
+            )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileDataInteractorTest.kt
new file mode 100644
index 0000000..c80eb13
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileDataInteractorTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.doman.interactor
+
+import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileDataInteractor
+import com.android.systemui.utils.leaks.FakeBatteryController
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toCollection
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class BatterySaverTileDataInteractorTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+    private val batteryController = FakeBatteryController(LeakCheck())
+    private val testUser = UserHandle.of(1)
+    private val underTest =
+        BatterySaverTileDataInteractor(testScope.testScheduler, batteryController)
+
+    @Test
+    fun availability_isTrue() =
+        testScope.runTest {
+            val availability = underTest.availability(testUser).toCollection(mutableListOf())
+
+            Truth.assertThat(availability).hasSize(1)
+            Truth.assertThat(availability.last()).isTrue()
+        }
+
+    @Test
+    fun tileData_matchesBatteryControllerPowerSaving() =
+        testScope.runTest {
+            val data by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            runCurrent()
+            Truth.assertThat(data!!.isPowerSaving).isFalse()
+
+            batteryController.setPowerSaveMode(true)
+            runCurrent()
+            Truth.assertThat(data!!.isPowerSaving).isTrue()
+
+            batteryController.setPowerSaveMode(false)
+            runCurrent()
+            Truth.assertThat(data!!.isPowerSaving).isFalse()
+        }
+
+    @Test
+    fun tileData_matchesBatteryControllerIsPluggedIn() =
+        testScope.runTest {
+            val data by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            runCurrent()
+            Truth.assertThat(data!!.isPluggedIn).isFalse()
+
+            batteryController.isPluggedIn = true
+            runCurrent()
+            Truth.assertThat(data!!.isPluggedIn).isTrue()
+
+            batteryController.isPluggedIn = false
+            runCurrent()
+            Truth.assertThat(data!!.isPluggedIn).isFalse()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..62c51e6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileUserActionInteractorTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.doman.interactor
+
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.provider.Settings
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.utils.leaks.FakeBatteryController
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class BatterySaverTileUserActionInteractorTest : SysuiTestCase() {
+    private val inputHandler = FakeQSTileIntentUserInputHandler()
+    private val controller = FakeBatteryController(LeakCheck())
+    private val underTest = BatterySaverTileUserActionInteractor(inputHandler, controller)
+
+    @Test
+    fun handleClickWhenNotPluggedIn_flipsPowerSaverMode() = runTest {
+        val originalPowerSaveMode = controller.isPowerSave
+        controller.isPluggedIn = false
+
+        underTest.handleInput(
+            QSTileInputTestKtx.click(BatterySaverTileModel.Standard(false, originalPowerSaveMode))
+        )
+
+        Truth.assertThat(controller.isPowerSave).isNotEqualTo(originalPowerSaveMode)
+    }
+
+    @Test
+    fun handleClickWhenPluggedIn_doesNotTurnOnPowerSaverMode() = runTest {
+        controller.setPowerSaveMode(false)
+        val originalPowerSaveMode = controller.isPowerSave
+        controller.isPluggedIn = true
+
+        underTest.handleInput(
+            QSTileInputTestKtx.click(
+                BatterySaverTileModel.Standard(controller.isPluggedIn, originalPowerSaveMode)
+            )
+        )
+
+        Truth.assertThat(controller.isPowerSave).isEqualTo(originalPowerSaveMode)
+    }
+
+    @Test
+    fun handleLongClick() = runTest {
+        underTest.handleInput(
+            QSTileInputTestKtx.longClick(BatterySaverTileModel.Standard(false, false))
+        )
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_BATTERY_SAVER_SETTINGS)
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
new file mode 100644
index 0000000..6e9db2c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.ui
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.impl.battery.qsBatterySaverTileConfig
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BatterySaverTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val batterySaverTileConfig = kosmos.qsBatterySaverTileConfig
+    private lateinit var mapper: BatterySaverTileMapper
+
+    @Before
+    fun setup() {
+        mapper =
+            BatterySaverTileMapper(
+                context.orCreateTestableResources
+                    .apply {
+                        addOverride(R.drawable.qs_battery_saver_icon_off, TestStubDrawable())
+                        addOverride(R.drawable.qs_battery_saver_icon_on, TestStubDrawable())
+                    }
+                    .resources,
+                context.theme,
+            )
+    }
+
+    @Test
+    fun map_standard_notPluggedInNotPowerSaving() {
+        val inputModel = BatterySaverTileModel.Standard(false, false)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.INACTIVE,
+                "",
+                R.drawable.qs_battery_saver_icon_off,
+                null,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_standard_notPluggedInPowerSaving() {
+        val inputModel = BatterySaverTileModel.Standard(false, true)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.ACTIVE,
+                "",
+                R.drawable.qs_battery_saver_icon_on,
+                null,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_standard_pluggedInPowerSaving() {
+        val inputModel = BatterySaverTileModel.Standard(true, true)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.UNAVAILABLE,
+                "",
+                R.drawable.qs_battery_saver_icon_on,
+                null,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_standard_pluggedInNotPowerSaving() {
+        val inputModel = BatterySaverTileModel.Standard(true, false)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.UNAVAILABLE,
+                "",
+                R.drawable.qs_battery_saver_icon_off,
+                null,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_extremeSaverDisabledNotPluggedInNotPowerSaving() {
+        val inputModel = BatterySaverTileModel.Extreme(false, false, false)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.INACTIVE,
+                "",
+                R.drawable.qs_battery_saver_icon_off,
+                null,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_extremeSaverDisabledNotPluggedInPowerSaving() {
+        val inputModel = BatterySaverTileModel.Extreme(false, true, false)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.ACTIVE,
+                context.getString(R.string.standard_battery_saver_text),
+                R.drawable.qs_battery_saver_icon_on,
+                context.getString(R.string.standard_battery_saver_text),
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_extremeSaverDisabledPluggedInPowerSaving() {
+        val inputModel = BatterySaverTileModel.Extreme(true, true, false)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.UNAVAILABLE,
+                "",
+                R.drawable.qs_battery_saver_icon_on,
+                null,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_extremeSaverDisabledPluggedInNotPowerSaving() {
+        val inputModel = BatterySaverTileModel.Extreme(true, false, false)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.UNAVAILABLE,
+                "",
+                R.drawable.qs_battery_saver_icon_off,
+                null,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_extremeSaverEnabledNotPluggedInNotPowerSaving() {
+        val inputModel = BatterySaverTileModel.Extreme(false, false, true)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.INACTIVE,
+                "",
+                R.drawable.qs_battery_saver_icon_off,
+                null,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_extremeSaverEnabledNotPluggedInPowerSaving() {
+        val inputModel = BatterySaverTileModel.Extreme(false, true, true)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.ACTIVE,
+                context.getString(R.string.extreme_battery_saver_text),
+                R.drawable.qs_battery_saver_icon_on,
+                context.getString(R.string.extreme_battery_saver_text),
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_extremeSaverEnabledPluggedInPowerSaving() {
+        val inputModel = BatterySaverTileModel.Extreme(true, true, true)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.UNAVAILABLE,
+                "",
+                R.drawable.qs_battery_saver_icon_on,
+                null,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_extremeSaverEnabledPluggedInNotPowerSaving() {
+        val inputModel = BatterySaverTileModel.Extreme(true, false, true)
+
+        val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+        val expectedState =
+            createBatterySaverTileState(
+                QSTileState.ActivationState.UNAVAILABLE,
+                "",
+                R.drawable.qs_battery_saver_icon_off,
+                null,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    private fun createBatterySaverTileState(
+        activationState: QSTileState.ActivationState,
+        secondaryLabel: String,
+        iconRes: Int,
+        stateDescription: CharSequence?,
+    ): QSTileState {
+        val label = context.getString(R.string.battery_detail_switch_title)
+        return QSTileState(
+            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            label,
+            activationState,
+            secondaryLabel,
+            if (activationState == QSTileState.ActivationState.UNAVAILABLE)
+                setOf(QSTileState.UserAction.LONG_CLICK)
+            else setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+            label,
+            stateDescription,
+            QSTileState.SideViewIcon.None,
+            QSTileState.EnabledState.ENABLED,
+            Switch::class.qualifiedName
+        )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
new file mode 100644
index 0000000..39755bf
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet.domain
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+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.common.shared.model.Text
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.qs.tiles.impl.internet.qsInternetTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InternetTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val internetTileConfig = kosmos.qsInternetTileConfig
+    private val mapper by lazy {
+        InternetTileMapper(
+            context.orCreateTestableResources
+                .apply {
+                    addOverride(R.drawable.ic_qs_no_internet_unavailable, TestStubDrawable())
+                    addOverride(wifiRes, TestStubDrawable())
+                }
+                .resources,
+            context.theme,
+            context
+        )
+    }
+
+    @Test
+    fun withActiveModel_mappedStateMatchesDataModel() {
+        val inputModel =
+            InternetTileModel.Active(
+                secondaryLabel = Text.Resource(R.string.quick_settings_networks_available),
+                iconId = wifiRes,
+                stateDescription = null,
+                contentDescription =
+                    ContentDescription.Resource(R.string.quick_settings_internet_label),
+            )
+
+        val outputState = mapper.map(internetTileConfig, inputModel)
+
+        val expectedState =
+            createInternetTileState(
+                QSTileState.ActivationState.ACTIVE,
+                context.getString(R.string.quick_settings_networks_available),
+                Icon.Loaded(context.getDrawable(wifiRes)!!, contentDescription = null),
+                context.getString(R.string.quick_settings_internet_label)
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun withInactiveModel_mappedStateMatchesDataModel() {
+        val inputModel =
+            InternetTileModel.Inactive(
+                secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable),
+                iconId = R.drawable.ic_qs_no_internet_unavailable,
+                stateDescription = null,
+                contentDescription =
+                    ContentDescription.Resource(R.string.quick_settings_networks_unavailable),
+            )
+
+        val outputState = mapper.map(internetTileConfig, inputModel)
+
+        val expectedState =
+            createInternetTileState(
+                QSTileState.ActivationState.INACTIVE,
+                context.getString(R.string.quick_settings_networks_unavailable),
+                Icon.Loaded(
+                    context.getDrawable(R.drawable.ic_qs_no_internet_unavailable)!!,
+                    contentDescription = null
+                ),
+                context.getString(R.string.quick_settings_networks_unavailable)
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    private fun createInternetTileState(
+        activationState: QSTileState.ActivationState,
+        secondaryLabel: String,
+        icon: Icon,
+        contentDescription: String,
+    ): QSTileState {
+        val label = context.getString(R.string.quick_settings_internet_label)
+        return QSTileState(
+            { icon },
+            label,
+            activationState,
+            secondaryLabel,
+            setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+            contentDescription,
+            null,
+            QSTileState.SideViewIcon.Chevron,
+            QSTileState.EnabledState.ENABLED,
+            Switch::class.qualifiedName
+        )
+    }
+
+    private companion object {
+        val wifiRes = WIFI_FULL_ICONS[4]
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
new file mode 100644
index 0000000..37ef6ad
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
@@ -0,0 +1,548 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet.domain.interactor
+
+import android.graphics.drawable.TestStubDrawable
+import android.os.UserHandle
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.AccessibilityContentDescriptions
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.shared.model.Text.Companion.loadText
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.connectivity.WifiIcons
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
+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.WifiInteractorImpl
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@TestableLooper.RunWithLooper
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InternetTileDataInteractorTest : SysuiTestCase() {
+    private val testUser = UserHandle.of(1)
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+
+    private lateinit var underTest: InternetTileDataInteractor
+    private lateinit var mobileIconsInteractor: MobileIconsInteractor
+
+    private val airplaneModeRepository = FakeAirplaneModeRepository()
+    private val connectivityRepository = FakeConnectivityRepository()
+    private val ethernetInteractor = EthernetInteractor(connectivityRepository)
+    private val wifiRepository = FakeWifiRepository()
+    private val userSetupRepo = FakeUserSetupRepository()
+    private val wifiInteractor =
+        WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope)
+
+    private val tableLogBuffer: TableLogBuffer = mock()
+    private val carrierConfigTracker: CarrierConfigTracker = mock()
+
+    private val mobileConnectionsRepository =
+        FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogBuffer)
+    private val mobileConnectionRepository =
+        FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer)
+
+    private val flags =
+        FakeFeatureFlagsClassic().also {
+            it.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true)
+        }
+
+    private val internet = context.getString(R.string.quick_settings_internet_label)
+
+    @Before
+    fun setUp() {
+        mobileConnectionRepository.apply {
+            setNetworkTypeKey(mobileConnectionsRepository.GSM_KEY)
+            isInService.value = true
+            dataConnectionState.value = DataConnectionState.Connected
+            dataEnabled.value = true
+        }
+
+        mobileConnectionsRepository.apply {
+            activeMobileDataRepository.value = mobileConnectionRepository
+            activeMobileDataSubscriptionId.value = SUB_1_ID
+            setMobileConnectionRepositoryMap(mapOf(SUB_1_ID to mobileConnectionRepository))
+        }
+
+        mobileIconsInteractor =
+            MobileIconsInteractorImpl(
+                mobileConnectionsRepository,
+                carrierConfigTracker,
+                tableLogBuffer,
+                connectivityRepository,
+                userSetupRepo,
+                testScope.backgroundScope,
+                context,
+                flags,
+            )
+
+        context.orCreateTestableResources.apply {
+            addOverride(com.android.internal.R.drawable.ic_signal_cellular, TestStubDrawable())
+            addOverride(
+                com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_0,
+                TestStubDrawable()
+            )
+            addOverride(
+                com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_1,
+                TestStubDrawable()
+            )
+            addOverride(
+                com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_2,
+                TestStubDrawable()
+            )
+            addOverride(
+                com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_3,
+                TestStubDrawable()
+            )
+            addOverride(
+                com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_4,
+                TestStubDrawable()
+            )
+            addOverride(com.android.settingslib.R.drawable.ic_hotspot_phone, TestStubDrawable())
+            addOverride(com.android.settingslib.R.drawable.ic_hotspot_laptop, TestStubDrawable())
+            addOverride(com.android.settingslib.R.drawable.ic_hotspot_tablet, TestStubDrawable())
+            addOverride(com.android.settingslib.R.drawable.ic_hotspot_watch, TestStubDrawable())
+            addOverride(com.android.settingslib.R.drawable.ic_hotspot_auto, TestStubDrawable())
+        }
+
+        underTest =
+            InternetTileDataInteractor(
+                context,
+                testScope.backgroundScope,
+                airplaneModeRepository,
+                connectivityRepository,
+                ethernetInteractor,
+                mobileIconsInteractor,
+                wifiInteractor,
+            )
+    }
+
+    @Test
+    fun noDefault_noNetworksAvailable() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            connectivityRepository.defaultConnections.value = DefaultConnectionModel()
+
+            assertThat(latest?.secondaryLabel)
+                .isEqualTo(Text.Resource(R.string.quick_settings_networks_unavailable))
+            assertThat(latest?.iconId).isEqualTo(R.drawable.ic_qs_no_internet_unavailable)
+        }
+
+    @Test
+    fun noDefault_networksAvailable() =
+        testScope.runTest {
+            // TODO(b/328419203): support [WifiInteractor.areNetworksAvailable]
+        }
+
+    @Test
+    fun wifiDefaultAndActive() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            val networkModel =
+                WifiNetworkModel.Active(
+                    networkId = 1,
+                    level = 4,
+                    ssid = "test ssid",
+                )
+            val wifiIcon =
+                WifiIcon.fromModel(model = networkModel, context = context, showHotspotInfo = true)
+                    as WifiIcon.Visible
+
+            connectivityRepository.setWifiConnected()
+            wifiRepository.setIsWifiDefault(true)
+            wifiRepository.setWifiNetwork(networkModel)
+
+            assertThat(latest?.secondaryTitle).isEqualTo("test ssid")
+            assertThat(latest?.secondaryLabel).isNull()
+            val expectedIcon =
+                Icon.Loaded(context.getDrawable(WifiIcons.WIFI_NO_INTERNET_ICONS[4])!!, null)
+
+            val actualIcon = latest?.icon
+            assertThat(actualIcon).isEqualTo(expectedIcon)
+            assertThat(latest?.iconId).isNull()
+            assertThat(latest?.contentDescription.loadContentDescription(context))
+                .isEqualTo("$internet,test ssid")
+            val expectedSd = wifiIcon.contentDescription
+            assertThat(latest?.stateDescription).isEqualTo(expectedSd)
+        }
+
+    @Test
+    fun wifiDefaultAndActive_hotspotNone() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            val networkModel =
+                WifiNetworkModel.Active(
+                    networkId = 1,
+                    level = 4,
+                    ssid = "test ssid",
+                    hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE,
+                )
+
+            connectivityRepository.setWifiConnected()
+            wifiRepository.setIsWifiDefault(true)
+            wifiRepository.setWifiNetwork(networkModel)
+
+            val expectedIcon =
+                Icon.Loaded(context.getDrawable(WifiIcons.WIFI_NO_INTERNET_ICONS[4])!!, null)
+            assertThat(latest?.icon).isEqualTo(expectedIcon)
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .doesNotContain(
+                    context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+                )
+        }
+
+    @Test
+    fun wifiDefaultAndActive_hotspotTablet() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.TABLET)
+
+            val expectedIcon =
+                Icon.Loaded(
+                    context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_tablet)!!,
+                    null
+                )
+            assertThat(latest?.icon).isEqualTo(expectedIcon)
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(
+                    context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+                )
+        }
+
+    @Test
+    fun wifiDefaultAndActive_hotspotLaptop() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.LAPTOP)
+
+            val expectedIcon =
+                Icon.Loaded(
+                    context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_laptop)!!,
+                    null
+                )
+            assertThat(latest?.icon).isEqualTo(expectedIcon)
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(
+                    context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+                )
+        }
+
+    @Test
+    fun wifiDefaultAndActive_hotspotWatch() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.WATCH)
+
+            val expectedIcon =
+                Icon.Loaded(
+                    context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_watch)!!,
+                    null
+                )
+            assertThat(latest?.icon).isEqualTo(expectedIcon)
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(
+                    context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+                )
+        }
+
+    @Test
+    fun wifiDefaultAndActive_hotspotAuto() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.AUTO)
+
+            val expectedIcon =
+                Icon.Loaded(
+                    context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_auto)!!,
+                    null
+                )
+            assertThat(latest?.icon).isEqualTo(expectedIcon)
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(
+                    context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+                )
+        }
+
+    @Test
+    fun wifiDefaultAndActive_hotspotPhone() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+            setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.PHONE)
+
+            val expectedIcon =
+                Icon.Loaded(
+                    context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!,
+                    null
+                )
+            assertThat(latest?.icon).isEqualTo(expectedIcon)
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(
+                    context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+                )
+        }
+
+    @Test
+    fun wifiDefaultAndActive_hotspotUnknown() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.UNKNOWN)
+
+            val expectedIcon =
+                Icon.Loaded(
+                    context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!,
+                    null
+                )
+            assertThat(latest?.icon).isEqualTo(expectedIcon)
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(
+                    context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+                )
+        }
+
+    @Test
+    fun wifiDefaultAndActive_hotspotInvalid() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.INVALID)
+
+            val expectedIcon =
+                Icon.Loaded(
+                    context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!,
+                    null
+                )
+            assertThat(latest?.icon).isEqualTo(expectedIcon)
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(
+                    context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+                )
+        }
+
+    @Test
+    fun wifiDefaultAndNotActive_noNetworksAvailable() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+            val networkModel = WifiNetworkModel.Inactive
+
+            connectivityRepository.setWifiConnected(validated = false)
+            wifiRepository.setIsWifiDefault(true)
+            wifiRepository.setWifiNetwork(networkModel)
+            wifiRepository.wifiScanResults.value = emptyList()
+
+            assertThat(latest).isEqualTo(NOT_CONNECTED_NETWORKS_UNAVAILABLE)
+        }
+
+    @Test
+    fun wifiDefaultAndNotActive_networksAvailable() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            val networkModel = WifiNetworkModel.Inactive
+
+            connectivityRepository.setWifiConnected(validated = false)
+            wifiRepository.setIsWifiDefault(true)
+            wifiRepository.setWifiNetwork(networkModel)
+            wifiRepository.wifiScanResults.value = listOf(WifiScanEntry("test 1"))
+
+            assertThat(latest?.secondaryLabel).isNull()
+            assertThat(latest?.secondaryTitle)
+                .isEqualTo(context.getString(R.string.quick_settings_networks_available))
+            assertThat(latest?.icon).isNull()
+            assertThat(latest?.iconId).isEqualTo(R.drawable.ic_qs_no_internet_available)
+            assertThat(latest?.stateDescription).isNull()
+            val expectedCd =
+                "$internet,${context.getString(R.string.quick_settings_networks_available)}"
+            assertThat(latest?.contentDescription.loadContentDescription(context))
+                .isEqualTo(expectedCd)
+        }
+
+    @Test
+    fun mobileDefault_usesNetworkNameAndIcon() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            connectivityRepository.setMobileConnected()
+            mobileConnectionsRepository.mobileIsDefault.value = true
+            mobileConnectionRepository.apply {
+                setAllLevels(3)
+                setAllRoaming(false)
+                networkName.value = NetworkNameModel.Default("test network")
+            }
+
+            assertThat(latest).isNotNull()
+            assertThat(latest?.secondaryTitle).isNotNull()
+            assertThat(latest?.secondaryTitle.toString()).contains("test network")
+            assertThat(latest?.secondaryLabel).isNull()
+            assertThat(latest?.icon).isInstanceOf(Icon.Loaded::class.java)
+            assertThat(latest?.iconId).isNull()
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(latest?.secondaryTitle.toString())
+            assertThat(latest?.contentDescription.loadContentDescription(context))
+                .isEqualTo(internet)
+        }
+
+    @Test
+    fun ethernetDefault_validated_matchesInteractor() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+            val ethernetIcon by collectLastValue(ethernetInteractor.icon)
+
+            connectivityRepository.setEthernetConnected(default = true, validated = true)
+
+            assertThat(latest?.secondaryLabel.loadText(context))
+                .isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context))
+            assertThat(latest?.secondaryTitle).isNull()
+            assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet_fully)
+            assertThat(latest?.icon).isNull()
+            assertThat(latest?.stateDescription).isNull()
+            assertThat(latest?.contentDescription.loadContentDescription(context))
+                .isEqualTo(latest?.secondaryLabel.loadText(context))
+        }
+
+    @Test
+    fun ethernetDefault_notValidated_matchesInteractor() =
+        testScope.runTest {
+            val latest by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+            val ethernetIcon by collectLastValue(ethernetInteractor.icon)
+
+            connectivityRepository.setEthernetConnected(default = true, validated = false)
+
+            assertThat(latest?.secondaryLabel.loadText(context))
+                .isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context))
+            assertThat(latest?.secondaryTitle).isNull()
+            assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet)
+            assertThat(latest?.icon).isNull()
+            assertThat(latest?.stateDescription).isNull()
+            assertThat(latest?.contentDescription.loadContentDescription(context))
+                .isEqualTo(latest?.secondaryLabel.loadText(context))
+        }
+
+    private fun setWifiNetworkWithHotspot(hotspot: WifiNetworkModel.HotspotDeviceType) {
+        val networkModel =
+            WifiNetworkModel.Active(
+                networkId = 1,
+                level = 4,
+                ssid = "test ssid",
+                hotspotDeviceType = hotspot,
+            )
+
+        connectivityRepository.setWifiConnected()
+        wifiRepository.setIsWifiDefault(true)
+        wifiRepository.setWifiNetwork(networkModel)
+    }
+
+    private companion object {
+        const val SUB_1_ID = 1
+
+        val NOT_CONNECTED_NETWORKS_UNAVAILABLE =
+            InternetTileModel.Inactive(
+                secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable),
+                iconId = R.drawable.ic_qs_no_internet_unavailable,
+                stateDescription = null,
+                contentDescription =
+                    ContentDescription.Resource(R.string.quick_settings_networks_unavailable),
+            )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..e1f3d97
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet.domain.interactor
+
+import android.platform.test.annotations.EnabledOnRavenwood
+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.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.dialog.InternetDialogManager
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.statusbar.connectivity.AccessPointController
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class InternetTileUserActionInteractorTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val inputHandler = FakeQSTileIntentUserInputHandler()
+
+    private lateinit var underTest: InternetTileUserActionInteractor
+
+    @Mock private lateinit var internetDialogManager: InternetDialogManager
+    @Mock private lateinit var controller: AccessPointController
+
+    @Before
+    fun setup() {
+        internetDialogManager = mock<InternetDialogManager>()
+        controller = mock<AccessPointController>()
+
+        underTest =
+            InternetTileUserActionInteractor(
+                kosmos.testScope.coroutineContext,
+                internetDialogManager,
+                controller,
+                inputHandler,
+            )
+    }
+
+    @Test
+    fun handleClickWhenActive() =
+        kosmos.testScope.runTest {
+            val input = InternetTileModel.Active()
+
+            underTest.handleInput(QSTileInputTestKtx.click(input))
+
+            verify(internetDialogManager).create(eq(true), anyBoolean(), anyBoolean(), nullable())
+        }
+
+    @Test
+    fun handleClickWhenInactive() =
+        kosmos.testScope.runTest {
+            val input = InternetTileModel.Inactive()
+
+            underTest.handleInput(QSTileInputTestKtx.click(input))
+
+            verify(internetDialogManager).create(eq(true), anyBoolean(), anyBoolean(), nullable())
+        }
+
+    @Test
+    fun handleLongClickWhenActive() =
+        kosmos.testScope.runTest {
+            val input = InternetTileModel.Active()
+
+            underTest.handleInput(QSTileInputTestKtx.longClick(input))
+
+            QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+                Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_WIFI_SETTINGS)
+            }
+        }
+
+    @Test
+    fun handleLongClickWhenInactive() =
+        kosmos.testScope.runTest {
+            val input = InternetTileModel.Inactive()
+
+            underTest.handleInput(QSTileInputTestKtx.longClick(input))
+
+            QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+                Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_WIFI_SETTINGS)
+            }
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractorTest.kt
new file mode 100644
index 0000000..266875e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractorTest.kt
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.rotation.domain.interactor
+
+import android.Manifest
+import android.content.packageManager
+import android.content.pm.PackageManager
+import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.camera.data.repository.fakeCameraAutoRotateRepository
+import com.android.systemui.camera.data.repository.fakeCameraSensorPrivacyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.leaks.FakeBatteryController
+import com.android.systemui.utils.leaks.FakeRotationLockController
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toCollection
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class RotationLockTileDataInteractorTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+    private val batteryController = FakeBatteryController(LeakCheck())
+    private val rotationController = FakeRotationLockController(LeakCheck())
+    private val fakeCameraAutoRotateRepository = kosmos.fakeCameraAutoRotateRepository
+    private val fakeCameraSensorPrivacyRepository = kosmos.fakeCameraSensorPrivacyRepository
+    private val packageManager = kosmos.packageManager
+
+    private val testUser = UserHandle.of(1)
+    private lateinit var underTest: RotationLockTileDataInteractor
+
+    @Before
+    fun setup() {
+        whenever(packageManager.rotationResolverPackageName).thenReturn(TEST_PACKAGE_NAME)
+        whenever(
+                packageManager.checkPermission(
+                    eq(Manifest.permission.CAMERA),
+                    eq(TEST_PACKAGE_NAME)
+                )
+            )
+            .thenReturn(PackageManager.PERMISSION_GRANTED)
+
+        underTest =
+            RotationLockTileDataInteractor(
+                rotationController,
+                batteryController,
+                fakeCameraAutoRotateRepository,
+                fakeCameraSensorPrivacyRepository,
+                packageManager,
+                context.orCreateTestableResources
+                    .apply {
+                        addOverride(com.android.internal.R.bool.config_allowRotationResolver, true)
+                    }
+                    .resources
+            )
+    }
+
+    @Test
+    fun availability_isTrue() =
+        testScope.runTest {
+            val availability = underTest.availability(testUser).toCollection(mutableListOf())
+
+            assertThat(availability).hasSize(1)
+            assertThat(availability.last()).isTrue()
+        }
+
+    @Test
+    fun tileData_isRotationLockedMatchesRotationController() =
+        testScope.runTest {
+            val data by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            runCurrent()
+            assertThat(data!!.isRotationLocked).isEqualTo(false)
+
+            rotationController.setRotationLocked(true, CALLER)
+            runCurrent()
+            assertThat(data!!.isRotationLocked).isEqualTo(true)
+
+            rotationController.setRotationLocked(false, CALLER)
+            runCurrent()
+            assertThat(data!!.isRotationLocked).isEqualTo(false)
+        }
+
+    @Test
+    fun tileData_cameraRotationMatchesBatteryController() =
+        testScope.runTest {
+            setupControllersToEnableCameraRotation()
+            val data by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            runCurrent()
+            assertThat(data!!.isCameraRotationEnabled).isTrue()
+
+            batteryController.setPowerSaveMode(true)
+            runCurrent()
+            assertThat(data!!.isCameraRotationEnabled).isFalse()
+
+            batteryController.setPowerSaveMode(false)
+            runCurrent()
+            assertThat(data!!.isCameraRotationEnabled).isTrue()
+        }
+
+    @Test
+    fun tileData_cameraRotationMatchesSensorPrivacyRepository() =
+        testScope.runTest {
+            setupControllersToEnableCameraRotation()
+            val lastValue by
+                this.collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+            runCurrent()
+            assertThat(lastValue!!.isCameraRotationEnabled).isTrue()
+
+            fakeCameraSensorPrivacyRepository.setEnabled(testUser, true)
+            runCurrent()
+            assertThat(lastValue!!.isCameraRotationEnabled).isFalse()
+
+            fakeCameraSensorPrivacyRepository.setEnabled(testUser, false)
+            runCurrent()
+            assertThat(lastValue!!.isCameraRotationEnabled).isTrue()
+        }
+
+    @Test
+    fun tileData_cameraRotationMatchesAutoRotateRepository() =
+        testScope.runTest {
+            setupControllersToEnableCameraRotation()
+
+            val lastValue by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+            runCurrent()
+            assertThat(lastValue!!.isCameraRotationEnabled).isTrue()
+
+            fakeCameraAutoRotateRepository.setEnabled(testUser, false)
+            runCurrent()
+            assertThat(lastValue!!.isCameraRotationEnabled).isFalse()
+
+            fakeCameraAutoRotateRepository.setEnabled(testUser, true)
+            runCurrent()
+            assertThat(lastValue!!.isCameraRotationEnabled).isTrue()
+        }
+
+    @Test
+    fun tileData_matchesPackageManagerPermissionDenied() =
+        testScope.runTest {
+            whenever(
+                    packageManager.checkPermission(
+                        eq(Manifest.permission.CAMERA),
+                        eq(TEST_PACKAGE_NAME)
+                    )
+                )
+                .thenReturn(PackageManager.PERMISSION_DENIED)
+
+            val lastValue by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+            runCurrent()
+            assertThat(lastValue!!.isCameraRotationEnabled).isEqualTo(false)
+        }
+
+    @Test
+    fun tileData_setConfigAllowRotationResolverToFalse_cameraRotationIsNotEnabled() =
+        testScope.runTest {
+            underTest.apply {
+                overrideResource(com.android.internal.R.bool.config_allowRotationResolver, false)
+            }
+            setupControllersToEnableCameraRotation()
+            val lastValue by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+            runCurrent()
+
+            assertThat(lastValue!!.isCameraRotationEnabled).isEqualTo(false)
+        }
+
+    private fun setupControllersToEnableCameraRotation() {
+        rotationController.setRotationLocked(true, CALLER)
+        batteryController.setPowerSaveMode(false)
+        fakeCameraSensorPrivacyRepository.setEnabled(testUser, false)
+        fakeCameraAutoRotateRepository.setEnabled(testUser, true)
+    }
+
+    private companion object {
+        private const val CALLER = "RotationLockTileDataInteractorTest"
+        private const val TEST_PACKAGE_NAME = "com.test"
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..1653ce3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractorTest.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.rotation.domain.interactor
+
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.provider.Settings
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel
+import com.android.systemui.utils.leaks.FakeRotationLockController
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class RotationLockTileUserActionInteractorTest : SysuiTestCase() {
+    private val controller = FakeRotationLockController(LeakCheck())
+    private val inputHandler = FakeQSTileIntentUserInputHandler()
+
+    private val underTest =
+        RotationLockTileUserActionInteractor(
+            controller,
+            inputHandler,
+        )
+
+    @Test
+    fun handleClickWhenEnabled() = runTest {
+        val wasEnabled = true
+        controller.setRotationLocked(wasEnabled, null)
+
+        underTest.handleInput(QSTileInputTestKtx.click(RotationLockTileModel(wasEnabled, false)))
+
+        assertThat(controller.isRotationLocked).isEqualTo(!wasEnabled)
+    }
+
+    @Test
+    fun handleClickWhenDisabled() = runTest {
+        val wasEnabled = false
+        controller.setRotationLocked(wasEnabled, null)
+
+        underTest.handleInput(QSTileInputTestKtx.click(RotationLockTileModel(wasEnabled, false)))
+
+        assertThat(controller.isRotationLocked).isEqualTo(!wasEnabled)
+    }
+
+    @Test
+    fun handleLongClickWhenDisabled() = runTest {
+        val enabled = false
+
+        underTest.handleInput(
+            QSTileInputTestKtx.longClick(
+                RotationLockTileModel(
+                    enabled,
+                    false,
+                )
+            )
+        )
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            assertThat(it.intent.action).isEqualTo(Settings.ACTION_AUTO_ROTATE_SETTINGS)
+        }
+    }
+
+    @Test
+    fun handleLongClickWhenEnabled() = runTest {
+        val enabled = true
+
+        underTest.handleInput(QSTileInputTestKtx.longClick(RotationLockTileModel(enabled, false)))
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            assertThat(it.intent.action).isEqualTo(Settings.ACTION_AUTO_ROTATE_SETTINGS)
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
new file mode 100644
index 0000000..60c69f4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.rotation.ui.mapper
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel
+import com.android.systemui.qs.tiles.impl.rotation.qsRotationLockTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.statusbar.policy.devicePostureController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RotationLockTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val rotationLockTileConfig = kosmos.qsRotationLockTileConfig
+    private val devicePostureController = kosmos.devicePostureController
+
+    private lateinit var mapper: RotationLockTileMapper
+
+    @Before
+    fun setup() {
+        whenever(devicePostureController.devicePosture)
+            .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED)
+
+        mapper =
+            RotationLockTileMapper(
+                context.orCreateTestableResources
+                    .apply {
+                        addOverride(R.drawable.qs_auto_rotate_icon_off, TestStubDrawable())
+                        addOverride(R.drawable.qs_auto_rotate_icon_on, TestStubDrawable())
+                        addOverride(com.android.internal.R.bool.config_allowRotationResolver, true)
+                        addOverride(
+                            com.android.internal.R.array.config_foldedDeviceStates,
+                            intArrayOf() // empty array <=> device is not foldable
+                        )
+                    }
+                    .resources,
+                context.theme,
+                devicePostureController
+            )
+    }
+
+    @Test
+    fun rotationNotLocked_cameraRotationDisabled() {
+        val inputModel = RotationLockTileModel(false, false)
+
+        val outputState = mapper.map(rotationLockTileConfig, inputModel)
+
+        val expectedState =
+            createRotationLockTileState(
+                QSTileState.ActivationState.ACTIVE,
+                EMPTY_SECONDARY_STRING,
+                R.drawable.qs_auto_rotate_icon_on
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun rotationNotLocked_cameraRotationEnabled() {
+        val inputModel = RotationLockTileModel(false, true)
+
+        val outputState = mapper.map(rotationLockTileConfig, inputModel)
+
+        val expectedState =
+            createRotationLockTileState(
+                QSTileState.ActivationState.ACTIVE,
+                context.getString(R.string.rotation_lock_camera_rotation_on),
+                R.drawable.qs_auto_rotate_icon_on
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun rotationLocked_cameraRotationNotEnabled() {
+        val inputModel = RotationLockTileModel(true, false)
+
+        val outputState = mapper.map(rotationLockTileConfig, inputModel)
+
+        val expectedState =
+            createRotationLockTileState(
+                QSTileState.ActivationState.INACTIVE,
+                EMPTY_SECONDARY_STRING,
+                R.drawable.qs_auto_rotate_icon_off
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun deviceFoldableAndClosed_secondaryLabelIsFoldableSpecific() {
+        setDeviceFoldable()
+        val inputModel = RotationLockTileModel(false, false)
+
+        val outputState = mapper.map(rotationLockTileConfig, inputModel)
+
+        val expectedSecondaryLabelEnding =
+            context.getString(R.string.quick_settings_rotation_posture_folded)
+        assertThat(
+                context.resources.getIntArray(
+                    com.android.internal.R.array.config_foldedDeviceStates
+                )
+            )
+            .isNotEmpty()
+        val actualSecondaryLabel = outputState.secondaryLabel
+        assertThat(actualSecondaryLabel).isNotNull()
+        assertThat(actualSecondaryLabel!!.endsWith(expectedSecondaryLabelEnding)).isTrue()
+    }
+
+    @Test
+    fun deviceFoldableAndNotClosed_secondaryLabelIsFoldableSpecific() {
+        setDeviceFoldable()
+        whenever(devicePostureController.devicePosture)
+            .thenReturn(DevicePostureController.DEVICE_POSTURE_OPENED)
+        val inputModel = RotationLockTileModel(false, false)
+
+        val outputState = mapper.map(rotationLockTileConfig, inputModel)
+
+        val expectedSecondaryLabelEnding =
+            context.getString(R.string.quick_settings_rotation_posture_unfolded)
+        assertThat(
+                context.orCreateTestableResources.resources.getIntArray(
+                    com.android.internal.R.array.config_foldedDeviceStates
+                )
+            )
+            .isNotEmpty()
+        val actualSecondaryLabel = outputState.secondaryLabel
+        assertThat(actualSecondaryLabel).isNotNull()
+        assertThat(actualSecondaryLabel!!.endsWith(expectedSecondaryLabelEnding)).isTrue()
+    }
+
+    private fun setDeviceFoldable() {
+        mapper.apply {
+            overrideResource(
+                com.android.internal.R.array.config_foldedDeviceStates,
+                intArrayOf(1, 2, 3)
+            )
+        }
+    }
+
+    private fun createRotationLockTileState(
+        activationState: QSTileState.ActivationState,
+        secondaryLabel: String,
+        iconRes: Int
+    ): QSTileState {
+        val label = context.getString(R.string.quick_settings_rotation_unlocked_label)
+        return QSTileState(
+            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            label,
+            activationState,
+            secondaryLabel,
+            setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+            context.getString(R.string.accessibility_quick_settings_rotation),
+            secondaryLabel,
+            QSTileState.SideViewIcon.None,
+            QSTileState.EnabledState.ENABLED,
+            Switch::class.qualifiedName
+        )
+    }
+
+    private companion object {
+        private const val EMPTY_SECONDARY_STRING = ""
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 1eb9adb..6108904 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -18,6 +18,10 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlagsClassic
@@ -26,10 +30,8 @@
 import com.android.systemui.qs.FooterActionsController
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
-import com.android.systemui.scene.shared.model.Direction
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.shared.model.UserAction
-import com.android.systemui.scene.shared.model.UserActionResult
+import com.android.systemui.res.R
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.privacyChipInteractor
 import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
@@ -107,22 +109,23 @@
                 shadeHeaderViewModel = shadeHeaderViewModel,
                 qsSceneAdapter = qsFlexiglassAdapter,
                 notifications = kosmos.notificationsPlaceholderViewModel,
-                footerActionsViewModelFactory,
-                footerActionsController,
+                footerActionsViewModelFactory = footerActionsViewModelFactory,
+                footerActionsController = footerActionsController,
             )
     }
 
     @Test
     fun destinationsNotCustomizing() =
         testScope.runTest {
+            overrideResource(R.bool.config_use_split_notification_shade, false)
             val destinations by collectLastValue(underTest.destinationScenes)
             qsFlexiglassAdapter.setCustomizing(false)
 
             assertThat(destinations)
                 .isEqualTo(
                     mapOf(
-                        UserAction.Back to UserActionResult(SceneKey.Shade),
-                        UserAction.Swipe(Direction.UP) to UserActionResult(SceneKey.Shade),
+                        Back to UserActionResult(Scenes.Shade),
+                        Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade),
                     )
                 )
         }
@@ -130,13 +133,45 @@
     @Test
     fun destinationsCustomizing() =
         testScope.runTest {
+            overrideResource(R.bool.config_use_split_notification_shade, false)
             val destinations by collectLastValue(underTest.destinationScenes)
             qsFlexiglassAdapter.setCustomizing(true)
 
             assertThat(destinations)
                 .isEqualTo(
                     mapOf(
-                        UserAction.Back to UserActionResult(SceneKey.QuickSettings),
+                        Back to UserActionResult(Scenes.QuickSettings),
+                    )
+                )
+        }
+
+    @Test
+    fun destinations_whenNotCustomizing_inSplitShade() =
+        testScope.runTest {
+            overrideResource(R.bool.config_use_split_notification_shade, true)
+            val destinations by collectLastValue(underTest.destinationScenes)
+            qsFlexiglassAdapter.setCustomizing(false)
+
+            assertThat(destinations)
+                .isEqualTo(
+                    mapOf(
+                        Back to UserActionResult(Scenes.Shade),
+                        Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade),
+                    )
+                )
+        }
+
+    @Test
+    fun destinations_whenCustomizing_inSplitShade() =
+        testScope.runTest {
+            overrideResource(R.bool.config_use_split_notification_shade, true)
+            val destinations by collectLastValue(underTest.destinationScenes)
+            qsFlexiglassAdapter.setCustomizing(true)
+
+            assertThat(destinations)
+                .isEqualTo(
+                    mapOf(
+                        Back to UserActionResult(Scenes.QuickSettings),
                     )
                 )
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 4e72843..af9abcd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -20,8 +20,12 @@
 
 import android.telecom.TelecomManager
 import android.telephony.TelephonyManager
+import android.testing.TestableLooper.RunWithLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.Swipe
 import com.android.internal.R
 import com.android.internal.util.EmergencyAffordanceManager
 import com.android.internal.util.emergencyAffordanceManager
@@ -40,6 +44,7 @@
 import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel
 import com.android.systemui.classifier.domain.interactor.falsingInteractor
 import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.classifier.falsingManager
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
@@ -56,17 +61,19 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.qs.footerActionsController
+import com.android.systemui.qs.footerActionsViewModelFactory
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.domain.startable.SceneContainerStartable
 import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
 import com.android.systemui.settings.FakeDisplayTracker
 import com.android.systemui.shade.domain.interactor.privacyChipInteractor
 import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
 import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
 import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
@@ -125,6 +132,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@RunWithLooper
 class SceneFrameworkIntegrationTest : SysuiTestCase() {
 
     private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
@@ -165,6 +173,7 @@
                     interactor = mock(),
                 ),
             notifications = kosmos.notificationsPlaceholderViewModel,
+            shadeInteractor = kosmos.shadeInteractor,
         )
     }
 
@@ -248,6 +257,9 @@
                 qsSceneAdapter = qsFlexiglassAdapter,
                 notifications = kosmos.notificationsPlaceholderViewModel,
                 mediaDataManager = mediaDataManager,
+                shadeInteractor = kosmos.shadeInteractor,
+                footerActionsController = kosmos.footerActionsController,
+                footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
             )
 
         kosmos.fakeDeviceEntryRepository.setUnlocked(false)
@@ -265,6 +277,7 @@
                 displayId = displayTracker.defaultDisplayId,
                 sceneLogger = mock(),
                 falsingCollector = kosmos.falsingCollector,
+                falsingManager = kosmos.falsingManager,
                 powerInteractor = powerInteractor,
                 bouncerInteractor = bouncerInteractor,
                 simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
@@ -285,27 +298,27 @@
     }
 
     @Test
-    fun startsInLockscreenScene() = testScope.runTest { assertCurrentScene(SceneKey.Lockscreen) }
+    fun startsInLockscreenScene() = testScope.runTest { assertCurrentScene(Scenes.Lockscreen) }
 
     @Test
     fun clickLockButtonAndEnterCorrectPin_unlocksDevice() =
         testScope.runTest {
-            emulateUserDrivenTransition(SceneKey.Bouncer)
+            emulateUserDrivenTransition(Scenes.Bouncer)
 
             fakeSceneDataSource.pause()
             enterPin()
             emulatePendingTransitionProgress(
                 expectedVisible = false,
             )
-            assertCurrentScene(SceneKey.Gone)
+            assertCurrentScene(Scenes.Gone)
         }
 
     @Test
     fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
         testScope.runTest {
-            val upDestinationSceneKey by
-                collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
-            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Bouncer)
+            val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+            assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(
                 to = upDestinationSceneKey,
             )
@@ -315,7 +328,7 @@
             emulatePendingTransitionProgress(
                 expectedVisible = false,
             )
-            assertCurrentScene(SceneKey.Gone)
+            assertCurrentScene(Scenes.Gone)
         }
 
     @Test
@@ -323,9 +336,9 @@
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
 
-            val upDestinationSceneKey by
-                collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
-            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone)
+            val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+            assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
             emulateUserDrivenTransition(
                 to = upDestinationSceneKey,
             )
@@ -334,15 +347,16 @@
     @Test
     fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
         testScope.runTest {
-            val upDestinationSceneKey by collectLastValue(shadeSceneViewModel.upDestinationSceneKey)
+            val destinationScenes by collectLastValue(shadeSceneViewModel.destinationScenes)
             setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
-            assertCurrentScene(SceneKey.Lockscreen)
+            assertCurrentScene(Scenes.Lockscreen)
 
             // Emulate a user swipe to the shade scene.
-            emulateUserDrivenTransition(to = SceneKey.Shade)
-            assertCurrentScene(SceneKey.Shade)
+            emulateUserDrivenTransition(to = Scenes.Shade)
+            assertCurrentScene(Scenes.Shade)
 
-            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Lockscreen)
+            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+            assertThat(upDestinationSceneKey).isEqualTo(Scenes.Lockscreen)
             emulateUserDrivenTransition(
                 to = upDestinationSceneKey,
             )
@@ -351,20 +365,21 @@
     @Test
     fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenDismissed_goesToGone() =
         testScope.runTest {
-            val upDestinationSceneKey by collectLastValue(shadeSceneViewModel.upDestinationSceneKey)
+            val destinationScenes by collectLastValue(shadeSceneViewModel.destinationScenes)
             setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
             assertThat(deviceEntryInteractor.canSwipeToEnter.value).isTrue()
-            assertCurrentScene(SceneKey.Lockscreen)
+            assertCurrentScene(Scenes.Lockscreen)
 
             // Emulate a user swipe to dismiss the lockscreen.
-            emulateUserDrivenTransition(to = SceneKey.Gone)
-            assertCurrentScene(SceneKey.Gone)
+            emulateUserDrivenTransition(to = Scenes.Gone)
+            assertCurrentScene(Scenes.Gone)
 
             // Emulate a user swipe to the shade scene.
-            emulateUserDrivenTransition(to = SceneKey.Shade)
-            assertCurrentScene(SceneKey.Shade)
+            emulateUserDrivenTransition(to = Scenes.Shade)
+            assertCurrentScene(Scenes.Shade)
 
-            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone)
+            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+            assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
             emulateUserDrivenTransition(
                 to = upDestinationSceneKey,
             )
@@ -375,10 +390,10 @@
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = false)
             putDeviceToSleep(instantlyLockDevice = false)
-            assertCurrentScene(SceneKey.Lockscreen)
+            assertCurrentScene(Scenes.Lockscreen)
 
             wakeUpDevice()
-            assertCurrentScene(SceneKey.Gone)
+            assertCurrentScene(Scenes.Gone)
         }
 
     @Test
@@ -386,76 +401,76 @@
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
             putDeviceToSleep(instantlyLockDevice = false)
-            assertCurrentScene(SceneKey.Lockscreen)
+            assertCurrentScene(Scenes.Lockscreen)
 
             wakeUpDevice()
-            assertCurrentScene(SceneKey.Lockscreen)
+            assertCurrentScene(Scenes.Lockscreen)
         }
 
     @Test
     fun deviceGoesToSleep_switchesToLockscreen() =
         testScope.runTest {
             unlockDevice()
-            assertCurrentScene(SceneKey.Gone)
+            assertCurrentScene(Scenes.Gone)
 
             putDeviceToSleep()
-            assertCurrentScene(SceneKey.Lockscreen)
+            assertCurrentScene(Scenes.Lockscreen)
         }
 
     @Test
     fun deviceGoesToSleep_wakeUp_unlock() =
         testScope.runTest {
             unlockDevice()
-            assertCurrentScene(SceneKey.Gone)
+            assertCurrentScene(Scenes.Gone)
             putDeviceToSleep()
-            assertCurrentScene(SceneKey.Lockscreen)
+            assertCurrentScene(Scenes.Lockscreen)
             wakeUpDevice()
-            assertCurrentScene(SceneKey.Lockscreen)
+            assertCurrentScene(Scenes.Lockscreen)
 
             unlockDevice()
-            assertCurrentScene(SceneKey.Gone)
+            assertCurrentScene(Scenes.Gone)
         }
 
     @Test
     fun deviceWakesUpWhileUnlocked_dismissesLockscreen() =
         testScope.runTest {
             unlockDevice()
-            assertCurrentScene(SceneKey.Gone)
+            assertCurrentScene(Scenes.Gone)
             putDeviceToSleep(instantlyLockDevice = false)
-            assertCurrentScene(SceneKey.Lockscreen)
+            assertCurrentScene(Scenes.Lockscreen)
             wakeUpDevice()
-            assertCurrentScene(SceneKey.Gone)
+            assertCurrentScene(Scenes.Gone)
         }
 
     @Test
     fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
         testScope.runTest {
             unlockDevice()
-            val upDestinationSceneKey by
-                collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
-            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone)
+            val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+            assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
         }
 
     @Test
     fun deviceGoesToSleep_withLockTimeout_staysOnLockscreen() =
         testScope.runTest {
             unlockDevice()
-            assertCurrentScene(SceneKey.Gone)
+            assertCurrentScene(Scenes.Gone)
             putDeviceToSleep(instantlyLockDevice = false)
-            assertCurrentScene(SceneKey.Lockscreen)
+            assertCurrentScene(Scenes.Lockscreen)
 
             // Pretend like the timeout elapsed and now lock the device.
             lockDevice()
-            assertCurrentScene(SceneKey.Lockscreen)
+            assertCurrentScene(Scenes.Lockscreen)
         }
 
     @Test
     fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() =
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.Password)
-            val upDestinationSceneKey by
-                collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
-            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Bouncer)
+            val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+            assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(
                 to = upDestinationSceneKey,
             )
@@ -464,16 +479,16 @@
             dismissIme()
 
             emulatePendingTransitionProgress()
-            assertCurrentScene(SceneKey.Lockscreen)
+            assertCurrentScene(Scenes.Lockscreen)
         }
 
     @Test
     fun bouncerActionButtonClick_opensEmergencyServicesDialer() =
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.Password)
-            val upDestinationSceneKey by
-                collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
-            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Bouncer)
+            val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+            assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(to = upDestinationSceneKey)
 
             val bouncerActionButton by collectLastValue(bouncerViewModel.actionButton)
@@ -491,9 +506,9 @@
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.Password)
             startPhoneCall()
-            val upDestinationSceneKey by
-                collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
-            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Bouncer)
+            val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+            val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+            assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(to = upDestinationSceneKey)
 
             val bouncerActionButton by collectLastValue(bouncerViewModel.actionButton)
@@ -511,7 +526,7 @@
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.None)
             introduceLockedSim()
-            assertCurrentScene(SceneKey.Bouncer)
+            assertCurrentScene(Scenes.Bouncer)
         }
 
     @Test
@@ -521,7 +536,7 @@
             introduceLockedSim()
             emulatePendingTransitionProgress(expectedVisible = true)
             enterSimPin(authMethodAfterSimUnlock = AuthenticationMethodModel.None)
-            assertCurrentScene(SceneKey.Gone)
+            assertCurrentScene(Scenes.Gone)
         }
 
     @Test
@@ -531,7 +546,7 @@
             introduceLockedSim()
             emulatePendingTransitionProgress(expectedVisible = true)
             enterSimPin(authMethodAfterSimUnlock = AuthenticationMethodModel.Pin)
-            assertCurrentScene(SceneKey.Lockscreen)
+            assertCurrentScene(Scenes.Lockscreen)
         }
 
     @Test
@@ -613,6 +628,7 @@
     private fun TestScope.emulatePendingTransitionProgress(
         expectedVisible: Boolean = true,
     ) {
+        val isVisible by collectLastValue(sceneContainerViewModel.isVisible)
         assertWithMessage("The FakeSceneDataSource has to be paused for this to do anything.")
             .that(fakeSceneDataSource.isPaused)
             .isTrue()
@@ -649,12 +665,12 @@
         runCurrent()
 
         assertWithMessage("Visibility mismatch after scene transition from $from to $to!")
-            .that(sceneContainerViewModel.isVisible.value)
+            .that(isVisible)
             .isEqualTo(expectedVisible)
         assertThat(sceneContainerViewModel.currentScene.value).isEqualTo(to)
 
         bouncerSceneJob =
-            if (to == SceneKey.Bouncer) {
+            if (to == Scenes.Bouncer) {
                 testScope.backgroundScope.launch {
                     bouncerViewModel.authMethodViewModel.collect {
                         // Do nothing. Need this to turn this otherwise cold flow, hot.
@@ -685,7 +701,7 @@
         sceneInteractor.changeScene(to, "reason")
 
         emulatePendingTransitionProgress(
-            expectedVisible = to != SceneKey.Gone,
+            expectedVisible = to != Scenes.Gone,
         )
     }
 
@@ -712,7 +728,7 @@
             .that(deviceEntryInteractor.isUnlocked.value)
             .isFalse()
 
-        emulateUserDrivenTransition(SceneKey.Bouncer)
+        emulateUserDrivenTransition(Scenes.Bouncer)
         fakeSceneDataSource.pause()
         enterPin()
         // This repository state is not changed by the AuthInteractor, it relies on
@@ -726,7 +742,7 @@
     /**
      * Enters the correct PIN in the bouncer UI.
      *
-     * Asserts that the current scene is [SceneKey.Bouncer] and that the current bouncer UI is a PIN
+     * Asserts that the current scene is [Scenes.Bouncer] and that the current bouncer UI is a PIN
      * before proceeding.
      *
      * Does not assert that the device is locked or unlocked.
@@ -734,7 +750,7 @@
     private fun TestScope.enterPin() {
         assertWithMessage("Cannot enter PIN when not on the Bouncer scene!")
             .that(getCurrentSceneInUi())
-            .isEqualTo(SceneKey.Bouncer)
+            .isEqualTo(Scenes.Bouncer)
         val authMethodViewModel by collectLastValue(bouncerViewModel.authMethodViewModel)
         assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
             .that(authMethodViewModel)
@@ -751,7 +767,7 @@
     /**
      * Enters the correct PIN in the sim bouncer UI.
      *
-     * Asserts that the current scene is [SceneKey.Bouncer] and that the current bouncer UI is a PIN
+     * Asserts that the current scene is [Scenes.Bouncer] and that the current bouncer UI is a PIN
      * before proceeding.
      *
      * Does not assert that the device is locked or unlocked.
@@ -761,7 +777,7 @@
     ) {
         assertWithMessage("Cannot enter PIN when not on the Bouncer scene!")
             .that(getCurrentSceneInUi())
-            .isEqualTo(SceneKey.Bouncer)
+            .isEqualTo(Scenes.Bouncer)
         val authMethodViewModel by collectLastValue(bouncerViewModel.authMethodViewModel)
         assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
             .that(authMethodViewModel)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index 1da3bc1..3d66192 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -20,14 +20,14 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.sceneContainerConfig
 import com.android.systemui.scene.sceneKeys
 import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -51,12 +51,12 @@
         assertThat(underTest.allSceneKeys())
             .isEqualTo(
                 listOf(
-                    SceneKey.QuickSettings,
-                    SceneKey.Shade,
-                    SceneKey.Lockscreen,
-                    SceneKey.Bouncer,
-                    SceneKey.Gone,
-                    SceneKey.Communal,
+                    Scenes.QuickSettings,
+                    Scenes.Shade,
+                    Scenes.Lockscreen,
+                    Scenes.Bouncer,
+                    Scenes.Gone,
+                    Scenes.Communal,
                 )
             )
     }
@@ -66,17 +66,17 @@
         testScope.runTest {
             val underTest = kosmos.sceneContainerRepository
             val currentScene by collectLastValue(underTest.currentScene)
-            assertThat(currentScene).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
 
-            underTest.changeScene(SceneKey.Shade)
-            assertThat(currentScene).isEqualTo(SceneKey.Shade)
+            underTest.changeScene(Scenes.Shade)
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
         }
 
     @Test(expected = IllegalStateException::class)
     fun changeScene_noSuchSceneInContainer_throws() {
-        kosmos.sceneKeys = listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
+        kosmos.sceneKeys = listOf(Scenes.QuickSettings, Scenes.Lockscreen)
         val underTest = kosmos.sceneContainerRepository
-        underTest.changeScene(SceneKey.Shade)
+        underTest.changeScene(Scenes.Shade)
     }
 
     @Test
@@ -111,7 +111,7 @@
             val underTest = kosmos.sceneContainerRepository
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(SceneKey.Lockscreen)
+                    ObservableTransitionState.Idle(Scenes.Lockscreen)
                 )
             underTest.setTransitionState(transitionState)
             val reflectedTransitionState by collectLastValue(underTest.transitionState)
@@ -120,8 +120,8 @@
             val progress = MutableStateFlow(1f)
             transitionState.value =
                 ObservableTransitionState.Transition(
-                    fromScene = SceneKey.Lockscreen,
-                    toScene = SceneKey.Shade,
+                    fromScene = Scenes.Lockscreen,
+                    toScene = Scenes.Shade,
                     progress = progress,
                     isInitiatedByUserInput = false,
                     isUserInputOngoing = flowOf(false),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index dd3eb68..f645f1c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -14,10 +14,13 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.scene.domain.interactor
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
@@ -26,11 +29,11 @@
 import com.android.systemui.scene.sceneContainerConfig
 import com.android.systemui.scene.sceneKeys
 import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.testKosmos
 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.runCurrent
@@ -64,23 +67,23 @@
     fun changeScene() =
         testScope.runTest {
             val currentScene by collectLastValue(underTest.currentScene)
-            assertThat(currentScene).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
 
-            underTest.changeScene(SceneKey.Shade, "reason")
-            assertThat(currentScene).isEqualTo(SceneKey.Shade)
+            underTest.changeScene(Scenes.Shade, "reason")
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
         }
 
     @Test
     fun changeScene_toGoneWhenUnl_doesNotThrow() =
         testScope.runTest {
             val currentScene by collectLastValue(underTest.currentScene)
-            assertThat(currentScene).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
 
             kosmos.fakeDeviceEntryRepository.setUnlocked(true)
             runCurrent()
 
-            underTest.changeScene(SceneKey.Gone, "reason")
-            assertThat(currentScene).isEqualTo(SceneKey.Gone)
+            underTest.changeScene(Scenes.Gone, "reason")
+            assertThat(currentScene).isEqualTo(Scenes.Gone)
         }
 
     @Test(expected = IllegalStateException::class)
@@ -88,18 +91,18 @@
         testScope.runTest {
             kosmos.fakeDeviceEntryRepository.setUnlocked(false)
 
-            underTest.changeScene(SceneKey.Gone, "reason")
+            underTest.changeScene(Scenes.Gone, "reason")
         }
 
     @Test
     fun sceneChanged_inDataSource() =
         testScope.runTest {
             val currentScene by collectLastValue(underTest.currentScene)
-            assertThat(currentScene).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
 
-            fakeSceneDataSource.changeScene(SceneKey.Shade)
+            fakeSceneDataSource.changeScene(Scenes.Shade)
 
-            assertThat(currentScene).isEqualTo(SceneKey.Shade)
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
         }
 
     @Test
@@ -108,7 +111,7 @@
             val underTest = kosmos.sceneContainerRepository
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(SceneKey.Lockscreen)
+                    ObservableTransitionState.Idle(Scenes.Lockscreen)
                 )
             underTest.setTransitionState(transitionState)
             val reflectedTransitionState by collectLastValue(underTest.transitionState)
@@ -117,8 +120,8 @@
             val progress = MutableStateFlow(1f)
             transitionState.value =
                 ObservableTransitionState.Transition(
-                    fromScene = SceneKey.Lockscreen,
-                    toScene = SceneKey.Shade,
+                    fromScene = Scenes.Lockscreen,
+                    toScene = Scenes.Shade,
                     progress = progress,
                     isInitiatedByUserInput = false,
                     isUserInputOngoing = flowOf(false),
@@ -150,27 +153,27 @@
             val transitionTo by collectLastValue(underTest.transitioningTo)
             assertThat(transitionTo).isNull()
 
-            underTest.changeScene(SceneKey.Shade, "reason")
+            underTest.changeScene(Scenes.Shade, "reason")
             assertThat(transitionTo).isNull()
 
             val progress = MutableStateFlow(0f)
             transitionState.value =
                 ObservableTransitionState.Transition(
                     fromScene = underTest.currentScene.value,
-                    toScene = SceneKey.Shade,
+                    toScene = Scenes.Shade,
                     progress = progress,
                     isInitiatedByUserInput = false,
                     isUserInputOngoing = flowOf(false),
                 )
-            assertThat(transitionTo).isEqualTo(SceneKey.Shade)
+            assertThat(transitionTo).isEqualTo(Scenes.Shade)
 
             progress.value = 0.5f
-            assertThat(transitionTo).isEqualTo(SceneKey.Shade)
+            assertThat(transitionTo).isEqualTo(Scenes.Shade)
 
             progress.value = 1f
-            assertThat(transitionTo).isEqualTo(SceneKey.Shade)
+            assertThat(transitionTo).isEqualTo(Scenes.Shade)
 
-            transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade)
+            transitionState.value = ObservableTransitionState.Idle(Scenes.Shade)
             assertThat(transitionTo).isNull()
         }
 
@@ -179,7 +182,7 @@
         testScope.runTest {
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(SceneKey.Shade)
+                    ObservableTransitionState.Idle(Scenes.Shade)
                 )
             val isTransitionUserInputOngoing by
                 collectLastValue(underTest.isTransitionUserInputOngoing)
@@ -194,8 +197,8 @@
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Transition(
-                        fromScene = SceneKey.Shade,
-                        toScene = SceneKey.Lockscreen,
+                        fromScene = Scenes.Shade,
+                        toScene = Scenes.Lockscreen,
                         progress = flowOf(0.5f),
                         isInitiatedByUserInput = true,
                         isUserInputOngoing = flowOf(true),
@@ -214,8 +217,8 @@
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Transition(
-                        fromScene = SceneKey.Shade,
-                        toScene = SceneKey.Lockscreen,
+                        fromScene = Scenes.Shade,
+                        toScene = Scenes.Lockscreen,
                         progress = flowOf(0.5f),
                         isInitiatedByUserInput = true,
                         isUserInputOngoing = flowOf(true),
@@ -229,8 +232,8 @@
 
             transitionState.value =
                 ObservableTransitionState.Transition(
-                    fromScene = SceneKey.Shade,
-                    toScene = SceneKey.Lockscreen,
+                    fromScene = Scenes.Shade,
+                    toScene = Scenes.Lockscreen,
                     progress = flowOf(0.6f),
                     isInitiatedByUserInput = true,
                     isUserInputOngoing = flowOf(false),
@@ -245,8 +248,8 @@
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Transition(
-                        fromScene = SceneKey.Shade,
-                        toScene = SceneKey.Lockscreen,
+                        fromScene = Scenes.Shade,
+                        toScene = Scenes.Lockscreen,
                         progress = flowOf(0.5f),
                         isInitiatedByUserInput = true,
                         isUserInputOngoing = flowOf(true),
@@ -258,7 +261,7 @@
 
             assertThat(isTransitionUserInputOngoing).isTrue()
 
-            transitionState.value = ObservableTransitionState.Idle(scene = SceneKey.Lockscreen)
+            transitionState.value = ObservableTransitionState.Idle(scene = Scenes.Lockscreen)
 
             assertThat(isTransitionUserInputOngoing).isFalse()
         }
@@ -275,4 +278,18 @@
             underTest.setVisible(true, "reason")
             assertThat(isVisible).isTrue()
         }
+
+    @Test
+    fun isVisible_duringRemoteUserInteraction_forcedVisible() =
+        testScope.runTest {
+            underTest.setVisible(false, "reason")
+            val isVisible by collectLastValue(underTest.isVisible)
+            assertThat(isVisible).isFalse()
+            underTest.onRemoteUserInteractionStarted("reason")
+            assertThat(isVisible).isTrue()
+
+            underTest.onUserInteractionFinished()
+
+            assertThat(isVisible).isFalse()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index f49b477..cc66f8b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -24,6 +24,8 @@
 import android.view.Display
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.Flags as AconfigFlags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -32,6 +34,7 @@
 import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.classifier.falsingManager
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
@@ -45,8 +48,7 @@
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
@@ -115,6 +117,7 @@
                 displayId = Display.DEFAULT_DISPLAY,
                 sceneLogger = mock(),
                 falsingCollector = falsingCollector,
+                falsingManager = kosmos.falsingManager,
                 powerInteractor = powerInteractor,
                 bouncerInteractor = bouncerInteractor,
                 simBouncerInteractor = { kosmos.simBouncerInteractor },
@@ -134,42 +137,42 @@
             val transitionStateFlow =
                 prepareState(
                     isDeviceUnlocked = true,
-                    initialSceneKey = SceneKey.Gone,
+                    initialSceneKey = Scenes.Gone,
                 )
-            assertThat(currentDesiredSceneKey).isEqualTo(SceneKey.Gone)
+            assertThat(currentDesiredSceneKey).isEqualTo(Scenes.Gone)
             assertThat(isVisible).isTrue()
 
             underTest.start()
             assertThat(isVisible).isFalse()
 
             fakeSceneDataSource.pause()
-            sceneInteractor.changeScene(SceneKey.Shade, "reason")
+            sceneInteractor.changeScene(Scenes.Shade, "reason")
             transitionStateFlow.value =
                 ObservableTransitionState.Transition(
-                    fromScene = SceneKey.Gone,
-                    toScene = SceneKey.Shade,
+                    fromScene = Scenes.Gone,
+                    toScene = Scenes.Shade,
                     progress = flowOf(0.5f),
                     isInitiatedByUserInput = false,
                     isUserInputOngoing = flowOf(false),
                 )
             assertThat(isVisible).isTrue()
-            fakeSceneDataSource.unpause(expectedScene = SceneKey.Shade)
-            transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Shade)
+            fakeSceneDataSource.unpause(expectedScene = Scenes.Shade)
+            transitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Shade)
             assertThat(isVisible).isTrue()
 
             fakeSceneDataSource.pause()
-            sceneInteractor.changeScene(SceneKey.Gone, "reason")
+            sceneInteractor.changeScene(Scenes.Gone, "reason")
             transitionStateFlow.value =
                 ObservableTransitionState.Transition(
-                    fromScene = SceneKey.Shade,
-                    toScene = SceneKey.Gone,
+                    fromScene = Scenes.Shade,
+                    toScene = Scenes.Gone,
                     progress = flowOf(0.5f),
                     isInitiatedByUserInput = false,
                     isUserInputOngoing = flowOf(false),
                 )
             assertThat(isVisible).isTrue()
-            fakeSceneDataSource.unpause(expectedScene = SceneKey.Gone)
-            transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
+            fakeSceneDataSource.unpause(expectedScene = Scenes.Gone)
+            transitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone)
             assertThat(isVisible).isFalse()
 
             kosmos.headsUpNotificationRepository.hasPinnedHeadsUp.value = true
@@ -185,7 +188,7 @@
             val isVisible by collectLastValue(sceneInteractor.isVisible)
             prepareState(
                 isDeviceUnlocked = true,
-                initialSceneKey = SceneKey.Lockscreen,
+                initialSceneKey = Scenes.Lockscreen,
                 isDeviceProvisioned = false,
                 isFrpActive = true,
             )
@@ -212,7 +215,7 @@
             underTest.start()
             runCurrent()
 
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
         }
 
     @Test
@@ -221,14 +224,14 @@
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
             prepareState(
                 isDeviceUnlocked = true,
-                initialSceneKey = SceneKey.Gone,
+                initialSceneKey = Scenes.Gone,
             )
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
             underTest.start()
 
             kosmos.fakeDeviceEntryRepository.setUnlocked(false)
 
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
         }
 
     @Test
@@ -237,14 +240,14 @@
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
             prepareState(
                 isDeviceUnlocked = false,
-                initialSceneKey = SceneKey.Bouncer,
+                initialSceneKey = Scenes.Bouncer,
             )
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Bouncer)
             underTest.start()
 
             kosmos.fakeDeviceEntryRepository.setUnlocked(true)
 
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
         }
 
     @Test
@@ -253,14 +256,14 @@
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
             prepareState(
                 isBypassEnabled = true,
-                initialSceneKey = SceneKey.Lockscreen,
+                initialSceneKey = Scenes.Lockscreen,
             )
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
             underTest.start()
 
             kosmos.fakeDeviceEntryRepository.setUnlocked(true)
 
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
         }
 
     @Test
@@ -269,16 +272,16 @@
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
             prepareState(
                 isBypassEnabled = false,
-                initialSceneKey = SceneKey.Lockscreen,
+                initialSceneKey = Scenes.Lockscreen,
             )
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
             underTest.start()
 
             // Authenticate using a passive auth method like face auth while bypass is disabled.
             faceAuthRepository.isAuthenticated.value = true
             kosmos.fakeDeviceEntryRepository.setUnlocked(true)
 
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
         }
 
     @Test
@@ -289,19 +292,19 @@
                 prepareState(
                     isBypassEnabled = true,
                     authenticationMethod = AuthenticationMethodModel.Pin,
-                    initialSceneKey = SceneKey.Lockscreen,
+                    initialSceneKey = Scenes.Lockscreen,
                 )
             underTest.start()
             runCurrent()
 
-            sceneInteractor.changeScene(SceneKey.Shade, "switch to shade")
-            transitionStateFlowValue.value = ObservableTransitionState.Idle(SceneKey.Shade)
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
+            sceneInteractor.changeScene(Scenes.Shade, "switch to shade")
+            transitionStateFlowValue.value = ObservableTransitionState.Idle(Scenes.Shade)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Shade)
 
             kosmos.fakeDeviceEntryRepository.setUnlocked(true)
             runCurrent()
 
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Shade)
         }
 
     @Test
@@ -310,16 +313,16 @@
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
             prepareState(
                 isBypassEnabled = false,
-                initialSceneKey = SceneKey.Bouncer,
+                initialSceneKey = Scenes.Bouncer,
             )
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Bouncer)
             underTest.start()
 
             // Authenticate using a passive auth method like face auth while bypass is disabled.
             faceAuthRepository.isAuthenticated.value = true
             kosmos.fakeDeviceEntryRepository.setUnlocked(true)
 
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
         }
 
     @Test
@@ -328,13 +331,13 @@
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
             prepareState(
                 isDeviceUnlocked = false,
-                initialSceneKey = SceneKey.Shade,
+                initialSceneKey = Scenes.Shade,
             )
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Shade)
             underTest.start()
             powerInteractor.setAsleepForTest()
 
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
         }
 
     @Test
@@ -346,14 +349,14 @@
             clearInvocations(sysUiState)
 
             listOf(
-                    SceneKey.Gone,
-                    SceneKey.Lockscreen,
-                    SceneKey.Bouncer,
-                    SceneKey.Shade,
-                    SceneKey.QuickSettings,
+                    Scenes.Gone,
+                    Scenes.Lockscreen,
+                    Scenes.Bouncer,
+                    Scenes.Shade,
+                    Scenes.QuickSettings,
                 )
                 .forEachIndexed { index, sceneKey ->
-                    if (sceneKey == SceneKey.Gone) {
+                    if (sceneKey == Scenes.Gone) {
                         kosmos.fakeDeviceEntryRepository.setUnlocked(true)
                         runCurrent()
                     }
@@ -377,15 +380,15 @@
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
             prepareState(
-                initialSceneKey = SceneKey.Lockscreen,
+                initialSceneKey = Scenes.Lockscreen,
                 authenticationMethod = AuthenticationMethodModel.None,
                 isLockscreenEnabled = false,
             )
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
             underTest.start()
             powerInteractor.setAwakeForTest()
 
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
         }
 
     @Test
@@ -393,15 +396,15 @@
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
             prepareState(
-                initialSceneKey = SceneKey.Lockscreen,
+                initialSceneKey = Scenes.Lockscreen,
                 authenticationMethod = AuthenticationMethodModel.None,
                 isLockscreenEnabled = true,
             )
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
             underTest.start()
             powerInteractor.setAwakeForTest()
 
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
         }
 
     @Test
@@ -409,14 +412,14 @@
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
             prepareState(
-                initialSceneKey = SceneKey.Lockscreen,
+                initialSceneKey = Scenes.Lockscreen,
                 authenticationMethod = AuthenticationMethodModel.Pin,
             )
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
             underTest.start()
             powerInteractor.setAwakeForTest()
 
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
         }
 
     @Test
@@ -424,12 +427,12 @@
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
             prepareState(
-                initialSceneKey = SceneKey.Lockscreen,
+                initialSceneKey = Scenes.Lockscreen,
                 authenticationMethod = AuthenticationMethodModel.Pin,
                 isDeviceUnlocked = false,
                 startsAwake = false
             )
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
             underTest.start()
 
             kosmos.fakeDeviceEntryRepository.setUnlocked(true)
@@ -437,14 +440,14 @@
             powerInteractor.setAwakeForTest()
             runCurrent()
 
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
         }
 
     @Test
     fun collectFalsingSignals_onSuccessfulUnlock() =
         testScope.runTest {
             prepareState(
-                initialSceneKey = SceneKey.Lockscreen,
+                initialSceneKey = Scenes.Lockscreen,
                 authenticationMethod = AuthenticationMethodModel.Pin,
                 isDeviceUnlocked = false,
             )
@@ -454,11 +457,11 @@
 
             // Move around scenes without unlocking.
             listOf(
-                    SceneKey.Shade,
-                    SceneKey.QuickSettings,
-                    SceneKey.Shade,
-                    SceneKey.Lockscreen,
-                    SceneKey.Bouncer,
+                    Scenes.Shade,
+                    Scenes.QuickSettings,
+                    Scenes.Shade,
+                    Scenes.Lockscreen,
+                    Scenes.Bouncer,
                 )
                 .forEach { sceneKey ->
                     sceneInteractor.changeScene(sceneKey, "reason")
@@ -469,17 +472,17 @@
             // Changing to the Gone scene should report a successful unlock.
             kosmos.fakeDeviceEntryRepository.setUnlocked(true)
             runCurrent()
-            sceneInteractor.changeScene(SceneKey.Gone, "reason")
+            sceneInteractor.changeScene(Scenes.Gone, "reason")
             runCurrent()
             verify(falsingCollector).onSuccessfulUnlock()
 
             // Move around scenes without changing back to Lockscreen, shouldn't report another
             // unlock.
             listOf(
-                    SceneKey.Shade,
-                    SceneKey.QuickSettings,
-                    SceneKey.Shade,
-                    SceneKey.Gone,
+                    Scenes.Shade,
+                    Scenes.QuickSettings,
+                    Scenes.Shade,
+                    Scenes.Gone,
                 )
                 .forEach { sceneKey ->
                     sceneInteractor.changeScene(sceneKey, "reason")
@@ -488,17 +491,17 @@
                 }
 
             // Changing to the Lockscreen scene shouldn't report a successful unlock.
-            sceneInteractor.changeScene(SceneKey.Lockscreen, "reason")
+            sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
             runCurrent()
             verify(falsingCollector, times(1)).onSuccessfulUnlock()
 
             // Move around scenes without unlocking.
             listOf(
-                    SceneKey.Shade,
-                    SceneKey.QuickSettings,
-                    SceneKey.Shade,
-                    SceneKey.Lockscreen,
-                    SceneKey.Bouncer,
+                    Scenes.Shade,
+                    Scenes.QuickSettings,
+                    Scenes.Shade,
+                    Scenes.Lockscreen,
+                    Scenes.Bouncer,
                 )
                 .forEach { sceneKey ->
                     sceneInteractor.changeScene(sceneKey, "reason")
@@ -507,7 +510,7 @@
                 }
 
             // Changing to the Gone scene should report a second successful unlock.
-            sceneInteractor.changeScene(SceneKey.Gone, "reason")
+            sceneInteractor.changeScene(Scenes.Gone, "reason")
             runCurrent()
             verify(falsingCollector, times(2)).onSuccessfulUnlock()
         }
@@ -516,7 +519,7 @@
     fun collectFalsingSignals_setShowingAod() =
         testScope.runTest {
             prepareState(
-                initialSceneKey = SceneKey.Lockscreen,
+                initialSceneKey = Scenes.Lockscreen,
                 authenticationMethod = AuthenticationMethodModel.Pin,
                 isDeviceUnlocked = false,
             )
@@ -538,7 +541,7 @@
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
             prepareState(
-                initialSceneKey = SceneKey.Lockscreen,
+                initialSceneKey = Scenes.Lockscreen,
                 authenticationMethod = AuthenticationMethodModel.Password,
                 isDeviceUnlocked = false,
             )
@@ -548,7 +551,7 @@
             bouncerInteractor.onImeHiddenByUser()
             runCurrent()
 
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
         }
 
     @Test
@@ -557,7 +560,7 @@
             kosmos.fakeKeyguardRepository.setAodAvailable(false)
             runCurrent()
             prepareState(
-                initialSceneKey = SceneKey.Lockscreen,
+                initialSceneKey = Scenes.Lockscreen,
                 authenticationMethod = AuthenticationMethodModel.Pin,
                 isDeviceUnlocked = false,
                 startsAwake = false,
@@ -605,7 +608,7 @@
             kosmos.fakeKeyguardRepository.setAodAvailable(true)
             runCurrent()
             prepareState(
-                initialSceneKey = SceneKey.Lockscreen,
+                initialSceneKey = Scenes.Lockscreen,
                 authenticationMethod = AuthenticationMethodModel.Pin,
                 isDeviceUnlocked = false,
             )
@@ -650,7 +653,7 @@
     fun collectFalsingSignals_bouncerVisibility() =
         testScope.runTest {
             prepareState(
-                initialSceneKey = SceneKey.Lockscreen,
+                initialSceneKey = Scenes.Lockscreen,
                 authenticationMethod = AuthenticationMethodModel.Pin,
                 isDeviceUnlocked = false,
             )
@@ -658,13 +661,13 @@
             runCurrent()
             verify(falsingCollector).onBouncerHidden()
 
-            sceneInteractor.changeScene(SceneKey.Bouncer, "reason")
+            sceneInteractor.changeScene(Scenes.Bouncer, "reason")
             runCurrent()
             verify(falsingCollector).onBouncerShown()
 
             kosmos.fakeDeviceEntryRepository.setUnlocked(true)
             runCurrent()
-            sceneInteractor.changeScene(SceneKey.Gone, "reason")
+            sceneInteractor.changeScene(Scenes.Gone, "reason")
             runCurrent()
             verify(falsingCollector, times(2)).onBouncerHidden()
         }
@@ -675,7 +678,7 @@
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
 
             prepareState(
-                initialSceneKey = SceneKey.Lockscreen,
+                initialSceneKey = Scenes.Lockscreen,
                 authenticationMethod = AuthenticationMethodModel.Pin,
                 isDeviceUnlocked = false,
             )
@@ -685,7 +688,7 @@
             kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true
             runCurrent()
 
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Bouncer)
         }
 
     @Test
@@ -695,7 +698,7 @@
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
 
             prepareState(
-                initialSceneKey = SceneKey.Bouncer,
+                initialSceneKey = Scenes.Bouncer,
                 authenticationMethod = AuthenticationMethodModel.Pin,
                 isDeviceUnlocked = false,
             )
@@ -704,7 +707,7 @@
             kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false
             runCurrent()
 
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
         }
 
     @Test
@@ -714,7 +717,7 @@
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
 
             prepareState(
-                initialSceneKey = SceneKey.Lockscreen,
+                initialSceneKey = Scenes.Lockscreen,
                 authenticationMethod = AuthenticationMethodModel.None,
                 isDeviceUnlocked = true,
                 isLockscreenEnabled = false,
@@ -724,7 +727,7 @@
             kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false
             runCurrent()
 
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
         }
 
     @Test
@@ -734,9 +737,9 @@
             val transitionStateFlow =
                 prepareState(
                     isDeviceUnlocked = true,
-                    initialSceneKey = SceneKey.Gone,
+                    initialSceneKey = Scenes.Gone,
                 )
-            assertThat(currentDesiredSceneKey).isEqualTo(SceneKey.Gone)
+            assertThat(currentDesiredSceneKey).isEqualTo(Scenes.Gone)
             verify(windowController, never()).setNotificationShadeFocusable(anyBoolean())
 
             underTest.start()
@@ -744,11 +747,11 @@
             verify(windowController, times(1)).setNotificationShadeFocusable(false)
 
             fakeSceneDataSource.pause()
-            sceneInteractor.changeScene(SceneKey.Shade, "reason")
+            sceneInteractor.changeScene(Scenes.Shade, "reason")
             transitionStateFlow.value =
                 ObservableTransitionState.Transition(
-                    fromScene = SceneKey.Gone,
-                    toScene = SceneKey.Shade,
+                    fromScene = Scenes.Gone,
+                    toScene = Scenes.Shade,
                     progress = flowOf(0.5f),
                     isInitiatedByUserInput = false,
                     isUserInputOngoing = flowOf(false),
@@ -756,17 +759,17 @@
             runCurrent()
             verify(windowController, times(1)).setNotificationShadeFocusable(false)
 
-            fakeSceneDataSource.unpause(expectedScene = SceneKey.Shade)
-            transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Shade)
+            fakeSceneDataSource.unpause(expectedScene = Scenes.Shade)
+            transitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Shade)
             runCurrent()
             verify(windowController, times(1)).setNotificationShadeFocusable(true)
 
             fakeSceneDataSource.pause()
-            sceneInteractor.changeScene(SceneKey.Gone, "reason")
+            sceneInteractor.changeScene(Scenes.Gone, "reason")
             transitionStateFlow.value =
                 ObservableTransitionState.Transition(
-                    fromScene = SceneKey.Shade,
-                    toScene = SceneKey.Gone,
+                    fromScene = Scenes.Shade,
+                    toScene = Scenes.Gone,
                     progress = flowOf(0.5f),
                     isInitiatedByUserInput = false,
                     isUserInputOngoing = flowOf(false),
@@ -774,8 +777,8 @@
             runCurrent()
             verify(windowController, times(1)).setNotificationShadeFocusable(true)
 
-            fakeSceneDataSource.unpause(expectedScene = SceneKey.Gone)
-            transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
+            fakeSceneDataSource.unpause(expectedScene = Scenes.Gone)
+            transitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone)
             runCurrent()
             verify(windowController, times(2)).setNotificationShadeFocusable(false)
         }
@@ -785,7 +788,7 @@
         testScope.runTest {
             val transitionStateFlow =
                 prepareState(
-                    initialSceneKey = SceneKey.Lockscreen,
+                    initialSceneKey = Scenes.Lockscreen,
                 )
             underTest.start()
             runCurrent()
@@ -794,7 +797,7 @@
             clearInvocations(centralSurfaces)
             emulateSceneTransition(
                 transitionStateFlow = transitionStateFlow,
-                toScene = SceneKey.Bouncer,
+                toScene = Scenes.Bouncer,
                 verifyBeforeTransition = {
                     verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                 },
@@ -813,7 +816,7 @@
             clearInvocations(centralSurfaces)
             emulateSceneTransition(
                 transitionStateFlow = transitionStateFlow,
-                toScene = SceneKey.Lockscreen,
+                toScene = Scenes.Lockscreen,
                 verifyBeforeTransition = {
                     verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                 },
@@ -832,7 +835,7 @@
             clearInvocations(centralSurfaces)
             emulateSceneTransition(
                 transitionStateFlow = transitionStateFlow,
-                toScene = SceneKey.Shade,
+                toScene = Scenes.Shade,
                 verifyBeforeTransition = {
                     verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                 },
@@ -851,7 +854,7 @@
             clearInvocations(centralSurfaces)
             emulateSceneTransition(
                 transitionStateFlow = transitionStateFlow,
-                toScene = SceneKey.Lockscreen,
+                toScene = Scenes.Lockscreen,
                 verifyBeforeTransition = {
                     verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                 },
@@ -870,7 +873,7 @@
             clearInvocations(centralSurfaces)
             emulateSceneTransition(
                 transitionStateFlow = transitionStateFlow,
-                toScene = SceneKey.QuickSettings,
+                toScene = Scenes.QuickSettings,
                 verifyBeforeTransition = {
                     verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                 },
@@ -889,7 +892,7 @@
             val transitionStateFlow =
                 prepareState(
                     isDeviceUnlocked = true,
-                    initialSceneKey = SceneKey.Gone,
+                    initialSceneKey = Scenes.Gone,
                 )
             underTest.start()
             verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
@@ -897,7 +900,7 @@
             clearInvocations(centralSurfaces)
             emulateSceneTransition(
                 transitionStateFlow = transitionStateFlow,
-                toScene = SceneKey.Bouncer,
+                toScene = Scenes.Bouncer,
                 verifyBeforeTransition = {
                     verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                 },
@@ -912,7 +915,7 @@
             clearInvocations(centralSurfaces)
             emulateSceneTransition(
                 transitionStateFlow = transitionStateFlow,
-                toScene = SceneKey.Lockscreen,
+                toScene = Scenes.Lockscreen,
                 verifyBeforeTransition = {
                     verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                 },
@@ -927,7 +930,7 @@
             clearInvocations(centralSurfaces)
             emulateSceneTransition(
                 transitionStateFlow = transitionStateFlow,
-                toScene = SceneKey.Shade,
+                toScene = Scenes.Shade,
                 verifyBeforeTransition = {
                     verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                 },
@@ -942,7 +945,7 @@
             clearInvocations(centralSurfaces)
             emulateSceneTransition(
                 transitionStateFlow = transitionStateFlow,
-                toScene = SceneKey.Lockscreen,
+                toScene = Scenes.Lockscreen,
                 verifyBeforeTransition = {
                     verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                 },
@@ -957,7 +960,7 @@
             clearInvocations(centralSurfaces)
             emulateSceneTransition(
                 transitionStateFlow = transitionStateFlow,
-                toScene = SceneKey.QuickSettings,
+                toScene = Scenes.QuickSettings,
                 verifyBeforeTransition = {
                     verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                 },
@@ -970,6 +973,20 @@
             )
         }
 
+    @Test
+    fun respondToFalsingDetections() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val transitionStateFlow = prepareState()
+            underTest.start()
+            emulateSceneTransition(transitionStateFlow, toScene = Scenes.Bouncer)
+            assertThat(currentScene).isNotEqualTo(Scenes.Lockscreen)
+
+            kosmos.falsingManager.sendFalsingBelief()
+
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+        }
+
     private fun TestScope.emulateSceneTransition(
         transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
         toScene: SceneKey,
@@ -1017,7 +1034,7 @@
             }
         }
 
-        check(initialSceneKey != SceneKey.Gone || isDeviceUnlocked) {
+        check(initialSceneKey != Scenes.Gone || isDeviceUnlocked) {
             "Cannot start on the Gone scene and have the device be locked at the same time."
         }
 
@@ -1027,7 +1044,7 @@
         runCurrent()
         val transitionStateFlow =
             MutableStateFlow<ObservableTransitionState>(
-                ObservableTransitionState.Idle(SceneKey.Lockscreen)
+                ObservableTransitionState.Idle(Scenes.Lockscreen)
             )
         sceneInteractor.setTransitionState(transitionStateFlow)
         initialSceneKey?.let {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegatorTest.kt
index ed4b1e6..32c0172 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegatorTest.kt
@@ -53,9 +53,9 @@
         testScope.runTest {
             val currentScene by collectLastValue(underTest.currentScene)
             underTest.setDelegate(null)
-            assertThat(currentScene).isNotEqualTo(SceneKey.Bouncer)
+            assertThat(currentScene).isNotEqualTo(Scenes.Bouncer)
 
-            underTest.changeScene(toScene = SceneKey.Bouncer)
+            underTest.changeScene(toScene = Scenes.Bouncer)
 
             assertThat(currentScene).isEqualTo(initialSceneKey)
         }
@@ -71,11 +71,11 @@
     fun currentScene_withDelegate_changesScenes() =
         testScope.runTest {
             val currentScene by collectLastValue(underTest.currentScene)
-            assertThat(currentScene).isNotEqualTo(SceneKey.Bouncer)
+            assertThat(currentScene).isNotEqualTo(Scenes.Bouncer)
 
-            underTest.changeScene(toScene = SceneKey.Bouncer)
+            underTest.changeScene(toScene = Scenes.Bouncer)
 
-            assertThat(currentScene).isEqualTo(SceneKey.Bouncer)
+            assertThat(currentScene).isEqualTo(Scenes.Bouncer)
         }
 
     @Test
@@ -83,8 +83,8 @@
         testScope.runTest {
             val currentScene by collectLastValue(underTest.currentScene)
 
-            fakeSceneDataSource.changeScene(toScene = SceneKey.Bouncer)
+            fakeSceneDataSource.changeScene(toScene = Scenes.Bouncer)
 
-            assertThat(currentScene).isEqualTo(SceneKey.Bouncer)
+            assertThat(currentScene).isEqualTo(Scenes.Bouncer)
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index ffbdafe..7b0127e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
 package com.android.systemui.scene.ui.viewmodel
 
+import android.view.MotionEvent
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -31,13 +30,13 @@
 import com.android.systemui.scene.sceneContainerConfig
 import com.android.systemui.scene.sceneKeys
 import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -50,7 +49,7 @@
 
     private val kosmos = testKosmos()
     private val testScope by lazy { kosmos.testScope }
-    private val interactor by lazy { kosmos.sceneInteractor }
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
     private val fakeSceneDataSource = kosmos.fakeSceneDataSource
     private val sceneContainerConfig = kosmos.sceneContainerConfig
     private val falsingManager = kosmos.fakeFalsingManager
@@ -62,7 +61,7 @@
         kosmos.fakeSceneContainerFlags.enabled = true
         underTest =
             SceneContainerViewModel(
-                sceneInteractor = interactor,
+                sceneInteractor = sceneInteractor,
                 falsingInteractor = kosmos.falsingInteractor,
                 powerInteractor = kosmos.powerInteractor,
             )
@@ -74,10 +73,10 @@
             val isVisible by collectLastValue(underTest.isVisible)
             assertThat(isVisible).isTrue()
 
-            interactor.setVisible(false, "reason")
+            sceneInteractor.setVisible(false, "reason")
             assertThat(isVisible).isFalse()
 
-            interactor.setVisible(true, "reason")
+            sceneInteractor.setVisible(true, "reason")
             assertThat(isVisible).isTrue()
         }
 
@@ -90,20 +89,20 @@
     fun sceneTransition() =
         testScope.runTest {
             val currentScene by collectLastValue(underTest.currentScene)
-            assertThat(currentScene).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
 
-            fakeSceneDataSource.changeScene(SceneKey.Shade)
+            fakeSceneDataSource.changeScene(Scenes.Shade)
 
-            assertThat(currentScene).isEqualTo(SceneKey.Shade)
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
         }
 
     @Test
     fun canChangeScene_whenAllowed_switchingFromGone_returnsTrue() =
         testScope.runTest {
             val currentScene by collectLastValue(underTest.currentScene)
-            fakeSceneDataSource.changeScene(toScene = SceneKey.Gone)
+            fakeSceneDataSource.changeScene(toScene = Scenes.Gone)
             runCurrent()
-            assertThat(currentScene).isEqualTo(SceneKey.Gone)
+            assertThat(currentScene).isEqualTo(Scenes.Gone)
 
             sceneContainerConfig.sceneKeys
                 .filter { it != currentScene }
@@ -118,9 +117,9 @@
     fun canChangeScene_whenAllowed_switchingFromLockscreen_returnsTrue() =
         testScope.runTest {
             val currentScene by collectLastValue(underTest.currentScene)
-            fakeSceneDataSource.changeScene(toScene = SceneKey.Lockscreen)
+            fakeSceneDataSource.changeScene(toScene = Scenes.Lockscreen)
             runCurrent()
-            assertThat(currentScene).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
 
             sceneContainerConfig.sceneKeys
                 .filter { it != currentScene }
@@ -136,15 +135,15 @@
         testScope.runTest {
             falsingManager.setIsFalseTouch(true)
             val currentScene by collectLastValue(underTest.currentScene)
-            fakeSceneDataSource.changeScene(toScene = SceneKey.Lockscreen)
+            fakeSceneDataSource.changeScene(toScene = Scenes.Lockscreen)
             runCurrent()
-            assertThat(currentScene).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
 
             sceneContainerConfig.sceneKeys
                 .filter { it != currentScene }
                 .filter {
                     // Moving to the Communal scene is not currently falsing protected.
-                    it != SceneKey.Communal
+                    it != Scenes.Communal
                 }
                 .forEach { toScene ->
                     assertWithMessage("Protected scene $toScene not properly protected")
@@ -158,14 +157,14 @@
         testScope.runTest {
             falsingManager.setIsFalseTouch(true)
             val currentScene by collectLastValue(underTest.currentScene)
-            fakeSceneDataSource.changeScene(toScene = SceneKey.Lockscreen)
+            fakeSceneDataSource.changeScene(toScene = Scenes.Lockscreen)
             runCurrent()
-            assertThat(currentScene).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
 
             sceneContainerConfig.sceneKeys
                 .filter {
                     // Moving to the Communal scene is not currently falsing protected.
-                    it == SceneKey.Communal
+                    it == Scenes.Communal
                 }
                 .forEach { toScene ->
                     assertWithMessage("Unprotected scene $toScene is incorrectly protected")
@@ -179,9 +178,9 @@
         testScope.runTest {
             falsingManager.setIsFalseTouch(true)
             val currentScene by collectLastValue(underTest.currentScene)
-            fakeSceneDataSource.changeScene(toScene = SceneKey.Gone)
+            fakeSceneDataSource.changeScene(toScene = Scenes.Gone)
             runCurrent()
-            assertThat(currentScene).isEqualTo(SceneKey.Gone)
+            assertThat(currentScene).isEqualTo(Scenes.Gone)
 
             sceneContainerConfig.sceneKeys
                 .filter { it != currentScene }
@@ -199,4 +198,20 @@
             underTest.onMotionEvent(mock())
             assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue()
         }
+
+    @Test
+    fun remoteUserInteraction_keepsContainerVisible() =
+        testScope.runTest {
+            sceneInteractor.setVisible(false, "reason")
+            val isVisible by collectLastValue(underTest.isVisible)
+            assertThat(isVisible).isFalse()
+            sceneInteractor.onRemoteUserInteractionStarted("reason")
+            assertThat(isVisible).isTrue()
+
+            underTest.onMotionEvent(
+                mock { whenever(actionMasked).thenReturn(MotionEvent.ACTION_UP) }
+            )
+
+            assertThat(isVisible).isFalse()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
index ec424b0..d3fa360 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
@@ -18,6 +18,8 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
@@ -28,8 +30,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.CommandQueue
@@ -87,7 +88,7 @@
             runCurrent()
 
             // THEN the shade remains collapsed and the post-collapse action ran
-            assertThat(sceneInteractor.currentScene.value).isEqualTo(SceneKey.Gone)
+            assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone)
             verify(testRunnable, times(1)).run()
         }
 
@@ -105,7 +106,7 @@
             runCurrent()
 
             // THEN the shade remains expanded and the post-collapse action did not run
-            assertThat(sceneInteractor.currentScene.value).isEqualTo(SceneKey.Shade)
+            assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Shade)
             assertThat(shadeInteractor.isAnyFullyExpanded.value).isTrue()
             verify(testRunnable, never()).run()
         }
@@ -122,7 +123,7 @@
             runCurrent()
 
             // THEN the shade collapses back to lockscreen and the post-collapse action ran
-            assertThat(sceneInteractor.currentScene.value).isEqualTo(SceneKey.Lockscreen)
+            assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Lockscreen)
         }
 
     @Test
@@ -137,7 +138,7 @@
             runCurrent()
 
             // THEN the shade collapses back to lockscreen and the post-collapse action ran
-            assertThat(sceneInteractor.currentScene.value).isEqualTo(SceneKey.Gone)
+            assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone)
         }
 
     @Test
@@ -181,21 +182,21 @@
     private fun setDeviceEntered(isEntered: Boolean) {
         setScene(
             if (isEntered) {
-                SceneKey.Gone
+                Scenes.Gone
             } else {
-                SceneKey.Lockscreen
+                Scenes.Lockscreen
             }
         )
         assertThat(deviceEntryInteractor.isDeviceEntered.value).isEqualTo(isEntered)
     }
 
     private fun setCollapsed() {
-        setScene(SceneKey.Gone)
+        setScene(Scenes.Gone)
         assertThat(shadeInteractor.isAnyExpanded.value).isFalse()
     }
 
     private fun setShadeFullyExpanded() {
-        setScene(SceneKey.Shade)
+        setScene(Scenes.Shade)
         assertThat(shadeInteractor.isAnyFullyExpanded.value).isTrue()
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
new file mode 100644
index 0000000..16b68cc
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.shade.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.fakeSceneDataSource
+import com.android.systemui.testKosmos
+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.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PanelExpansionInteractorImplTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
+    private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
+    private val sceneInteractor = kosmos.sceneInteractor
+    private val transitionState =
+        MutableStateFlow<ObservableTransitionState>(
+            ObservableTransitionState.Idle(Scenes.Lockscreen)
+        )
+    private val fakeSceneDataSource = kosmos.fakeSceneDataSource
+
+    private lateinit var underTest: PanelExpansionInteractorImpl
+
+    @Before
+    fun setUp() {
+        sceneInteractor.setTransitionState(transitionState)
+    }
+
+    @Test
+    @EnableSceneContainer
+    fun legacyPanelExpansion_whenIdle_whenLocked() =
+        testScope.runTest {
+            underTest = kosmos.panelExpansionInteractorImpl
+            setUnlocked(false)
+            val panelExpansion by collectLastValue(underTest.legacyPanelExpansion)
+
+            changeScene(Scenes.Lockscreen) { assertThat(panelExpansion).isEqualTo(1f) }
+            assertThat(panelExpansion).isEqualTo(1f)
+
+            changeScene(Scenes.Bouncer) { assertThat(panelExpansion).isEqualTo(1f) }
+            assertThat(panelExpansion).isEqualTo(1f)
+
+            changeScene(Scenes.Shade) { assertThat(panelExpansion).isEqualTo(1f) }
+            assertThat(panelExpansion).isEqualTo(1f)
+
+            changeScene(Scenes.QuickSettings) { assertThat(panelExpansion).isEqualTo(1f) }
+            assertThat(panelExpansion).isEqualTo(1f)
+
+            changeScene(Scenes.Communal) { assertThat(panelExpansion).isEqualTo(1f) }
+            assertThat(panelExpansion).isEqualTo(1f)
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun legacyPanelExpansion_whenIdle_whenUnlocked() =
+        testScope.runTest {
+            underTest = kosmos.panelExpansionInteractorImpl
+            setUnlocked(true)
+            val panelExpansion by collectLastValue(underTest.legacyPanelExpansion)
+
+            changeScene(Scenes.Gone) { assertThat(panelExpansion).isEqualTo(0f) }
+            assertThat(panelExpansion).isEqualTo(0f)
+
+            changeScene(Scenes.Shade) { progress -> assertThat(panelExpansion).isEqualTo(progress) }
+            assertThat(panelExpansion).isEqualTo(1f)
+
+            changeScene(Scenes.QuickSettings) {
+                // Shade's already expanded, so moving to QS should also be 1f.
+                assertThat(panelExpansion).isEqualTo(1f)
+            }
+            assertThat(panelExpansion).isEqualTo(1f)
+
+            changeScene(Scenes.Communal) { assertThat(panelExpansion).isEqualTo(1f) }
+            assertThat(panelExpansion).isEqualTo(1f)
+        }
+    private fun TestScope.setUnlocked(isUnlocked: Boolean) {
+        val isDeviceUnlocked by collectLastValue(deviceUnlockedInteractor.isDeviceUnlocked)
+        deviceEntryRepository.setUnlocked(isUnlocked)
+        runCurrent()
+
+        assertThat(isDeviceUnlocked).isEqualTo(isUnlocked)
+    }
+
+    private fun TestScope.changeScene(
+        toScene: SceneKey,
+        assertDuringProgress: ((progress: Float) -> Unit) = {},
+    ) {
+        val currentScene by collectLastValue(sceneInteractor.currentScene)
+        val progressFlow = MutableStateFlow(0f)
+        transitionState.value =
+            ObservableTransitionState.Transition(
+                fromScene = checkNotNull(currentScene),
+                toScene = toScene,
+                progress = progressFlow,
+                isInitiatedByUserInput = true,
+                isUserInputOngoing = flowOf(true),
+            )
+        runCurrent()
+        assertDuringProgress(progressFlow.value)
+
+        progressFlow.value = 0.2f
+        runCurrent()
+        assertDuringProgress(progressFlow.value)
+
+        progressFlow.value = 0.6f
+        runCurrent()
+        assertDuringProgress(progressFlow.value)
+
+        progressFlow.value = 1f
+        runCurrent()
+        assertDuringProgress(progressFlow.value)
+
+        transitionState.value = ObservableTransitionState.Idle(toScene)
+        fakeSceneDataSource.changeScene(toScene)
+        runCurrent()
+        assertDuringProgress(progressFlow.value)
+
+        assertThat(currentScene).isEqualTo(toScene)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt
index 1ef07fa..bb40591 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt
@@ -18,12 +18,12 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -53,8 +53,8 @@
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Transition(
-                        fromScene = SceneKey.QuickSettings,
-                        toScene = SceneKey.Shade,
+                        fromScene = Scenes.QuickSettings,
+                        toScene = Scenes.Shade,
                         progress = MutableStateFlow(.1f),
                         isInitiatedByUserInput = false,
                         isUserInputOngoing = flowOf(false),
@@ -76,8 +76,8 @@
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Transition(
-                        fromScene = SceneKey.QuickSettings,
-                        toScene = SceneKey.Gone,
+                        fromScene = Scenes.QuickSettings,
+                        toScene = Scenes.Gone,
                         progress = MutableStateFlow(.1f),
                         isInitiatedByUserInput = false,
                         isUserInputOngoing = flowOf(false),
@@ -99,8 +99,8 @@
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Transition(
-                        fromScene = SceneKey.QuickSettings,
-                        toScene = SceneKey.Gone,
+                        fromScene = Scenes.QuickSettings,
+                        toScene = Scenes.Gone,
                         progress = MutableStateFlow(.1f),
                         isInitiatedByUserInput = false,
                         isUserInputOngoing = flowOf(true),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
index ec4da04..d309c6b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
@@ -19,13 +19,14 @@
 import android.content.applicationContext
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shared.recents.utilities.Utilities
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -35,12 +36,14 @@
 import kotlinx.coroutines.test.runTest
 import org.junit.Assume
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@Ignore("b/328827631")
 class ShadeBackActionInteractorImplTest : SysuiTestCase() {
     val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
     val testScope = kosmos.testScope
@@ -56,45 +59,45 @@
     @Test
     fun animateCollapseQs_notOnQs() =
         testScope.runTest {
-            setScene(SceneKey.Shade)
+            setScene(Scenes.Shade)
             underTest.animateCollapseQs(true)
             runCurrent()
-            assertThat(sceneInteractor.currentScene.value).isEqualTo(SceneKey.Shade)
+            assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Shade)
         }
 
     @Test
     fun animateCollapseQs_fullyCollapse_entered() =
         testScope.runTest {
             enterDevice()
-            setScene(SceneKey.QuickSettings)
+            setScene(Scenes.QuickSettings)
             underTest.animateCollapseQs(true)
             runCurrent()
-            assertThat(sceneInteractor.currentScene.value).isEqualTo(SceneKey.Gone)
+            assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone)
         }
 
     @Test
     fun animateCollapseQs_fullyCollapse_locked() =
         testScope.runTest {
             deviceEntryRepository.setUnlocked(false)
-            setScene(SceneKey.QuickSettings)
+            setScene(Scenes.QuickSettings)
             underTest.animateCollapseQs(true)
             runCurrent()
-            assertThat(sceneInteractor.currentScene.value).isEqualTo(SceneKey.Lockscreen)
+            assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Lockscreen)
         }
 
     @Test
     fun animateCollapseQs_notFullyCollapse() =
         testScope.runTest {
-            setScene(SceneKey.QuickSettings)
+            setScene(Scenes.QuickSettings)
             underTest.animateCollapseQs(false)
             runCurrent()
-            assertThat(sceneInteractor.currentScene.value).isEqualTo(SceneKey.Shade)
+            assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Shade)
         }
 
     private fun enterDevice() {
         deviceEntryRepository.setUnlocked(true)
         testScope.runCurrent()
-        setScene(SceneKey.Gone)
+        setScene(Scenes.Gone)
     }
 
     private fun setScene(key: SceneKey) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
index bf136cd..8c9036a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
@@ -27,11 +28,13 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.userRepository
 import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
@@ -45,19 +48,20 @@
 @RunWith(AndroidJUnit4::class)
 class ShadeInteractorSceneContainerImplTest : SysuiTestCase() {
 
-    val kosmos = testKosmos()
-    val testComponent = kosmos.testScope
-    val configurationRepository = kosmos.fakeConfigurationRepository
-    val keyguardRepository = kosmos.fakeKeyguardRepository
-    val keyguardTransitionRepository = kosmos.keyguardTransitionRepository
-    val sceneInteractor = kosmos.sceneInteractor
-    val userRepository = kosmos.userRepository
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val configurationRepository = kosmos.fakeConfigurationRepository
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
+    private val keyguardTransitionRepository = kosmos.keyguardTransitionRepository
+    private val sceneInteractor = kosmos.sceneInteractor
+    private val userRepository = kosmos.userRepository
+    private val shadeRepository = kosmos.shadeRepository
 
-    val underTest = kosmos.shadeInteractorSceneContainerImpl
+    private val underTest = kosmos.shadeInteractorSceneContainerImpl
 
     @Test
     fun qsExpansionWhenInSplitShadeAndQsExpanded() =
-        testComponent.runTest {
+        testScope.runTest {
             val actual by collectLastValue(underTest.qsExpansion)
 
             // WHEN split shade is enabled and QS is expanded
@@ -67,8 +71,8 @@
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Transition(
-                        fromScene = SceneKey.QuickSettings,
-                        toScene = SceneKey.Shade,
+                        fromScene = Scenes.QuickSettings,
+                        toScene = Scenes.Shade,
                         progress = MutableStateFlow(.3f),
                         isInitiatedByUserInput = false,
                         isUserInputOngoing = flowOf(false),
@@ -84,7 +88,7 @@
 
     @Test
     fun qsExpansionWhenNotInSplitShadeAndQsExpanded() =
-        testComponent.runTest {
+        testScope.runTest {
             val actual by collectLastValue(underTest.qsExpansion)
 
             // WHEN split shade is not enabled and QS is expanded
@@ -96,8 +100,8 @@
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Transition(
-                        fromScene = SceneKey.QuickSettings,
-                        toScene = SceneKey.Shade,
+                        fromScene = Scenes.QuickSettings,
+                        toScene = Scenes.Shade,
                         progress = progress,
                         isInitiatedByUserInput = false,
                         isUserInputOngoing = flowOf(false),
@@ -112,7 +116,7 @@
 
     @Test
     fun qsFullscreen_falseWhenTransitioning() =
-        testComponent.runTest {
+        testScope.runTest {
             val actual by collectLastValue(underTest.isQsFullscreen)
 
             // WHEN scene transition active
@@ -120,8 +124,8 @@
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Transition(
-                        fromScene = SceneKey.QuickSettings,
-                        toScene = SceneKey.Shade,
+                        fromScene = Scenes.QuickSettings,
+                        toScene = Scenes.Shade,
                         progress = MutableStateFlow(.3f),
                         isInitiatedByUserInput = false,
                         isUserInputOngoing = flowOf(false),
@@ -136,14 +140,14 @@
 
     @Test
     fun qsFullscreen_falseWhenIdleNotQS() =
-        testComponent.runTest {
+        testScope.runTest {
             val actual by collectLastValue(underTest.isQsFullscreen)
 
             // WHEN Idle but not on QuickSettings scene
             keyguardRepository.setStatusBarState(StatusBarState.SHADE)
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(SceneKey.Shade)
+                    ObservableTransitionState.Idle(Scenes.Shade)
                 )
             sceneInteractor.setTransitionState(transitionState)
             runCurrent()
@@ -154,14 +158,14 @@
 
     @Test
     fun qsFullscreen_trueWhenIdleQS() =
-        testComponent.runTest {
+        testScope.runTest {
             val actual by collectLastValue(underTest.isQsFullscreen)
 
             // WHEN Idle on QuickSettings scene
             keyguardRepository.setStatusBarState(StatusBarState.SHADE)
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(SceneKey.QuickSettings)
+                    ObservableTransitionState.Idle(Scenes.QuickSettings)
                 )
             sceneInteractor.setTransitionState(transitionState)
             runCurrent()
@@ -172,9 +176,9 @@
 
     @Test
     fun lockscreenShadeExpansion_idle_onScene() =
-        testComponent.runTest {
+        testScope.runTest {
             // GIVEN an expansion flow based on transitions to and from a scene
-            val key = SceneKey.Shade
+            val key = Scenes.Shade
             val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
             val expansionAmount by collectLastValue(expansion)
 
@@ -189,15 +193,15 @@
 
     @Test
     fun lockscreenShadeExpansion_idle_onDifferentScene() =
-        testComponent.runTest {
+        testScope.runTest {
             // GIVEN an expansion flow based on transitions to and from a scene
-            val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.Shade)
+            val expansion = underTest.sceneBasedExpansion(sceneInteractor, Scenes.Shade)
             val expansionAmount by collectLastValue(expansion)
 
             // WHEN transition state is idle on a different scene
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(SceneKey.Lockscreen)
+                    ObservableTransitionState.Idle(Scenes.Lockscreen)
                 )
             sceneInteractor.setTransitionState(transitionState)
 
@@ -207,9 +211,9 @@
 
     @Test
     fun lockscreenShadeExpansion_transitioning_toScene() =
-        testComponent.runTest {
+        testScope.runTest {
             // GIVEN an expansion flow based on transitions to and from a scene
-            val key = SceneKey.QuickSettings
+            val key = Scenes.QuickSettings
             val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
             val expansionAmount by collectLastValue(expansion)
 
@@ -218,7 +222,7 @@
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Transition(
-                        fromScene = SceneKey.Lockscreen,
+                        fromScene = Scenes.Lockscreen,
                         toScene = key,
                         progress = progress,
                         isInitiatedByUserInput = false,
@@ -245,9 +249,9 @@
 
     @Test
     fun lockscreenShadeExpansion_transitioning_fromScene() =
-        testComponent.runTest {
+        testScope.runTest {
             // GIVEN an expansion flow based on transitions to and from a scene
-            val key = SceneKey.QuickSettings
+            val key = Scenes.QuickSettings
             val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
             val expansionAmount by collectLastValue(expansion)
 
@@ -257,7 +261,7 @@
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Transition(
                         fromScene = key,
-                        toScene = SceneKey.Lockscreen,
+                        toScene = Scenes.Lockscreen,
                         progress = progress,
                         isInitiatedByUserInput = false,
                         isUserInputOngoing = flowOf(false),
@@ -282,7 +286,7 @@
         }
 
     fun isQsBypassingShade_goneToQs() =
-        testComponent.runTest {
+        testScope.runTest {
             val actual by collectLastValue(underTest.isQsBypassingShade)
 
             // WHEN transitioning from QS directly to Gone
@@ -290,8 +294,8 @@
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Transition(
-                        fromScene = SceneKey.Gone,
-                        toScene = SceneKey.QuickSettings,
+                        fromScene = Scenes.Gone,
+                        toScene = Scenes.QuickSettings,
                         progress = MutableStateFlow(.1f),
                         isInitiatedByUserInput = false,
                         isUserInputOngoing = flowOf(false),
@@ -305,7 +309,7 @@
         }
 
     fun isQsBypassingShade_shadeToQs() =
-        testComponent.runTest {
+        testScope.runTest {
             val actual by collectLastValue(underTest.isQsBypassingShade)
 
             // WHEN transitioning from QS to Shade
@@ -313,8 +317,8 @@
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Transition(
-                        fromScene = SceneKey.Shade,
-                        toScene = SceneKey.QuickSettings,
+                        fromScene = Scenes.Shade,
+                        toScene = Scenes.QuickSettings,
                         progress = MutableStateFlow(.1f),
                         isInitiatedByUserInput = false,
                         isUserInputOngoing = flowOf(false),
@@ -329,9 +333,9 @@
 
     @Test
     fun lockscreenShadeExpansion_transitioning_toAndFromDifferentScenes() =
-        testComponent.runTest {
+        testScope.runTest {
             // GIVEN an expansion flow based on transitions to and from a scene
-            val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.QuickSettings)
+            val expansion = underTest.sceneBasedExpansion(sceneInteractor, Scenes.QuickSettings)
             val expansionAmount by collectLastValue(expansion)
 
             // WHEN transition state is starting to between different scenes
@@ -339,8 +343,8 @@
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Transition(
-                        fromScene = SceneKey.Lockscreen,
-                        toScene = SceneKey.Shade,
+                        fromScene = Scenes.Lockscreen,
+                        toScene = Scenes.Shade,
                         progress = progress,
                         isInitiatedByUserInput = false,
                         isUserInputOngoing = flowOf(false),
@@ -366,9 +370,9 @@
 
     @Test
     fun userInteracting_idle() =
-        testComponent.runTest {
+        testScope.runTest {
             // GIVEN an interacting flow based on transitions to and from a scene
-            val key = SceneKey.Shade
+            val key = Scenes.Shade
             val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
             val interacting by collectLastValue(interactingFlow)
 
@@ -383,9 +387,9 @@
 
     @Test
     fun userInteracting_transitioning_toScene_programmatic() =
-        testComponent.runTest {
+        testScope.runTest {
             // GIVEN an interacting flow based on transitions to and from a scene
-            val key = SceneKey.QuickSettings
+            val key = Scenes.QuickSettings
             val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
             val interacting by collectLastValue(interactingFlow)
 
@@ -394,7 +398,7 @@
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Transition(
-                        fromScene = SceneKey.Lockscreen,
+                        fromScene = Scenes.Lockscreen,
                         toScene = key,
                         progress = progress,
                         isInitiatedByUserInput = false,
@@ -421,9 +425,9 @@
 
     @Test
     fun userInteracting_transitioning_toScene_userInputDriven() =
-        testComponent.runTest {
+        testScope.runTest {
             // GIVEN an interacting flow based on transitions to and from a scene
-            val key = SceneKey.QuickSettings
+            val key = Scenes.QuickSettings
             val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
             val interacting by collectLastValue(interactingFlow)
 
@@ -432,7 +436,7 @@
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Transition(
-                        fromScene = SceneKey.Lockscreen,
+                        fromScene = Scenes.Lockscreen,
                         toScene = key,
                         progress = progress,
                         isInitiatedByUserInput = true,
@@ -459,9 +463,9 @@
 
     @Test
     fun userInteracting_transitioning_fromScene_programmatic() =
-        testComponent.runTest {
+        testScope.runTest {
             // GIVEN an interacting flow based on transitions to and from a scene
-            val key = SceneKey.QuickSettings
+            val key = Scenes.QuickSettings
             val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
             val interacting by collectLastValue(interactingFlow)
 
@@ -471,7 +475,7 @@
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Transition(
                         fromScene = key,
-                        toScene = SceneKey.Lockscreen,
+                        toScene = Scenes.Lockscreen,
                         progress = progress,
                         isInitiatedByUserInput = false,
                         isUserInputOngoing = flowOf(false),
@@ -497,9 +501,9 @@
 
     @Test
     fun userInteracting_transitioning_fromScene_userInputDriven() =
-        testComponent.runTest {
+        testScope.runTest {
             // GIVEN an interacting flow based on transitions to and from a scene
-            val key = SceneKey.QuickSettings
+            val key = Scenes.QuickSettings
             val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
             val interacting by collectLastValue(interactingFlow)
 
@@ -509,7 +513,7 @@
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Transition(
                         fromScene = key,
-                        toScene = SceneKey.Lockscreen,
+                        toScene = Scenes.Lockscreen,
                         progress = progress,
                         isInitiatedByUserInput = true,
                         isUserInputOngoing = flowOf(false),
@@ -535,9 +539,9 @@
 
     @Test
     fun userInteracting_transitioning_toAndFromDifferentScenes() =
-        testComponent.runTest {
+        testScope.runTest {
             // GIVEN an interacting flow based on transitions to and from a scene
-            val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, SceneKey.Shade)
+            val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, Scenes.Shade)
             val interacting by collectLastValue(interactingFlow)
 
             // WHEN transition state is starting to between different scenes
@@ -545,8 +549,8 @@
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Transition(
-                        fromScene = SceneKey.Lockscreen,
-                        toScene = SceneKey.QuickSettings,
+                        fromScene = Scenes.Lockscreen,
+                        toScene = Scenes.QuickSettings,
                         progress = MutableStateFlow(0f),
                         isInitiatedByUserInput = true,
                         isUserInputOngoing = flowOf(false),
@@ -557,4 +561,19 @@
             // THEN interacting is false
             Truth.assertThat(interacting).isFalse()
         }
+
+    @Test
+    fun shadeMode() =
+        testScope.runTest {
+            val shadeMode by collectLastValue(underTest.shadeMode)
+
+            shadeRepository.setShadeMode(ShadeMode.Split)
+            assertThat(shadeMode).isEqualTo(ShadeMode.Split)
+
+            shadeRepository.setShadeMode(ShadeMode.Single)
+            assertThat(shadeMode).isEqualTo(ShadeMode.Single)
+
+            shadeRepository.setShadeMode(ShadeMode.Split)
+            assertThat(shadeMode).isEqualTo(ShadeMode.Split)
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
new file mode 100644
index 0000000..31dacdd
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.startable
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadeStartableTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val shadeInteractor = kosmos.shadeInteractor
+    private val fakeConfigurationRepository = kosmos.fakeConfigurationRepository
+
+    private val underTest = kosmos.shadeStartable
+
+    @Test
+    fun hydrateShadeMode() =
+        testScope.runTest {
+            overrideResource(R.bool.config_use_split_notification_shade, false)
+            val shadeMode by collectLastValue(shadeInteractor.shadeMode)
+
+            underTest.start()
+            assertThat(shadeMode).isEqualTo(ShadeMode.Single)
+
+            overrideResource(R.bool.config_use_split_notification_shade, true)
+            fakeConfigurationRepository.onAnyConfigurationChange()
+            assertThat(shadeMode).isEqualTo(ShadeMode.Split)
+
+            overrideResource(R.bool.config_use_split_notification_shade, false)
+            fakeConfigurationRepository.onAnyConfigurationChange()
+            assertThat(shadeMode).isEqualTo(ShadeMode.Single)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index d655ade..1c54961 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -16,8 +16,11 @@
 
 package com.android.systemui.shade.ui.viewmodel
 
+import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -28,11 +31,18 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.qs.footerActionsController
+import com.android.systemui.qs.footerActionsViewModelFactory
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
+import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.domain.interactor.privacyChipInteractor
 import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.domain.startable.shadeStartable
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -57,12 +67,14 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
 class ShadeSceneViewModelTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val sceneInteractor by lazy { kosmos.sceneInteractor }
     private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
+    private val shadeRepository by lazy { kosmos.shadeRepository }
 
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
     private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
@@ -113,59 +125,66 @@
                 qsSceneAdapter = qsFlexiglassAdapter,
                 notifications = kosmos.notificationsPlaceholderViewModel,
                 mediaDataManager = mediaDataManager,
+                shadeInteractor = kosmos.shadeInteractor,
+                footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
+                footerActionsController = kosmos.footerActionsController,
             )
     }
 
     @Test
     fun upTransitionSceneKey_deviceLocked_lockScreen() =
         testScope.runTest {
-            val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+            val destinationScenes by collectLastValue(underTest.destinationScenes)
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin
             )
             kosmos.fakeDeviceEntryRepository.setUnlocked(false)
 
-            assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
+            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
+                .isEqualTo(Scenes.Lockscreen)
         }
 
     @Test
     fun upTransitionSceneKey_deviceUnlocked_gone() =
         testScope.runTest {
-            val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+            val destinationScenes by collectLastValue(underTest.destinationScenes)
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin
             )
             kosmos.fakeDeviceEntryRepository.setUnlocked(true)
 
-            assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
+            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
+                .isEqualTo(Scenes.Gone)
         }
 
     @Test
     fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
         testScope.runTest {
-            val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+            val destinationScenes by collectLastValue(underTest.destinationScenes)
             kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.None
             )
-            sceneInteractor.changeScene(SceneKey.Lockscreen, "reason")
+            sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
 
-            assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
+            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
+                .isEqualTo(Scenes.Lockscreen)
         }
 
     @Test
     fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
         testScope.runTest {
-            val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+            val destinationScenes by collectLastValue(underTest.destinationScenes)
             kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
             kosmos.fakeDeviceEntryRepository.setUnlocked(true)
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.None
             )
             runCurrent()
-            sceneInteractor.changeScene(SceneKey.Gone, "reason")
+            sceneInteractor.changeScene(Scenes.Gone, "reason")
 
-            assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
+            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
+                .isEqualTo(Scenes.Gone)
         }
 
     @Test
@@ -206,7 +225,7 @@
 
             underTest.onContentClicked()
 
-            assertThat(currentScene).isEqualTo(SceneKey.Gone)
+            assertThat(currentScene).isEqualTo(Scenes.Gone)
         }
 
     @Test
@@ -221,7 +240,7 @@
 
             underTest.onContentClicked()
 
-            assertThat(currentScene).isEqualTo(SceneKey.Bouncer)
+            assertThat(currentScene).isEqualTo(Scenes.Bouncer)
         }
 
     @Test
@@ -239,4 +258,38 @@
 
             assertThat(underTest.isMediaVisible()).isFalse()
         }
+
+    @Test
+    fun downTransitionSceneKey_inSplitShade_null() =
+        testScope.runTest {
+            overrideResource(R.bool.config_use_split_notification_shade, true)
+            kosmos.shadeStartable.start()
+            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.toScene).isNull()
+        }
+
+    @Test
+    fun downTransitionSceneKey_notSplitShade_quickSettings() =
+        testScope.runTest {
+            overrideResource(R.bool.config_use_split_notification_shade, false)
+            kosmos.shadeStartable.start()
+            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.toScene)
+                .isEqualTo(Scenes.QuickSettings)
+        }
+
+    @Test
+    fun shadeMode() =
+        testScope.runTest {
+            val shadeMode by collectLastValue(underTest.shadeMode)
+
+            shadeRepository.setShadeMode(ShadeMode.Split)
+            assertThat(shadeMode).isEqualTo(ShadeMode.Split)
+
+            shadeRepository.setShadeMode(ShadeMode.Single)
+            assertThat(shadeMode).isEqualTo(ShadeMode.Single)
+
+            shadeRepository.setShadeMode(ShadeMode.Split)
+            assertThat(shadeMode).isEqualTo(ShadeMode.Split)
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index d505b27..7fabe33 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -25,6 +25,7 @@
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
 import static android.app.Flags.FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS;
+import static android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES;
 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;
@@ -115,7 +116,8 @@
     public static List<FlagsParameterization> getParams() {
         return FlagsParameterization.allCombinationsOf(
                 FLAG_ALLOW_PRIVATE_PROFILE,
-                FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS);
+                FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS,
+                FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
     }
 
     public NotificationLockscreenUserManagerTest(FlagsParameterization flags) {
@@ -872,7 +874,7 @@
     }
 
     @Test
-    @EnableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+    @EnableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
     public void testProfileAvailabilityIntent() {
         mLockscreenUserManager.mCurrentProfiles.clear();
         assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
@@ -883,7 +885,7 @@
     }
 
     @Test
-    @EnableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+    @EnableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
     public void testProfileUnAvailabilityIntent() {
         mLockscreenUserManager.mCurrentProfiles.clear();
         assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
@@ -894,7 +896,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+    @DisableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
     public void testManagedProfileAvailabilityIntent() {
         mLockscreenUserManager.mCurrentProfiles.clear();
         mLockscreenUserManager.mCurrentManagedProfiles.clear();
@@ -908,7 +910,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+    @DisableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
     public void testManagedProfileUnAvailabilityIntent() {
         mLockscreenUserManager.mCurrentProfiles.clear();
         mLockscreenUserManager.mCurrentManagedProfiles.clear();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index efd8f00..47918c8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -20,6 +20,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.coroutines.collectLastValue
@@ -28,8 +29,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationStackAppearanceViewModel
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
@@ -92,19 +92,19 @@
         testScope.runTest {
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(scene = SceneKey.Gone)
+                    ObservableTransitionState.Idle(scene = Scenes.Gone)
                 )
             sceneInteractor.setTransitionState(transitionState)
             val expandFraction by collectLastValue(appearanceViewModel.expandFraction)
             assertThat(expandFraction).isEqualTo(0f)
 
             fakeSceneDataSource.pause()
-            sceneInteractor.changeScene(SceneKey.Shade, "reason")
+            sceneInteractor.changeScene(Scenes.Shade, "reason")
             val transitionProgress = MutableStateFlow(0f)
             transitionState.value =
                 ObservableTransitionState.Transition(
-                    fromScene = SceneKey.Gone,
-                    toScene = SceneKey.Shade,
+                    fromScene = Scenes.Gone,
+                    toScene = Scenes.Shade,
                     progress = transitionProgress,
                     isInitiatedByUserInput = false,
                     isUserInputOngoing = flowOf(false),
@@ -117,7 +117,7 @@
                 assertThat(expandFraction).isWithin(0.01f).of(progress)
             }
 
-            fakeSceneDataSource.unpause(expectedScene = SceneKey.Shade)
+            fakeSceneDataSource.unpause(expectedScene = Scenes.Shade)
             assertThat(expandFraction).isWithin(0.01f).of(1f)
         }
 
@@ -126,7 +126,7 @@
         testScope.runTest {
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(scene = SceneKey.Lockscreen)
+                    ObservableTransitionState.Idle(scene = Scenes.Lockscreen)
                 )
             sceneInteractor.setTransitionState(transitionState)
             val expandFraction by collectLastValue(appearanceViewModel.expandFraction)
@@ -138,19 +138,19 @@
         testScope.runTest {
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(scene = SceneKey.Shade)
+                    ObservableTransitionState.Idle(scene = Scenes.Shade)
                 )
             sceneInteractor.setTransitionState(transitionState)
             val expandFraction by collectLastValue(appearanceViewModel.expandFraction)
             assertThat(expandFraction).isEqualTo(1f)
 
             fakeSceneDataSource.pause()
-            sceneInteractor.changeScene(SceneKey.QuickSettings, "reason")
+            sceneInteractor.changeScene(Scenes.QuickSettings, "reason")
             val transitionProgress = MutableStateFlow(0f)
             transitionState.value =
                 ObservableTransitionState.Transition(
-                    fromScene = SceneKey.Shade,
-                    toScene = SceneKey.QuickSettings,
+                    fromScene = Scenes.Shade,
+                    toScene = Scenes.QuickSettings,
                     progress = transitionProgress,
                     isInitiatedByUserInput = false,
                     isUserInputOngoing = flowOf(false),
@@ -163,7 +163,7 @@
                 assertThat(expandFraction).isEqualTo(1f)
             }
 
-            fakeSceneDataSource.unpause(expectedScene = SceneKey.QuickSettings)
+            fakeSceneDataSource.unpause(expectedScene = Scenes.QuickSettings)
             assertThat(expandFraction).isEqualTo(1f)
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt
index e188f5b..8e765f7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt
@@ -196,10 +196,14 @@
     }
 
     @Test
-    fun zenModeAlarms_ringAndNotifications_muted() {
+    fun zenModeAlarms_ringedStreams_muted() {
         with(kosmos) {
             val expectedToBeMuted =
-                setOf(AudioManager.STREAM_RING, AudioManager.STREAM_NOTIFICATION)
+                setOf(
+                    AudioManager.STREAM_RING,
+                    AudioManager.STREAM_NOTIFICATION,
+                    AudioManager.STREAM_SYSTEM,
+                )
             testScope.runTest {
                 notificationsSoundPolicyRepository.updateNotificationPolicy()
                 notificationsSoundPolicyRepository.updateZenMode(ZenMode(Global.ZEN_MODE_ALARMS))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 0641c61..e683f34c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -25,15 +25,13 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
-import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.communal.shared.model.CommunalSceneKey
-import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.shared.model.BurnInModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -47,8 +45,6 @@
 import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.mockLargeScreenHeaderHelper
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
-import com.android.systemui.statusbar.policy.SplitShadeStateController
-import com.android.systemui.statusbar.policy.splitShadeStateController
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
@@ -68,8 +64,7 @@
 @RunWith(AndroidJUnit4::class)
 class SharedNotificationContainerViewModelTest : SysuiTestCase() {
     val aodBurnInViewModel = mock(AodBurnInViewModel::class.java)
-    val splitShadeStateController = mock(SplitShadeStateController::class.java)
-    lateinit var translationYFlow: MutableStateFlow<Float>
+    lateinit var movementFlow: MutableStateFlow<BurnInModel>
 
     val kosmos =
         testKosmos().apply {
@@ -81,15 +76,14 @@
 
     init {
         kosmos.aodBurnInViewModel = aodBurnInViewModel
-        kosmos.splitShadeStateController = splitShadeStateController
     }
+
     val testScope = kosmos.testScope
     val configurationRepository = kosmos.fakeConfigurationRepository
     val keyguardRepository = kosmos.fakeKeyguardRepository
     val keyguardInteractor = kosmos.keyguardInteractor
     val keyguardRootViewModel = kosmos.keyguardRootViewModel
     val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
-    val communalInteractor = kosmos.communalInteractor
     val shadeRepository = kosmos.shadeRepository
     val sharedNotificationContainerInteractor = kosmos.sharedNotificationContainerInteractor
     val largeScreenHeaderHelper = kosmos.mockLargeScreenHeaderHelper
@@ -98,17 +92,16 @@
 
     @Before
     fun setUp() {
-        whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())).thenReturn(false)
-        translationYFlow = MutableStateFlow(0f)
-        whenever(aodBurnInViewModel.translationY(any())).thenReturn(translationYFlow)
+        overrideResource(R.bool.config_use_split_notification_shade, false)
+        movementFlow = MutableStateFlow(BurnInModel())
+        whenever(aodBurnInViewModel.movement(any())).thenReturn(movementFlow)
         underTest = kosmos.sharedNotificationContainerViewModel
     }
 
     @Test
     fun validateMarginStartInSplitShade() =
         testScope.runTest {
-            whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
-                .thenReturn(true)
+            overrideResource(R.bool.config_use_split_notification_shade, true)
             overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
 
             val dimens by collectLastValue(underTest.configurationBasedDimensions)
@@ -121,8 +114,7 @@
     @Test
     fun validateMarginStart() =
         testScope.runTest {
-            whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
-                .thenReturn(false)
+            overrideResource(R.bool.config_use_split_notification_shade, false)
             overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
 
             val dimens by collectLastValue(underTest.configurationBasedDimensions)
@@ -137,8 +129,7 @@
         testScope.runTest {
             mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
             whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
-            whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
-                .thenReturn(true)
+            overrideResource(R.bool.config_use_split_notification_shade, true)
             overrideResource(R.bool.config_use_large_screen_shade_header, true)
             overrideResource(R.dimen.large_screen_shade_header_height, 10)
             overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
@@ -156,8 +147,7 @@
         testScope.runTest {
             mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
             whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
-            whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
-                .thenReturn(true)
+            overrideResource(R.bool.config_use_split_notification_shade, true)
             overrideResource(R.bool.config_use_large_screen_shade_header, true)
             overrideResource(R.dimen.large_screen_shade_header_height, 10)
             overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
@@ -172,8 +162,7 @@
     @Test
     fun validatePaddingTop() =
         testScope.runTest {
-            whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
-                .thenReturn(false)
+            overrideResource(R.bool.config_use_split_notification_shade, false)
             overrideResource(R.dimen.large_screen_shade_header_height, 10)
             overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
 
@@ -247,7 +236,7 @@
         }
 
     @Test
-    fun glanceableHubAlpha() =
+    fun glanceableHubAlpha_lockscreenToHub() =
         testScope.runTest {
             val alpha by collectLastValue(underTest.glanceableHubAlpha)
 
@@ -286,12 +275,6 @@
                     value = 1f,
                 )
             )
-            val idleTransitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(
-                    ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
-                )
-            communalInteractor.setTransitionState(idleTransitionState)
-            runCurrent()
             assertThat(alpha).isEqualTo(0f)
 
             // While state is GLANCEABLE_HUB, verify alpha is restored to full if glanceable hub is
@@ -301,6 +284,50 @@
         }
 
     @Test
+    fun glanceableHubAlpha_dreamToHub() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.glanceableHubAlpha)
+
+            // Start on dream
+            showDream()
+            assertThat(alpha).isEqualTo(1f)
+
+            // Start transitioning to glanceable hub
+            val progress = 0.6f
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.DREAMING,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    value = 0f,
+                )
+            )
+            runCurrent()
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.DREAMING,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    value = progress,
+                )
+            )
+            runCurrent()
+            // Keep notifications hidden during the transition from dream to hub
+            assertThat(alpha).isEqualTo(0)
+
+            // Finish transition to glanceable hub
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.DREAMING,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    value = 1f,
+                )
+            )
+            assertThat(alpha).isEqualTo(0f)
+        }
+
+    @Test
     fun validateMarginTop() =
         testScope.runTest {
             overrideResource(R.bool.config_use_large_screen_shade_header, false)
@@ -399,12 +426,11 @@
             assertThat(isOnGlanceableHubWithoutShade).isFalse()
 
             // Move to glanceable hub
-            val idleTransitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(
-                    ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
-                )
-            communalInteractor.setTransitionState(idleTransitionState)
-            runCurrent()
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GLANCEABLE_HUB,
+                testScope = this
+            )
             assertThat(isOnGlanceableHubWithoutShade).isTrue()
 
             // While state is GLANCEABLE_HUB, validate variations of both shade and qs expansion
@@ -431,8 +457,7 @@
             val bounds by collectLastValue(underTest.bounds)
 
             // When not in split shade
-            whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
-                .thenReturn(false)
+            overrideResource(R.bool.config_use_split_notification_shade, false)
             configurationRepository.onAnyConfigurationChange()
             runCurrent()
 
@@ -454,8 +479,7 @@
 
             // When in split shade
             whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
-            whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
-                .thenReturn(true)
+            overrideResource(R.bool.config_use_split_notification_shade, true)
             overrideResource(R.bool.config_use_large_screen_shade_header, true)
             overrideResource(R.dimen.large_screen_shade_header_height, 10)
             overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
@@ -483,8 +507,7 @@
 
             // When in split shade
             whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
-            whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
-                .thenReturn(true)
+            overrideResource(R.bool.config_use_split_notification_shade, true)
             overrideResource(R.bool.config_use_large_screen_shade_header, true)
             overrideResource(R.dimen.large_screen_shade_header_height, 10)
             overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
@@ -543,8 +566,7 @@
 
             showLockscreen()
 
-            whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
-                .thenReturn(false)
+            overrideResource(R.bool.config_use_split_notification_shade, false)
             configurationRepository.onAnyConfigurationChange()
             keyguardInteractor.setNotificationContainerBounds(
                 NotificationContainerBounds(top = 1f, bottom = 2f)
@@ -567,8 +589,7 @@
 
             showLockscreen()
 
-            whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
-                .thenReturn(false)
+            overrideResource(R.bool.config_use_split_notification_shade, false)
             configurationRepository.onAnyConfigurationChange()
             keyguardInteractor.setNotificationContainerBounds(
                 NotificationContainerBounds(top = 1f, bottom = 2f)
@@ -604,8 +625,7 @@
             // Show lockscreen with shade expanded
             showLockscreenWithShadeExpanded()
 
-            whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
-                .thenReturn(false)
+            overrideResource(R.bool.config_use_split_notification_shade, false)
             configurationRepository.onAnyConfigurationChange()
             keyguardInteractor.setNotificationContainerBounds(
                 NotificationContainerBounds(top = 1f, bottom = 2f)
@@ -623,7 +643,7 @@
             showLockscreen()
             assertThat(translationY).isEqualTo(0)
 
-            translationYFlow.value = 150f
+            movementFlow.value = BurnInModel(translationY = 150)
             assertThat(translationY).isEqualTo(150f)
         }
 
@@ -701,6 +721,32 @@
             assertThat(fadeIn).isEqualTo(false)
         }
 
+    @Test
+    fun shadeCollapseFadeIn_doesNotRunIfTransitioningToAod() =
+        testScope.runTest {
+            val fadeIn by collectLastValue(underTest.shadeCollapseFadeIn)
+
+            // Start on lockscreen without the shade
+            underTest.setShadeCollapseFadeInComplete(false)
+            showLockscreen()
+            assertThat(fadeIn).isEqualTo(false)
+
+            // ... then the shade expands
+            showLockscreenWithShadeExpanded()
+            assertThat(fadeIn).isEqualTo(false)
+
+            // ... then user hits power to go to AOD
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                testScope,
+            )
+            // ... followed by a shade collapse
+            showLockscreen()
+            // ... does not trigger a fade in
+            assertThat(fadeIn).isEqualTo(false)
+        }
+
     private suspend fun TestScope.showLockscreen() {
         shadeRepository.setLockscreenShadeExpansion(0f)
         shadeRepository.setQsExpansion(0f)
@@ -714,6 +760,19 @@
         )
     }
 
+    private suspend fun TestScope.showDream() {
+        shadeRepository.setLockscreenShadeExpansion(0f)
+        shadeRepository.setQsExpansion(0f)
+        runCurrent()
+        keyguardRepository.setDreaming(true)
+        runCurrent()
+        keyguardTransitionRepository.sendTransitionSteps(
+            from = KeyguardState.LOCKSCREEN,
+            to = KeyguardState.DREAMING,
+            testScope,
+        )
+    }
+
     private suspend fun TestScope.showLockscreenWithShadeExpanded() {
         shadeRepository.setLockscreenShadeExpansion(1f)
         shadeRepository.setQsExpansion(0f)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index c01f1c7..8aa0e3fc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -35,10 +35,10 @@
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shade.ShadeController
-import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.shade.data.repository.ShadeAnimationRepository
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl
+import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -76,7 +76,7 @@
     @Mock private lateinit var biometricUnlockController: BiometricUnlockController
     @Mock private lateinit var keyguardViewMediator: KeyguardViewMediator
     @Mock private lateinit var shadeController: ShadeController
-    @Mock private lateinit var shadeViewController: ShadeViewController
+    @Mock private lateinit var commandQueue: CommandQueue
     @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
     @Mock private lateinit var mActivityTransitionAnimator: ActivityTransitionAnimator
     @Mock private lateinit var lockScreenUserManager: NotificationLockscreenUserManager
@@ -105,7 +105,7 @@
                 Lazy { biometricUnlockController },
                 Lazy { keyguardViewMediator },
                 Lazy { shadeController },
-                Lazy { shadeViewController },
+                commandQueue,
                 shadeAnimationInteractor,
                 Lazy { statusBarKeyguardViewManager },
                 Lazy { notifShadeWindowController },
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index 87d25dd..db4d42f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -19,8 +19,6 @@
 import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
 
 import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
-import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -41,31 +39,24 @@
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.Person;
-import android.content.Context;
 import android.content.Intent;
-import android.graphics.Region;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.testing.TestableLooper;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.filters.SmallTest;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
 
-import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.settings.FakeGlobalSettings;
-import com.android.systemui.util.settings.GlobalSettings;
 import com.android.systemui.util.time.FakeSystemClock;
-import com.android.systemui.util.time.SystemClock;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -81,17 +72,13 @@
     @Rule
     public MockitoRule rule = MockitoJUnit.rule();
 
-    private static final String TEST_PACKAGE_NAME = "BaseHeadsUpManagerTest";
-
-    private static final int TEST_TOUCH_ACCEPTANCE_TIME = 200;
-    private static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
+    static final int TEST_TOUCH_ACCEPTANCE_TIME = 200;
+    static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
 
     private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
     private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
     @Mock private AccessibilityManagerWrapper mAccessibilityMgr;
 
-    private static final int TEST_UID = 0;
-
     protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
     protected static final int TEST_AUTO_DISMISS_TIME = 600;
     protected static final int TEST_STICKY_AUTO_DISMISS_TIME = 800;
@@ -110,143 +97,6 @@
         assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME);
     }
 
-    private final class TestableHeadsUpManager extends BaseHeadsUpManager {
-
-        private HeadsUpEntry mLastCreatedEntry;
-
-        TestableHeadsUpManager(Context context,
-                HeadsUpManagerLogger logger,
-                DelayableExecutor executor,
-                GlobalSettings globalSettings,
-                SystemClock systemClock,
-                AccessibilityManagerWrapper accessibilityManagerWrapper,
-                UiEventLogger uiEventLogger) {
-            super(context, logger, mockExecutorHandler(executor), globalSettings, systemClock,
-                    executor, accessibilityManagerWrapper, uiEventLogger);
-
-            mTouchAcceptanceDelay = TEST_TOUCH_ACCEPTANCE_TIME;
-            mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
-            mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
-            mStickyForSomeTimeAutoDismissTime = TEST_STICKY_AUTO_DISMISS_TIME;
-
-        }
-
-        @Override
-        protected HeadsUpEntry createHeadsUpEntry() {
-            mLastCreatedEntry = spy(super.createHeadsUpEntry());
-            return mLastCreatedEntry;
-        }
-
-        @Override
-        public int getContentFlag() {
-            return FLAG_CONTENT_VIEW_CONTRACTED;
-        }
-
-        // The following are only implemented by HeadsUpManagerPhone. If you need them, use that.
-        @Override
-        public void addHeadsUpPhoneListener(@NonNull OnHeadsUpPhoneListenerChange listener) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void addSwipedOutNotification(@NonNull String key) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void extendHeadsUp() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Nullable
-        @Override
-        public Region getTouchableRegion() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public boolean isHeadsUpGoingAway() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void onExpandingFinished() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public boolean removeNotification(@NonNull String key, boolean releaseImmediately,
-                boolean animate) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void setAnimationStateHandler(@NonNull AnimationStateHandler handler) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void setGutsShown(@NonNull NotificationEntry entry, boolean gutsShown) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void setHeadsUpGoingAway(boolean headsUpGoingAway) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void setRemoteInputActive(@NonNull NotificationEntry entry,
-                boolean remoteInputActive) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void setTrackingHeadsUp(boolean tracking) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public boolean shouldSwallowClick(@NonNull String key) {
-            throw new UnsupportedOperationException();
-        }
-    }
-
-    protected StatusBarNotification createSbn(int id, Notification n) {
-        return new StatusBarNotification(
-                TEST_PACKAGE_NAME /* pkg */,
-                TEST_PACKAGE_NAME,
-                id,
-                null /* tag */,
-                TEST_UID,
-                0 /* initialPid */,
-                n,
-                new UserHandle(ActivityManager.getCurrentUser()),
-                null /* overrideGroupKey */,
-                0 /* postTime */);
-    }
-
-    protected StatusBarNotification createSbn(int id, Notification.Builder n) {
-        return createSbn(id, n.build());
-    }
-
-    protected StatusBarNotification createSbn(int id) {
-        final Notification.Builder b = new Notification.Builder(mContext, "")
-                .setSmallIcon(R.drawable.ic_person)
-                .setContentTitle("Title")
-                .setContentText("Text");
-        return createSbn(id, b);
-    }
-
-    protected NotificationEntry createEntry(int id, Notification n) {
-        return new NotificationEntryBuilder().setSbn(createSbn(id, n)).build();
-    }
-
-    protected NotificationEntry createEntry(int id) {
-        return new NotificationEntryBuilder().setSbn(createSbn(id)).build();
-    }
-
-
     private BaseHeadsUpManager createHeadsUpManager() {
         return new TestableHeadsUpManager(mContext, mLogger, mExecutor, mGlobalSettings,
                 mSystemClock, mAccessibilityMgr, mUiEventLoggerFake);
@@ -257,7 +107,7 @@
                 .setSmallIcon(R.drawable.ic_person)
                 .setFullScreenIntent(mock(PendingIntent.class), /* highPriority */ true)
                 .build();
-        return createEntry(id, notif);
+        return HeadsUpManagerTestUtil.createEntry(id, notif);
     }
 
     private NotificationEntry createStickyForSomeTimeEntry(int id) {
@@ -265,7 +115,7 @@
                 .setSmallIcon(R.drawable.ic_person)
                 .setFlag(FLAG_FSI_REQUESTED_BUT_DENIED, true)
                 .build();
-        return createEntry(id, notif);
+        return HeadsUpManagerTestUtil.createEntry(id, notif);
     }
 
     private PendingIntent createFullScreenIntent() {
@@ -279,7 +129,7 @@
                 .setSmallIcon(R.drawable.ic_person)
                 .setFullScreenIntent(createFullScreenIntent(), /* highPriority */ true)
                 .build();
-        return createEntry(id, notif);
+        return HeadsUpManagerTestUtil.createEntry(id, notif);
     }
 
 
@@ -293,10 +143,16 @@
         }
     }
 
+    @Override
+    public void SysuiSetup() throws Exception {
+        super.SysuiSetup();
+        mSetFlagsRule.disableFlags(NotificationThrottleHun.FLAG_NAME);
+    }
+
     @Test
     public void testShowNotification_addsEntry() {
         final BaseHeadsUpManager alm = createHeadsUpManager();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
+        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
 
         alm.showNotification(entry);
 
@@ -308,7 +164,7 @@
     @Test
     public void testShowNotification_autoDismisses() {
         final BaseHeadsUpManager alm = createHeadsUpManager();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
+        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
 
         alm.showNotification(entry);
         mSystemClock.advanceTime(TEST_AUTO_DISMISS_TIME * 3 / 2);
@@ -319,7 +175,7 @@
     @Test
     public void testRemoveNotification_removeDeferred() {
         final BaseHeadsUpManager alm = createHeadsUpManager();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
+        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
 
         alm.showNotification(entry);
 
@@ -332,7 +188,7 @@
     @Test
     public void testRemoveNotification_forceRemove() {
         final BaseHeadsUpManager alm = createHeadsUpManager();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
+        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
 
         alm.showNotification(entry);
 
@@ -346,7 +202,7 @@
     public void testReleaseAllImmediately() {
         final BaseHeadsUpManager alm = createHeadsUpManager();
         for (int i = 0; i < TEST_NUM_NOTIFICATIONS; i++) {
-            final NotificationEntry entry = createEntry(i);
+            final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(i, mContext);
             entry.setRow(mRow);
             alm.showNotification(entry);
         }
@@ -359,7 +215,7 @@
     @Test
     public void testCanRemoveImmediately_notShownLongEnough() {
         final BaseHeadsUpManager alm = createHeadsUpManager();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
+        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
 
         alm.showNotification(entry);
 
@@ -370,7 +226,8 @@
     @Test
     public void testHunRemovedLogging() {
         final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry notifEntry = createEntry(/* id = */ 0);
+        final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
+                mContext);
         final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = mock(
                 BaseHeadsUpManager.HeadsUpEntry.class);
         headsUpEntry.mEntry = notifEntry;
@@ -413,7 +270,7 @@
     @Test
     public void testShouldHeadsUpBecomePinned_noFSI_false() {
         final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
+        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
 
         assertFalse(hum.shouldHeadsUpBecomePinned(entry));
     }
@@ -422,7 +279,7 @@
     @Test
     public void testShowNotification_autoDismissesIncludingTouchAcceptanceDelay() {
         final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
+        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
         useAccessibilityTimeout(false);
 
         hum.showNotification(entry);
@@ -435,7 +292,7 @@
     @Test
     public void testShowNotification_autoDismissesWithDefaultTimeout() {
         final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
+        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
         useAccessibilityTimeout(false);
 
         hum.showNotification(entry);
@@ -476,7 +333,7 @@
     @Test
     public void testShowNotification_autoDismissesWithAccessibilityTimeout() {
         final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
+        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
         useAccessibilityTimeout(true);
 
         hum.showNotification(entry);
@@ -504,7 +361,7 @@
     @Test
     public void testRemoveNotification_beforeMinimumDisplayTime() {
         final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
+        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
         useAccessibilityTimeout(false);
 
         hum.showNotification(entry);
@@ -523,7 +380,7 @@
     @Test
     public void testRemoveNotification_afterMinimumDisplayTime() {
         final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
+        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
         useAccessibilityTimeout(false);
 
         hum.showNotification(entry);
@@ -541,7 +398,7 @@
     @Test
     public void testRemoveNotification_releaseImmediately() {
         final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
+        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
 
         hum.showNotification(entry);
 
@@ -555,7 +412,8 @@
     @Test
     public void testIsSticky_rowPinnedAndExpanded_true() {
         final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry notifEntry = createEntry(/* id = */ 0);
+        final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
+                mContext);
         when(mRow.isPinned()).thenReturn(true);
         notifEntry.setRow(mRow);
 
@@ -571,7 +429,8 @@
     @Test
     public void testIsSticky_remoteInputActive_true() {
         final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry notifEntry = createEntry(/* id = */ 0);
+        final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
+                mContext);
 
         hum.showNotification(notifEntry);
 
@@ -607,7 +466,8 @@
     @Test
     public void testIsSticky_false() {
         final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry notifEntry = createEntry(/* id = */ 0);
+        final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
+                mContext);
 
         hum.showNotification(notifEntry);
 
@@ -653,14 +513,14 @@
 
         final BaseHeadsUpManager.HeadsUpEntry ongoingCall = hum.new HeadsUpEntry();
         ongoingCall.setEntry(new NotificationEntryBuilder()
-                .setSbn(createSbn(/* id = */ 0,
+                .setSbn(HeadsUpManagerTestUtil.createSbn(/* id = */ 0,
                         new Notification.Builder(mContext, "")
                                 .setCategory(Notification.CATEGORY_CALL)
                                 .setOngoing(true)))
                 .build());
 
         final BaseHeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry();
-        activeRemoteInput.setEntry(createEntry(/* id = */ 1));
+        activeRemoteInput.setEntry(HeadsUpManagerTestUtil.createEntry(/* id = */ 1, mContext));
         activeRemoteInput.mRemoteInputActive = true;
 
         assertThat(ongoingCall.compareTo(activeRemoteInput)).isLessThan(0);
@@ -675,14 +535,14 @@
         final Person person = new Person.Builder().setName("person").build();
         final PendingIntent intent = mock(PendingIntent.class);
         incomingCall.setEntry(new NotificationEntryBuilder()
-                .setSbn(createSbn(/* id = */ 0,
+                .setSbn(HeadsUpManagerTestUtil.createSbn(/* id = */ 0,
                         new Notification.Builder(mContext, "")
                                 .setStyle(Notification.CallStyle
                                         .forIncomingCall(person, intent, intent))))
                 .build());
 
         final BaseHeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry();
-        activeRemoteInput.setEntry(createEntry(/* id = */ 1));
+        activeRemoteInput.setEntry(HeadsUpManagerTestUtil.createEntry(/* id = */ 1, mContext));
         activeRemoteInput.mRemoteInputActive = true;
 
         assertThat(incomingCall.compareTo(activeRemoteInput)).isLessThan(0);
@@ -712,7 +572,8 @@
     @Test
     public void testSetUserActionMayIndirectlyRemove() {
         final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry notifEntry = createEntry(/* id = */ 0);
+        final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
+                mContext);
 
         hum.showNotification(notifEntry);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
similarity index 87%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
index c350de2..c032d7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.statusbar.policy;
 
 import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
 import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
@@ -26,10 +26,10 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
 import androidx.test.filters.SmallTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -39,11 +39,10 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.BaseHeadsUpManagerTest;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
+import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.GlobalSettings;
@@ -61,7 +60,7 @@
 import kotlinx.coroutines.flow.StateFlowKt;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest {
     @Rule public MockitoRule rule = MockitoJUnit.rule();
@@ -138,6 +137,8 @@
 
     @Before
     public void setUp() {
+        mSetFlagsRule.disableFlags(NotificationThrottleHun.FLAG_NAME);
+
         when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
         final AccessibilityManagerWrapper accessibilityMgr =
                 mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
@@ -152,7 +153,7 @@
     @Test
     public void testSnooze() {
         final HeadsUpManager hmp = createHeadsUpManagerPhone();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
+        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
 
         hmp.showNotification(entry);
         hmp.snooze();
@@ -163,7 +164,7 @@
     @Test
     public void testSwipedOutNotification() {
         final HeadsUpManager hmp = createHeadsUpManagerPhone();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
+        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
 
         hmp.showNotification(entry);
         hmp.addSwipedOutNotification(entry.getKey());
@@ -179,7 +180,7 @@
     @Test
     public void testCanRemoveImmediately_swipedOut() {
         final HeadsUpManager hmp = createHeadsUpManagerPhone();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
+        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
 
         hmp.showNotification(entry);
         hmp.addSwipedOutNotification(entry.getKey());
@@ -192,8 +193,10 @@
     @Test
     public void testCanRemoveImmediately_notTopEntry() {
         final HeadsUpManager hmp = createHeadsUpManagerPhone();
-        final NotificationEntry earlierEntry = createEntry(/* id = */ 0);
-        final NotificationEntry laterEntry = createEntry(/* id = */ 1);
+        final NotificationEntry earlierEntry =
+                HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
+        final NotificationEntry laterEntry =
+                HeadsUpManagerTestUtil.createEntry(/* id = */ 1, mContext);
         laterEntry.setRow(mRow);
 
         hmp.showNotification(earlierEntry);
@@ -206,7 +209,7 @@
     @Test
     public void testExtendHeadsUp() {
         final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
+        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
 
         hmp.showNotification(entry);
         hmp.extendHeadsUp();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java
new file mode 100644
index 0000000..c70b03b
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+import android.app.ActivityManager;
+import android.os.UserHandle;
+
+import android.content.Context;
+import android.service.notification.StatusBarNotification;
+import android.app.Notification;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+
+/**
+ * Test helper class for HeadsUpEntry creation.
+ */
+public class HeadsUpManagerTestUtil {
+
+    private static final String TEST_PACKAGE_NAME = "HeadsUpManagerTestUtil";
+    private static final int TEST_UID = 0;
+
+    protected static StatusBarNotification createSbn(int id, Notification.Builder n) {
+        return createSbn(id, n.build());
+    }
+
+    protected static StatusBarNotification createSbn(int id, Context context) {
+        final Notification.Builder b = new Notification.Builder(context, "")
+                .setSmallIcon(com.android.systemui.res.R.drawable.ic_person)
+                .setContentTitle("Title")
+                .setContentText("Text");
+        return createSbn(id, b);
+    }
+
+    protected static StatusBarNotification createSbn(int id, Notification n) {
+        return new StatusBarNotification(
+                TEST_PACKAGE_NAME /* pkg */,
+                TEST_PACKAGE_NAME,
+                id,
+                null /* tag */,
+                TEST_UID,
+                0 /* initialPid */,
+                n,
+                new UserHandle(ActivityManager.getCurrentUser()),
+                null /* overrideGroupKey */,
+                0 /* postTime */);
+    }
+
+    protected static NotificationEntry createEntry(int id, Notification n) {
+        return new NotificationEntryBuilder().setSbn(createSbn(id, n)).build();
+    }
+
+    protected static NotificationEntry createEntry(int id, Context context) {
+        return new NotificationEntryBuilder().setSbn(
+                HeadsUpManagerTestUtil.createSbn(id, context)).build();
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
new file mode 100644
index 0000000..2747629
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
+import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
+
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.graphics.Region;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.SystemClock;
+
+class TestableHeadsUpManager extends BaseHeadsUpManager {
+
+    private HeadsUpEntry mLastCreatedEntry;
+
+    TestableHeadsUpManager(Context context,
+            HeadsUpManagerLogger logger,
+            DelayableExecutor executor,
+            GlobalSettings globalSettings,
+            SystemClock systemClock,
+            AccessibilityManagerWrapper accessibilityManagerWrapper,
+            UiEventLogger uiEventLogger) {
+        super(context, logger, mockExecutorHandler(executor), globalSettings, systemClock,
+                executor, accessibilityManagerWrapper, uiEventLogger);
+
+        mTouchAcceptanceDelay = BaseHeadsUpManagerTest.TEST_TOUCH_ACCEPTANCE_TIME;
+        mMinimumDisplayTime = BaseHeadsUpManagerTest.TEST_MINIMUM_DISPLAY_TIME;
+        mAutoDismissTime = BaseHeadsUpManagerTest.TEST_AUTO_DISMISS_TIME;
+        mStickyForSomeTimeAutoDismissTime = BaseHeadsUpManagerTest.TEST_STICKY_AUTO_DISMISS_TIME;
+    }
+
+    @Override
+    protected HeadsUpEntry createHeadsUpEntry() {
+        mLastCreatedEntry = spy(super.createHeadsUpEntry());
+        return mLastCreatedEntry;
+    }
+
+    @Override
+    public int getContentFlag() {
+        return FLAG_CONTENT_VIEW_CONTRACTED;
+    }
+
+    // The following are only implemented by HeadsUpManagerPhone. If you need them, use that.
+    @Override
+    public void addHeadsUpPhoneListener(@NonNull OnHeadsUpPhoneListenerChange listener) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void addSwipedOutNotification(@NonNull String key) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void extendHeadsUp() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Nullable
+    @Override
+    public Region getTouchableRegion() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isHeadsUpGoingAway() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void onExpandingFinished() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean removeNotification(@NonNull String key, boolean releaseImmediately,
+            boolean animate) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setAnimationStateHandler(@NonNull AnimationStateHandler handler) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setGutsShown(@NonNull NotificationEntry entry, boolean gutsShown) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setHeadsUpGoingAway(boolean headsUpGoingAway) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setRemoteInputActive(@NonNull NotificationEntry entry,
+            boolean remoteInputActive) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setTrackingHeadsUp(boolean tracking) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean shouldSwallowClick(@NonNull String key) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/ClientTrackingWakeLockTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/ClientTrackingWakeLockTest.kt
new file mode 100644
index 0000000..fdfcdc4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/ClientTrackingWakeLockTest.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.wakelock
+
+import android.os.Build
+import android.os.PowerManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.After
+import org.junit.Assert
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ClientTrackingWakeLockTest : SysuiTestCase() {
+
+    private val WHY = "test"
+    private val WHY_2 = "test2"
+
+    lateinit var mWakeLock: ClientTrackingWakeLock
+    lateinit var mInner: PowerManager.WakeLock
+
+    @Before
+    fun setUp() {
+        mInner =
+            WakeLock.createWakeLockInner(mContext, "WakeLockTest", PowerManager.PARTIAL_WAKE_LOCK)
+        mWakeLock = ClientTrackingWakeLock(mInner, null, 20000)
+    }
+
+    @After
+    fun tearDown() {
+        mInner.setReferenceCounted(false)
+        mInner.release()
+    }
+
+    @Test
+    fun createPartialInner_notHeldYet() {
+        Assert.assertFalse(mInner.isHeld)
+    }
+
+    @Test
+    fun wakeLock_acquire() {
+        mWakeLock.acquire(WHY)
+        Assert.assertTrue(mInner.isHeld)
+    }
+
+    @Test
+    fun wakeLock_release() {
+        mWakeLock.acquire(WHY)
+        mWakeLock.release(WHY)
+        Assert.assertFalse(mInner.isHeld)
+    }
+
+    @Test
+    fun wakeLock_acquiredReleasedMultipleSources_stillHeld() {
+        mWakeLock.acquire(WHY)
+        mWakeLock.acquire(WHY_2)
+        mWakeLock.release(WHY)
+
+        Assert.assertTrue(mInner.isHeld)
+        mWakeLock.release(WHY_2)
+        Assert.assertFalse(mInner.isHeld)
+    }
+
+    @Test
+    fun wakeLock_releasedTooManyTimes_stillReleased_noThrow() {
+        Assume.assumeFalse(Build.IS_ENG)
+        mWakeLock.acquire(WHY)
+        mWakeLock.acquire(WHY_2)
+        mWakeLock.release(WHY)
+        mWakeLock.release(WHY_2)
+        mWakeLock.release(WHY)
+        Assert.assertFalse(mInner.isHeld)
+    }
+
+    @Test
+    fun wakeLock_wrap() {
+        val ran = BooleanArray(1)
+        val wrapped = mWakeLock.wrap { ran[0] = true }
+        Assert.assertTrue(mInner.isHeld)
+        Assert.assertFalse(ran[0])
+        wrapped.run()
+        Assert.assertTrue(ran[0])
+        Assert.assertFalse(mInner.isHeld)
+    }
+
+    @Test
+    fun prodBuild_wakeLock_releaseWithoutAcquire_noThrow() {
+        Assume.assumeFalse(Build.IS_ENG)
+        // shouldn't throw an exception on production builds
+        mWakeLock.release(WHY)
+    }
+
+    @Test
+    fun acquireSeveralLocks_stringReportsCorrectCount() {
+        mWakeLock.acquire(WHY)
+        mWakeLock.acquire(WHY_2)
+        mWakeLock.acquire(WHY)
+        mWakeLock.acquire(WHY)
+        mWakeLock.acquire(WHY_2)
+        Assert.assertEquals(5, mWakeLock.activeClients())
+
+        mWakeLock.release(WHY_2)
+        mWakeLock.release(WHY_2)
+        Assert.assertEquals(3, mWakeLock.activeClients())
+
+        mWakeLock.release(WHY)
+        mWakeLock.release(WHY)
+        mWakeLock.release(WHY)
+        Assert.assertEquals(0, mWakeLock.activeClients())
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
index a2f3ccb..e06efe8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
@@ -113,6 +113,28 @@
     }
 
     @Test
+    fun zenMuted_cantChange() {
+        with(kosmos) {
+            testScope.runTest {
+                notificationsSoundPolicyRepository.updateNotificationPolicy()
+                notificationsSoundPolicyRepository.updateZenMode(
+                    ZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS)
+                )
+
+                val canChangeVolume by
+                    collectLastValue(
+                        underTest.canChangeVolume(AudioStream(AudioManager.STREAM_NOTIFICATION))
+                    )
+
+                underTest.setMuted(AudioStream(AudioManager.STREAM_RING), true)
+                runCurrent()
+
+                assertThat(canChangeVolume).isFalse()
+            }
+        }
+    }
+
+    @Test
     fun streamIsMuted_getStream_volumeZero() {
         with(kosmos) {
             testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
index 243aab2..dcf635e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
@@ -32,8 +32,8 @@
 import com.android.systemui.volume.localMediaRepository
 import com.android.systemui.volume.mediaController
 import com.android.systemui.volume.mediaControllerRepository
+import com.android.systemui.volume.mediaOutputActionsInteractor
 import com.android.systemui.volume.mediaOutputInteractor
-import com.android.systemui.volume.panel.mediaOutputActionsInteractor
 import com.android.systemui.volume.panel.volumePanelViewModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt
new file mode 100644
index 0000000..737b7f3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.spatial
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.spatializerInteractor
+import com.android.systemui.volume.mediaOutputInteractor
+import com.android.systemui.volume.panel.component.spatial.domain.interactor.SpatialAudioComponentInteractor
+
+val Kosmos.spatialAudioComponentInteractor by
+    Kosmos.Fixture {
+        SpatialAudioComponentInteractor(
+            mediaOutputInteractor,
+            spatializerInteractor,
+            testScope.backgroundScope
+        )
+    }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
new file mode 100644
index 0000000..449e8bf
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.spatial.domain
+
+import android.media.session.MediaSession
+import android.media.session.PlaybackState
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.media.BluetoothMediaDevice
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.spatializerRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.volume.localMediaRepository
+import com.android.systemui.volume.mediaController
+import com.android.systemui.volume.mediaControllerRepository
+import com.android.systemui.volume.panel.component.spatial.spatialAudioComponentInteractor
+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
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper(setAsMainLooper = true)
+class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val cachedBluetoothDevice: CachedBluetoothDevice = mock {
+        whenever(address).thenReturn("test_address")
+    }
+    private val bluetoothMediaDevice: BluetoothMediaDevice = mock {
+        whenever(cachedDevice).thenReturn(cachedBluetoothDevice)
+    }
+
+    private lateinit var underTest: SpatialAudioAvailabilityCriteria
+
+    @Before
+    fun setup() {
+        with(kosmos) {
+            mediaControllerRepository.setActiveLocalMediaController(
+                mediaController.apply {
+                    whenever(packageName).thenReturn("test.pkg")
+                    whenever(sessionToken).thenReturn(MediaSession.Token(0, mock {}))
+                    whenever(playbackState).thenReturn(PlaybackState.Builder().build())
+                }
+            )
+
+            underTest = SpatialAudioAvailabilityCriteria(spatialAudioComponentInteractor)
+        }
+    }
+
+    @Test
+    fun noSpatialAudio_noHeadTracking_unavailable() {
+        with(kosmos) {
+            testScope.runTest {
+                localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
+                spatializerRepository.defaultHeadTrackingAvailable = false
+                spatializerRepository.defaultSpatialAudioAvailable = false
+
+                val isAvailable by collectLastValue(underTest.isAvailable())
+                runCurrent()
+
+                assertThat(isAvailable).isFalse()
+            }
+        }
+    }
+
+    @Test
+    fun spatialAudio_noHeadTracking_available() {
+        with(kosmos) {
+            testScope.runTest {
+                localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
+                spatializerRepository.defaultHeadTrackingAvailable = false
+                spatializerRepository.defaultSpatialAudioAvailable = true
+
+                val isAvailable by collectLastValue(underTest.isAvailable())
+                runCurrent()
+
+                assertThat(isAvailable).isTrue()
+            }
+        }
+    }
+
+    @Test
+    fun spatialAudio_headTracking_available() {
+        with(kosmos) {
+            testScope.runTest {
+                localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
+                spatializerRepository.defaultHeadTrackingAvailable = true
+                spatializerRepository.defaultSpatialAudioAvailable = true
+
+                val isAvailable by collectLastValue(underTest.isAvailable())
+                runCurrent()
+
+                assertThat(isAvailable).isTrue()
+            }
+        }
+    }
+
+    @Test
+    fun spatialAudio_headTracking_noDevice_unavailable() {
+        with(kosmos) {
+            testScope.runTest {
+                spatializerRepository.defaultHeadTrackingAvailable = true
+                spatializerRepository.defaultSpatialAudioAvailable = true
+
+                val isAvailable by collectLastValue(underTest.isAvailable())
+                runCurrent()
+
+                assertThat(isAvailable).isFalse()
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
new file mode 100644
index 0000000..06ae220
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.spatial.domain.interactor
+
+import android.media.AudioDeviceAttributes
+import android.media.AudioDeviceInfo
+import android.media.session.MediaSession
+import android.media.session.PlaybackState
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.media.BluetoothMediaDevice
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.spatializerInteractor
+import com.android.systemui.media.spatializerRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.volume.localMediaRepository
+import com.android.systemui.volume.mediaController
+import com.android.systemui.volume.mediaControllerRepository
+import com.android.systemui.volume.mediaOutputInteractor
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
+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
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class SpatialAudioComponentInteractorTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private lateinit var underTest: SpatialAudioComponentInteractor
+
+    @Before
+    fun setup() {
+        with(kosmos) {
+            val cachedBluetoothDevice: CachedBluetoothDevice = mock {
+                whenever(address).thenReturn("test_address")
+            }
+            localMediaRepository.updateCurrentConnectedDevice(
+                mock<BluetoothMediaDevice> {
+                    whenever(name).thenReturn("test_device")
+                    whenever(cachedDevice).thenReturn(cachedBluetoothDevice)
+                }
+            )
+
+            whenever(mediaController.packageName).thenReturn("test.pkg")
+            whenever(mediaController.sessionToken).thenReturn(MediaSession.Token(0, mock {}))
+            whenever(mediaController.playbackState).thenReturn(PlaybackState.Builder().build())
+
+            mediaControllerRepository.setActiveLocalMediaController(mediaController)
+
+            spatializerRepository.setIsSpatialAudioAvailable(
+                AudioDeviceAttributes(
+                    AudioDeviceAttributes.ROLE_OUTPUT,
+                    AudioDeviceInfo.TYPE_BLE_HEADSET,
+                    "test_address"
+                ),
+                true
+            )
+            spatializerRepository.defaultHeadTrackingAvailable = true
+
+            underTest =
+                SpatialAudioComponentInteractor(
+                    mediaOutputInteractor,
+                    spatializerInteractor,
+                    testScope.backgroundScope,
+                )
+        }
+    }
+
+    @Test
+    fun setEnabled_changesIsEnabled() {
+        with(kosmos) {
+            testScope.runTest {
+                val values by collectValues(underTest.isEnabled)
+
+                underTest.setEnabled(SpatialAudioEnabledModel.Disabled)
+                runCurrent()
+                underTest.setEnabled(SpatialAudioEnabledModel.HeadTrackingEnabled)
+                runCurrent()
+                underTest.setEnabled(SpatialAudioEnabledModel.SpatialAudioEnabled)
+                runCurrent()
+
+                assertThat(values)
+                    .containsExactly(
+                        SpatialAudioEnabledModel.Disabled,
+                        SpatialAudioEnabledModel.HeadTrackingEnabled,
+                        SpatialAudioEnabledModel.SpatialAudioEnabled,
+                    )
+                    .inOrder()
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index f51e109..7341015 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -304,6 +304,15 @@
     <!-- An explanation text that the password needs to be entered since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] -->
     <string name="kg_prompt_reason_timeout_password">For additional security, use password instead</string>
 
+    <!-- An explanation text that the pin needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] -->
+    <string name="kg_prompt_added_security_pin">PIN required for additional security</string>
+
+    <!-- An explanation text that the pattern needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] -->
+    <string name="kg_prompt_added_security_pattern">Pattern required for additional security</string>
+
+    <!-- An explanation text that the password needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] -->
+    <string name="kg_prompt_added_security_password">Password required for additional security</string>
+
     <!-- An explanation text that the credential needs to be entered because a device admin has
     locked the device. [CHAR LIMIT=80] -->
     <string name="kg_prompt_reason_device_admin">Device locked by admin</string>
diff --git a/packages/SystemUI/res/drawable/battery_unified_attr_charging.xml b/packages/SystemUI/res/drawable/battery_unified_attr_charging.xml
new file mode 100644
index 0000000..8e3b89b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/battery_unified_attr_charging.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="8dp"
+    android:height="10dp"
+    android:viewportWidth="16.0"
+    android:viewportHeight="20.0">
+    <path
+        android:pathData="M4,20L5,13H0L9,0H11L10,8H16L6,20H4Z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/battery_unified_attr_defend.xml b/packages/SystemUI/res/drawable/battery_unified_attr_defend.xml
new file mode 100644
index 0000000..e7beee2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/battery_unified_attr_defend.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="8dp"
+    android:height="9dp"
+    android:viewportWidth="8.0"
+    android:viewportHeight="9.0">
+    <path
+        android:pathData="M3.698,9C2.629,8.765 1.746,8.181 1.048,7.247C0.349,6.306 0,5.266 0,4.126V1.422L3.698,0L7.397,1.422V4.126C7.397,5.266 7.048,6.306 6.349,7.247C5.651,8.181 4.767,8.765 3.698,9ZM3.698,7.846C4.439,7.596 5.052,7.129 5.537,6.445C6.029,5.754 6.274,4.981 6.274,4.126V2.191L3.698,1.197L1.122,2.191V4.126C1.122,4.981 1.365,5.754 1.849,6.445C2.341,7.129 2.957,7.596 3.698,7.846ZM3.698,7.183C3.1,6.99 2.605,6.616 2.213,6.061C1.828,5.505 1.635,4.888 1.635,4.211V2.651L3.698,1.86L5.761,2.651V4.211C5.761,4.888 5.565,5.505 5.173,6.061C4.789,6.616 4.297,6.99 3.698,7.183Z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/battery_unified_attr_powersave.xml b/packages/SystemUI/res/drawable/battery_unified_attr_powersave.xml
new file mode 100644
index 0000000..a2c0cbae
--- /dev/null
+++ b/packages/SystemUI/res/drawable/battery_unified_attr_powersave.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!-- This drawable is inset for now until the unified battery attrs can get their own paddings -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+    android:insetRight="0.5dp"
+    >
+    <vector
+        android:width="8dp"
+        android:height="8dp"
+        android:viewportWidth="8.0"
+        android:viewportHeight="8.0"
+        >
+        <path
+            android:pathData="M3.242,4.758H0V3.257H3.242V0H4.758V3.257H8V4.758H4.758V8.015H3.242V4.758Z"
+            android:fillColor="#000"/>
+    </vector>
+</inset>
diff --git a/packages/SystemUI/res/drawable/battery_unified_frame.xml b/packages/SystemUI/res/drawable/battery_unified_frame.xml
new file mode 100644
index 0000000..016b88b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/battery_unified_frame.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!-- Unified battery frame for BatteryLayersDrawable.kt -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="14dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="14.0">
+    <!-- body -->
+    <path
+        android:pathData="@string/battery_unified_frame_path_string"
+        android:strokeColor="#000"
+        android:strokeWidth="1.5"
+        />
+    <!-- cap -->
+    <path
+        android:pathData="M0,4C0,3.448 0.448,3 1,3H1.5V11H1C0.448,11 0,10.552 0,10V4Z"
+        android:fillColor="#000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/battery_unified_frame_bg.xml b/packages/SystemUI/res/drawable/battery_unified_frame_bg.xml
new file mode 100644
index 0000000..8e04f10
--- /dev/null
+++ b/packages/SystemUI/res/drawable/battery_unified_frame_bg.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!-- Vector description of the battery gutter: the background of the fill -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="14dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="14.0">
+    <path
+        android:pathData="@string/battery_unified_frame_path_string"
+        android:fillColor="#fff" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/bg_shutdown_finder_message.xml b/packages/SystemUI/res/drawable/bg_shutdown_finder_message.xml
new file mode 100644
index 0000000..324ae0c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/bg_shutdown_finder_message.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <corners android:radius="28dp" />
+    <solid android:color="@color/global_actions_lite_button_background" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_finder_active.xml b/packages/SystemUI/res/drawable/ic_finder_active.xml
new file mode 100644
index 0000000..8ca221a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_finder_active.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M12,0L12,0A12,12 0,0 1,24 12L24,12A12,12 0,0 1,12 24L12,24A12,12 0,0 1,0 12L0,12A12,12 0,0 1,12 0z"
+      android:fillColor="#00677D"/>
+  <path
+      android:pathData="M12.797,4.005C11.949,3.936 11.203,4.597 11.203,5.467V6.659C8.855,7.001 6.998,8.856 6.653,11.203H5.467C4.597,11.203 3.936,11.948 4.005,12.796L4.006,12.802L4.006,12.809C4.38,16.605 7.399,19.625 11.195,20C12.051,20.087 12.803,19.404 12.803,18.547V17.355C15.154,17.012 17.013,15.154 17.355,12.803H18.54C19.406,12.803 20.079,12.058 19.992,11.196C19.618,7.4 16.606,4.388 12.812,4.006L12.804,4.006L12.797,4.005ZM11.203,9.344V8.283C9.741,8.591 8.588,9.741 8.278,11.203H9.344C9.585,10.4 10.179,9.754 10.942,9.437C11.027,9.402 11.114,9.371 11.203,9.344ZM11.998,13.171C11.358,13.175 10.828,12.651 10.827,12.004H10.827C10.827,11.959 10.83,11.915 10.835,11.871C10.885,11.427 11.185,11.056 11.59,10.902C11.694,10.863 11.806,10.838 11.921,10.83C11.948,10.833 11.976,10.834 12.003,10.834C12.65,10.834 13.177,11.356 13.179,12.007C13.177,12.622 12.695,13.13 12.091,13.175C12.06,13.172 12.029,13.17 11.998,13.171ZM17.353,11.203H18.383C18.028,8.289 15.72,5.979 12.804,5.616V6.658C15.153,7 17.004,8.852 17.353,11.203ZM14.663,11.203C14.395,10.311 13.692,9.611 12.804,9.344V8.283C14.265,8.59 15.414,9.736 15.727,11.203H14.663ZM5.615,12.803H6.654C7.001,15.15 8.855,17.002 11.203,17.346V18.391C8.287,18.034 5.972,15.719 5.615,12.803ZM11.203,14.666C10.316,14.394 9.613,13.692 9.345,12.803H8.279C8.591,14.264 9.741,15.412 11.203,15.721V14.666ZM14.661,12.811H15.729C15.418,14.272 14.266,15.422 12.804,15.73V14.662C13.689,14.396 14.391,13.699 14.661,12.811Z"
+      android:fillColor="#ffffff"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_head_tracking.xml b/packages/SystemUI/res/drawable/ic_head_tracking.xml
new file mode 100644
index 0000000..d4a44fd
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_head_tracking.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M480,520Q414,520 367,473Q320,426 320,360Q320,294 367,247Q414,200 480,200Q546,200 593,247Q640,294 640,360Q640,426 593,473Q546,520 480,520ZM160,840L160,728Q160,695 177,666Q194,637 224,622Q275,596 339,578Q403,560 480,560Q557,560 621,578Q685,596 736,622Q766,637 783,666Q800,695 800,728L800,840L160,840ZM240,760L720,760L720,728Q720,717 714.5,708Q709,699 700,694Q664,676 607.5,658Q551,640 480,640Q409,640 352.5,658Q296,676 260,694Q251,699 245.5,708Q240,717 240,728L240,760ZM480,440Q513,440 536.5,416.5Q560,393 560,360Q560,327 536.5,303.5Q513,280 480,280Q447,280 423.5,303.5Q400,327 400,360Q400,393 423.5,416.5Q447,440 480,440ZM39,200L39,120Q56,120 70,113.5Q84,107 95,96Q106,85 112,71Q118,57 118,40L199,40Q199,73 186.5,102Q174,131 152,153Q130,175 101,187.5Q72,200 39,200ZM39,361L39,281Q90,281 133.5,262Q177,243 209,210Q241,177 260,133.5Q279,90 279,40L360,40Q360,106 335,164.5Q310,223 266,267Q222,311 164,336Q106,361 39,361ZM920,361Q854,361 795.5,336Q737,311 693,267Q649,223 624,164.5Q599,106 599,40L679,40Q679,90 698,133.5Q717,177 750,210Q783,243 826.5,262Q870,281 920,281L920,361ZM920,200Q887,200 858,187.5Q829,175 807,153Q785,131 772.5,102Q760,73 760,40L840,40Q840,57 846.5,71Q853,85 864,96Q875,107 889,113.5Q903,120 920,120L920,200ZM480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360ZM480,760L480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760L480,760L480,760Z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_spatial_audio.xml b/packages/SystemUI/res/drawable/ic_spatial_audio.xml
new file mode 100644
index 0000000..0ee609a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_spatial_audio.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M920,401Q848,401 782,373.5Q716,346 665,295Q614,244 586.5,178Q559,112 559,40L639,40Q639,97 660,148Q681,199 721,239Q761,279 812,300.5Q863,322 920,322L920,401ZM920,242Q879,242 842.5,227Q806,212 777,183Q748,154 733,117.5Q718,81 718,40L797,40Q797,65 806.5,87.5Q816,110 833,127Q850,144 872.5,153Q895,162 920,162L920,242ZM400,520Q334,520 287,473Q240,426 240,360Q240,294 287,247Q334,200 400,200Q466,200 513,247Q560,294 560,360Q560,426 513,473Q466,520 400,520ZM80,840L80,728Q80,695 97,666Q114,637 144,622Q195,596 259,578Q323,560 400,560Q477,560 541,578Q605,596 656,622Q686,637 703,666Q720,695 720,728L720,840L80,840ZM160,760L640,760L640,728Q640,717 634.5,708Q629,699 620,694Q584,676 527.5,658Q471,640 400,640Q329,640 272.5,658Q216,676 180,694Q171,699 165.5,708Q160,717 160,728L160,760ZM400,440Q433,440 456.5,416.5Q480,393 480,360Q480,327 456.5,303.5Q433,280 400,280Q367,280 343.5,303.5Q320,327 320,360Q320,393 343.5,416.5Q367,440 400,440ZM400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360ZM400,760L400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760L400,760L400,760Z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_spatial_audio_off.xml b/packages/SystemUI/res/drawable/ic_spatial_audio_off.xml
new file mode 100644
index 0000000..c7d3272
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_spatial_audio_off.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M750,550L806,494Q766,454 743.5,402.5Q721,351 721,294Q721,237 743.5,186Q766,135 806,95L750,37Q699,88 670,155Q641,222 641,294Q641,366 670,432.5Q699,499 750,550ZM862,436L918,380Q901,363 891,341Q881,319 881,294Q881,269 891,247Q901,225 918,208L862,151Q833,180 817,216Q801,252 801,293Q801,334 817,371Q833,408 862,436ZM400,520Q334,520 287,473Q240,426 240,360Q240,294 287,247Q334,200 400,200Q466,200 513,247Q560,294 560,360Q560,426 513,473Q466,520 400,520ZM80,840L80,728Q80,695 97,666Q114,637 144,622Q195,596 259,578Q323,560 400,560Q477,560 541,578Q605,596 656,622Q686,637 703,666Q720,695 720,728L720,840L80,840ZM160,760L640,760L640,728Q640,717 634.5,708Q629,699 620,694Q584,676 527.5,658Q471,640 400,640Q329,640 272.5,658Q216,676 180,694Q171,699 165.5,708Q160,717 160,728L160,760ZM400,440Q433,440 456.5,416.5Q480,393 480,360Q480,327 456.5,303.5Q433,280 400,280Q367,280 343.5,303.5Q320,327 320,360Q320,393 343.5,416.5Q367,440 400,440ZM400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360ZM400,760L400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760L400,760L400,760Z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml b/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml
new file mode 100644
index 0000000..cf7a730
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <!-- Since this layer list has just one layer, we can remove it and replace with the inner
+         layer drawable. However this should only be done when the flag
+         com.android.systemui.qs_tile_focus_state has completed all its stages and this drawable
+         fully replaces the previous one to ensure consistency with code sections searching for
+         specific ids in drawable hierarchy -->
+    <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" />
+                </selector>
+            </item>
+            <!-- In the layer below we have negative insets because we need the focus outline
+                 to draw outside the bounds, around the main background. We use 5dp because
+                 the outline stroke is 3dp and the required padding is 2dp.-->
+            <item
+                android:top="-5dp"
+                android:right="-5dp"
+                android:left="-5dp"
+                android:bottom="-5dp">
+                <selector>
+                    <item
+                        android:state_focused="true"
+                        android:drawable="@drawable/qs_tile_focused_background"/>
+                </selector>
+            </item>
+        </layer-list>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_tile_focused_background.xml b/packages/SystemUI/res/drawable/qs_tile_focused_background.xml
new file mode 100644
index 0000000..fd456df
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_tile_focused_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:shape="rectangle">
+    <corners android:radius="30dp"/>
+    <stroke android:width="3dp" android:color="?androidprv:attr/materialColorSecondaryFixed"/>
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
new file mode 100644
index 0000000..5fe74aa
--- /dev/null
+++ b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
@@ -0,0 +1,322 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.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">
+
+    <ImageView
+        android:id="@+id/background"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:contentDescription="@string/biometric_dialog_empty_space_description"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <View
+        android:id="@+id/panel"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="?android:attr/colorBackgroundFloating"
+        android:clickable="true"
+        android:clipToOutline="true"
+        android:importantForAccessibility="no"
+        android:paddingHorizontal="16dp"
+        android:paddingVertical="16dp"
+        android:visibility="visible"
+        app:layout_constraintBottom_toTopOf="@+id/bottomGuideline"
+        app:layout_constraintEnd_toStartOf="@+id/rightGuideline"
+        app:layout_constraintStart_toStartOf="@+id/leftGuideline"
+        app:layout_constraintTop_toTopOf="@+id/topGuideline" />
+
+    <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
+        android:id="@+id/biometric_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.8"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:srcCompat="@tools:sample/avatars" />
+
+    <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
+        android:id="@+id/biometric_icon_overlay"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_gravity="center"
+        android:contentDescription="@null"
+        android:scaleType="fitXY"
+        app:layout_constraintBottom_toBottomOf="@+id/biometric_icon"
+        app:layout_constraintEnd_toEndOf="@+id/biometric_icon"
+        app:layout_constraintStart_toStartOf="@+id/biometric_icon"
+        app:layout_constraintTop_toTopOf="@+id/biometric_icon" />
+
+    <ScrollView
+        android:id="@+id/scrollView"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:fillViewport="true"
+        android:padding="16dp"
+        app:layout_constrainedHeight="true"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintBottom_toTopOf="@+id/buttonBarrier"
+        app:layout_constraintEnd_toStartOf="@+id/midGuideline"
+        app:layout_constraintStart_toStartOf="@id/leftGuideline"
+        app:layout_constraintTop_toTopOf="@+id/topGuideline">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/innerConstraint"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <ImageView
+                android:id="@+id/logo"
+                android:layout_width="@dimen/biometric_auth_icon_size"
+                android:layout_height="@dimen/biometric_auth_icon_size"
+                android:layout_gravity="center"
+                android:scaleType="fitXY"
+                android:visibility="visible"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <LinearLayout
+                android:id="@+id/customized_view_container"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="center_vertical"
+                android:orientation="vertical"
+                android:paddingHorizontal="0dp"
+                android:visibility="gone"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintHorizontal_bias="0.0"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/subtitle"
+                app:layout_constraintVertical_bias="0.0" />
+
+            <Space
+                android:id="@+id/space_above_content"
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/biometric_prompt_space_above_content"
+                android:visibility="gone" />
+
+            <TextView
+                android:id="@+id/title"
+                style="@style/TextAppearance.AuthCredential.Title"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="@integer/biometric_dialog_text_gravity"
+                android:paddingHorizontal="0dp"
+                android:textAlignment="viewStart"
+                app:layout_constraintBottom_toTopOf="@+id/subtitle"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintHorizontal_bias="0.0"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/logo"
+                app:layout_constraintVertical_bias="0.0"
+                app:layout_constraintVertical_chainStyle="packed" />
+
+            <TextView
+                android:id="@+id/subtitle"
+                style="@style/TextAppearance.AuthCredential.Subtitle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="@integer/biometric_dialog_text_gravity"
+                android:paddingHorizontal="0dp"
+                android:textAlignment="viewStart"
+                app:layout_constraintBottom_toTopOf="@+id/contentBarrier"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintHorizontal_bias="0.0"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/title" />
+
+            <TextView
+                android:id="@+id/description"
+                style="@style/TextAppearance.AuthCredential.Description"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="@integer/biometric_dialog_text_gravity"
+                android:paddingHorizontal="0dp"
+                android:textAlignment="viewStart"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintHorizontal_bias="0.0"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/subtitle"
+                app:layout_constraintVertical_bias="0.0" />
+
+            <TextView
+                android:id="@+id/logo_description"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:ellipsize="marquee"
+                android:gravity="@integer/biometric_dialog_text_gravity"
+                android:marqueeRepeatLimit="1"
+                android:singleLine="true"
+                android:textAlignment="viewStart"
+                android:paddingLeft="8dp"
+                app:layout_constraintBottom_toBottomOf="@+id/logo"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/logo"
+                app:layout_constraintTop_toTopOf="@+id/logo" />
+
+            <androidx.constraintlayout.widget.Barrier
+                android:id="@+id/contentBarrier"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                app:barrierAllowsGoneWidgets="false"
+                app:barrierDirection="top"
+                app:constraint_referenced_ids="description, customized_view_container" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </ScrollView>
+
+    <TextView
+        android:id="@+id/indicator"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:accessibilityLiveRegion="polite"
+        android:fadingEdge="horizontal"
+        android:gravity="center_horizontal"
+        android:marqueeRepeatLimit="marquee_forever"
+        android:scrollHorizontally="true"
+        android:textColor="@color/biometric_dialog_gray"
+        android:textSize="12sp"
+        app:layout_constraintBottom_toTopOf="@+id/buttonBarrier"
+        app:layout_constraintEnd_toEndOf="@+id/biometric_icon"
+        app:layout_constraintStart_toStartOf="@+id/biometric_icon"
+        app:layout_constraintTop_toBottomOf="@+id/biometric_icon"
+        app:layout_constraintVertical_bias="0.0" />
+
+    <!-- Negative Button, reserved for app -->
+    <Button
+        android:id="@+id/button_negative"
+        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginBottom="8dp"
+        android:layout_marginLeft="8dp"
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline"
+        app:layout_constraintStart_toStartOf="@+id/scrollView" />
+
+    <!-- Cancel Button, replaces negative button when biometric is accepted -->
+    <Button
+        android:id="@+id/button_cancel"
+        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginBottom="8dp"
+        android:layout_marginLeft="8dp"
+        android:text="@string/cancel"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline"
+        app:layout_constraintStart_toStartOf="@+id/scrollView" />
+
+    <!-- "Use Credential" Button, replaces if device credential is allowed -->
+    <Button
+        android:id="@+id/button_use_credential"
+        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginBottom="8dp"
+        android:layout_marginLeft="8dp"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline"
+        app:layout_constraintStart_toStartOf="@+id/scrollView" />
+
+    <!-- Positive Button -->
+    <Button
+        android:id="@+id/button_confirm"
+        style="@*android:style/Widget.DeviceDefault.Button.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginBottom="8dp"
+        android:layout_marginRight="8dp"
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:text="@string/biometric_dialog_confirm"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline"
+        app:layout_constraintEnd_toEndOf="@+id/scrollView"
+        tools:visibility="invisible" />
+
+    <!-- Try Again Button -->
+    <Button
+        android:id="@+id/button_try_again"
+        style="@*android:style/Widget.DeviceDefault.Button.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginBottom="8dp"
+        android:layout_marginRight="8dp"
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:text="@string/biometric_dialog_try_again"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline"
+        app:layout_constraintEnd_toEndOf="@+id/scrollView" />
+
+    <!-- Guidelines for setting panel border -->
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/topBarrier"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierAllowsGoneWidgets="false"
+        app:barrierDirection="top"
+        app:constraint_referenced_ids="scrollView" />
+
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/buttonBarrier"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierAllowsGoneWidgets="false"
+        app:barrierDirection="top"
+        app:constraint_referenced_ids="button_negative, button_cancel, button_use_credential, button_confirm, button_try_again" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/leftGuideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/rightGuideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/midGuideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_begin="406dp" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/bottomGuideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/topGuideline"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
new file mode 100644
index 0000000..5b30dfb
--- /dev/null
+++ b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
@@ -0,0 +1,304 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.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">
+
+    <ImageView
+        android:id="@+id/background"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:contentDescription="@string/biometric_dialog_empty_space_description"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <View
+        android:id="@+id/panel"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="?android:attr/colorBackgroundFloating"
+        android:clickable="true"
+        android:clipToOutline="true"
+        android:importantForAccessibility="no"
+        android:paddingHorizontal="16dp"
+        android:paddingVertical="16dp"
+        android:visibility="visible"
+        app:layout_constraintBottom_toTopOf="@+id/bottomGuideline"
+        app:layout_constraintEnd_toStartOf="@+id/rightGuideline"
+        app:layout_constraintStart_toStartOf="@+id/leftGuideline"
+        app:layout_constraintTop_toTopOf="@+id/topBarrier" />
+
+    <Button
+        android:id="@+id/button_negative"
+        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginBottom="8dp"
+        android:layout_marginLeft="8dp"
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="@+id/panel"
+        app:layout_constraintStart_toStartOf="@+id/panel" />
+
+    <Button
+        android:id="@+id/button_cancel"
+        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginBottom="8dp"
+        android:layout_marginLeft="8dp"
+        android:text="@string/cancel"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="@+id/panel"
+        app:layout_constraintStart_toStartOf="@+id/panel" />
+
+    <Button
+        android:id="@+id/button_use_credential"
+        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginBottom="8dp"
+        android:layout_marginLeft="8dp"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="@+id/panel"
+        app:layout_constraintStart_toStartOf="@+id/panel" />
+
+    <Button
+        android:id="@+id/button_confirm"
+        style="@*android:style/Widget.DeviceDefault.Button.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginBottom="8dp"
+        android:layout_marginRight="8dp"
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:text="@string/biometric_dialog_confirm"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="@+id/panel"
+        app:layout_constraintEnd_toEndOf="@+id/panel"
+        tools:visibility="invisible" />
+
+    <Button
+        android:id="@+id/button_try_again"
+        style="@*android:style/Widget.DeviceDefault.Button.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginBottom="8dp"
+        android:layout_marginRight="8dp"
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:text="@string/biometric_dialog_try_again"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="@+id/panel"
+        app:layout_constraintEnd_toEndOf="@+id/panel" />
+
+    <!-- Negative Button, reserved for app -->
+
+    <ScrollView
+        android:id="@+id/scrollView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:fillViewport="true"
+        android:padding="16dp"
+        app:layout_constrainedHeight="true"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
+        app:layout_constraintEnd_toEndOf="@id/panel"
+        app:layout_constraintStart_toStartOf="@id/panel"
+        app:layout_constraintTop_toTopOf="@+id/topGuideline"
+        app:layout_constraintVertical_bias="1.0">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/innerConstraint"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <ImageView
+                android:id="@+id/logo"
+                android:layout_width="@dimen/biometric_auth_icon_size"
+                android:layout_height="@dimen/biometric_auth_icon_size"
+                android:layout_gravity="center"
+                android:scaleType="fitXY"
+                android:visibility="visible"
+                app:layout_constraintBottom_toTopOf="@+id/logo_description"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <LinearLayout
+                android:id="@+id/customized_view_container"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="center_vertical"
+                android:orientation="vertical"
+                android:paddingHorizontal="@dimen/biometric_prompt_content_container_padding_horizontal"
+                android:visibility="gone"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/subtitle" />
+
+            <Space
+                android:id="@+id/space_above_content"
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/biometric_prompt_space_above_content"
+                android:visibility="gone" />
+
+            <TextView
+                android:id="@+id/title"
+                style="@style/TextAppearance.AuthCredential.Title"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="@integer/biometric_dialog_text_gravity"
+                app:layout_constraintBottom_toTopOf="@+id/subtitle"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/logo_description" />
+
+            <TextView
+                android:id="@+id/subtitle"
+                style="@style/TextAppearance.AuthCredential.Subtitle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="@integer/biometric_dialog_text_gravity"
+                app:layout_constraintBottom_toTopOf="@+id/contentBarrier"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/title" />
+
+            <TextView
+                android:id="@+id/logo_description"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:ellipsize="marquee"
+                android:gravity="@integer/biometric_dialog_text_gravity"
+                android:marqueeRepeatLimit="1"
+                android:singleLine="true"
+                app:layout_constraintBottom_toTopOf="@+id/title"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/logo" />
+
+            <TextView
+                android:id="@+id/description"
+                style="@style/TextAppearance.AuthCredential.Description"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="@integer/biometric_dialog_text_gravity"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/subtitle" />
+
+            <androidx.constraintlayout.widget.Barrier
+                android:id="@+id/contentBarrier"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                app:barrierAllowsGoneWidgets="false"
+                app:barrierDirection="top"
+                app:constraint_referenced_ids="description, customized_view_container" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </ScrollView>
+
+    <!-- Cancel Button, replaces negative button when biometric is accepted -->
+    <TextView
+        android:id="@+id/indicator"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:accessibilityLiveRegion="polite"
+        android:fadingEdge="horizontal"
+        android:gravity="center_horizontal"
+        android:marqueeRepeatLimit="marquee_forever"
+        android:scrollHorizontally="true"
+        android:textColor="@color/biometric_dialog_gray"
+        android:textSize="12sp"
+        app:layout_constraintBottom_toTopOf="@+id/buttonBarrier"
+        app:layout_constraintEnd_toEndOf="@+id/panel"
+        app:layout_constraintStart_toStartOf="@+id/panel"
+        app:layout_constraintTop_toBottomOf="@+id/biometric_icon"
+        app:layout_constraintVertical_bias="0.0" />
+
+    <!-- "Use Credential" Button, replaces if device credential is allowed -->
+
+    <!-- Positive Button -->
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/topBarrier"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierAllowsGoneWidgets="false"
+        app:barrierDirection="top"
+        app:constraint_referenced_ids="scrollView" />
+
+    <!-- Try Again Button -->
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/buttonBarrier"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierAllowsGoneWidgets="false"
+        app:barrierDirection="top"
+        app:constraint_referenced_ids="button_negative, button_cancel, button_use_credential, button_confirm, button_try_again" />
+
+    <!-- Guidelines for setting panel border -->
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/leftGuideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/rightGuideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/bottomGuideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/topGuideline"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_percent="0.25171" />
+
+    <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
+        android:id="@+id/biometric_icon"
+        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"
+        app:layout_constraintVertical_bias="0.8"
+        tools:srcCompat="@tools:sample/avatars" />
+
+    <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
+        android:id="@+id/biometric_icon_overlay"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_gravity="center"
+        android:contentDescription="@null"
+        android:scaleType="fitXY"
+        app:layout_constraintBottom_toBottomOf="@+id/biometric_icon"
+        app:layout_constraintEnd_toEndOf="@+id/biometric_icon"
+        app:layout_constraintStart_toStartOf="@+id/biometric_icon"
+        app:layout_constraintTop_toTopOf="@+id/biometric_icon" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
index 0ecdcfc..74292b4 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
@@ -1,27 +1,9 @@
 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.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">
-
-    <ImageView
-        android:id="@+id/logo"
-        android:layout_width="@dimen/biometric_auth_icon_size"
-        android:layout_height="@dimen/biometric_auth_icon_size"
-        android:layout_gravity="center"
-        android:scaleType="fitXY"
-        android:visibility="gone" />
-
-    <TextView
-        android:id="@+id/logo_description"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="@integer/biometric_dialog_text_gravity"
-        android:singleLine="true"
-        android:marqueeRepeatLimit="1"
-        android:ellipsize="marquee"
-        android:visibility="gone"/>
+    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">
 
     <ImageView
         android:id="@+id/background"
@@ -47,7 +29,7 @@
         app:layout_constraintBottom_toTopOf="@+id/bottomGuideline"
         app:layout_constraintEnd_toStartOf="@+id/rightGuideline"
         app:layout_constraintStart_toStartOf="@+id/leftGuideline"
-        app:layout_constraintTop_toTopOf="@+id/title" />
+        app:layout_constraintTop_toTopOf="@+id/topBarrier" />
 
     <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
         android:id="@+id/biometric_icon"
@@ -69,90 +51,133 @@
         android:scaleType="fitXY"
         app:layout_constraintBottom_toBottomOf="@+id/biometric_icon"
         app:layout_constraintEnd_toEndOf="@+id/biometric_icon"
-        app:layout_constraintHorizontal_bias="1.0"
         app:layout_constraintStart_toStartOf="@+id/biometric_icon"
-        app:layout_constraintTop_toTopOf="@+id/biometric_icon"
-        app:layout_constraintVertical_bias="0.0" />
-
-    <TextView
-        android:id="@+id/title"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:gravity="@integer/biometric_dialog_text_gravity"
-        android:singleLine="true"
-        android:marqueeRepeatLimit="1"
-        android:ellipsize="marquee"
-        style="@style/TextAppearance.AuthCredential.Title"
-        app:layout_constraintBottom_toTopOf="@+id/subtitle"
-        app:layout_constraintEnd_toEndOf="@+id/panel"
-        app:layout_constraintStart_toStartOf="@+id/panel" />
-
-    <TextView
-        android:id="@+id/subtitle"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:gravity="@integer/biometric_dialog_text_gravity"
-        android:singleLine="true"
-        android:marqueeRepeatLimit="1"
-        android:ellipsize="marquee"
-        style="@style/TextAppearance.AuthCredential.Subtitle"
-        app:layout_constraintBottom_toTopOf="@+id/description"
-        app:layout_constraintEnd_toEndOf="@+id/panel"
-        app:layout_constraintStart_toStartOf="@+id/panel" />
-
-    <Space
-        android:id="@+id/space_above_content"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/biometric_prompt_space_above_content"
-        android:visibility="gone"
-        app:layout_constraintTop_toBottomOf="@+id/subtitle"
-        app:layout_constraintEnd_toEndOf="@+id/panel"
-        app:layout_constraintStart_toStartOf="@+id/panel"/>
+        app:layout_constraintTop_toTopOf="@+id/biometric_icon" />
 
     <ScrollView
-        android:id="@+id/customized_view_container"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:fillViewport="true"
-        android:fadeScrollbars="false"
-        android:gravity="center_vertical"
-        android:orientation="vertical"
-        android:paddingHorizontal="@dimen/biometric_prompt_content_container_padding_horizontal"
-        android:scrollbars="vertical"
-        android:visibility="gone"
-        app:layout_constraintTop_toBottomOf="@+id/space_above_content"
-        app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
-        app:layout_constraintEnd_toEndOf="@+id/panel"
-        app:layout_constraintStart_toStartOf="@+id/panel"/>
-
-    <TextView
-        android:id="@+id/description"
-        android:layout_width="wrap_content"
+        android:id="@+id/scrollView"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginBottom="24dp"
-        android:scrollbars="vertical"
-        android:gravity="@integer/biometric_dialog_text_gravity"
-        style="@style/TextAppearance.AuthCredential.Description"
+        android:fillViewport="true"
+        android:padding="16dp"
+        app:layout_constrainedHeight="true"
+        app:layout_constrainedWidth="true"
         app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
-        app:layout_constraintEnd_toEndOf="@+id/panel"
-        app:layout_constraintStart_toStartOf="@+id/panel" />
+        app:layout_constraintEnd_toEndOf="@id/rightGuideline"
+        app:layout_constraintStart_toStartOf="@id/leftGuideline"
+        app:layout_constraintTop_toTopOf="@+id/topGuideline"
+        app:layout_constraintVertical_bias="1.0">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/innerConstraint"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <ImageView
+                android:id="@+id/logo"
+                android:layout_width="@dimen/biometric_auth_icon_size"
+                android:layout_height="@dimen/biometric_auth_icon_size"
+                android:layout_gravity="center"
+                android:scaleType="fitXY"
+                android:visibility="visible"
+                app:layout_constraintBottom_toTopOf="@+id/logo_description"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <LinearLayout
+                android:id="@+id/customized_view_container"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="center_vertical"
+                android:orientation="vertical"
+                android:paddingHorizontal="@dimen/biometric_prompt_content_container_padding_horizontal"
+                android:visibility="gone"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/subtitle" />
+
+            <Space
+                android:id="@+id/space_above_content"
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/biometric_prompt_space_above_content"
+                android:visibility="gone" />
+
+            <TextView
+                android:id="@+id/title"
+                style="@style/TextAppearance.AuthCredential.Title"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="@integer/biometric_dialog_text_gravity"
+                app:layout_constraintBottom_toTopOf="@+id/subtitle"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/logo_description" />
+
+            <TextView
+                android:id="@+id/subtitle"
+                style="@style/TextAppearance.AuthCredential.Subtitle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="@integer/biometric_dialog_text_gravity"
+                app:layout_constraintBottom_toTopOf="@+id/contentBarrier"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/title" />
+
+            <TextView
+                android:id="@+id/logo_description"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:ellipsize="marquee"
+                android:gravity="@integer/biometric_dialog_text_gravity"
+                android:marqueeRepeatLimit="1"
+                android:singleLine="true"
+                app:layout_constraintBottom_toTopOf="@+id/title"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/logo" />
+
+            <TextView
+                android:id="@+id/description"
+                style="@style/TextAppearance.AuthCredential.Description"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="@integer/biometric_dialog_text_gravity"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/subtitle" />
+
+            <androidx.constraintlayout.widget.Barrier
+                android:id="@+id/contentBarrier"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                app:barrierAllowsGoneWidgets="false"
+                app:barrierDirection="top"
+                app:constraint_referenced_ids="description, customized_view_container" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </ScrollView>
 
     <TextView
         android:id="@+id/indicator"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginTop="16dp"
-        android:gravity="center_horizontal"
-        android:textColor="@color/biometric_dialog_gray"
-        android:textSize="12sp"
         android:accessibilityLiveRegion="polite"
+        android:fadingEdge="horizontal"
+        android:gravity="center_horizontal"
         android:marqueeRepeatLimit="marquee_forever"
         android:scrollHorizontally="true"
-        android:fadingEdge="horizontal"
+        android:textColor="@color/biometric_dialog_gray"
+        android:textSize="12sp"
+        app:layout_constraintBottom_toTopOf="@+id/buttonBarrier"
         app:layout_constraintEnd_toEndOf="@+id/panel"
-        app:layout_constraintHorizontal_bias="0.5"
         app:layout_constraintStart_toStartOf="@+id/panel"
-        app:layout_constraintTop_toBottomOf="@+id/biometric_icon" />
+        app:layout_constraintTop_toBottomOf="@+id/biometric_icon"
+        app:layout_constraintVertical_bias="0.0" />
 
     <!-- Negative Button, reserved for app -->
     <Button
@@ -230,6 +255,22 @@
         app:layout_constraintEnd_toEndOf="@+id/panel" />
 
     <!-- Guidelines for setting panel border -->
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/topBarrier"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierAllowsGoneWidgets="false"
+        app:barrierDirection="top"
+        app:constraint_referenced_ids="scrollView" />
+
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/buttonBarrier"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierAllowsGoneWidgets="false"
+        app:barrierDirection="top"
+        app:constraint_referenced_ids="button_negative, button_cancel, button_use_credential, button_confirm, button_try_again" />
+
     <androidx.constraintlayout.widget.Guideline
         android:id="@+id/leftGuideline"
         android:layout_width="wrap_content"
@@ -251,4 +292,11 @@
         android:orientation="horizontal"
         app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
 
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/topGuideline"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_percent="0.25171" />
+
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/biometric_prompt_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
index e759074..9842109 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
@@ -70,7 +70,7 @@
         android:layout_height="@dimen/biometric_prompt_space_above_content"
         android:visibility="gone" />
 
-    <ScrollView
+    <LinearLayout
         android:id="@+id/customized_view_container"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index ac781ec..13355f3 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -181,12 +181,11 @@
 
             <TextView
                 android:id="@+id/bluetooth_auto_on_toggle_info_text"
-                android:layout_width="wrap_content"
+                android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="20dp"
                 android:paddingStart="36dp"
                 android:paddingEnd="40dp"
-                android:text="@string/turn_on_bluetooth_auto_info"
                 android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index 1365a11..22d156d 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -263,8 +263,9 @@
                         android:layout_gravity="center_vertical|start">
                         <ImageView
                             android:id="@+id/wifi_connected_icon"
-                            android:layout_width="wrap_content"
-                            android:layout_height="wrap_content"
+                            android:layout_width="match_parent"
+                            android:layout_height="match_parent"
+                            android:scaleType="fitCenter"
                             android:layout_gravity="center"/>
                     </FrameLayout>
 
diff --git a/packages/SystemUI/res/layout/internet_list_item.xml b/packages/SystemUI/res/layout/internet_list_item.xml
index f6a2136..0da0f2b 100644
--- a/packages/SystemUI/res/layout/internet_list_item.xml
+++ b/packages/SystemUI/res/layout/internet_list_item.xml
@@ -35,8 +35,9 @@
             android:layout_gravity="center_vertical|start">
             <ImageView
                 android:id="@+id/wifi_icon"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:scaleType="fitCenter"
                 android:layout_gravity="center"/>
         </FrameLayout>
 
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
index 61f69c0..f6042e4 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
@@ -128,7 +128,8 @@
         android:layout_marginStart="49dp"
         android:layout_marginEnd="49dp"
         android:overScrollMode="never"
-        android:layout_marginBottom="16dp">
+        android:layout_marginBottom="16dp"
+        android:scrollbars="none">
         <LinearLayout
             android:id="@+id/keyboard_shortcuts_container"
             android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/notif_half_shelf.xml b/packages/SystemUI/res/layout/notif_half_shelf.xml
index 68c8dd9..d8d2985 100644
--- a/packages/SystemUI/res/layout/notif_half_shelf.xml
+++ b/packages/SystemUI/res/layout/notif_half_shelf.xml
@@ -19,11 +19,11 @@
         xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
         android:id="@+id/half_shelf_dialog"
         android:orientation="vertical"
-        android:layout_width="wrap_content"
+        android:layout_width="@dimen/large_dialog_width"
         android:layout_height="wrap_content"
         android:layout_gravity="center_horizontal|bottom"
-        android:paddingStart="4dp"
-        android:paddingEnd="4dp">
+        android:paddingLeft="@dimen/dialog_side_padding"
+        android:paddingRight="@dimen/dialog_side_padding">
 
     <LinearLayout
         android:id="@+id/half_shelf"
diff --git a/packages/SystemUI/res/layout/scene_window_root.xml b/packages/SystemUI/res/layout/scene_window_root.xml
index bb8de4c..0dcd15b 100644
--- a/packages/SystemUI/res/layout/scene_window_root.xml
+++ b/packages/SystemUI/res/layout/scene_window_root.xml
@@ -24,7 +24,7 @@
     android:id="@+id/scene_window_root"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:fitsSystemWindows="false">
+    android:fitsSystemWindows="true">
 
     <include layout="@layout/super_notification_shade"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/shutdown_dialog_finder_active.xml b/packages/SystemUI/res/layout/shutdown_dialog_finder_active.xml
new file mode 100644
index 0000000..b6db7fc
--- /dev/null
+++ b/packages/SystemUI/res/layout/shutdown_dialog_finder_active.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.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">
+
+    <TextView
+        android:id="@android:id/text1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="24dp"
+        android:fontFamily="google-sans"
+        android:gravity="center"
+        android:text="@string/shutdown_progress"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textDirection="locale"
+        android:textSize="18sp"
+        android:visibility="gone"
+        app:layout_constraintBottom_toTopOf="@android:id/text2"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintVertical_bias="0.375"
+        app:layout_constraintVertical_chainStyle="packed" />
+
+    <TextView
+        android:id="@android:id/text2"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="24dp"
+        android:fontFamily="google-sans"
+        android:gravity="center"
+        android:text="@string/shutdown_progress"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:textDirection="locale"
+        android:textSize="24sp"
+        app:layout_constraintBottom_toTopOf="@android:id/progress"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@android:id/text1" />
+
+    <ProgressBar
+        android:id="@android:id/progress"
+        style="?android:attr/progressBarStyleLarge"
+        android:layout_width="30dp"
+        android:layout_height="30dp"
+        android:importantForAccessibility="no"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@android:id/text2" />
+
+    <TextView
+        android:id="@+id/finer_hint"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="32dp"
+        android:background="@drawable/bg_shutdown_finder_message"
+        android:drawablePadding="16dp"
+        android:drawableStart="@drawable/ic_finder_active"
+        android:fontFamily="google-sans"
+        android:gravity="start"
+        android:padding="20dp"
+        android:text="@string/finder_active"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textColor="@android:color/secondary_text_dark"
+        android:textDirection="locale"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintTop_toBottomOf="@android:id/progress"
+        app:layout_constraintVertical_bias="1" />
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 4002517..2e2d286 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Skakel aan"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Skakel aan"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nee, dankie"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standaard"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ekstreem"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Outodraai skerm"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Gee <xliff:g id="APPLICATION">%1$s</xliff:g> toegang tot <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Laat <xliff:g id="APPLICATION">%1$s</xliff:g> toe om by <xliff:g id="USB_DEVICE">%2$s</xliff:g> in te gaan?\nOpneemtoestemming is nie aan hierdie program verleen nie, maar dit kan oudio deur hierdie USB-toestel vasvang."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ontkoppel"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiveer"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Skakel dit môre outomaties weer aan"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Kenmerke soos Kitsdeel, Kry My Toestel en toestelligging gebruik Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batterykrag"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Oudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Kopstuk"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Voeg meer legstukke by"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Langdruk om legstukke te pasmaak"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Pasmaak legstukke"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App-ikoon vir gedeaktiveerde legstuk"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Wysig legstuk"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Verwyder"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Voeg legstuk by"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Lui"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibreer"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Demp"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Saai uit"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Onbeskikbaar omdat luitoon gedemp is"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Tik om te ontdemp."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tik om op vibreer te stel. Toeganklikheidsdienste kan dalk gedemp wees."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Tik om te demp. Toeganklikheidsdienste kan dalk gedemp wees."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tik om op vibreer te stel."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Tik om te demp."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Geraasbeheer"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Tik om luiermodus te verander"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"demp"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ontdemp"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Aan/af-kieslys"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Bladsy <xliff:g id="ID_1">%1$d</xliff:g> van <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Sluitskerm"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Jy kan hierdie foon met Kry My Toestel opspoor selfs wanneer dit afgeskakel is"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Sit tans af …"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Sien versorgingstappe"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Sien versorgingstappe"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Prop jou toestel uit"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Maak notas"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Maak notas, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Deel tans oudio"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Uitsaai"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Hou op om <xliff:g id="APP_NAME">%1$s</xliff:g> uit te saai?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"As jy <xliff:g id="SWITCHAPP">%1$s</xliff:g> uitsaai of die uitvoer verander, sal jou huidige uitsending stop"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Keer die foon om vir hoër resolusie"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Voubare toestel word ontvou"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Voubare toestel word omgekeer"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"gevou"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"oopgevou"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> batterykrag oor"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Koppel jou stilus aan ’n laaier"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Stilus se battery is amper pap"</string>
diff --git a/packages/SystemUI/res/values-af/tiles_states_strings.xml b/packages/SystemUI/res/values-af/tiles_states_strings.xml
index 662aa71..1427574 100644
--- a/packages/SystemUI/res/values-af/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-af/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Af"</item>
     <item msgid="578444932039713369">"Aan"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Onbeskikbaar"</item>
+    <item msgid="9061144428113385092">"Af"</item>
+    <item msgid="2984256114867200368">"Aan"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Onbeskikbaar"</item>
     <item msgid="8707481475312432575">"Af"</item>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index ebc2002..897820c 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"አብራ"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"አብራ"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"አይ፣ አመሰግናለሁ"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"መደበኛ"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"እጅግ ከፍተኛ"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"ማያ በራስ ሰር አሽከርክር"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> <xliff:g id="USB_DEVICE">%2$s</xliff:g>ን እንዲደርስበት ይፈቀድለት?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> <xliff:g id="USB_DEVICE">%2$s</xliff:g>ን እንዲደርስ ይፈቀድለት?\nይህ መተግበሪያ የመቅዳት ፈቃድ አልተሰጠውም፣ ነገር ግን በዩኤስቢ መሣሪያ በኩል ኦዲዮን መቅዳት ይችላል።"</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ግንኙነትን አቋርጥ"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ያግብሩ"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"ነገ እንደገና በራስ-ሰር አስጀምር"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"እንደ ፈጣን ማጋራት፣ የእኔን መሣሪያ አግኝ እና የመሣሪያ አካባቢ ያሉ ባህሪያት ብሉቱዝን ይጠቀማሉ"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ባትሪ"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ኦዲዮ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ማዳመጫ"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ተጨማሪ ምግብሮችን ያክሉ"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ምግብሮችን ለማበጀት በረጅሙ ይጫኑ"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ምግብሮችን አብጅ"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"የመተግበሪያ አዶ ለተሰናከለ ምግብር"</string>
     <string name="edit_widget" msgid="9030848101135393954">"ምግብርን አርትዕ"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"አስወግድ"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ምግብር አክል"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"ጥሪ"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"ንዘር"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"ድምጸ-ከል አድርግ"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Cast"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"የጥሪ ድምጽ ስለተዘጋ አይገኝም"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s። ድምጸ-ከል ለማድረግ መታ ያድርጉ"</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s። ወደ ንዝረት ለማቀናበር መታ ያድርጉ። የተደራሽነት አገልግሎቶች ድምጸ-ከል ሊደረግባቸው ይችላል።"</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s። ድምጸ-ከል ለማድረግ መታ ያድርጉ። የተደራሽነት አገልግሎቶች ድምጸ-ከል ሊደረግባቸው ይችላል።"</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s። ወደ ንዝረት ለማቀናበር መታ ያድርጉ።"</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s። ድምጸ-ከል ለማድረግ መታ ያድርጉ።"</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"የጫጫታ መቆጣጠሪያ"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"የደዋይ ሁነታን ለመቀየር መታ ያድርጉ"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"ድምጸ-ከል አድርግ"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ድምጸ-ከልን አንሳ"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"የኃይል ምናሌ"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"ገፅ <xliff:g id="ID_1">%1$d</xliff:g> ከ <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"ማያ ገፅ ቁልፍ"</string>
+    <string name="finder_active" msgid="7907846989716941952">"ይህ መሣሪያ ኃይል ጠፍቶ ቢሆንም እንኳን በየእኔን መሣሪያ አግኝ ማግኘት ይችላሉ"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"በመዝጋት ላይ…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"የእንክብካቤ ደረጃዎችን ይመልከቱ"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"የእንክብካቤ ደረጃዎችን ይመልከቱ"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"መሣሪያዎን ይንቀሉ"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>፣ <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"የማስታወሻ አያያዝ"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"የማስታወሻ አያያዝ፣ <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"ኦዲዮ በማጋራት ላይ"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"በማሰራጨት ላይ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g>ን ማሰራጨት ይቁም?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g>ን ካሰራጩ ወይም ውፅዓትን ከቀየሩ የአሁኑ ስርጭትዎ ይቆማል"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ለከፍተኛ ጥራት ስልኩን ይቀይሩ"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"መታጠፍ የሚችል መሣሪያ እየተዘረጋ ነው"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"መታጠፍ የሚችል መሣሪያ እየተገለበጠ ነው"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"የታጠፈ"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"የተዘረጋ"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ባትሪ ይቀራል"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ብሮስፌዎን ከኃይል መሙያ ጋር ያገናኙ"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"የብሮስፌ ባትሪ ዝቅተኛ ነው"</string>
diff --git a/packages/SystemUI/res/values-am/tiles_states_strings.xml b/packages/SystemUI/res/values-am/tiles_states_strings.xml
index e5d68d9..ab0b68b 100644
--- a/packages/SystemUI/res/values-am/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-am/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"ጠፍቷል"</item>
     <item msgid="578444932039713369">"በርቷል"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"አይገኝም"</item>
+    <item msgid="9061144428113385092">"አጥፋ"</item>
+    <item msgid="2984256114867200368">"አብራ"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"አይገኝም"</item>
     <item msgid="8707481475312432575">"ጠፍቷል"</item>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index ec72578..89b4ccf 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"تفعيل"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"تفعيل"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"لا، شكرًا"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"عادي"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"التوفير العالي"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"التدوير التلقائي للشاشة"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"هل تريد السماح لتطبيق <xliff:g id="APPLICATION">%1$s</xliff:g> بالدخول إلى <xliff:g id="USB_DEVICE">%2$s</xliff:g>؟"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"‏هل تريد السماح لتطبيق <xliff:g id="APPLICATION">%1$s</xliff:g> بالدخول إلى <xliff:g id="USB_DEVICE">%2$s</xliff:g>؟\nلم يتم منح هذا التطبيق إذن تسجيل، ولكن يمكنه تسجيل الصوت من خلال جهاز USB هذا."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"إلغاء الربط"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"تفعيل"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"تفعيل البلوتوث تلقائيًا مرة أخرى غدًا"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"‏يُستخدَم البلوتوث في ميزات مثل Quick Share و\"العثور على جهازي\" والموقع الجغرافي للجهاز"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"مستوى طاقة البطارية <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"صوت"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"سماعة الرأس"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"إضافة المزيد من التطبيقات المصغّرة"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"اضغط مع الاستمرار لتخصيص التطبيقات المصغّرة."</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"تخصيص التطبيقات المصغَّرة"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"رمز التطبيق المصغّر غير المفعّل"</string>
     <string name="edit_widget" msgid="9030848101135393954">"تعديل التطبيق المصغَّر"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"إزالة"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"إضافة تطبيق مصغّر"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"استصدار رنين"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"اهتزاز"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"كتم الصوت"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"البثّ"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"يتعذّر التغيير بسبب كتم صوت الرنين."</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"‏%1$s. انقر لإلغاء التجاهل."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"‏%1$s. انقر للتعيين على الاهتزاز. قد يتم تجاهل خدمات \"سهولة الاستخدام\"."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"‏%1$s. انقر للتجاهل. قد يتم تجاهل خدمات \"سهولة الاستخدام\"."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"‏%1$s. انقر للتعيين على الاهتزاز."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"‏%1$s. انقر لكتم الصوت."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"التحكُّم في مستوى الضجيج"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"انقر لتغيير وضع الرنين."</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"كتم الصوت"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"إعادة الصوت"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"قائمة زر التشغيل"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"الصفحة <xliff:g id="ID_1">%1$d</xliff:g> من <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"شاشة القفل"</string>
+    <string name="finder_active" msgid="7907846989716941952">"يمكنك تحديد مكان هذا الهاتف باستخدام تطبيق \"العثور على جهازي\" حتى عندما يكون مُطفئًا."</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"جارٍ إيقاف التشغيل…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"الاطّلاع على خطوات العناية"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"الاطّلاع على خطوات العناية"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"افصِل جهازك"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>، <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"تدوين الملاحظات"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"تدوين الملاحظات في \"<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>\""</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"تتم مشاركة الصوت"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"البث"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"هل تريد إيقاف بث تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g>؟"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"إذا أجريت بث تطبيق <xliff:g id="SWITCHAPP">%1$s</xliff:g> أو غيَّرت جهاز الإخراج، سيتوقَف البث الحالي."</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"للحصول على درجة دقة أعلى، اقلِب الهاتف."</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"جهاز قابل للطي يجري فتحه"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"جهاز قابل للطي يجري قلبه"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"مطوي"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"غير مطوي"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"‫%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"النسبة المئوية المتبقية من شحن البطارية: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"عليك توصيل قلم الشاشة بشاحن."</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"بطارية قلم الشاشة منخفضة"</string>
diff --git a/packages/SystemUI/res/values-ar/tiles_states_strings.xml b/packages/SystemUI/res/values-ar/tiles_states_strings.xml
index 856ae1d..364737d 100644
--- a/packages/SystemUI/res/values-ar/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ar/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"الميزة غير مفعّلة"</item>
     <item msgid="578444932039713369">"الميزة مفعّلة"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"غير متوفّر"</item>
+    <item msgid="9061144428113385092">"غير مفعَّل"</item>
+    <item msgid="2984256114867200368">"مفعَّل"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"الميزة غير متاحة"</item>
     <item msgid="8707481475312432575">"الميزة غير مفعّلة"</item>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index f66650b..9a2d744 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"অন কৰক"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"অন কৰক"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"নালাগে, ধন্যবাদ"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"মানক"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"চৰম"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"স্বয়ং-ঘূৰ্ণন স্ক্ৰীন"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> এক্সেছ কৰিবলৈ <xliff:g id="APPLICATION">%1$s</xliff:g>ক অনুমতি দিবনে?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g>ক <xliff:g id="USB_DEVICE">%2$s</xliff:g> এক্সেছ কৰিবলৈ অনুমতি দিবনে?\nএই এপ্‌টোক ৰেকর্ড কৰাৰ অনুমতি দিয়া হোৱা নাই কিন্তু ই এই ইউএছবি ডিভাইচটোৰ জৰিয়তে অডিঅ\' ৰেকর্ড কৰিব পাৰে।"</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"সংযোগ বিচ্ছিন্ন কৰক"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"সক্ৰিয় কৰক"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"কাইলৈ পুনৰ স্বয়ংক্ৰিয়ভাৱে অন কৰক"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Quick Share, Find My Device আৰু ডিভাইচৰ অৱস্থানৰ দৰে সুবিধাই ব্লুটুথ ব্যৱহাৰ কৰে"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"বেটাৰী <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"অডিঅ’"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"হেডছেট"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"অধিক ৱিজেট যোগ দিয়ক"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ৱিজেট কাষ্টমাইজ কৰিবলৈ দীঘলীয়াকৈ টিপক"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ৱিজেট কাষ্টমাইজ কৰক"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"অক্ষম কৰা ৱিজেটৰ বাবে এপৰ চিহ্ন"</string>
     <string name="edit_widget" msgid="9030848101135393954">"ৱিজেট সম্পাদনা কৰক"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"আঁতৰাওক"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ৱিজেট যোগ দিয়ক"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"ৰিং"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"কম্পন"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"মিউট"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"কাষ্ট কৰক"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"ৰিং মিউট কৰি থোৱাৰ বাবে উপলব্ধ নহয়"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s। আনমিউট কৰিবৰ বাবে টিপক।"</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s। কম্পনৰ বাবে টিপক। দিব্য়াংগসকলৰ বাবে থকা সেৱা মিউট হৈ থাকিব পাৰে।"</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s। মিউট কৰিবলৈ টিপক। দিব্য়াংগসকলৰ বাবে থকা সেৱা মিউট হৈ থাকিব পাৰে।"</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s। কম্পন অৱস্থাত ছেট কৰিবলৈ টিপক।"</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s। মিউট কৰিবলৈ টিপক।"</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"কোলাহল নিয়ন্ত্ৰণ"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"ৰিংগাৰ ম’ড সলনি কৰিবলৈ টিপক"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"মিউট কৰক"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"আনমিউট কৰক"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"পাৱাৰ মেনু"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g>ৰ পৃষ্ঠা <xliff:g id="ID_1">%1$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"লক স্ক্ৰীন"</string>
+    <string name="finder_active" msgid="7907846989716941952">"পাৱাৰ অফ কৰা থাকিলেও Find My Deviceৰ জৰিয়তে আপুনি এই ফ’নটোৰ অৱস্থান নিৰ্ধাৰণ কৰিব পাৰে"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"বন্ধ কৰি থকা হৈছে…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"যত্ন লোৱাৰ পদক্ষেপসমূহ চাওক"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"যত্ন লোৱাৰ পদক্ষেপসমূহ চাওক"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"আপোনাৰ ডিভাইচটো আনপ্লাগ কৰক"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"টোকা গ্ৰহণ কৰা"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"টোকা গ্ৰহণ কৰা, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"অডিঅ’ শ্বেয়াৰ কৰি থকা হৈছে"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"সম্প্ৰচাৰণ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ সম্প্ৰচাৰ কৰা বন্ধ কৰিবনে?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"যদি আপুনি <xliff:g id="SWITCHAPP">%1$s</xliff:g>ৰ সম্প্ৰচাৰ কৰে অথবা আউটপুট সলনি কৰে, তেন্তে, আপোনাৰ বৰ্তমানৰ সম্প্ৰচাৰ বন্ধ হৈ যাব"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"অধিক ৰিজ’লিউছনৰ বাবে, ফ’নটো লুটিয়াই দিয়ক"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"জপাব পৰা ডিভাইচৰ জাপ খুলি থকা হৈছে"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"জপাব পৰা ডিভাইচৰ ওলোটাই থকা হৈছে"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> বেটাৰী বাকী আছে"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"আপোনাৰ ষ্টাইলাছ এটা চাৰ্জাৰৰ সৈতে সংযোগ কৰক"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"ষ্টাইলাছৰ বেটাৰী কম আছে"</string>
diff --git a/packages/SystemUI/res/values-as/tiles_states_strings.xml b/packages/SystemUI/res/values-as/tiles_states_strings.xml
index a9c3e3b..767b34d 100644
--- a/packages/SystemUI/res/values-as/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-as/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"অফ আছে"</item>
     <item msgid="578444932039713369">"অন কৰা আছে"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"উপলব্ধ নহয়"</item>
+    <item msgid="9061144428113385092">"অফ আছে"</item>
+    <item msgid="2984256114867200368">"অন আছে"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"উপলব্ধ নহয়"</item>
     <item msgid="8707481475312432575">"অফ আছে"</item>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 7048441..ebd4073 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Aktivləşdirin"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Aktiv edin"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Xeyr, təşəkkür"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standart"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ekstremal"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Ekranın avtomatik dönməsi"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> tətbiqinə <xliff:g id="USB_DEVICE">%2$s</xliff:g> cihazına giriş icazəsi verilsin?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> tətbiqinə <xliff:g id="USB_DEVICE">%2$s</xliff:g> cihazına giriş verilsin?\nTətbiqə qeydə almaq icazəsi verilməsə də, bu USB vasitəsilə səsi qeydə ala bilər."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"əlaqəni kəsin"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivləşdirin"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Sabah avtomatik aktiv edin"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Cəld Paylaşım, Cihazın Tapılması və cihaz məkanı kimi funksiyalar Bluetooth istifadə edir"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batareya"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Qulaqlıq"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Vidcetlər əlavə edin"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Basıb saxlayaraq vidcetləri fərdiləşdirin"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Vidcetləri fərdiləşdirin"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Deaktiv edilmiş vidcet üçün tətbiq ikonası"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Vidceti redaktə edin"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Silin"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Vidcet əlavə edin"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Zəng"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrasiya"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Susdurun"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Yayım"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Zəng səssiz edildiyi üçün əlçatan deyil"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Səsli etmək üçün tıklayın."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Vibrasiyanı ayarlamaq üçün tıklayın. Əlçatımlılıq xidmətləri səssiz edilmiş ola bilər."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Səssiz etmək üçün tıklayın. Əlçatımlılıq xidmətləri səssiz edilmiş ola bilər."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Vibrasiyanı ayarlamaq üçün klikləyin."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Səssiz etmək üçün klikləyin."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Səs-küy idarəetməsi"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Zəng rejimini dəyişmək üçün toxunun"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"susdurun"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"səssiz rejimdən çıxarın"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Qidalanma düyməsi menyusu"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g> səhifədən <xliff:g id="ID_1">%1$d</xliff:g> səhifə"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Ekran kilidi"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Bu telefon sönülü olsa belə, Cihazın Tapılması ilə onu tapa bilərsiniz"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Söndürülür…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Ehtiyat tədbiri mərhələlərinə baxın"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ehtiyat tədbiri mərhələlərinə baxın"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Cihazınızı ayırın"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Qeydgötürmə"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Qeydgötürmə, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Audio paylaşılır"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Yayım"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> tətbiqinin yayımlanması dayandırılsın?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> tətbiqini yayımlasanız və ya nəticəni dəyişsəniz, cari yayımınız dayandırılacaq"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Daha yüksək ayırdetmə dəqiqliyi üçün telefonu çevirin"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Qatlana bilən cihaz açılır"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Qatlana bilən cihaz fırladılır"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"qatlanmış"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"açıq"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> enerji qalıb"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Qələmi adapterə qoşun"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Qələm enerjisi azdır"</string>
diff --git a/packages/SystemUI/res/values-az/tiles_states_strings.xml b/packages/SystemUI/res/values-az/tiles_states_strings.xml
index d973e4e..3457a71 100644
--- a/packages/SystemUI/res/values-az/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-az/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Deaktiv"</item>
     <item msgid="578444932039713369">"Aktiv"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Əlçatan deyil"</item>
+    <item msgid="9061144428113385092">"Deaktiv"</item>
+    <item msgid="2984256114867200368">"Aktiv"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Əlçatan deyil"</item>
     <item msgid="8707481475312432575">"Deaktiv"</item>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index a598d8f..17599b2 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Uključi"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Uključi"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Ne, hvala"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standardno"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ekstremno"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Automatsko rotiranje ekrana"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Dozvoljavate da <xliff:g id="APPLICATION">%1$s</xliff:g> pristupa uređaju <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Želite li da dozvolite da <xliff:g id="APPLICATION">%1$s</xliff:g> pristupa uređaju <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nOva aplikacija nema dozvolu za snimanje, ali bi mogla da snima zvuk pomoću ovog USB uređaja."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekinite vezu"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivirajte"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatski ponovo uključi sutra"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funkcije kao što su Quick Share, Pronađi moj uređaj i lokacija uređaja koriste Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Nivo baterije je <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalice"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Dodajte još vidžeta"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Dugi pritisak za prilagođavanje vidžeta"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prilagodi vidžete"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacije za onemogućen vidžet"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Izmeni vidžet"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Ukloni"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodaj vidžet"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Aktiviraj zvono"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibriraj"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Isključi zvuk"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Prebacivanje"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Nedostupno jer je zvuk isključen"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Dodirnite da biste uključili zvuk."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Dodirnite da biste podesili na vibraciju. Zvuk usluga pristupačnosti će možda biti isključen."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Dodirnite da biste isključili zvuk. Zvuk usluga pristupačnosti će možda biti isključen."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Dodirnite da biste podesili na vibraciju."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Dodirnite da biste isključili zvuk."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Kontrola šuma"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Dodirnite da biste promenili režim zvona"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"isključite zvuk"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"uključite zvuk"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Meni dugmeta za uključivanje"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_1">%1$d</xliff:g>. strana od <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Zaključan ekran"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Možete da locirate ovaj telefon pomoću usluge Pronađi moj uređaj čak i kada je isključen"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Isključuje se…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Pogledajte upozorenja"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Pogledajte upozorenja"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Isključite uređaj"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Pravljenje beležaka"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Pravljenje beležaka, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Deli se zvuk"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Emitovanje"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Želite da zaustavite emitovanje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ako emitujete aplikaciju <xliff:g id="SWITCHAPP">%1$s</xliff:g> ili promenite izlaz, aktuelno emitovanje će se zaustaviti"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Za veću rezoluciju obrnite telefon"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Uređaj na preklop se otvara"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Uređaj na preklop se obrće"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"zatvoreno"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"otvoreno"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Preostalo je još<xliff:g id="PERCENTAGE">%s</xliff:g> baterije"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Povežite pisaljku sa punjačem"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Nizak nivo baterije pisaljke"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/tiles_states_strings.xml b/packages/SystemUI/res/values-b+sr+Latn/tiles_states_strings.xml
index 32051ef..75fb325 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Isključeno"</item>
     <item msgid="578444932039713369">"Uključeno"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Nedostupno"</item>
+    <item msgid="9061144428113385092">"Isključeno"</item>
+    <item msgid="2984256114867200368">"Uključeno"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Nedostupno"</item>
     <item msgid="8707481475312432575">"Isključeno"</item>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 2b137fb..93921fc 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Уключыць"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Уключыць"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Не, дзякуй"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Стандартны рэжым"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Максімальная"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Аўтаматычны паварот экрана"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Дазволіць праграме <xliff:g id="APPLICATION">%1$s</xliff:g> доступ да прылады <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Даць праграме \"<xliff:g id="APPLICATION">%1$s</xliff:g>\" доступ да прылады \"<xliff:g id="USB_DEVICE">%2$s</xliff:g>\"?\nУ гэтай праграмы няма дазволу на запіс, аднак яна зможа запісваць аўдыя праз гэту прыладу USB."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"адключыць"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"актываваць"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Аўтаматычнае ўключэнне заўтра"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Такія функцыі, як вызначэнне месцазнаходжання прылады, Хуткае абагульванне і Знайсці прыладу, выкарыстоўваюць Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Узровень зараду: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Гук"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнітура"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Дадаць іншыя віджэты"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Доўга націскайце, каб наладзіць віджэты"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Наладзіць віджэты"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Значок праграмы для адключанага віджэта"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Змяніць віджэт"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Выдаліць"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Дадаць віджэт"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Званок"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Вібрацыя"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Гук выключаны"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Трансляцыя"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Недаступна, бо выключаны гук выклікаў"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Дакраніцеся, каб уключыць гук."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Дакраніцеся, каб уключыць вібрацыю. Можа быць адключаны гук службаў спецыяльных магчымасцей."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Дакраніцеся, каб адключыць гук. Можа быць адключаны гук службаў спецыяльных магчымасцей."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Дакраніцеся, каб уключыць вібрацыю."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Дакраніцеся, каб адключыць гук"</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Кантроль шуму"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Націсніце, каб змяніць рэжым званка"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"выключыць гук"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"уключыць гук"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Меню кнопкі сілкавання"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Старонка <xliff:g id="ID_1">%1$d</xliff:g> з <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Экран блакіроўкі"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Вы можаце знайсці свой тэлефон з дапамогай праграмы \"Знайсці прыладу\", нават калі ён выключаны"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Ідзе завяршэнне працы…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Глядзець паэтапную дапамогу"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Глядзець паэтапную дапамогу"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Адключыце прыладу"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Стварэнне нататак"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Стварэнне нататак, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Ідзе абагульванне аўдыя"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Перадача даных"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Спыніць трансляцыю праграмы \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Пры пераключэнні на праграму \"<xliff:g id="SWITCHAPP">%1$s</xliff:g>\" ці змяненні вываду бягучая трансляцыя спыняецца"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Каб зрабіць фота з больш высокай раздзяляльнасцю, павярніце тэлефон"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Складная прылада ў раскладзеным выглядзе"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Перавернутая складная прылада"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Засталося зараду: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Падключыце пяро да зараднай прылады"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Нізкі ўзровень зараду пяра"</string>
diff --git a/packages/SystemUI/res/values-be/tiles_states_strings.xml b/packages/SystemUI/res/values-be/tiles_states_strings.xml
index e71c29b..74fc7c6 100644
--- a/packages/SystemUI/res/values-be/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-be/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Выключана"</item>
     <item msgid="578444932039713369">"Уключана"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Недаступна"</item>
+    <item msgid="9061144428113385092">"Выключана"</item>
+    <item msgid="2984256114867200368">"Уключана"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Недаступна"</item>
     <item msgid="8707481475312432575">"Выключана"</item>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index aa3da9c..f3ae441 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Включване"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Включване"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Не, благодаря"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Стандартен"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Екстремен режим"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Авт. завъртане на екрана"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Да се разреши ли на <xliff:g id="APPLICATION">%1$s</xliff:g> достъп до <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Наистина ли искате да разрешите на <xliff:g id="APPLICATION">%1$s</xliff:g> достъп до <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nНа приложението не е предоставено разрешение за записване, но е възможно да запише звук чрез това USB устройство."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"прекратяване на връзката"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активиране"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Автоматично включване отново утре"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Bluetooth се използва от различни функции, като например „Бързо споделяне“, „Намиране на устройството ми“ и местоположението на устройството"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Батерия: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Слушалки"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Добавете още приспособления"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Натиснете продължително за персонализ. на приспос."</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Персонализиране на приспособленията"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Икона на приложение за деактивирано приспособление"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Редактиране на приспособлението"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Премахване"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Добавяне на приспособление"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Позвъняване"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Вибриране"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Без звук"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Предаване"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Не е налице, защото звъненето е спряно"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Докоснете, за да включите отново звука."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Докоснете, за да зададете вибриране. Възможно е звукът на услугите за достъпност да бъде заглушен."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Докоснете, за да заглушите звука. Възможно е звукът на услугите за достъпност да бъде заглушен."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Докоснете, за да зададете вибриране."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Докоснете, за да заглушите звука."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Управление на шума"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Докоснете, за да промените режима на звънене"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"спиране"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"пускане"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Меню за включване/изключване"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Страница <xliff:g id="ID_1">%1$d</xliff:g> от <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Заключен екран"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Можете да откриете този телефон посредством „Намиране на устройството ми“ дори когато е изключен"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Изключва се…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Вижте стъпките, които да предприемете"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Вижте стъпките, които да предприемете"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Изключете устройството си"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Водене на бележки"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Водене на бележки, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Споделя аудио"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Излъчване"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Да се спре ли предаването на <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ако предавате <xliff:g id="SWITCHAPP">%1$s</xliff:g> или промените изхода, текущото ви предаване ще бъде прекратено"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"За по-висока разделителна способност обърнете телефона"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Разгъване на сгъваемо устройство"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Обръщане на сгъваемо устройство"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"затворено"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"отворено"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Оставаща батерия: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Свържете писалката към зарядно устройство"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Батерията на писалката е изтощена"</string>
diff --git a/packages/SystemUI/res/values-bg/tiles_states_strings.xml b/packages/SystemUI/res/values-bg/tiles_states_strings.xml
index 24b41d2..ddd0c3f 100644
--- a/packages/SystemUI/res/values-bg/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-bg/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Изкл."</item>
     <item msgid="578444932039713369">"Вкл."</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Не е налице"</item>
+    <item msgid="9061144428113385092">"Изкл."</item>
+    <item msgid="2984256114867200368">"Вкл."</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Не е налице"</item>
     <item msgid="8707481475312432575">"Изкл."</item>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 12e24c0..0e95c2c 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"চালু করুন"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"চালু করুন"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"না থাক"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"সাধারণ"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"এক্সট্রিম"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"অটো-রোটেট স্ক্রিন"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> কে <xliff:g id="USB_DEVICE">%2$s</xliff:g> অ্যাক্সেস করতে দেবেন?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> অ্যাক্সেস করতে <xliff:g id="APPLICATION">%1$s</xliff:g>-কে কি অনুমতি দেবেন?\nএই অ্যাপকে রেকর্ড করার অনুমতি দেওয়া হয়নি কিন্তু USB ডিভাইসের মাধ্যমে সেটি অডিও রেকর্ড করতে পারে।"</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ডিসকানেক্ট করুন"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"চালু করুন"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"আগামীকাল অটোমেটিক আবার চালু হবে"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"দ্রুত শেয়ার, Find My Device ও ডিভাইসের লোকেশন ব্লুটুথ ব্যবহার করে"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"চার্জ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"অডিও"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"হেডসেট"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"আরও উইজেট যোগ করুন"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"উইজেট কাস্টমাইজ করতে বেশিক্ষণ প্রেস করুন"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"উইজেট কাস্টমাইজ করুন"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"বন্ধ করা উইজেটের জন্য অ্যাপের আইকন"</string>
     <string name="edit_widget" msgid="9030848101135393954">"উইজেট এডিট করুন"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"সরান"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"উইজেট যোগ করুন"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"রিং"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"ভাইব্রেট"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"মিউট"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"কাস্ট করুন"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"রিং মিউট করা হয়েছে বলে উপলভ্য নেই"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s। সশব্দ করতে আলতো চাপুন।"</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s। কম্পন এ সেট করতে আলতো চাপুন। অ্যাক্সেসযোগ্যতার পরিষেবাগুলিকে মিউট করা হতে পারে।"</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s। মিউট করতে আলতো চাপুন। অ্যাক্সেসযোগ্যতার পরিষেবাগুলিকে মিউট করা হতে পারে।"</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s। ভাইব্রেট করতে ট্যাপ করুন।"</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s। মিউট করতে ট্যাপ করুন।"</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"আশপাশের আওয়াজ কন্ট্রোল করা"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"রিঙ্গার মোড পরিবর্তন করতে ট্যাপ করুন"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"মিউট করুন"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"আনমিউট করুন"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"পাওয়ার মেনু"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g>টির মধ্যে <xliff:g id="ID_1">%1$d</xliff:g> নং পৃষ্ঠা"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"লক স্ক্রিন"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Find My Device-এর মাধ্যমে, ফোনটি বন্ধ করা থাকলেও এটির লোকেশন শনাক্ত করতে পারবেন"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"বন্ধ হচ্ছে…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"ডিভাইস রক্ষণাবেক্ষণের ধাপগুলি দেখুন"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ডিভাইস রক্ষণাবেক্ষণের ধাপগুলি দেখুন"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"আপনার ডিভাইস আনপ্লাগ করা"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"নোট নেওয়া"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"নোট নেওয়া, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"অডিও শেয়ার করা হচ্ছে"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ব্রডকাস্ট করা হচ্ছে"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> সম্প্রচার বন্ধ করবেন?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"আপনি <xliff:g id="SWITCHAPP">%1$s</xliff:g> সম্প্রচার করলে বা আউটপুট পরিবর্তন করলে, আপনার বর্তমান সম্প্রচার বন্ধ হয়ে যাবে"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"আরও বেশি রেজোলিউশনের জন্য, ফোন ফ্লিপ করুন"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ফোল্ড করা যায় এমন ডিভাইস খোলা হচ্ছে"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ফোল্ড করা যায় এমন ডিভাইস উল্টানো হচ্ছে"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ব্যাটারির চার্জ বাকি আছে"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"কোনও চার্জারের সাথে আপনার স্টাইলাস কানেক্ট করুন"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"স্টাইলাস ব্যাটারিতে চার্জ কম আছে"</string>
diff --git a/packages/SystemUI/res/values-bn/tiles_states_strings.xml b/packages/SystemUI/res/values-bn/tiles_states_strings.xml
index 59061c2..ad32560 100644
--- a/packages/SystemUI/res/values-bn/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-bn/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"বন্ধ আছে"</item>
     <item msgid="578444932039713369">"চালু আছে"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"উপলভ্য নেই"</item>
+    <item msgid="9061144428113385092">"বন্ধ আছে"</item>
+    <item msgid="2984256114867200368">"চালু আছে"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"উপলভ্য নেই"</item>
     <item msgid="8707481475312432575">"বন্ধ আছে"</item>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index f79e2d9..7abe5ff 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Uključi"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Uključi"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Ne, hvala"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standardno"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ekstremno"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Automatsko rotiranje ekrana"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Dozvoliti aplikaciji <xliff:g id="APPLICATION">%1$s</xliff:g> pristup uređaju: <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Dozvoliti aplikaciji <xliff:g id="APPLICATION">%1$s</xliff:g> pristup uređaju <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nOvoj aplikaciji nije dato odobrenje za snimanje, ali može snimati zvuk putem ovog USB uređaja."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekid veze"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiviranje"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatski uključi ponovo sutra"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funkcije kao što su Quick Share, Pronađi moj uređaj i lokacija uređaja koriste Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> baterije"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvuk"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalice"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Dodajte još vidžeta"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pritisnite i zadržite da prilagodite vidžete"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prilagodite vidžete"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacije za onemogućeni vidžet"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Uredite vidžet"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Uklanjanje"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodajte vidžet"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Zvono"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibriranje"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Isključi zvuk"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Emitiraj"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Nedostupno zbog isključenog zvona"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Dodirnite da uključite zvukove."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Dodirnite za postavljanje vibracije. Zvukovi usluga pristupačnosti mogu biti isključeni."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Dodirnite da isključite zvuk. Zvukovi usluga pristupačnosti mogu biti isključeni."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Dodirnite da postavite vibraciju."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Dodirnite da isključite zvuk."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Upravljanje bukom"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Dodirnite da promijenite način rada zvuka zvona"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"isključite zvuk"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"uključite zvuk"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Meni napajanja"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Stranica <xliff:g id="ID_1">%1$d</xliff:g> od <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Zaključani ekran"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Možete pronaći telefon putem usluge Pronađi moj uređaj čak i kada je isključen"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Isključivanje…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Pogledajte korake za zaštitu"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Pogledajte korake za zaštitu"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Iskopčajte uređaj"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Pisanje bilješki"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Pisanje bilješki, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Dijeljenje zvuka"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Emitiranje"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Zaustaviti emitiranje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ako emitirate aplikaciju <xliff:g id="SWITCHAPP">%1$s</xliff:g> ili promijenite izlaz, trenutno emitiranje će se zaustaviti"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Za višu rezoluciju obrnite telefon"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Sklopivi uređaj se rasklapa"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Sklopivi uređaj se obrće"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"zatvoreno"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"otvoreno"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Preostalo baterije: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Priključite pisaljku na punjač"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Baterija pisaljke je slaba"</string>
diff --git a/packages/SystemUI/res/values-bs/tiles_states_strings.xml b/packages/SystemUI/res/values-bs/tiles_states_strings.xml
index 32051ef..75fb325 100644
--- a/packages/SystemUI/res/values-bs/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-bs/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Isključeno"</item>
     <item msgid="578444932039713369">"Uključeno"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Nedostupno"</item>
+    <item msgid="9061144428113385092">"Isključeno"</item>
+    <item msgid="2984256114867200368">"Uključeno"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Nedostupno"</item>
     <item msgid="8707481475312432575">"Isključeno"</item>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 583275f..29ef2f9 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Activa"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Activa"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"No, gràcies"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Estàndard"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extrem"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Gira la pantalla automàticament"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Vols permetre que <xliff:g id="APPLICATION">%1$s</xliff:g> accedeixi a <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Vols permetre que <xliff:g id="APPLICATION">%1$s</xliff:g> accedeixi a <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nAquesta aplicació no té permís de gravació, però pot capturar àudio a través d\'aquest dispositiu USB."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconnecta"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activa"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Torna\'l a activar automàticament demà"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funcions com ara Quick Share, Troba el meu dispositiu i la ubicació del dispositiu utilitzen el Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de bateria"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Àudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculars"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Afegeix més widgets"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantén premut per personalitzar els widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalitza els widgets"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icona de l\'aplicació per a widget desactivat"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Edita el widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Suprimeix"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Afegeix un widget"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Fes sonar"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibra"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Silencia"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Emet"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"No disponible perquè el so està silenciat"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Toca per activar el so."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Toca per activar la vibració. Pot ser que els serveis d\'accessibilitat se silenciïn."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Toca per silenciar el so. Pot ser que els serveis d\'accessibilitat se silenciïn."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Toca per activar la vibració."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Toca per silenciar."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Control de soroll"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Toca per canviar el mode de timbre"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"silenciar"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"deixar de silenciar"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menú d\'engegada"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Pàgina <xliff:g id="ID_1">%1$d</xliff:g> (<xliff:g id="ID_2">%2$d</xliff:g> en total)"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Pantalla de bloqueig"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Pots localitzar aquest telèfon amb Troba el meu dispositiu fins i tot quan estigui apagat"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"S\'està apagant…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Mostra els passos de manteniment"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Mostra els passos de manteniment"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Desconnecta el dispositiu"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Presa de notes"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Presa de notes, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"S\'està compartint l\'àudio"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"S\'està emetent"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Vols deixar d\'emetre <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Si emets <xliff:g id="SWITCHAPP">%1$s</xliff:g> o canvies la sortida, l\'emissió actual s\'aturarà"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Per a una resolució més alta, gira el telèfon"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositiu plegable desplegant-se"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositiu plegable girant"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Queda un <xliff:g id="PERCENTAGE">%s</xliff:g> de bateria"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connecta el llapis òptic a un carregador"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Bateria del llapis òptic baixa"</string>
diff --git a/packages/SystemUI/res/values-ca/tiles_states_strings.xml b/packages/SystemUI/res/values-ca/tiles_states_strings.xml
index e99926c..c926e9e 100644
--- a/packages/SystemUI/res/values-ca/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ca/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Desactivat"</item>
     <item msgid="578444932039713369">"Activat"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"No disponible"</item>
+    <item msgid="9061144428113385092">"Desactivat"</item>
+    <item msgid="2984256114867200368">"Activat"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"No disponible"</item>
     <item msgid="8707481475312432575">"Desactivat"</item>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 6cdb3d8..0bbeb9c 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Zapnout"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Zapnout"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Ne, díky"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standardní"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extrémní"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Automatické otočení obrazovky"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Povolit aplikaci <xliff:g id="APPLICATION">%1$s</xliff:g> přístup k zařízení <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Povolit aplikaci <xliff:g id="APPLICATION">%1$s</xliff:g> přístup k zařízení <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nTato aplikace nemá oprávnění k nahrávání, ale může zaznamenávat zvuk prostřednictvím tohoto zařízení USB."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"odpojit"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivovat"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Zítra znovu automaticky zapnout"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funkce jako Quick Share, Najdi moje zařízení a vyhledávání zařízení používají Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Baterie: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvuk"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Sluchátka"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Přidat další widgety"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Dlouhým stisknutím můžete přizpůsobit widgety"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Přizpůsobit widgety"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikace s deaktivovaným widgetem"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Upravit widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Odstranit"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Přidat widget"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Vyzvánění"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrace"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Ztlumení"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Odesílání"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Nedostupné, protože vyzvánění je ztlumené"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Klepnutím zapnete zvuk."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Klepnutím aktivujete režim vibrací. Služby přístupnosti mohou být ztlumeny."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Klepnutím vypnete zvuk. Služby přístupnosti mohou být ztlumeny."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Klepnutím nastavíte vibrace."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Klepnutím vypnete zvuk."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Omezení hluku"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Klepnutím změníte režim vyzvánění"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"vypnout zvuk"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"zapnout zvuk"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Nabídka vypínače"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Stránka <xliff:g id="ID_1">%1$d</xliff:g> z <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Obrazovka uzamčení"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Tento telefon můžete pomocí funkce Najdi moje zařízení najít, i když je vypnutý"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Vypínání…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Zobrazit pokyny, co dělat"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Zobrazit pokyny, co dělat"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Odpojte zařízení"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g> <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Psaní poznámek"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Psaní poznámek, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Sdílení zvuku"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Vysílání"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Zastavit vysílání v aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Pokud budete vysílat v aplikaci <xliff:g id="SWITCHAPP">%1$s</xliff:g> nebo změníte výstup, aktuální vysílání se zastaví"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Otočte telefon, abyste dosáhli vyššího rozlišení"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Rozkládání rozkládacího zařízení"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Otáčení rozkládacího zařízení"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"složené"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"rozložené"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Zbývá <xliff:g id="PERCENTAGE">%s</xliff:g> baterie"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Připojte dotykové pero k nabíječce"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Slabá baterie dotykového pera"</string>
diff --git a/packages/SystemUI/res/values-cs/tiles_states_strings.xml b/packages/SystemUI/res/values-cs/tiles_states_strings.xml
index 6359f94..5345569 100644
--- a/packages/SystemUI/res/values-cs/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-cs/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Vypnuto"</item>
     <item msgid="578444932039713369">"Zapnuto"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Není k dispozici"</item>
+    <item msgid="9061144428113385092">"Vypnuto"</item>
+    <item msgid="2984256114867200368">"Zapnuto"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Nedostupné"</item>
     <item msgid="8707481475312432575">"Vypnuto"</item>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 79b01a8..2ea1e12 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Aktivér"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Aktivér"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nej tak"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ekstrem"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Roter skærm automatisk"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Vil du give <xliff:g id="APPLICATION">%1$s</xliff:g> adgang til <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Vil du give <xliff:g id="APPLICATION">%1$s</xliff:g> adgang til <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nDenne app har ikke fået tilladelse til at optage, men optager muligvis lyd via denne USB-enhed."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"afbryd forbindelse"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivér"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Aktivér automatisk igen i morgen"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funktioner som f.eks. Quick Share, Find min enhed og enhedslokation anvender Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batteri"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Lyd"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Tilføj flere widgets"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Hold fingeren nede for at tilpasse widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Tilpas widgets"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Appikon for deaktiveret widget"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Rediger widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Fjern"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Tilføj widget"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ring"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibration"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Slå lyden fra"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Cast"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Ikke muligt, da ringetonen er slået fra"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Tryk for at slå lyden til."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tryk for at konfigurere til at vibrere. Tilgængelighedstjenester kan blive deaktiveret."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Tryk for at slå lyden fra. Lyden i tilgængelighedstjenester kan blive slået fra."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tryk for at aktivere vibration."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Tryk for at slå lyden fra."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Støjstyring"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Tryk for at ændre ringetilstand"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"slå lyden fra"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"slå lyden til"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menu for afbryderknappen"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Side <xliff:g id="ID_1">%1$d</xliff:g> af <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Låseskærm"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Du kan finde denne telefon med Find min enhed, også selvom den er slukket"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Lukker ned…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Se håndteringsvejledning"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Se håndteringsvejledning"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Træk stikket ud af din enhed"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Notetagning"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Notetagning, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Deler lyd"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Udsender"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Stop udsendelsen <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Hvis du udsender <xliff:g id="SWITCHAPP">%1$s</xliff:g> eller skifter output, stopper din aktuelle udsendelse"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Vend telefonen for at få højere opløsning"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldbar enhed foldes ud"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldbar enhed vendes om"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"foldet"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"foldet ud"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> batteri tilbage"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Slut din styluspen til en oplader"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Lavt batteriniveau på styluspen"</string>
diff --git a/packages/SystemUI/res/values-da/tiles_states_strings.xml b/packages/SystemUI/res/values-da/tiles_states_strings.xml
index 1daed4c..5a53149 100644
--- a/packages/SystemUI/res/values-da/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-da/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Fra"</item>
     <item msgid="578444932039713369">"Til"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Ikke tilgængelig"</item>
+    <item msgid="9061144428113385092">"Fra"</item>
+    <item msgid="2984256114867200368">"Til"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Ikke tilgængelig"</item>
     <item msgid="8707481475312432575">"Fra"</item>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 43f799b..edd718f 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Aktivieren"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Aktivieren"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nein danke"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extrem"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Bildschirm automatisch drehen"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> den Zugriff auf <xliff:g id="USB_DEVICE">%2$s</xliff:g> gewähren?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> den Zugriff auf <xliff:g id="USB_DEVICE">%2$s</xliff:g> gewähren?\nDiese App hat noch nicht die Berechtigung zum Aufnehmen erhalten, könnte jedoch Audio über dieses USB-Gerät aufnehmen."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"Verknüpfung aufheben"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivieren"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Morgen automatisch wieder aktivieren"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Für Funktionen wie Quick Share, „Mein Gerät finden“ und den Gerätestandort wird Bluetooth verwendet"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akkustand: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Weitere Widgets hinzufügen"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Lange drücken, um Widgets anzupassen"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Widgets anpassen"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App-Symbol für deaktiviertes Widget"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Widget bearbeiten"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Entfernen"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Widget hinzufügen"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Klingeln lassen"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrieren"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Stummschalten"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Stream"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Nicht verfügbar, da Klingelton stumm"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Zum Aufheben der Stummschaltung tippen."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tippen, um Vibrieren festzulegen. Bedienungshilfen werden unter Umständen stummgeschaltet."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Zum Stummschalten tippen. Bedienungshilfen werden unter Umständen stummgeschaltet."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Zum Aktivieren der Vibration tippen."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Zum Stummschalten tippen."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Geräuschunterdrückung"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Zum Ändern des Klingeltonmodus tippen"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"Stummschalten"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"Aufheben der Stummschaltung"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Ein-/Aus-Menü"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Seite <xliff:g id="ID_1">%1$d</xliff:g> von <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Sperrbildschirm"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Du kannst dieses Smartphone über „Mein Gerät finden“ orten, auch wenn es ausgeschaltet ist"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Wird heruntergefahren…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Schritte zur Abkühlung des Geräts ansehen"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Schritte zur Abkühlung des Geräts ansehen"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Gerät vom Stromnetz trennen"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Notizen"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Notizen, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Audio wird geteilt"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Übertragung läuft"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> nicht mehr streamen?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Wenn du <xliff:g id="SWITCHAPP">%1$s</xliff:g> streamst oder die Ausgabe änderst, wird dein aktueller Stream beendet"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Für höhere Auflösung Smartphone umdrehen"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Faltbares Gerät wird geöffnet"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Faltbares Gerät wird umgeklappt"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Akku bei <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Schließe deinen Eingabestift an ein Ladegerät an"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Stylus-Akkustand niedrig"</string>
diff --git a/packages/SystemUI/res/values-de/tiles_states_strings.xml b/packages/SystemUI/res/values-de/tiles_states_strings.xml
index 9a08747..e5f8655 100644
--- a/packages/SystemUI/res/values-de/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-de/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Aus"</item>
     <item msgid="578444932039713369">"An"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Nicht verfügbar"</item>
+    <item msgid="9061144428113385092">"Aus"</item>
+    <item msgid="2984256114867200368">"An"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Nicht verfügbar"</item>
     <item msgid="8707481475312432575">"Aus"</item>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 43ab777..54303b5 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Ενεργοποίηση"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Ενεργοποίηση"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Όχι, ευχαριστώ"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Βασική"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Μέγιστη"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Αυτόματη περιστροφή οθόνης"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Να επιτρέπεται η πρόσβαση της εφαρμογής <xliff:g id="APPLICATION">%1$s</xliff:g> στη συσκευή <xliff:g id="USB_DEVICE">%2$s</xliff:g>;"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Να επιτρέπεται στο <xliff:g id="APPLICATION">%1$s</xliff:g> να έχει πρόσβαση στη συσκευή <xliff:g id="USB_DEVICE">%2$s</xliff:g>;\nΔεν έχει εκχωρηθεί άδεια εγγραφής σε αυτή την εφαρμογή, αλλά μέσω αυτής της συσκευής USB θα μπορεί να εγγράφει ήχο."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"αποσύνδεση"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ενεργοποίηση"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Αυτόματη ενεργοποίηση ξανά αύριο"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Λειτουργίες όπως το Quick Share, η Εύρεση συσκευής και η τοποθεσία της συσκευής χρησιμοποιούν Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Μπαταρία <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ήχος"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ακουστικά"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Προσθήκη περισσότερων γραφικών στοιχείων"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Παρατεταμένο πάτημα για προσαρμογή γραφ. στοιχείων"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Προσαρμογή γραφικών στοιχείων"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Εικονίδιο εφαρμογής για απενεργοποιημένο γραφικό στοιχείο"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Επεξεργασία γραφικού στοιχείου"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Κατάργηση"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Προσθήκη γραφικού στοιχείου"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Κουδούνισμα"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Δόνηση"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Σίγαση"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Μετάδοση"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Μη διαθέσιμο λόγω σίγασης ήχου κλήσης"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Πατήστε για κατάργηση σίγασης."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Πατήστε για ενεργοποιήσετε τη δόνηση. Οι υπηρεσίες προσβασιμότητας ενδέχεται να τεθούν σε σίγαση."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Πατήστε για σίγαση. Οι υπηρεσίες προσβασιμότητας ενδέχεται να τεθούν σε σίγαση."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Πατήστε για να ενεργοποιήσετε τη δόνηση."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Πατήστε για σίγαση."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Έλεγχος θορύβου"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Πατήστε για να αλλάξετε τη λειτουργία ειδοποίησης ήχου"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"σίγαση"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"κατάργηση σίγασης"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Μενού λειτουργίας"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Σελίδα <xliff:g id="ID_1">%1$d</xliff:g> από <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Οθόνη κλειδώματος"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Μπορείτε να εντοπίσετε το συγκεκριμένο τηλέφωνο με την Εύρεση συσκευής ακόμα και όταν είναι απενεργοποιημένο"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Τερματισμός λειτουργίας…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Δείτε βήματα αντιμετώπισης."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Δείτε βήματα αντιμετώπισης."</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Αποσυνδέστε τη συσκευή"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Δημιουργία σημειώσεων"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Δημιουργία σημειώσεων, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Κοινοποίηση ήχου"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Μετάδοση"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Διακοπή μετάδοσης με την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>;"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Εάν κάνετε μετάδοση με την εφαρμογή <xliff:g id="SWITCHAPP">%1$s</xliff:g> ή αλλάξετε την έξοδο, η τρέχουσα μετάδοση θα σταματήσει"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Για υψηλότερη ανάλυση, αναστρέψτε το τηλέφωνο"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Αναδιπλούμενη συσκευή που ξεδιπλώνει"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Αναδιπλούμενη συσκευή που διπλώνει"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Απομένει το <xliff:g id="PERCENTAGE">%s</xliff:g> της μπαταρίας"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Συνδέστε τη γραφίδα σε έναν φορτιστή"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Χαμηλή στάθμη μπαταρίας γραφίδας"</string>
diff --git a/packages/SystemUI/res/values-el/tiles_states_strings.xml b/packages/SystemUI/res/values-el/tiles_states_strings.xml
index 4d94515..a697711 100644
--- a/packages/SystemUI/res/values-el/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-el/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Ανενεργό"</item>
     <item msgid="578444932039713369">"Ενεργό"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Μη διαθέσιμη"</item>
+    <item msgid="9061144428113385092">"Ανενεργή"</item>
+    <item msgid="2984256114867200368">"Ενεργή"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Μη διαθέσιμο"</item>
     <item msgid="8707481475312432575">"Ανενεργό"</item>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 9d2742a..0fcd2b0 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Turn on"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Turn on"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"No, thanks"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extreme"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Auto-rotate screen"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Allow <xliff:g id="APPLICATION">%1$s</xliff:g> to access <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Allow <xliff:g id="APPLICATION">%1$s</xliff:g> to access <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nThis app has not been granted record permission but could capture audio through this USB device."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatically turn on again tomorrow"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Features like Quick Share, Find My Device and device location use Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Add more widgets"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customise widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Customise widgets"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App icon for disabled widget"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ring"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrate"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Mute"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Cast"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Unavailable because ring is muted"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Tap to unmute."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tap to set to vibrate. Accessibility services may be muted."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Tap to mute. Accessibility services may be muted."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tap to set to vibrate."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Tap to mute."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Noise control"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Tap to change ringer mode"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"mute"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"unmute"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Power menu"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Page <xliff:g id="ID_1">%1$d</xliff:g> of <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Lock screen"</string>
+    <string name="finder_active" msgid="7907846989716941952">"You can locate this phone with Find My Device even when powered off"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Shutting down…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"See care steps"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"See care steps"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Unplug your device"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Note-taking"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Note-taking, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Sharing audio"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Broadcasting"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Stop broadcasting <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"If you broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g> or change the output, your current broadcast will stop"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"For higher resolution, flip the phone"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"folded"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"unfolded"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> battery remaining"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connect your stylus to a charger"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Stylus battery low"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/tiles_states_strings.xml b/packages/SystemUI/res/values-en-rAU/tiles_states_strings.xml
index 0cf2868..d97c4c9 100644
--- a/packages/SystemUI/res/values-en-rAU/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Off"</item>
     <item msgid="578444932039713369">"On"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Unavailable"</item>
+    <item msgid="9061144428113385092">"Off"</item>
+    <item msgid="2984256114867200368">"On"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Unavailable"</item>
     <item msgid="8707481475312432575">"Off"</item>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index f77f660..da1dcd8 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Turn on"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Turn on"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"No thanks"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extreme"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Auto-rotate screen"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Allow <xliff:g id="APPLICATION">%1$s</xliff:g> to access <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Allow <xliff:g id="APPLICATION">%1$s</xliff:g> to access <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nThis app has not been granted record permission but could capture audio through this USB device."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatically turn on again tomorrow"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Features like Quick Share, Find My Device, and device location use Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Add more widgets"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customize widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Customize widgets"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App icon for disabled widget"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string>
@@ -573,11 +579,18 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ring"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrate"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Mute"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Cast"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Unavailable because ring is muted"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Tap to unmute."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tap to set to vibrate. Accessibility services may be muted."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Tap to mute. Accessibility services may be muted."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tap to set to vibrate."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Tap to mute."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Noise Control"</string>
+    <string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"Spatial Audio"</string>
+    <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Off"</string>
+    <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"Fixed"</string>
+    <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Head Tracking"</string>
     <string name="volume_ringer_change" msgid="3574969197796055532">"Tap to change ringer mode"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"mute"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"unmute"</string>
@@ -832,6 +845,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Power menu"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Page <xliff:g id="ID_1">%1$d</xliff:g> of <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Lock screen"</string>
+    <string name="finder_active" msgid="7907846989716941952">"You can locate this phone with Find My Device even when powered off"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Shutting down…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"See care steps"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"See care steps"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Unplug your device"</string>
@@ -1186,6 +1201,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Note-taking"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Note-taking, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Sharing audio"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Broadcasting"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Stop broadcasting <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"If you broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g> or change the output, your current broadcast will stop"</string>
@@ -1216,6 +1232,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"For higher resolution, flip the phone"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"folded"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"unfolded"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> battery remaining"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connect your stylus to a charger"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Stylus battery low"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/tiles_states_strings.xml b/packages/SystemUI/res/values-en-rCA/tiles_states_strings.xml
index 0cf2868..d97c4c9 100644
--- a/packages/SystemUI/res/values-en-rCA/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Off"</item>
     <item msgid="578444932039713369">"On"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Unavailable"</item>
+    <item msgid="9061144428113385092">"Off"</item>
+    <item msgid="2984256114867200368">"On"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Unavailable"</item>
     <item msgid="8707481475312432575">"Off"</item>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 9d2742a..0fcd2b0 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Turn on"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Turn on"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"No, thanks"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extreme"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Auto-rotate screen"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Allow <xliff:g id="APPLICATION">%1$s</xliff:g> to access <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Allow <xliff:g id="APPLICATION">%1$s</xliff:g> to access <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nThis app has not been granted record permission but could capture audio through this USB device."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatically turn on again tomorrow"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Features like Quick Share, Find My Device and device location use Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Add more widgets"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customise widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Customise widgets"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App icon for disabled widget"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ring"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrate"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Mute"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Cast"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Unavailable because ring is muted"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Tap to unmute."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tap to set to vibrate. Accessibility services may be muted."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Tap to mute. Accessibility services may be muted."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tap to set to vibrate."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Tap to mute."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Noise control"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Tap to change ringer mode"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"mute"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"unmute"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Power menu"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Page <xliff:g id="ID_1">%1$d</xliff:g> of <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Lock screen"</string>
+    <string name="finder_active" msgid="7907846989716941952">"You can locate this phone with Find My Device even when powered off"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Shutting down…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"See care steps"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"See care steps"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Unplug your device"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Note-taking"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Note-taking, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Sharing audio"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Broadcasting"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Stop broadcasting <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"If you broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g> or change the output, your current broadcast will stop"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"For higher resolution, flip the phone"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"folded"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"unfolded"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> battery remaining"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connect your stylus to a charger"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Stylus battery low"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/tiles_states_strings.xml b/packages/SystemUI/res/values-en-rGB/tiles_states_strings.xml
index 0cf2868..d97c4c9 100644
--- a/packages/SystemUI/res/values-en-rGB/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Off"</item>
     <item msgid="578444932039713369">"On"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Unavailable"</item>
+    <item msgid="9061144428113385092">"Off"</item>
+    <item msgid="2984256114867200368">"On"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Unavailable"</item>
     <item msgid="8707481475312432575">"Off"</item>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 9d2742a..0fcd2b0 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Turn on"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Turn on"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"No, thanks"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extreme"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Auto-rotate screen"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Allow <xliff:g id="APPLICATION">%1$s</xliff:g> to access <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Allow <xliff:g id="APPLICATION">%1$s</xliff:g> to access <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nThis app has not been granted record permission but could capture audio through this USB device."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatically turn on again tomorrow"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Features like Quick Share, Find My Device and device location use Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Add more widgets"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customise widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Customise widgets"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App icon for disabled widget"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ring"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrate"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Mute"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Cast"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Unavailable because ring is muted"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Tap to unmute."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tap to set to vibrate. Accessibility services may be muted."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Tap to mute. Accessibility services may be muted."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tap to set to vibrate."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Tap to mute."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Noise control"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Tap to change ringer mode"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"mute"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"unmute"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Power menu"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Page <xliff:g id="ID_1">%1$d</xliff:g> of <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Lock screen"</string>
+    <string name="finder_active" msgid="7907846989716941952">"You can locate this phone with Find My Device even when powered off"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Shutting down…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"See care steps"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"See care steps"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Unplug your device"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Note-taking"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Note-taking, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Sharing audio"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Broadcasting"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Stop broadcasting <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"If you broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g> or change the output, your current broadcast will stop"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"For higher resolution, flip the phone"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"folded"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"unfolded"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> battery remaining"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connect your stylus to a charger"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Stylus battery low"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/tiles_states_strings.xml b/packages/SystemUI/res/values-en-rIN/tiles_states_strings.xml
index 0cf2868..d97c4c9 100644
--- a/packages/SystemUI/res/values-en-rIN/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Off"</item>
     <item msgid="578444932039713369">"On"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Unavailable"</item>
+    <item msgid="9061144428113385092">"Off"</item>
+    <item msgid="2984256114867200368">"On"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Unavailable"</item>
     <item msgid="8707481475312432575">"Off"</item>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 6f5c133..516c3af 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‏‏‏‏‎‎‏‎‏‎‎‎‏‎‎‎‏‏‎‏‎‎‏‎‏‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‎‎‏‏‎‎‏‏‏‏‎‏‏‏‎‎Turn on‎‏‎‎‏‎"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‎‏‏‏‎‏‎‎‎‏‎‏‎‏‏‎‎‏‏‏‎‏‏‎‏‏‏‎‎‏‎‎‎‎‎‏‎‏‎‎‎‏‎‎‏‎‎‏‎‎‏‎‎‎Turn on‎‏‎‎‏‎"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‏‎‏‎‎‏‎‎‏‏‎‎‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‏‏‏‎‏‏‎‏‎‏‏‎‏‏‏‎‏‎‏‏‎‎‏‎‎‏‎‎No thanks‎‏‎‎‏‎"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‎‎‏‎‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‏‏‏‎‏‏‎‏‏‎‎‎‎‏‏‏‎‏‎‏‏‏‎‎‏‏‎‏‏‎‎‏‏‏‎Standard‎‏‎‎‏‎"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‎‏‎‎‎‏‎‎‏‎‏‎‎‎‏‎‎‏‎‏‏‎‏‎‎‎‏‎‎‏‏‏‏‎‏‎‏‎‏‎‏‎‏‏‏‏‎‏‏‏‎Extreme‎‏‎‎‏‎"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‎‏‏‎‏‏‎‏‎‏‏‎‏‎‎‎‏‎‎‎‎‏‏‎‏‎‏‏‏‎‏‎‏‎‎‎‏‏‎‎‏‎‏‏‏‏‎‏‏‏‎‎‎Auto-rotate screen‎‏‎‎‏‎"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‎‎‏‎‎‎‎‏‏‏‎‏‏‏‎‎‏‎‎‎‎‏‏‏‎‏‏‎‏‎‎‎‏‏‎‏‎‎‏‎‎‏‏‎‎‎‏‎‎‏‏‎‎‎Allow ‎‏‎‎‏‏‎<xliff:g id="APPLICATION">%1$s</xliff:g>‎‏‎‎‏‏‏‎ to access ‎‏‎‎‏‏‎<xliff:g id="USB_DEVICE">%2$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‎‎‎‎‏‎‏‏‏‎‏‎‏‏‎‏‎‏‎‎‏‏‏‎‎‎‎‏‏‏‏‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‎‏‎‏‎‎‎‎Allow ‎‏‎‎‏‏‎<xliff:g id="APPLICATION">%1$s</xliff:g>‎‏‎‎‏‏‏‎ to access ‎‏‎‎‏‏‎<xliff:g id="USB_DEVICE">%2$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎This app has not been granted record permission but could capture audio through this USB device.‎‏‎‎‏‎"</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‏‎‎‎‏‎‏‏‏‎‏‏‎‏‏‏‏‏‎‎‏‏‏‏‏‎‎‎‏‎‏‎‎‏‎‎‏‏‏‏‎‏‎‎‏‏‎‏‎‎‏‏‏‏‎‎disconnect‎‏‎‎‏‎"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‏‏‏‏‎‏‏‎‎‎‎‎‏‎‏‏‎‏‎‏‏‏‎‏‏‎‏‎‏‏‏‎‏‎‏‏‎‎‎‏‏‎‏‎‎‏‎‏‏‎‏‏‎activate‎‏‎‎‏‎"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‏‎‎‎‎‎‏‏‏‎‎‏‎‏‏‎‏‏‏‎‎‎‏‏‎‏‏‎‏‏‏‎‏‎‏‎‏‏‏‎‎‎‎‏‎‎‏‏‎‏‏‎‎‏‎‎Automatically turn on again tomorrow‎‏‎‎‏‎"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‏‏‏‎‏‏‏‏‎‎‎‏‎‎‏‏‏‎‎‏‏‎‏‎‎‏‏‎‏‎‎‎‏‎‎‎‏‏‎‏‏‎‎‎‎‎‏‎‏‎‎‎Features like Quick Share, Find My Device, and device location use Bluetooth‎‏‎‎‏‎"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‎‏‎‎‏‏‎‎‏‎‎‏‎‏‎‎‏‏‏‏‎‏‎‏‏‏‏‎‏‏‏‏‏‎‎‎‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>‎‏‎‎‏‏‏‎ battery‎‏‎‎‏‎"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‏‎‏‎‏‎‎‎‏‎‎‏‏‎‏‎‎‎‏‎‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‎‏‏‎‏‎‏‎‏‏‎‎‏‎Audio‎‏‎‎‏‎"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‎‏‏‏‎‏‎‏‎‎‎‎‏‎‎‏‎‎‏‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎‏‏‏‎‎‏‏‎‏‏‎‎‏‎‏‎‎‎Headset‎‏‎‎‏‎"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‎‎‎‏‏‎‏‎‎‏‎‎‏‏‏‎‎‎‏‎‏‏‏‎‎‎‏‏‎‏‎‎‏‎‎‎‎‎‏‎‏‏‏‏‎‎‏‏‏‏‏‎‏‏‎Add more widgets‎‏‎‎‏‎"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‎‏‎‎‏‏‎‏‏‏‏‎‎‏‎‏‎‏‏‏‎‎‏‎‎‎‎‏‎‎‏‏‎‏‎‏‎‎‎‎‎‏‏‎Long press to customize widgets‎‏‎‎‏‎"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‏‎‏‏‎‎‎‏‏‏‏‏‎‏‎‎‎‎‏‏‎‏‏‎‎‎‎‏‎‎‎‏‏‏‎‎‏‎‏‏‎‏‏‏‏‏‏‏‎‎‎‏‎‏‎Customize widgets‎‏‎‎‏‎"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‏‎‏‏‎‎‎‏‏‏‏‎‏‏‏‎‏‎‎‏‎‏‎‎‎‏‎‏‎‏‏‎‏‏‎‎‎‏‎‎‎‏‏‏‏‏‎App icon for disabled widget‎‏‎‎‏‎"</string>
     <string name="edit_widget" msgid="9030848101135393954">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‎‎‎‎‏‎‎‎‎‎‏‎‏‎‏‏‏‏‏‎‏‎‎‏‎‎‏‎‏‎‎‎‏‎‎Edit widget‎‏‎‎‏‎"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‎‎‏‎‏‎‏‏‎‏‎‏‏‏‎‏‎‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎Remove‎‏‎‎‏‎"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‎‎‎‏‏‎‎‏‏‎‎‏‏‏‏‏‎‎‎‎‎‏‎‏‏‎‏‏‏‎‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‎‏‎Add widget‎‏‎‎‏‎"</string>
@@ -573,11 +579,18 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‎‎‏‎‏‎‏‎‎‏‏‏‎‎‏‎‏‏‏‏‏‏‏‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‏‏‎‎‏‎‎‎‎‏‏‎‏‎‏‏‏‎Ring‎‏‎‎‏‎"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‏‏‎‏‎‏‎‏‏‎‎‎‏‏‎‏‎‏‎‎‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‏‎‎‎‎‎‏‎‎‎‎‏‎Vibrate‎‏‎‎‏‎"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‏‎‏‎‎‎‏‏‏‎‎‎‎‎‏‏‏‎‎‎‎‎‎‎‎‏‎‏‏‏‎‎‏‎‎‎‎‏‎‏‎‎‎‏‎‎‏‏‎‎‏‏‎Mute‎‏‎‎‏‎"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‏‏‎‏‏‎‎‎‎‏‎‎‏‎‏‏‎‎‎‏‏‎‎‏‎‎‏‎‎‏‏‎‏‏‏‎‏‎‏‎‎‎‏‏‏‎‏‏‏‏‎‎‏‎‎‎Cast‎‏‎‎‏‎"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‎‏‏‏‏‎‏‏‏‎‎‏‏‏‏‎‎‏‏‎‏‎‏‏‎‎‏‎‏‏‎‏‎‎‎‏‎‎‏‎‎‏‏‎Unavailable because ring is muted‎‏‎‎‏‎"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‎‏‎‎‏‏‏‏‏‎‎‎‏‏‎‎‎‎‎‏‏‎‏‏‏‎‎‏‏‏‏‏‎‏‎‎‎‏‏‎‏‎‎‏‎‎‎‏‎‎‎‏‎%1$s. Tap to unmute.‎‏‎‎‏‎"</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‎‏‎‏‏‎‏‏‏‏‎‏‏‎‎‎‏‏‏‏‎‏‎‎‎‏‏‎‎‎‏‎‏‏‎‎‎‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‏‎%1$s. Tap to set to vibrate. Accessibility services may be muted.‎‏‎‎‏‎"</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‏‏‎‏‏‏‎‏‎‏‏‏‏‎‏‏‏‎‎‎‎‎‏‏‎‏‏‎‏‎‎‏‏‏‎‏‏‎‏‎‏‏‏‎‎‏‏‎‏‏‎‎‎‎%1$s. Tap to mute. Accessibility services may be muted.‎‏‎‎‏‎"</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‎‎‏‏‏‎‏‎‏‏‎‏‏‎‏‎‏‎‎‏‏‏‎‏‎‏‏‎‎‎‏‎‏‏‎‏‏‏‎‎‎‏‎‎‏‏‎‏‎‏‏‏‏‏‎%1$s. Tap to set to vibrate.‎‏‎‎‏‎"</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‎‏‏‏‎‏‏‏‎‏‎‎‏‎‎‎‏‎‎‏‎‏‎‏‏‎‏‏‏‏‏‏‏‎‎‎‏‏‎‎‎‏‏‎‎‎%1$s. Tap to mute.‎‏‎‎‏‎"</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‏‎‎‎‏‏‏‎‏‎‎‏‎‎‎‎‏‏‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‎‏‎‎‏‎‎‏‏‎‏‎‏‎‏‎‏‏‎‏‎‎Noise Control‎‏‎‎‏‎"</string>
+    <string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‎‏‎‏‎‎‏‎‎‎‏‏‎‎‎‏‏‎‎‏‏‏‎‎‏‏‎‏‏‎‎‏‎‎‎‏‎‎‏‏‏‎‏‎‏‎‎‎Spatial Audio‎‏‎‎‏‎"</string>
+    <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‏‏‎‎‏‎‏‏‎‏‏‎‏‎‎‎‏‏‎‎‏‎‏‎‎‎‎‎‏‏‎‏‏‎‏‎‎‎‎‎‎‎‏‏‎‏‏‏‏‏‏‎‏‎Off‎‏‎‎‏‎"</string>
+    <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‎‎‎‏‎‏‏‎‎‏‏‎‎‎‎‏‎‎‎‏‎‏‎‎‎‎‏‎‏‎‎‏‏‎‎‎‎‏‏‎‎‎‎‎‎‎‏‏‏‏‏‏‏‎‎Fixed‎‏‎‎‏‎"</string>
+    <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‎‏‎‎‏‏‏‏‎‎Head Tracking‎‏‎‎‏‎"</string>
     <string name="volume_ringer_change" msgid="3574969197796055532">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‎‏‏‏‎‎‏‏‎‏‎‏‏‏‏‎‎‎‏‎‏‏‎‏‎‏‏‏‎‏‏‎‎‏‏‎‎‎‏‏‎‏‏‎‎‏‏‏‏‎‏‏‎‎‎Tap to change ringer mode‎‏‎‎‏‎"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‎‎‏‎‏‏‎‎‎‎‏‎‎‎‏‎‏‏‎‎‏‏‎‏‏‎‏‏‎‏‎‏‏‎‏‏‎‎‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‏‏‎‎mute‎‏‎‎‏‎"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‎‏‎‏‏‎‏‎‏‏‏‎‏‎‎‎‏‏‎‏‏‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‏‎‏‎‎‏‏‎‏‏‎‎‏‎‎‎‎‎unmute‎‏‎‎‏‎"</string>
@@ -832,6 +845,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‎‏‏‏‎‎‎‎‎‏‎‏‎‏‏‎‏‎‎‎‏‏‎‎‏‎‏‎‎‎‏‎‎‎‎‎‎‏‎‎‏‏‏‏‏‏‎‎‏‏‎‎‎Power menu‎‏‎‎‏‎"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‏‎‏‏‏‏‎‏‎‎‎‎‏‎‏‎‏‎‎‏‏‏‎‎‏‏‎‎‏‎‏‏‎‏‎‏‎‏‎‏‏‎‎‏‎‏‏‏‎‎‎‏‎Page ‎‏‎‎‏‏‎<xliff:g id="ID_1">%1$d</xliff:g>‎‏‎‎‏‏‏‎ of ‎‏‎‎‏‏‎<xliff:g id="ID_2">%2$d</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‎‏‏‏‎‏‎‏‏‏‎‏‏‎‎‏‎‎‎‎‏‎‏‎‎‏‎‏‏‎‏‏‎‏‎‏‏‎‎‎‎‏‎‎‏‏‏‎‎‎‎‎‎Lock screen‎‏‎‎‏‎"</string>
+    <string name="finder_active" msgid="7907846989716941952">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‏‏‎‎‏‎‏‎‎‎‎‏‏‏‏‎‎‎‎‏‎‎‎‏‎‎‎‎‎‏‏‏‎‎‎‎‏‏‎‏‏‎‎‏‎‎‎‎‎‎‎‎You can locate this phone with Find My Device even when powered off‎‏‎‎‏‎"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‎‏‎‏‎‎‏‏‏‎‎‎‎‎‏‎‎‎‏‏‎‏‏‎‎‏‎‎‏‎‏‎‎‏‎‏‎‏‏‎‎‏‏‎‎‎‎‎‏‎‎‎‏‎‎Shutting down…‎‏‎‎‏‎"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‎‎‎‎‎‏‎‎‏‏‏‏‎‏‎‎‏‎‏‎‎‏‏‎‏‎‎‏‏‏‎‏‎‏‏‎‎‏‎‏‏‎‎‎‎‎‎‏‏‏‎‏‎‏‎See care steps‎‏‎‎‏‎"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‎‏‎‏‏‏‎‏‎‎‎‏‎‏‎‏‎‏‏‏‏‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‏‎‏‏‎‏‏‎‎‎‎‏‎‎See care steps‎‏‎‎‏‎"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‎‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‏‏‎‏‎‎‎‏‎‏‎‏‎‎‏‎‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‏‎‎‏‎Unplug your device‎‏‎‎‏‎"</string>
@@ -1186,6 +1201,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‏‏‏‎‎‎‏‎‎‏‏‏‎‎‏‏‎‏‏‎‏‏‎‎‏‎‏‎‏‏‎‏‏‎‏‏‏‏‎‏‏‎‎‏‎‏‎‎‎‎‏‎‎‎‎‎‎‏‎‎‏‏‎<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>‎‏‎‎‏‏‏‎, ‎‏‎‎‏‏‎<xliff:g id="TEMPERATURE">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‎‎‏‏‎‎‎‏‏‎‎‏‏‎‏‎‏‎‏‎‎‎‏‎‎‏‏‎‏‏‏‎‏‎‎‎‎‎‏‎‎‎‎‏‎‎‏‎‎‏‎‎‏‏‎‎‎Note-taking‎‏‎‎‏‎"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‎‏‎‎‎‎‎‏‎‏‎‎‎‎‏‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‎‏‏‎‎‎‏‎‎‎‎‏‏‏‎‎‎‏‎Note-taking, ‎‏‎‎‏‏‎<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‏‏‏‎‎‎‏‎‏‏‎‏‎‏‎‏‎‎‎‎‏‏‎‎‎‏‎‏‏‏‎‎‏‏‏‎‎‏‎‏‎‎‏‎‎‏‏‏‎‏‎‎‎Sharing audio‎‏‎‎‏‎"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‎‏‎‎‎‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‎‎‎‎‏‎‎‎‏‎‏‏‏‎‏‏‎‏‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‎‎Broadcasting‎‏‎‎‏‎"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‎‎‎‎‏‏‏‎‏‏‏‎‏‎‎‎‏‏‏‎‏‎‏‎‏‏‎‏‏‏‎‎‎‏‏‏‏‎‎‏‏‎Stop broadcasting ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‏‎‎‏‏‎‎‎‏‎‎‎‏‏‏‏‏‎‏‎‎‎‏‏‎‏‎‏‏‎‎‏‎‎‎‎‎‎‏‎If you broadcast ‎‏‎‎‏‏‎<xliff:g id="SWITCHAPP">%1$s</xliff:g>‎‏‎‎‏‏‏‎ or change the output, your current broadcast will stop‎‏‎‎‏‎"</string>
@@ -1216,6 +1232,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‏‎‏‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‏‎‎‎‎‏‏‎‏‎‎‏‎‎‎‎‎‏‎‎‏‏‏‎‎‎‎‎‏‎‎‏‎For higher resolution, flip the phone‎‏‎‎‏‎"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‎‏‏‎‎‎‏‎‎‎‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‏‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎Foldable device being unfolded‎‏‎‎‏‎"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‎‎‎‏‎‎‎‏‎‎‎‎‎‎‏‏‏‎‏‏‎‎‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎‏‏‏‎‎‎‎‏‏‎‎‏‎‎‎‎‎Foldable device being flipped around‎‏‎‎‏‎"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‏‏‎‏‏‏‎‏‎‎‎‎‏‎‏‏‏‏‎‎‏‎‎‎‎‎‎‎‎‏‎‎‎‏‏‏‏‎‎‎‎‏‎‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎folded‎‏‎‎‏‎"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‏‏‏‏‎‎‎‎‎‎‏‏‏‏‏‎‎‎‏‏‏‎‎‎‏‎‏‎‏‏‎‏‎‏‎‎‎‏‎‎‎‎‏‎‎‏‎‎‏‎‏‎‎unfolded‎‏‎‎‏‎"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‎‏‎‎‏‏‏‎‏‎‏‏‎‎‎‎‎‏‏‏‎‏‏‎‎‏‎‎‎‏‏‎‎‎‎‎‏‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‎%1$s / %2$s‎‏‎‎‏‎"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‎‏‏‏‏‎‏‏‏‎‏‎‎‎‏‏‎‏‎‏‏‏‎‏‎‏‎‏‏‏‎‎‏‎‎‏‎‎‎‏‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%s</xliff:g>‎‏‎‎‏‏‏‎ battery remaining‎‏‎‎‏‎"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‏‏‏‎‎‎‏‎‏‏‏‏‎‎‏‎‏‎‏‏‎‎‏‏‏‎‏‎‏‎‏‎‏‎‏‎‏‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‏‎Connect your stylus to a charger‎‏‎‎‏‎"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‎‎‎‎‏‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‎‏‎‎‎‏‏‏‎‎‏‎‏‏‎‏‎‏‏‎‏‏‏‏‎‎‎‎Stylus battery low‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/tiles_states_strings.xml b/packages/SystemUI/res/values-en-rXC/tiles_states_strings.xml
index b9c8e5f..2facf58 100644
--- a/packages/SystemUI/res/values-en-rXC/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‏‏‏‏‏‎‏‎‏‎‎‏‏‎‎‎‏‏‎‎‎‏‎‏‏‏‎‎‏‎‎‏‏‎‏‎‎‏‎‎‏‎‎‏‏‏‏‏‏‎‎‎‏‎Off‎‏‎‎‏‎"</item>
     <item msgid="578444932039713369">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‎‎‎‎‏‏‏‎‎‎‎‏‏‎‎‏‎‎‏‏‎‎‏‏‏‎‏‏‎‎‎‎‏‎‏‏‎‏‎‎‎‏‎‎‎‏‎‎‏‎‏‏‎‎‏‎On‎‏‎‎‏‎"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‏‎‎‎‏‎‏‎‏‎‏‏‎‎‏‎‏‏‎‎‎‎‎‎‏‏‏‏‏‏‏‎‎‏‏‎‏‎‏‎‏‏‏‎Unavailable‎‏‎‎‏‎"</item>
+    <item msgid="9061144428113385092">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‎‏‏‎‎‏‎‏‏‏‎‎‎‎‎‎‏‏‎‏‎‏‎‏‎‏‎‏‏‎‏‎‎‎‎‏‎‎‎Off‎‏‎‎‏‎"</item>
+    <item msgid="2984256114867200368">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‎‏‏‎‏‎‏‎‎‎‏‏‎‏‎‏‎‎‏‎‎‎‎‎‎‎‎‎‏‏‎‏‎‏‏‏‎‎‏‎‎‏‏‎‎‎‎‏‎‏‏‏‎‎‎‎‎On‎‏‎‎‏‎"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‎‏‏‎‏‏‏‎‎‏‎‎‏‎‎‏‎‎‎‏‎‏‏‎‎‎‏‎‏‎‏‎‎‎‎‏‎‎‏‎‎‏‎‏‎‏‎‎‎‎‎‎‏‏‎Unavailable‎‏‎‎‏‎"</item>
     <item msgid="8707481475312432575">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‎‏‏‏‎‎‏‏‎‎‎‎‎‏‎‎‎‏‎‎‏‎‎‎‎‎‎‎‎‏‎‎‏‎‏‎‏‎‏‏‎‏‎‏‏‎‏‏‏‏‏‏‎Off‎‏‎‎‏‎"</item>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 8c64a07..11e31e1 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Activar"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Activar"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"No, gracias"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Estándar"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extremo"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Girar la pantalla automáticamente"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"¿Deseas permitir que <xliff:g id="APPLICATION">%1$s</xliff:g> acceda a <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"¿Quieres permitir que <xliff:g id="APPLICATION">%1$s</xliff:g> acceda a <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nLa app no tiene permiso para grabar, pero podría capturar audio mediante este dispositivo USB."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activar"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Volver a activar automáticamente mañana"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Las funciones como Quick Share, Encontrar mi dispositivo y la ubicación del dispositivo usan Bluetooth."</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de batería"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculares"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Agregar más widgets"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantén presionado para personalizar los widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ícono de la app de widget inhabilitado"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Modificar widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Quitar"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Agregar widget"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Timbre"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrar"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Silenciar"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Transmisión"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"No disponible por timbre silenciado"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Presiona para dejar de silenciar."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Presiona para establecer el modo vibración. Es posible que los servicios de accesibilidad estén silenciados."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Presiona para silenciar. Es posible que los servicios de accesibilidad estén silenciados."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Presiona para establecer el modo vibración."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Presiona para silenciar."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Control de ruido"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Presiona para cambiar el modo de timbre"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"silenciar"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"dejar de silenciar"</string>
@@ -792,8 +809,8 @@
     <string name="right_keycode" msgid="2480715509844798438">"Clave de código derecho"</string>
     <string name="left_icon" msgid="5036278531966897006">"Ícono izquierdo"</string>
     <string name="right_icon" msgid="1103955040645237425">"Ícono derecho"</string>
-    <string name="drag_to_add_tiles" msgid="8933270127508303672">"Mantén presionado y arrastra para agregar tarjetas"</string>
-    <string name="drag_to_rearrange_tiles" msgid="2143204300089638620">"Mantén presionado y arrastra para reorganizar las tarjetas"</string>
+    <string name="drag_to_add_tiles" msgid="8933270127508303672">"Mantén presionada la tarjeta y arrástrala para agregarla"</string>
+    <string name="drag_to_rearrange_tiles" msgid="2143204300089638620">"Mantén presionada la tarjeta y arrástrala para reorganizarla"</string>
     <string name="drag_to_remove_tiles" msgid="4682194717573850385">"Arrastra aquí para quitar"</string>
     <string name="drag_to_remove_disabled" msgid="933046987838658850">"Necesitas al menos <xliff:g id="MIN_NUM_TILES">%1$d</xliff:g> tarjetas"</string>
     <string name="qs_edit" msgid="5583565172803472437">"Editar"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menú de encendido"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Página <xliff:g id="ID_1">%1$d</xliff:g> de <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Pantalla de bloqueo"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Puedes ubicar este teléfono con Encontrar mi dispositivo, incluso si está apagado"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Apagando…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Ver pasos de mantenimiento"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ver pasos de mantenimiento"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Desenchufa el dispositivo"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Tomar notas"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Tomar notas, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Compartiendo audio"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Transmitiendo"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"¿Quieres dejar de transmitir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Si transmites <xliff:g id="SWITCHAPP">%1$s</xliff:g> o cambias la salida, tu transmisión actual se detendrá"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para obtener una resolución más alta, gira el teléfono"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo plegable siendo desplegado"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo plegable siendo girado"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> de batería restante"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conecta tu pluma stylus a un cargador"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"La pluma stylus tiene poca batería"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/tiles_states_strings.xml b/packages/SystemUI/res/values-es-rUS/tiles_states_strings.xml
index bb3983b..6446bdf 100644
--- a/packages/SystemUI/res/values-es-rUS/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Desactivado"</item>
     <item msgid="578444932039713369">"Activado"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"No disponible"</item>
+    <item msgid="9061144428113385092">"Desactivado"</item>
+    <item msgid="2984256114867200368">"Activado"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"No disponible"</item>
     <item msgid="8707481475312432575">"Desactivado"</item>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 692bb26..ec03f8d 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Activar"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Activar"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"No, gracias"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Estándar"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extremo"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Girar pantalla automáticamente"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"¿Permitir que <xliff:g id="APPLICATION">%1$s</xliff:g> acceda a <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"¿Quieres que <xliff:g id="APPLICATION">%1$s</xliff:g> pueda acceder a <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nEsta aplicación no tiene permisos para grabar, pero podría capturar audio a través de este dispositivo USB."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activar"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Volver a activar automáticamente mañana"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funciones como Quick Share, Encontrar mi dispositivo y la ubicación del dispositivo usan Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de batería"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculares"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Añade más widgets"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantén pulsado para personalizar los widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icono de la aplicación de widget inhabilitado"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Quitar"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Añadir widget"</string>
@@ -530,7 +536,7 @@
     <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"Tu padre o madre gestionan este dispositivo y pueden ver y controlar cierta información, como las aplicaciones que utilizas, tu ubicación y tu tiempo de pantalla."</string>
     <string name="legacy_vpn_name" msgid="4174223520162559145">"VPN"</string>
     <string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"Desbloqueado por TrustAgent"</string>
-    <string name="kg_prompt_after_adaptive_auth_lock" msgid="1265107698772588299">"Protección antirrobo\nDispositivo bloqueado; demasiados intentos"</string>
+    <string name="kg_prompt_after_adaptive_auth_lock" msgid="1265107698772588299">"Protección antirrobo\nDispositivo bloqueado por nº de intentos"</string>
     <string name="zen_mode_and_condition" msgid="5043165189511223718">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
     <string name="accessibility_volume_settings" msgid="1458961116951564784">"Ajustes de sonido"</string>
     <string name="volume_odi_captions_tip" msgid="8825655463280990941">"Subtitular automáticamente"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Hacer sonar"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrar"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Silenciar"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Enviar"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"No disponible (el tono está silenciado)"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Toca para activar el sonido."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Toca para poner el dispositivo en vibración. Los servicios de accesibilidad pueden silenciarse."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Toca para silenciar. Los servicios de accesibilidad pueden silenciarse."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Toca para activar la vibración."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Toca para silenciar."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Control de ruido"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Toca para cambiar el modo de timbre"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"silenciar"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"dejar de silenciar"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menú de encendido"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Página <xliff:g id="ID_1">%1$d</xliff:g> de <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Pantalla de bloqueo"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Puedes localizar este teléfono con Encontrar mi dispositivo, aunque esté apagado"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Apagando…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Ver pasos de mantenimiento"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ver pasos de mantenimiento"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Desenchufa tu dispositivo"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Toma de notas"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Toma de notas, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Compartiendo audio"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Emitiendo"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"¿Dejar de emitir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Si emites <xliff:g id="SWITCHAPP">%1$s</xliff:g> o cambias la salida, tu emisión actual se detendrá"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para una mayor resolución, gira el teléfono"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo plegable desplegándose"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo plegable mostrado desde varios ángulos"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"plegado"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"desplegado"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Batería restante: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conecta tu lápiz óptico a un cargador"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Batería del lápiz óptico baja"</string>
diff --git a/packages/SystemUI/res/values-es/tiles_states_strings.xml b/packages/SystemUI/res/values-es/tiles_states_strings.xml
index 66c7ee5..4cc0c67 100644
--- a/packages/SystemUI/res/values-es/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-es/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Desactivado"</item>
     <item msgid="578444932039713369">"Activado"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"No disponible"</item>
+    <item msgid="9061144428113385092">"Desactivado"</item>
+    <item msgid="2984256114867200368">"Activado"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"No disponible"</item>
     <item msgid="8707481475312432575">"Desactivado"</item>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 070f200..dfbb1ee 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Lülita sisse"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Lülita sisse"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Tänan, ei"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Tavaline"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ekstreemne"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Kuva automaatne pööramine"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Kas lubada rakendusele <xliff:g id="APPLICATION">%1$s</xliff:g> juurdepääs seadmele <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Kas lubada rakendusel <xliff:g id="APPLICATION">%1$s</xliff:g> seadmele <xliff:g id="USB_DEVICE">%2$s</xliff:g> juurde pääseda?\nSellele rakendusele pole antud salvestamise luba, kuid see saab heli jäädvustada selle USB-seadme kaudu."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"katkesta ühendus"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiveeri"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Lülita automaatselt homme uuesti sisse"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funktsioonid, nagu Kiirjagamine, Leia mu seade ja seadme asukoht, kasutavad Bluetoothi"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> akut"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Heli"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Peakomplekt"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Lisage rohkem vidinaid"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Vajutage pikalt vidinate kohandamiseks"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Kohanda vidinaid"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Keelatud vidina rakenduseikoon"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Muuda vidinat"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Eemalda"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Lisa vidin"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Helisemine"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibreerimine"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Vaigistatud"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Ülekandmine"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Pole saadaval, kuna helin on vaigistatud"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Puudutage vaigistuse tühistamiseks."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Puudutage värinarežiimi määramiseks. Juurdepääsetavuse teenused võidakse vaigistada."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Puudutage vaigistamiseks. Juurdepääsetavuse teenused võidakse vaigistada."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Puudutage vibreerimise määramiseks."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Puudutage vaigistamiseks."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Mürasummutus"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Puudutage telefonihelina režiimi muutmiseks"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"vaigistamine"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"vaigistuse tühistamine"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Toitemenüü"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Leht <xliff:g id="ID_1">%1$d</xliff:g>/<xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Lukustuskuva"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Saate selle telefoni funktsiooniga Leia mu seade leida ka siis, kui see on välja lülitatud"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Väljalülitamine …"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Vaadake hooldusjuhiseid"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Vaadake hooldusjuhiseid"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Eemaldage seade"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Märkmete tegemine"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Märkmete tegemine <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Jagab heli"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Edastamine"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Kas peatada rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> ülekandmine?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Kui kannate rakendust <xliff:g id="SWITCHAPP">%1$s</xliff:g> üle või muudate väljundit, peatatakse teie praegune ülekanne"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Suurema eraldusvõime saavutamiseks pöörake telefon ümber"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Volditava seadme lahtivoltimine"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Volditava seadme ümberpööramine"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Akutase on <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Ühendage elektronpliiats laadijaga"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Elektronpliiatsi akutase on madal"</string>
diff --git a/packages/SystemUI/res/values-et/tiles_states_strings.xml b/packages/SystemUI/res/values-et/tiles_states_strings.xml
index 6a9edbb..f16d552 100644
--- a/packages/SystemUI/res/values-et/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-et/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Väljas"</item>
     <item msgid="578444932039713369">"Sees"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Pole saadaval"</item>
+    <item msgid="9061144428113385092">"Väljas"</item>
+    <item msgid="2984256114867200368">"Sees"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Pole saadaval"</item>
     <item msgid="8707481475312432575">"Väljas"</item>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 36eac0c..e41333c 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Aktibatu"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Aktibatu"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Ez, eskerrik asko"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Arrunta"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Muturrekoa"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Biratu pantaila automatikoki"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> erabiltzeko baimena eman nahi diozu <xliff:g id="APPLICATION">%1$s</xliff:g> aplikazioari?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> erabiltzeko baimena eman nahi diozu <xliff:g id="APPLICATION">%1$s</xliff:g> aplikazioari?\nAplikazioak ez du grabatzeko baimenik, baina baliteke USB bidezko gailu horren bidez audioa grabatzea."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"deskonektatu"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktibatu"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Aktibatu automatikoki berriro bihar"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Quick Share, Bilatu nire gailua, gailuaren kokapena eta beste eginbide batzuek Bluetootha darabilte"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audioa"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Entzungailua"</string>
@@ -346,7 +351,7 @@
     <string name="qs_record_issue_stop" msgid="3531747965741982657">"Gelditu"</string>
     <string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Gailuaren erabileraren zer alderdiri eragin dio?"</string>
     <string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Hautatu arazo mota"</string>
-    <string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Pantaila-grabagailua"</string>
+    <string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Pantaila-grabaketa"</string>
   <string-array name="qs_record_issue_types">
     <item msgid="2947988124014085798">"Errendimendua"</item>
     <item msgid="1627504621139124393">"Erabiltzaile-interfazea"</item>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Gehitu widget gehiago"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Widgetak pertsonalizatzeko, sakatu luze"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Pertsonalizatu widgetak"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Desgaitutako widgetaren aplikazio-ikonoa"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Editatu widgeta"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Kendu"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Gehitu widget bat"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Jo tonua"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Dardara"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Ez jo tonua"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Igorri"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Ez dago erabilgarri, tonua desaktibatuta dagoelako"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Sakatu audioa aktibatzeko."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Sakatu dardara ezartzeko. Baliteke erabilerraztasun-eginbideen audioa desaktibatzea."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Sakatu audioa desaktibatzeko. Baliteke erabilerraztasun-eginbideen audioa desaktibatzea."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Sakatu hau dardara ezartzeko."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Sakatu hau audioa desaktibatzeko."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Zarata-murrizketa"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Sakatu tonu-jotzailearen modua aldatzeko"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"desaktibatu audioa"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"aktibatu audioa"</string>
@@ -725,7 +742,7 @@
     <string name="group_system_full_screenshot" msgid="5742204844232667785">"Atera pantaila-argazki bat"</string>
     <string name="group_system_access_system_app_shortcuts" msgid="8562482996626694026">"Erakutsi lasterbideak"</string>
     <string name="group_system_go_back" msgid="2730322046244918816">"Egin atzera"</string>
-    <string name="group_system_access_home_screen" msgid="4130366993484706483">"Joan hasierako pantailara"</string>
+    <string name="group_system_access_home_screen" msgid="4130366993484706483">"Joan orri nagusira"</string>
     <string name="group_system_overview_open_apps" msgid="5659958952937994104">"Ikusi azkenaldiko aplikazioak"</string>
     <string name="group_system_cycle_forward" msgid="5478663965957647805">"Ikusi azken aplikazioak banan-banan (aurrerantz)"</string>
     <string name="group_system_cycle_back" msgid="8194102916946802902">"Ikusi azken aplikazioak banan-banan (atzerantz)"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Itzaltzeko menua"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_1">%1$d</xliff:g>/<xliff:g id="ID_2">%2$d</xliff:g> orria"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Pantaila blokeatua"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Itzalita badago ere aurki dezakezu telefonoa Bilatu nire gailua erabilita"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Itzaltzen…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Ikusi zaintzeko urratsak"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ikusi zaintzeko urratsak"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Deskonektatu gailua"</string>
@@ -1085,7 +1104,7 @@
     <string name="build_number_copy_toast" msgid="877720921605503046">"Kopiatu da konpilazio-zenbakia arbelean."</string>
     <string name="basic_status" msgid="2315371112182658176">"Elkarrizketa irekia"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Elkarrizketa-widgetak"</string>
-    <string name="select_conversation_text" msgid="3376048251434956013">"Sakatu elkarrizketa bat hasierako pantailan gehitzeko"</string>
+    <string name="select_conversation_text" msgid="3376048251434956013">"Sakatu elkarrizketa bat orri nagusian gehitzeko"</string>
     <string name="no_conversations_text" msgid="5354115541282395015">"Azken elkarrizketak agertuko dira hemen"</string>
     <string name="priority_conversations" msgid="3967482288896653039">"Lehentasunezko elkarrizketak"</string>
     <string name="recent_conversations" msgid="8531874684782574622">"Azken elkarrizketak"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Oharrak idaztea"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Oharrak idaztea, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Audioa partekatzen"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Igortzen"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioaren audioa igortzeari utzi nahi diozu?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> aplikazioaren audioa igortzen baduzu, edo audio-irteera aldatzen baduzu, une hartako igorpena eten egingo da"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Irauli telefonoa bereizmen handiago a lortzeko"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Gailu tolesgarria zabaltzen"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Gailu tolesgarria biratzen"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"tolestuta"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"tolestu gabe"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Bateriaren <xliff:g id="PERCENTAGE">%s</xliff:g> geratzen da"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Konektatu arkatza kargagailu batera"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Arkatzak bateria gutxi du"</string>
diff --git a/packages/SystemUI/res/values-eu/tiles_states_strings.xml b/packages/SystemUI/res/values-eu/tiles_states_strings.xml
index d023076..0f6570c 100644
--- a/packages/SystemUI/res/values-eu/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-eu/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Desaktibatuta"</item>
     <item msgid="578444932039713369">"Aktibatuta"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Ez dago erabilgarri"</item>
+    <item msgid="9061144428113385092">"Desaktibatuta"</item>
+    <item msgid="2984256114867200368">"Aktibatuta"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Ez dago erabilgarri"</item>
     <item msgid="8707481475312432575">"Desaktibatuta"</item>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index b3a5e36..6b5c4cc 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"روشن کردن"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"روشن کردن"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"نه متشکرم"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"استاندارد"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"نهایت"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"چرخش خودکار صفحه"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"به <xliff:g id="APPLICATION">%1$s</xliff:g> برای دسترسی به <xliff:g id="USB_DEVICE">%2$s</xliff:g> اجازه داده شود؟"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"‏به <xliff:g id="APPLICATION">%1$s</xliff:g> اجازه می‌دهید به <xliff:g id="USB_DEVICE">%2$s</xliff:g>دسترسی داشته باشد؟\nمجوز ضبط به این برنامه داده نشده است اما می‌تواند صدا را ازطریق این دستگاه USB ضبط کند."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"قطع اتصال"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"فعال کردن"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"فردا دوباره به‌طور خودکار روشن شود"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"ویژگی‌هایی مثل «هم‌رسانی سریع»، «پیدا کردن دستگاهم»، و مکان دستگاه از بلوتوث استفاده می‌کنند"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"شارژ باتری <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"صوت"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"هدست"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"افزودن ابزارک‌های بیشتر"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"برای سفارشی‌سازی ابزارک‌ها، فشار طولانی دهید"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"سفارشی‌سازی ابزارک‌ها"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"نماد برنامه برای ابزارک غیرفعال"</string>
     <string name="edit_widget" msgid="9030848101135393954">"ویرایش ابزارک"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"برداشتن"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"افزودن ابزارک"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"زنگ زدن"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"لرزش"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"صامت"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"ارسال محتوا"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"دردسترس نیست، چون زنگ بی‌صدا شده است"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"‏%1$s. برای باصدا کردن ضربه بزنید."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"‏%1$s. برای تنظیم روی لرزش ضربه بزنید. ممکن است سرویس‌های دسترس‌پذیری بی‌صدا شوند."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"‏%1$s. برای صامت کردن ضربه بزنید. ممکن است سرویس‌های دسترس‌پذیری صامت شود."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"‏%1$s. برای تنظیم روی لرزش، ضربه بزنید."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"‏%1$s. برای صامت کردن ضربه بزنید."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"کنترل صدای محیط"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"برای تغییر حالت زنگ، ضربه بزنید"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"صامت کردن"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"باصدا کردن"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"منوی روشن/خاموش"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"صفحه <xliff:g id="ID_1">%1$d</xliff:g> از <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"صفحه قفل"</string>
+    <string name="finder_active" msgid="7907846989716941952">"حتی وقتی این تلفن خاموش است، می‌توانید با «پیدا کردن دستگاهم» آن را مکان‌یابی کنید"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"درحال خاموش شدن…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"دیدن اقدامات محافظتی"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"دیدن اقدامات محافظتی"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"دستگاه را جدا کنید"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>، <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"یادداشت‌برداری"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"یادداشت‌برداری، <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"هم‌رسانی صدا"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"همه‌فرستی"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"همه‌فرستی <xliff:g id="APP_NAME">%1$s</xliff:g> متوقف شود؟"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"اگر <xliff:g id="SWITCHAPP">%1$s</xliff:g> را همه‌فرستی کنید یا خروجی را تغییر دهید، همه‌فرستی کنونی متوقف خواهد شد"</string>
@@ -1213,9 +1233,15 @@
     <string name="rear_display_folded_bottom_sheet_title" msgid="3930008746560711990">"باز کردن تلفن"</string>
     <string name="rear_display_unfolded_bottom_sheet_title" msgid="6291111173057304055">"صفحه‌ها جابه‌جا شود؟"</string>
     <string name="rear_display_folded_bottom_sheet_description" msgid="6842767125783222695">"برای وضوح بیشتر، از دوربین پشت استفاده کنید"</string>
-    <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"برای وضوح بیشتر، تلفن را برگردانید"</string>
+    <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"برای وضوح بیشتر، تلفن را بچرخانید"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"دستگاه تاشو درحال باز شدن"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"دستگاه تاشو درحال چرخش به اطراف"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> باتری باقی مانده است"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"قلم را به شارژر وصل کنید"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"باتری قلم ضعیف است"</string>
diff --git a/packages/SystemUI/res/values-fa/tiles_states_strings.xml b/packages/SystemUI/res/values-fa/tiles_states_strings.xml
index b341e9e..6d6954e 100644
--- a/packages/SystemUI/res/values-fa/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-fa/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"خاموش"</item>
     <item msgid="578444932039713369">"روشن"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"دردسترس نیست"</item>
+    <item msgid="9061144428113385092">"خاموش"</item>
+    <item msgid="2984256114867200368">"روشن"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"دردسترس نیست"</item>
     <item msgid="8707481475312432575">"خاموش"</item>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 47e7f2d..78f401f 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Laita päälle"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Laita päälle"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Ei kiitos"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Tavallinen"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extreme"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Näytön automaattinen kääntö"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Saako <xliff:g id="APPLICATION">%1$s</xliff:g> käyttöoikeuden (<xliff:g id="USB_DEVICE">%2$s</xliff:g>)?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Saako <xliff:g id="APPLICATION">%1$s</xliff:g> tämän pääsyoikeuden: <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nSovellus ei ole saanut tallennuslupaa, mutta voi tallentaa ääntä tämän USB-laitteen avulla."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"katkaise yhteys"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivoi"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Laita automaattisesti päälle taas huomenna"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Ominaisuudet (esim. Quick Share ja Paikanna laite) ja laitteen sijainti käyttävät Bluetoothia"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akun taso <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ääni"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Lisää widgetejä"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Yksilöi widgetit pitkällä painalluksella"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Muokkaa widgettejä"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Käytöstä poistetun widgetin sovelluskuvake"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Muokkaa widgetiä"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Poista"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Lisää widget"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Soittoääni"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Värinä"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Äänetön"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Striimaa"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Ei käytettävissä, soittoääni mykistetty"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Poista mykistys koskettamalla."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Siirry värinätilaan koskettamalla. Myös esteettömyyspalvelut saattavat mykistyä."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Mykistä koskettamalla. Myös esteettömyyspalvelut saattavat mykistyä."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Siirry värinätilaan napauttamalla."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Mykistä napauttamalla."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Melunvaimennus"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Vaihda soittoäänen tilaa napauttamalla"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"mykistä"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"poista mykistys"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Virtavalikko"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Sivu <xliff:g id="ID_1">%1$d</xliff:g>/<xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Lukitusnäyttö"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Voit löytää tämän puhelimen Paikanna laite ‑sovelluksella, vaikka se olisi sammutettuna"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Sammutetaan…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Katso huoltovaiheet"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Katso huoltovaiheet"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Irrota laite"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Muistiinpanojen tekeminen"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Muistiinpanot, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Audiota jaetaan"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Lähettää"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Lopetetaanko <xliff:g id="APP_NAME">%1$s</xliff:g>-sovelluksen lähettäminen?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Jos lähetät <xliff:g id="SWITCHAPP">%1$s</xliff:g>-sovellusta tai muutat ulostuloa, nykyinen lähetyksesi loppuu"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Resoluutio on parempi, kun käännät puhelimen"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Taitettava laite taitetaan"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Taitettava laite käännetään ympäri"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Akkua jäljellä <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Yhdistä näyttökynä laturiin"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Näyttökynän akku vähissä"</string>
diff --git a/packages/SystemUI/res/values-fi/tiles_states_strings.xml b/packages/SystemUI/res/values-fi/tiles_states_strings.xml
index bbd64fd..545abc9 100644
--- a/packages/SystemUI/res/values-fi/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-fi/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Poissa päältä"</item>
     <item msgid="578444932039713369">"Päällä"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Ei saatavilla"</item>
+    <item msgid="9061144428113385092">"Pois päältä"</item>
+    <item msgid="2984256114867200368">"Päällä"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Ei saatavilla"</item>
     <item msgid="8707481475312432575">"Poissa päältä"</item>
diff --git a/packages/SystemUI/res/values-fr-feminine/strings.xml b/packages/SystemUI/res/values-fr-feminine/strings.xml
new file mode 100644
index 0000000..ebdc3fb
--- /dev/null
+++ b/packages/SystemUI/res/values-fr-feminine/strings.xml
@@ -0,0 +1,25 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="zen_priority_introduction" msgid="3159291973383796646">"Vous ne serez pas dérangée par des sons ou des vibrations, hormis ceux des alarmes, des rappels, des événements et des appelants de votre choix. Vous entendrez encore les sons que vous choisirez de jouer, notamment la musique, les vidéos et les jeux."</string>
+    <string name="zen_alarms_introduction" msgid="3987266042682300470">"Vous ne serez pas dérangée par des sons ou des vibrations, hormis ceux des alarmes. Vous entendrez encore les sons que vous choisirez de jouer, notamment la musique, les vidéos et les jeux."</string>
+    <string name="guest_wipe_session_title" msgid="7147965814683990944">"Heureux de vous revoir, Invitée"</string>
+</resources>
diff --git a/packages/SystemUI/res/values-fr-masculine/strings.xml b/packages/SystemUI/res/values-fr-masculine/strings.xml
new file mode 100644
index 0000000..6b94970
--- /dev/null
+++ b/packages/SystemUI/res/values-fr-masculine/strings.xml
@@ -0,0 +1,25 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="zen_priority_introduction" msgid="3159291973383796646">"Vous ne serez pas dérangé par des sons ou des vibrations, hormis ceux des alarmes, des rappels, des événements et des appelants de votre choix. Vous entendrez encore les sons que vous choisirez de jouer, notamment la musique, les vidéos et les jeux."</string>
+    <string name="zen_alarms_introduction" msgid="3987266042682300470">"Vous ne serez pas dérangé par des sons ou des vibrations, hormis ceux des alarmes. Vous entendrez encore les sons que vous choisirez de jouer, notamment la musique, les vidéos et les jeux."</string>
+    <string name="guest_wipe_session_title" msgid="7147965814683990944">"Heureux de vous revoir, Invité"</string>
+</resources>
diff --git a/packages/SystemUI/res/values-fr-neuter/strings.xml b/packages/SystemUI/res/values-fr-neuter/strings.xml
new file mode 100644
index 0000000..e9d0191
--- /dev/null
+++ b/packages/SystemUI/res/values-fr-neuter/strings.xml
@@ -0,0 +1,25 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="zen_priority_introduction" msgid="3159291973383796646">"Vous ne serez pas dérangé·e par des sons ou des vibrations, hormis ceux des alarmes, des rappels, des événements et des appelants de votre choix. Vous entendrez encore les sons que vous choisirez de jouer, notamment la musique, les vidéos et les jeux."</string>
+    <string name="zen_alarms_introduction" msgid="3987266042682300470">"Vous ne serez pas dérangé·e par des sons ou des vibrations, hormis ceux des alarmes. Vous entendrez encore les sons que vous choisirez de jouer, notamment la musique, les vidéos et les jeux."</string>
+    <string name="guest_wipe_session_title" msgid="7147965814683990944">"Heureux de vous revoir, Invité·e"</string>
+</resources>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 1bb8147..4c4913e 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Activer"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Activer"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Non merci"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extrême"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Rotation auto de l\'écran"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Autoriser <xliff:g id="APPLICATION">%1$s</xliff:g> à accéder à <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Autorisé <xliff:g id="APPLICATION">%1$s</xliff:g> à accéder à <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nCette application n\'a pas été autorisée à effectuer des enregistrements, mais elle pourrait enregistrer du contenu audio par l\'intermédiaire de cet appareil USB."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"Déconnecter"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"Activer"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Activer le Bluetooth automatiquement demain"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Les fonctionnalités comme le Partage rapide, Localiser mon appareil et la position de l\'appareil utilisent le Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Pile : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Écouteurs"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Ajouter plus de widgets"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Maintenez le doigt pour personnaliser les widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personnaliser les widgets"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icône d\'application pour un widget désactivé"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Modifier le widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Retirer"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ajouter un widget"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Sonnerie"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibration"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Sonnerie désactivée"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Diffuser"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Inaccessible : sonnerie en sourdine"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Touchez pour réactiver le son."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Touchez pour activer les vibrations. Il est possible de couper le son des services d\'accessibilité."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Touchez pour couper le son. Il est possible de couper le son des services d\'accessibilité."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Touchez pour activer les vibrations."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Touchez pour couper le son."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Contrôle du bruit"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Touchez pour modifier le mode de sonnerie"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"désactiver le son"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"réactiver le son"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menu de l\'interrupteur"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Page <xliff:g id="ID_1">%1$d</xliff:g> sur <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Écran de verrouillage"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Vous pouvez localiser ce téléphone à l\'aide de Localiser mon appareil, même lorsqu\'il est éteint"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Arrêt en cours…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Afficher les étapes d\'entretien"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Afficher les étapes d\'entretien"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Débranchez votre appareil"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Prise de note"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Prise de note, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Audio partagé"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Diffusion en cours…"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Arrêter la diffusion de <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Si vous diffusez <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou changez la sortie, votre diffusion actuelle s\'arrêtera"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Pour une meilleure résolution, retournez le téléphone"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Appareil pliable en cours de dépliage"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Appareil pliable en train d\'être retourné"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"plié"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"déplié"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Charge restante de la pile : <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connectez votre stylet à un chargeur"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Pile du stylet faible"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml b/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml
index b969841..d89484d 100644
--- a/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Désactivé"</item>
     <item msgid="578444932039713369">"Activé"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Non accessible"</item>
+    <item msgid="9061144428113385092">"Désactivé"</item>
+    <item msgid="2984256114867200368">"Activé"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Non disponible"</item>
     <item msgid="8707481475312432575">"Désactivé"</item>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 84160af..5bd34d7 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Activer"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Activer"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Non, merci"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ultra"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Rotation automatique de l\'écran"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Autoriser <xliff:g id="APPLICATION">%1$s</xliff:g> à accéder à <xliff:g id="USB_DEVICE">%2$s</xliff:g> ?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Autoriser <xliff:g id="APPLICATION">%1$s</xliff:g> à accéder à <xliff:g id="USB_DEVICE">%2$s</xliff:g> ?\nCette application n\'a pas été autorisée à effectuer des enregistrements, mais elle pourrait enregistrer du contenu audio via ce périphérique USB."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"dissocier"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activer"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Réactiver automatiquement demain"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Certaines fonctionnalités telles que Quick Share, Localiser mon appareil ou encore la position de l\'appareil utilisent le Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de batterie"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Casque"</string>
@@ -385,8 +390,8 @@
     <string name="sensor_privacy_dialog_open_settings" msgid="5635865896053011859">"Ouvrir les paramètres"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Autre appareil"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Activer/Désactiver l\'écran Récents"</string>
-    <string name="zen_priority_introduction" msgid="3159291973383796646">"Vous ne serez dérangé par aucun son ni aucune vibration, hormis ceux des alarmes, des rappels, des événements et des appels des contacts de votre choix. Le son continuera de fonctionner notamment pour la musique, les vidéos et les jeux."</string>
-    <string name="zen_alarms_introduction" msgid="3987266042682300470">"Vous ne serez dérangé par aucun son ni aucune vibration, hormis ceux des alarmes. Le son continuera de fonctionner notamment pour la musique, les vidéos et les jeux."</string>
+    <string name="zen_priority_introduction" msgid="3159291973383796646">"Vous ne serez pas dérangé par des sons ou des vibrations, hormis ceux des alarmes, des rappels, des événements et des appelants de votre choix. Vous entendrez encore les sons que vous choisirez de jouer, notamment la musique, les vidéos et les jeux."</string>
+    <string name="zen_alarms_introduction" msgid="3987266042682300470">"Vous ne serez pas dérangé par des sons ou des vibrations, hormis ceux des alarmes. Vous entendrez encore les sons que vous choisirez de jouer, notamment la musique, les vidéos et les jeux."</string>
     <string name="zen_priority_customize_button" msgid="4119213187257195047">"Personnaliser"</string>
     <string name="zen_silence_introduction_voice" msgid="853573681302712348">"Cette option permet de bloquer TOUS les sons et les vibrations, y compris pour les alarmes, la musique, les vidéos et les jeux. Vous pourrez encore passer des appels téléphoniques."</string>
     <string name="zen_silence_introduction" msgid="6117517737057344014">"Cette option permet de bloquer TOUS les sons et les vibrations, y compris pour les alarmes, la musique, les vidéos et les jeux."</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Ajouter des widgets"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Appuyez de manière prolongée pour personnaliser les widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personnaliser les widgets"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icône d\'appli du widget désactivé"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Modifier le widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Supprimer"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ajouter un widget"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Sonnerie"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibreur"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Couper le son"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Caster"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Indisponible, car la sonnerie est coupée"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Appuyez pour ne plus ignorer."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Appuyez pour mettre en mode vibreur. Vous pouvez ignorer les services d\'accessibilité."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Appuyez pour ignorer. Vous pouvez ignorer les services d\'accessibilité."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Appuyez pour mettre en mode vibreur."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Appuyez pour ignorer."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Contrôle du bruit"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Appuyez pour changer le mode de la sonnerie"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"couper le son"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"réactiver le son"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menu Marche/Arrêt"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Page <xliff:g id="ID_1">%1$d</xliff:g> sur <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Écran de verrouillage"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Vous pouvez localiser ce téléphone avec Localiser mon appareil même lorsqu\'il est éteint"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Arrêt…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Afficher les étapes d\'entretien"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Afficher les étapes d\'entretien"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Débrancher votre appareil"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Prise de notes"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Prise de notes, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Audio partagé"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Diffusion…"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Arrêter la diffusion de <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Si vous diffusez <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou que vous modifiez le résultat, votre annonce actuelle s\'arrêtera"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Pour une résolution plus élevée, retournez le téléphone"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Appareil pliable qui est déplié"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Appareil pliable qui est retourné"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"plié"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"déplié"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> de batterie restante"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connectez votre stylet à un chargeur"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"La batterie du stylet est faible"</string>
diff --git a/packages/SystemUI/res/values-fr/tiles_states_strings.xml b/packages/SystemUI/res/values-fr/tiles_states_strings.xml
index 34440a0..a560ff0 100644
--- a/packages/SystemUI/res/values-fr/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-fr/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Désactivé"</item>
     <item msgid="578444932039713369">"Activé"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Non disponible"</item>
+    <item msgid="9061144428113385092">"Désactivé"</item>
+    <item msgid="2984256114867200368">"Activé"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Indisponible"</item>
     <item msgid="8707481475312432575">"Désactivée"</item>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index b78199a..e694d14 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Activar"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Activar"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Non, grazas"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Estándar"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"extremo"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Xirar pantalla automaticamente"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Queres permitir que <xliff:g id="APPLICATION">%1$s</xliff:g> acceda a <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Queres permitir que a aplicación <xliff:g id="APPLICATION">%1$s</xliff:g> acceda ao dispositivo (<xliff:g id="USB_DEVICE">%2$s</xliff:g>)?\nEsta aplicación non está autorizada para realizar gravacións, pero podería capturar audio a través deste dispositivo USB."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activar"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Volver activar automaticamente mañá"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"As funcións como Quick Share, Localizar o meu dispositivo ou a de localización do dispositivo utilizan o Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de batería"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculares"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Engadir máis widgets"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pulsación longa para personalizar os widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icona da aplicación de widget desactivado"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Quitar"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Engadir widget"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Facer soar"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrar"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Silenciar"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Emitir"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Non dispoñible (o son está silenciado)"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Toca para activar o son."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Toca para establecer a vibración. Pódense silenciar os servizos de accesibilidade."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Toca para silenciar. Pódense silenciar os servizos de accesibilidade."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Toca para establecer a vibración."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Toca para silenciar."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Control de ruído"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Toca para cambiar o modo de timbre"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"silenciar"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"activar o son"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menú de acendido"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Páxina <xliff:g id="ID_1">%1$d</xliff:g> de <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Pantalla de bloqueo"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Podes atopar este teléfono (mesmo se está apagado) con Localizar o meu dispositivo"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Apagando…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Ver pasos de mantemento"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ver pasos de mantemento"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Desconectar o dispositivo"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Toma de notas"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Toma de notas (<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>)"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Compartindo audio"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Difusión"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Queres deixar de emitir contido a través de <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Se emites contido a través de <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou cambias de saída, a emisión en curso deterase"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Dálle a volta ao teléfono para gozar dunha maior resolución"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo pregable abríndose"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo pregable xirando"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"dispositivo pregado"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"dispositivo despregado"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Batería restante: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conecta o lapis óptico a un cargador"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"O lapis óptico ten pouca batería"</string>
diff --git a/packages/SystemUI/res/values-gl/tiles_states_strings.xml b/packages/SystemUI/res/values-gl/tiles_states_strings.xml
index b03f311..1cde1ab 100644
--- a/packages/SystemUI/res/values-gl/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-gl/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Non"</item>
     <item msgid="578444932039713369">"Si"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Opción non dispoñible"</item>
+    <item msgid="9061144428113385092">"Opción desactivada"</item>
+    <item msgid="2984256114867200368">"Opción activada"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Non dispoñible"</item>
     <item msgid="8707481475312432575">"Non"</item>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index d84b1f5..5099773 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ચાલુ કરો"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"ચાલુ કરો"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"ના, આભાર"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"સ્ટૅન્ડર્ડ"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"એક્સ્ટ્રીમ"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"ઑટો રોટેટ સ્ક્રીન"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="USB_DEVICE">%2$s</xliff:g>ના ઍક્સેસ માટે <xliff:g id="APPLICATION">%1$s</xliff:g>ને મંજૂરી આપીએ?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g>ને <xliff:g id="USB_DEVICE">%2$s</xliff:g> ઍક્સેસ કરવાની મંજૂરી આપીએ?\nઆ ઍપને રેકૉર્ડ કરવાની પરવાનગી આપવામાં આવી નથી પરંતુ તે આ USB ડિવાઇસ મારફત ઑડિયો કૅપ્ચર કરી શકે છે."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ડિસ્કનેક્ટ કરો"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"સક્રિય કરો"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"આવતીકાલે ફરીથી ઑટોમૅટિક રીતે ચાલુ કરો"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"ક્વિક શેર, Find My Device અને ડિવાઇસના લોકેશન જેવી સુવિધાઓ બ્લૂટૂથનો ઉપયોગ કરે છે"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> બૅટરી"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ઑડિયો"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"હૅડસેટ"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"વધુ વિજેટ ઉમેરો"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"વિજેટ કસ્ટમાઇઝ કરવા માટે થોડીવાર દબાવી રાખો"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"વિજેટ કસ્ટમાઇઝ કરો"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"બંધ કરેલા વિજેટ માટેની ઍપનું આઇકન"</string>
     <string name="edit_widget" msgid="9030848101135393954">"વિજેટમાં ફેરફાર કરો"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"કાઢી નાખો"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"વિજેટ ઉમેરો"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"રિંગ કરો"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"વાઇબ્રેટ"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"મ્યૂટ કરો"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"કાસ્ટ કરો"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"રિંગ મ્યૂટ કરી હોવાના કારણે અનુપલબ્ધ છે"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. અનમ્યૂટ કરવા માટે ટૅપ કરો."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. વાઇબ્રેટ પર સેટ કરવા માટે ટૅપ કરો. ઍક્સેસિબિલિટી સેવાઓ મ્યૂટ કરવામાં આવી શકે છે."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. મ્યૂટ કરવા માટે ટૅપ કરો. ઍક્સેસિબિલિટી સેવાઓ મ્યૂટ કરવામાં આવી શકે છે."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. કંપન પર સેટ કરવા માટે ટૅપ કરો."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. મ્યૂટ કરવા માટે ટૅપ કરો."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"અવાજનું નિયંત્રણ"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"રિંગર મોડ બદલવા માટે ટૅપ કરો"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"મ્યૂટ કરો"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"અનમ્યૂટ કરો"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"પાવર મેનૂ"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g> માંથી <xliff:g id="ID_1">%1$d</xliff:g> પૃષ્ઠ"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"લૉક સ્ક્રીન"</string>
+    <string name="finder_active" msgid="7907846989716941952">"આ ફોનનો પાવર બંધ હોય ત્યારે પણ Find My Device વડે તમે તેનું લોકેશન જાણી શકો છો"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"શટ ડાઉન કરી રહ્યાં છીએ…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"સારસંભાળના પગલાં જુઓ"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"સારસંભાળના પગલાં જુઓ"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"તમારા ડિવાઇસને અનપ્લગ કરો"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"નોંધ લેવી"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"નોંધ લેવી, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"ઑડિયો શેર કરી રહ્યાં છીએ"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"બ્રૉડકાસ્ટ કરી રહ્યાં છે"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> બ્રોડકાસ્ટ કરવાનું રોકીએ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"જો તમે <xliff:g id="SWITCHAPP">%1$s</xliff:g> બ્રોડકાસ્ટ કરો અથવા આઉટપુટ બદલો, તો તમારું હાલનું બ્રોડકાસ્ટ બંધ થઈ જશે"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"વધુ રિઝોલ્યુશન માટે, ફોનને ફ્લિપ કરો"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ફોલ્ડ કરી શકાય એવું ડિવાઇસ અનફોલ્ડ કરવામાં આવી રહ્યું છે"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ફોલ્ડ કરી શકાય એવું ડિવાઇસ ફ્લિપ કરવામાં આવી રહ્યું છે"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> બૅટરી બાકી છે"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"તમારા સ્ટાઇલસને ચાર્જર સાથે કનેક્ટ કરો"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"સ્ટાઇલસની બૅટરીમાં ચાર્જ ઓછો છે"</string>
diff --git a/packages/SystemUI/res/values-gu/tiles_states_strings.xml b/packages/SystemUI/res/values-gu/tiles_states_strings.xml
index 5d1ad6f..65b6133 100644
--- a/packages/SystemUI/res/values-gu/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-gu/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"બંધ છે"</item>
     <item msgid="578444932039713369">"ચાલુ છે"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"અનુપલબ્ધ છે"</item>
+    <item msgid="9061144428113385092">"બંધ છે"</item>
+    <item msgid="2984256114867200368">"ચાલુ છે"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"ઉપલબ્ધ નથી"</item>
     <item msgid="8707481475312432575">"બંધ છે"</item>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 8d1ac11..ea4ccc0 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"चालू करें"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"चालू करें"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"रहने दें"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"स्टैंडर्ड मोड"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"एक्सट्रीम"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"स्‍क्रीन अपने आप घुमाना"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> को <xliff:g id="USB_DEVICE">%2$s</xliff:g> के ऐक्सेस की अनुमति दें?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"आप <xliff:g id="APPLICATION">%1$s</xliff:g> को <xliff:g id="USB_DEVICE">%2$s</xliff:g> ऐक्सेस करने की अनुमति देना चाहते हैं?\nइस ऐप्लिकेशन को रिकॉर्ड करने की अनुमति नहीं दी गई है. हालांकि, ऐप्लिकेशन इस यूएसबी डिवाइस से ऑडियो कैप्चर कर सकता है."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"डिसकनेक्ट करें"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"चालू करें"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"कल फिर से अपने-आप चालू हो जाएगा"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"क्विक शेयर, Find My Device, और डिवाइस की जगह की जानकारी जैसी सुविधाएं ब्लूटूथ का इस्तेमाल करती हैं"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> बैटरी"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ऑडियो"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"हेडसेट"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ज़्यादा विजेट जोड़ें"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"विजेट पसंद के मुताबिक बनाने के लिए उसे दबाकर रखें"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"विजेट अपनी पसंद के मुताबिक बनाएं"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"बंद किए गए विजेट के लिए ऐप्लिकेशन आइकॉन"</string>
     <string name="edit_widget" msgid="9030848101135393954">"विजेट में बदलाव करें"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"हटाएं"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"विजेट जोड़ें"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"आवाज़ चालू है"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"वाइब्रेशन"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"आवाज़ बंद है"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"कास्ट करें"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"रिंग म्यूट होने से आवाज़ नहीं सुनाई दी"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. अनम्यूट करने के लिए टैप करें."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. कंपन पर सेट करने के लिए टैप करें. सुलभता सेवाएं म्यूट हो सकती हैं."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. म्यूट करने के लिए टैप करें. सुलभता सेवाएं म्यूट हो सकती हैं."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. कंपन (वाइब्रेशन) पर सेट करने के लिए छूएं."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. म्यूट करने के लिए टैप करें."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"शोर को कंट्रोल करने की सुविधा"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"रिंगर मोड बदलने के लिए टैप करें"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"म्यूट करें"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"अनम्यूट करें"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"पावर मेन्यू"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"पेज <xliff:g id="ID_2">%2$d</xliff:g> में से <xliff:g id="ID_1">%1$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"लॉक स्‍क्रीन"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Find My Device की मदद से, फ़ोन बंद होने पर भी इस फ़ोन की जगह की जानकारी का पता लगाया जा सकता है"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"बंद हो रहा है…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"डिवाइस के रखरखाव के तरीके देखें"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"डिवाइस के रखरखाव के तरीके देखें"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"डिवाइस को अनप्लग करें"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"नोट बनाएं"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"नोट लेने के लिए, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"ऑडियो शेयर किया जा रहा है"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ब्रॉडकास्ट ऐप्लिकेशन"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> पर ब्रॉडकास्ट करना रोकें?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> पर ब्रॉडकास्ट शुरू करने पर या आउटपुट बदलने पर, आपका मौजूदा ब्रॉडकास्ट बंद हो जाएगा"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"बेहतर रिज़ॉल्यूशन वाली फ़ोटो खींचने के लिए, फ़ोन को फ़्लिप करें"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"फ़ोल्ड किया जा सकने वाला डिवाइस अनफ़ोल्ड किया जा रहा है"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"फ़ोल्ड किया जा सकने वाला डिवाइस पलटा जा रहा है"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"डिवाइस फ़ोल्ड किया गया"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"डिवाइस अनफ़ोल्ड किया गया"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> बैटरी बची है"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"अपने स्टाइलस को चार्ज करें"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"स्टाइलस की बैटरी कम है"</string>
@@ -1236,9 +1259,9 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"सेटिंग में जाकर, नोट लेने की सुविधा देने वाले ऐप्लिकेशन को डिफ़ॉल्ट के तौर पर सेट करें"</string>
     <string name="install_app" msgid="5066668100199613936">"ऐप्लिकेशन इंस्टॉल करें"</string>
     <string name="dismissible_keyguard_swipe" msgid="8377597870094949432">"जारी रखने के लिए, ऊपर की ओर स्वाइप करें"</string>
-    <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"क्या आपको किसी बाहरी डिवाइस पर डिसप्ले करना है?"</string>
+    <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"क्या आपको किसी बाहरी डिवाइस पर स्क्रीन शेयर करनी है?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"आपके फ़ोन के इनर डिसप्ले की स्क्रीन शेयर की जाएगी. फ़्रंट डिसप्ले को बंद कर दिया जाएगा."</string>
-    <string name="mirror_display" msgid="2515262008898122928">"डिसप्ले करें"</string>
+    <string name="mirror_display" msgid="2515262008898122928">"स्क्रीन शेयर करें"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"खारिज करें"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"डिसप्ले कनेक्ट किया गया"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"माइक्रोफ़ोन और कैमरा"</string>
diff --git a/packages/SystemUI/res/values-hi/tiles_states_strings.xml b/packages/SystemUI/res/values-hi/tiles_states_strings.xml
index cd29fb9..b49d3b9 100644
--- a/packages/SystemUI/res/values-hi/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-hi/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"बंद है"</item>
     <item msgid="578444932039713369">"चालू है"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"उपलब्ध नहीं है"</item>
+    <item msgid="9061144428113385092">"बंद है"</item>
+    <item msgid="2984256114867200368">"चालू है"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"उपलब्ध नहीं है"</item>
     <item msgid="8707481475312432575">"बंद है"</item>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index d4e460d..a6cfeb9 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Uključi"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Uključi"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Ne, hvala"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standardno"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ekstremno"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Automatski zakreni zaslon"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Želite li dopustiti aplikaciji <xliff:g id="APPLICATION">%1$s</xliff:g> pristup uređaju <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Želite li dopustiti aplikaciji <xliff:g id="APPLICATION">%1$s</xliff:g> da pristupa uređaju <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nTa aplikacija nema dopuštenje za snimanje, no mogla bi primati zvuk putem tog USB uređaja."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekini vezu"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiviraj"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatski ponovo uključi sutra"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Značajke kao što su brzo dijeljenje, Pronađi moj uređaj i lokacija uređaja upotrebljavaju Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> baterije"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalice"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Dodavanje još widgeta"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Dugo pritisnite za prilagodbu widgeta"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prilagodi widgete"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacije za onemogućeni widget"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Uredi widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Ukloni"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodaj widget"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Zvonjenje"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibriranje"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Zvuk je isključen"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Emitiraj"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Nedostupno jer je zvono utišano"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Dodirnite da biste uključili zvuk."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Dodirnite da biste postavili na vibraciju. Usluge pristupačnosti možda neće imati zvuk."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Dodirnite da biste isključili zvuk. Usluge pristupačnosti možda neće imati zvuk."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Dodirnite da biste postavili na vibraciju."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Dodirnite da biste isključili zvuk."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Kontrola buke"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Dodirnite da biste promijenili način softvera zvona"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"isključivanje zvuka"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"uključivanje zvuka"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Izbornik tipke za uključivanje/isključivanje"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Stranica <xliff:g id="ID_1">%1$d</xliff:g> od <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Zaključani zaslon"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Telefon možete pronaći pomoću usluge Pronađi moj uređaj čak i kada je isključen"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Isključivanje…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Pročitajte upute za održavanje"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Pročitajte upute za održavanje"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Iskopčajte uređaj"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Pisanje bilježaka"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Pisanje bilježaka, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Dijeljenje zvuka"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Emitiranje"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Zaustaviti emitiranje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ako emitirate aplikaciju <xliff:g id="SWITCHAPP">%1$s</xliff:g> ili promijenite izlaz, vaše će se trenutačno emitiranje zaustaviti"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Za višu razlučivost okrenite telefon"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Rasklopljen sklopivi uređaj"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Okretanje sklopivog uređaja sa svih strana"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"zatvoreno"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"otvoreno"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Preostalo je <xliff:g id="PERCENTAGE">%s</xliff:g> baterije"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Priključite pisaljku na punjač"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Slaba baterija pisaljke"</string>
diff --git a/packages/SystemUI/res/values-hr/tiles_states_strings.xml b/packages/SystemUI/res/values-hr/tiles_states_strings.xml
index 32051ef..75fb325 100644
--- a/packages/SystemUI/res/values-hr/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-hr/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Isključeno"</item>
     <item msgid="578444932039713369">"Uključeno"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Nedostupno"</item>
+    <item msgid="9061144428113385092">"Isključeno"</item>
+    <item msgid="2984256114867200368">"Uključeno"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Nedostupno"</item>
     <item msgid="8707481475312432575">"Isključeno"</item>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 7c62c7a..7a3af4f 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Bekapcsolás"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Bekapcsolás"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Most nem"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Normál"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extrém"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Képernyő automatikus forgatása"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Engedélyezi a(z) <xliff:g id="APPLICATION">%1$s</xliff:g> számára, hogy hozzáférjen a következőhöz: <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Lehetővé teszi a(z) <xliff:g id="APPLICATION">%1$s</xliff:g> alkalmazásnak, hogy hozzáférjen a következőhöz: <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nEz az alkalmazás nem rendelkezik rögzítési engedéllyel, de ezzel az USB-eszközzel képes a hangfelvételre."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"leválasztás"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiválás"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatikus visszakapcsolás holnap"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Az olyan funkciók, mint a Quick Share, a Készülékkereső és az eszköz helyadatai Bluetootht használnak"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akkumulátor: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Hang"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"További modulok hozzáadása"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Nyomja meg hosszan a modulok személyre szabásához"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Modulok személyre szabása"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Letiltott modul alkalmazásikonja"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Modul szerkesztése"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Eltávolítás"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Modul hozzáadása"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Csörgés"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Rezgés"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Néma"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Átküldés"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Nem lehetséges, a csörgés le van némítva"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Koppintson a némítás megszüntetéséhez."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Koppintson a rezgés beállításához. Előfordulhat, hogy a kisegítő lehetőségek szolgáltatásai le vannak némítva."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Koppintson a némításhoz. Előfordulhat, hogy a kisegítő lehetőségek szolgáltatásai le vannak némítva."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Koppintson a rezgés beállításához."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Koppintson a némításhoz."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Zajszabályozás"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Koppintson a csengés módjának módosításához"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"némítás"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"némítás feloldása"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Bekapcsológombhoz tartozó menü"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_1">%1$d</xliff:g>. oldal, összesen: <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Lezárási képernyő"</string>
+    <string name="finder_active" msgid="7907846989716941952">"A Készülékkereső segítségével akár a kikapcsolt telefon helyét is meghatározhatja."</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Leállítás…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Olvassa el a kímélő használat lépéseit"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Olvassa el a kímélő használat lépéseit"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Húzza ki az eszközt"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Jegyzetelés"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Jegyzetelés, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Hang megosztása…"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Sugárzás"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Leállítja a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> közvetítését?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"A(z) <xliff:g id="SWITCHAPP">%1$s</xliff:g> közvetítése vagy a kimenet módosítása esetén a jelenlegi közvetítés leáll"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"A nagyobb felbontás érdekében fordítsa meg a telefont"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Összehajtható eszköz kihajtása"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Összehajtható eszköz körbeforgatása"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"összehajtva"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"kihajtva"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Akkumulátor töltöttségi szintje: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Tegye töltőre az érintőceruzát"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Az érintőceruza töltöttsége alacsony"</string>
diff --git a/packages/SystemUI/res/values-hu/tiles_states_strings.xml b/packages/SystemUI/res/values-hu/tiles_states_strings.xml
index 157c552..3ca3914 100644
--- a/packages/SystemUI/res/values-hu/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-hu/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Ki"</item>
     <item msgid="578444932039713369">"Be"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Nem áll rendelkezésre"</item>
+    <item msgid="9061144428113385092">"Kikapcsolva"</item>
+    <item msgid="2984256114867200368">"Bekapcsolva"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Nem áll rendelkezésre"</item>
     <item msgid="8707481475312432575">"Ki"</item>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 8427fc3..d3b50d7 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Միացնել"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Միացնել"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Ոչ, շնորհակալություն"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Ստանդարտ"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Առավելագույն"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Ինքնապտտվող էկրան"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Թույլատրե՞լ <xliff:g id="APPLICATION">%1$s</xliff:g> հավելվածին օգտագործել <xliff:g id="USB_DEVICE">%2$s</xliff:g> լրասարքը։"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Թույլատրե՞լ <xliff:g id="APPLICATION">%1$s</xliff:g> հավելվածին օգտագործել <xliff:g id="USB_DEVICE">%2$s</xliff:g>ը։\nՀավելվածը ձայնագրելու թույլտվություն չունի, սակայն կկարողանա գրանցել ձայնն այս USB սարքի միջոցով։"</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"անջատել"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ակտիվացնել"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Վաղը նորից ավտոմատ միացնել"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Գործառույթները, ինչպիսիք են Quick Share-ը, «Գտնել իմ սարքը» գործառույթը և սարքի տեղորոշումը, օգտագործում են Bluetooth-ը"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Մարտկոցի լիցքը՝ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Աուդիո"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ականջակալ"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Ավելացնել վիջեթներ"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Երկար սեղմեք՝ վիջեթները հարմարեցնելու համար"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Հարմարեցնել վիջեթները"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Հավելվածի պատկերակ անջատված վիջեթի համար"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Փոփոխել վիջեթը"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Հեռացնել"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ավելացնել վիջեթ"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Սովորական"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Թրթռոց"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Անձայն"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Հեռարձակում"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Հասանելի չէ, երբ զանգի ձայնն անջատված է"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s: Հպեք՝ ձայնը միացնելու համար:"</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s: Հպեք՝ թրթռումը միացնելու համար: Մատչելիության ծառայությունների ձայնը կարող է անջատվել:"</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s: Հպեք՝ ձայնն անջատելու համար: Մատչելիության ծառայությունների ձայնը կարող է անջատվել:"</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s։ Հպեք՝ թրթռոցը միացնելու համար։"</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s։ Հպեք՝ ձայնը անջատելու համար։"</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Աղմուկի կառավարում"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Հպեք՝ զանգակի ռեժիմը փոխելու համար"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"անջատել ձայնը"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"միացնել ձայնը"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Սնուցման կոճակի ընտրացանկ"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Էջ <xliff:g id="ID_1">%1$d</xliff:g> / <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Կողպէկրան"</string>
+    <string name="finder_active" msgid="7907846989716941952">"«Գտնել իմ սարքը» ծառայության օգնությամբ դուք կարող եք տեղորոշել այս հեռախոսը, նույնիսկ եթե այն անջատված է"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Անջատվում է…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Քայլեր գերտաքացման ահազանգի դեպքում"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Քայլեր գերտաքացման ահազանգի դեպքում"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Անջատեք սարքը"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Նշումների ստեղծում"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Նշումների ստեղծում, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Փոխանցում է ձայնը"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Հեռարձակում"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Կանգնեցնել <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի հեռարձակումը"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Եթե հեռարձակեք <xliff:g id="SWITCHAPP">%1$s</xliff:g> հավելվածը կամ փոխեք աուդիո ելքը, ձեր ընթացիկ հեռարձակումը կկանգնեցվի։"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Ավելի մեծ լուծաչափի համար շրջեք հեռախոսը"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Ծալովի սարք՝ բացված վիճակում"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Ծալովի սարք՝ շրջված վիճակում"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Մարտկոցի լիցքը՝ <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Ձեր ստիլուսը միացրեք լիցքավորիչի"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Ստիլուսի մարտկոցի լիցքի ցածր մակարդակ"</string>
diff --git a/packages/SystemUI/res/values-hy/tiles_states_strings.xml b/packages/SystemUI/res/values-hy/tiles_states_strings.xml
index 089716f..89a94e8 100644
--- a/packages/SystemUI/res/values-hy/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-hy/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Անջատված է"</item>
     <item msgid="578444932039713369">"Միացված է"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Հասանելի չէ"</item>
+    <item msgid="9061144428113385092">"Անջատված է"</item>
+    <item msgid="2984256114867200368">"Միացված է"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Հասանելի չէ"</item>
     <item msgid="8707481475312432575">"Անջատված է"</item>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index a2f05b9..3e34e5f 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Aktifkan"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Aktifkan"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Lain kali"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standar"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ekstrem"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Putar layar otomatis"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Izinkan <xliff:g id="APPLICATION">%1$s</xliff:g> mengakses <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Izinkan <xliff:g id="APPLICATION">%1$s</xliff:g> mengakses <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nAplikasi ini belum diberi izin merekam, tetapi dapat merekam audio melalui perangkat USB ini."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"putuskan koneksi"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktifkan"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Otomatis aktifkan lagi besok"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Fitur seperti Quick Share, Temukan Perangkat Saya, dan lokasi perangkat menggunakan Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Baterai <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Tambahkan widget lainnya"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Tekan lama untuk menyesuaikan widget"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Sesuaikan widget"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikon aplikasi untuk widget yang dinonaktifkan"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Hapus"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Tambahkan widget"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Dering"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Getar"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Nonaktifkan"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Transmisi"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Tidak tersedia karena volume dering dibisukan"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Ketuk untuk menyuarakan."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Ketuk untuk menyetel agar bergetar. Layanan aksesibilitas mungkin dibisukan."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Ketuk untuk membisukan. Layanan aksesibilitas mungkin dibisukan."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Ketuk untuk menyetel agar bergetar."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Ketuk untuk menonaktifkan."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Kontrol Bising"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Ketuk untuk mengubah mode pendering"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"Tanpa suara"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"aktifkan"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menu daya"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Halaman <xliff:g id="ID_1">%1$d</xliff:g> dari <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Layar kunci"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Anda dapat menemukan lokasi ponsel ini dengan Temukan Perangkat Saya meskipun ponsel dimatikan"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Sedang mematikan…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Lihat langkah-langkah perawatan"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Lihat langkah-langkah perawatan"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Cabut perangkat"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Pembuatan catatan"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Pembuatan catatan, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Berbagi audio"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Menyiarkan"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Hentikan siaran <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Jika Anda menyiarkan <xliff:g id="SWITCHAPP">%1$s</xliff:g> atau mengubah output, siaran saat ini akan dihentikan"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Untuk resolusi lebih tinggi, balik ponsel"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Perangkat foldable sedang dibentangkan"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Perangkat foldable sedang dibalik"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ditutup"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"dibuka"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Baterai tersisa <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Hubungkan stilus ke pengisi daya"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Baterai stilus lemah"</string>
diff --git a/packages/SystemUI/res/values-in/tiles_states_strings.xml b/packages/SystemUI/res/values-in/tiles_states_strings.xml
index 71460a71..e1d5338 100644
--- a/packages/SystemUI/res/values-in/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-in/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Nonaktif"</item>
     <item msgid="578444932039713369">"Aktif"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Tidak tersedia"</item>
+    <item msgid="9061144428113385092">"Nonaktif"</item>
+    <item msgid="2984256114867200368">"Aktif"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Tidak tersedia"</item>
     <item msgid="8707481475312432575">"Nonaktif"</item>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 9c9a29f..a707d36 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Kveikja"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Kveikja"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nei, takk"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Staðlað"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Mikill"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Snúa skjá sjálfkrafa"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Viltu veita <xliff:g id="APPLICATION">%1$s</xliff:g> aðgang að <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Viltu veita <xliff:g id="APPLICATION">%1$s</xliff:g> aðgang að <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nÞetta forrit hefur ekki fengið heimild fyrir upptöku en gæti tekið upp hljóð í gegnum þetta USB-tæki."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"aftengja"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"virkja"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Kveikja sjálfkrafa aftur á morgun"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Eiginleikar á borð við flýtideilingu, „Finna tækið mitt“ og staðsetningu tækis nota Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> rafhlöðuhleðsla"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Hljóð"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Höfuðtól"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Bæta við fleiri græjum"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Haltu inni til að sérsníða græjur"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Sérsníða græjur"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Forritstákn fyrir græju sem slökkt er á"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Breyta græju"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Fjarlægja"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Bæta græju við"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Hringing"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Titringur"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Hljóð af"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Senda út"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Ekki í boði þar sem hringing er þögguð"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Ýttu til að hætta að þagga."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Ýttu til að stilla á titring. Hugsanlega verður slökkt á hljóði aðgengisþjónustu."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Ýttu til að þagga. Hugsanlega verður slökkt á hljóði aðgengisþjónustu."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Ýttu til að stilla á titring."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Ýttu til að þagga."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Hávaðavörn"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Ýta til að skipta um hringjarastillingu"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"þagga"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"hætta að þagga"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Aflrofavalmynd"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Blaðsíða <xliff:g id="ID_1">%1$d</xliff:g> af <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Lásskjár"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Þú getur fundið þennan síma með „Finna tækið mitt“, jafnvel þótt slökkt sé á honum"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Slekkur…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Sjá varúðarskref"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Sjá varúðarskref"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Taktu tækið úr sambandi"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Glósugerð"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Glósugerð, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Deilir hljóði"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Útsending í gangi"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Hætta að senda út <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ef þú sendir út <xliff:g id="SWITCHAPP">%1$s</xliff:g> eða skiptir um úttak lýkur yfirstandandi útsendingu"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Snúðu símanum til að fá betri upplausn"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Samanbrjótanlegt tæki opnað"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Samanbrjótanlegu tæki snúið við"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"samanbrotið"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"opið"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> hleðsla eftir á rafhlöðu"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Tengdu pennann við hleðslutæki"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Rafhlaða pennans er að tæmast"</string>
diff --git a/packages/SystemUI/res/values-is/tiles_states_strings.xml b/packages/SystemUI/res/values-is/tiles_states_strings.xml
index 17aaf6c..1bd38ba 100644
--- a/packages/SystemUI/res/values-is/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-is/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Slökkt"</item>
     <item msgid="578444932039713369">"Kveikt"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Ekki tiltækt"</item>
+    <item msgid="9061144428113385092">"Slökkt"</item>
+    <item msgid="2984256114867200368">"Kveikt"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Ekki í boði"</item>
     <item msgid="8707481475312432575">"Slökkt"</item>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index ec58af8..dc95717 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Attiva"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Attiva"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"No, grazie"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Estremo"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Rotazione automatica schermo"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Consentire a <xliff:g id="APPLICATION">%1$s</xliff:g> di accedere a <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Vuoi consentire all\'app <xliff:g id="APPLICATION">%1$s</xliff:g> di accedere a <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nA questa app non è stata concessa l\'autorizzazione di registrazione, ma l\'app potrebbe acquisire l\'audio tramite questo dispositivo USB."</string>
@@ -99,7 +101,7 @@
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> e altre app aperte hanno rilevato questo screenshot."</string>
     <string name="app_clips_save_add_to_note" msgid="3460200751278069445">"Aggiungi alla nota"</string>
     <string name="screenrecord_title" msgid="4257171601439507792">"Registrazione dello schermo"</string>
-    <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Elaboraz. registraz. schermo"</string>
+    <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Elaborazione registrazione…"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notifica costante per una sessione di registrazione dello schermo"</string>
     <string name="screenrecord_permission_dialog_title" msgid="303380743267672953">"Iniziare a registrare?"</string>
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="4152602778470789965">"Quando registri, Android ha accesso a qualsiasi elemento visibile sul tuo schermo o in riproduzione sul tuo dispositivo. Presta quindi attenzione a password, dettagli sui pagamenti, messaggi, foto, audio e video."</string>
@@ -111,7 +113,7 @@
     <string name="screenrecord_mic_label" msgid="2111264835791332350">"Microfono"</string>
     <string name="screenrecord_device_audio_and_mic_label" msgid="1831323771978646841">"Audio del dispositivo e microfono"</string>
     <string name="screenrecord_continue" msgid="4055347133700593164">"Inizia"</string>
-    <string name="screenrecord_ongoing_screen_only" msgid="4459670242451527727">"Registrazione schermo"</string>
+    <string name="screenrecord_ongoing_screen_only" msgid="4459670242451527727">"Registrazione schermo in corso…"</string>
     <string name="screenrecord_ongoing_screen_and_audio" msgid="5351133763125180920">"Registrazione schermo e audio"</string>
     <string name="screenrecord_taps_label" msgid="1595690528298857649">"Mostra tocchi sullo schermo"</string>
     <string name="screenrecord_stop_label" msgid="72699670052087989">"Interrompi"</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnetti"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"attiva"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Riattiva automaticamente domani"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funzionalità come Quick Share, Trova il mio dispositivo e la posizione del dispositivo usano il Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Batteria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auricolare"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Aggiungi altri widget"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Premi a lungo per personalizzare i widget"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizza widget"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icona dell\'app per widget disattivati"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Modifica widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Rimuovi"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Aggiungi widget"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Attiva suoneria"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Attiva vibrazione"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Silenzia"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Trasmissione"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Non disponibile con l\'audio disattivato"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Tocca per riattivare l\'audio."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tocca per attivare la vibrazione. L\'audio dei servizi di accessibilità può essere disattivato."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Tocca per disattivare l\'audio. L\'audio dei servizi di accessibilità può essere disattivato."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tocca per attivare la vibrazione."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Tocca per disattivare l\'audio."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Controllo del rumore"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Tocca per cambiare la modalità della suoneria"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"silenzia"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"riattiva l\'audio"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menu del tasto di accensione"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Pagina <xliff:g id="ID_1">%1$d</xliff:g> di <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Schermata di blocco"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Puoi trovare questo smartphone tramite Trova il mio dispositivo anche quando è spento"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Arresto in corso…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Leggi le misure da adottare"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Leggi le misure da adottare"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Scollega il dispositivo"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Aggiunta di note"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Aggiunta di note, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Condivisione di audio"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Trasmissione in corso…"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Vuoi interrompere la trasmissione dell\'app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Se trasmetti l\'app <xliff:g id="SWITCHAPP">%1$s</xliff:g> o cambi l\'uscita, la trasmissione attuale viene interrotta"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Gira il telefono per una maggiore risoluzione"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo pieghevole che viene aperto"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo pieghevole che viene capovolto"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"Piegato"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"Non piegato"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> batteria rimanente"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connetti lo stilo a un caricabatterie"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Batteria stilo in esaurimento"</string>
diff --git a/packages/SystemUI/res/values-it/tiles_states_strings.xml b/packages/SystemUI/res/values-it/tiles_states_strings.xml
index 7aa09d4..f7abea5 100644
--- a/packages/SystemUI/res/values-it/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-it/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Off"</item>
     <item msgid="578444932039713369">"On"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Non disponibile"</item>
+    <item msgid="9061144428113385092">"Off"</item>
+    <item msgid="2984256114867200368">"On"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Non disponibile"</item>
     <item msgid="8707481475312432575">"Off"</item>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index c9a71c7..f88b104 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"הפעלה"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"הפעלה"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"לא תודה"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"רגיל"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"משמעותי"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"סיבוב אוטומטי של המסך"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"לתת לאפליקציה <xliff:g id="APPLICATION">%1$s</xliff:g> גישה אל <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"‏האם לאפשר לאפליקציה <xliff:g id="APPLICATION">%1$s</xliff:g> גישה אל <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nאפליקציה זו לא קיבלה הרשאה להקליט אך יכולה לתעד אודיו באמצעות מכשיר USB זה."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ניתוק"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"הפעלה"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"החיבור יופעל שוב אוטומטית מחר"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"‏תכונות כמו \'שיתוף מהיר\', \'איפה המכשיר שלי\' ומיקום המכשיר משתמשות בחיבור Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> סוללה"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"אודיו"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"אוזניות"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"הוספת ווידג\'טים"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"לוחצים לחיצה ארוכה כדי להתאים אישית את הווידג\'טים"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"התאמה אישית של ווידג\'טים"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"סמל האפליקציה לווידג\'ט שהושבת"</string>
     <string name="edit_widget" msgid="9030848101135393954">"עריכת הווידג\'ט"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"הסרה"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"הוספת ווידג\'ט"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"צלצול"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"רטט"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"השתקה"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"‏הפעלת Cast"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"לא זמין כי הצלצול מושתק"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"‏%1$s. יש להקיש כדי לבטל את ההשתקה."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"‏%1$s. צריך להקיש כדי להגדיר רטט. ייתכן ששירותי הנגישות מושתקים."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"‏%1$s. יש להקיש כדי להשתיק. ייתכן ששירותי הנגישות יושתקו."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"‏%1$s. יש להקיש כדי להעביר למצב רטט."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"‏%1$s. יש להקיש כדי להשתיק."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"בקרת הרעש"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"יש להקיש כדי לשנות את מצב תוכנת הצלצול"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"השתקה"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ביטול ההשתקה"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"תפריט הפעלה"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"דף <xliff:g id="ID_1">%1$d</xliff:g> מתוך <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"מסך נעילה"</string>
+    <string name="finder_active" msgid="7907846989716941952">"אפשר לאתר את הטלפון הזה עם שירות \'איפה המכשיר שלי\' גם כשהוא כבוי"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"בתהליך כיבוי…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"לצפייה בשלבי הטיפול"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"לצפייה בשלבי הטיפול"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"ניתוק המכשיר"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"כתיבת הערות"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"כתיבת הערות, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"שיתוף האודיו"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"שידור"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"האם להפסיק לשדר את התוכן מאפליקציית <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"אם משדרים את התוכן מאפליקציית <xliff:g id="SWITCHAPP">%1$s</xliff:g> או משנים את הפלט, השידור הנוכחי יפסיק לפעול"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"כדי לצלם תמונה ברזולוציה גבוהה יותר, כדאי להפוך את הטלפון"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"מכשיר מתקפל עובר למצב לא מקופל"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"מכשיר מתקפל עובר למצב מהופך"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"מצב מקופל"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"מצב לא מקופל"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"‏%1$s‏ / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"רמת הטעינה שנותרה בסוללה: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"כדאי לחבר את הסטיילוס למטען"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"הסוללה של הסטיילוס חלשה"</string>
diff --git a/packages/SystemUI/res/values-iw/tiles_states_strings.xml b/packages/SystemUI/res/values-iw/tiles_states_strings.xml
index bd2a6f7..1948685 100644
--- a/packages/SystemUI/res/values-iw/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-iw/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"כבוי"</item>
     <item msgid="578444932039713369">"פועל"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"לא זמין"</item>
+    <item msgid="9061144428113385092">"מצב מושבת"</item>
+    <item msgid="2984256114867200368">"מצב פעיל"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"לא זמין"</item>
     <item msgid="8707481475312432575">"כבוי"</item>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index dc33b0d..36fc26f 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ONにする"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"ON にする"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"いいえ"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"標準"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"スーパー"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"自動回転画面"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> に <xliff:g id="USB_DEVICE">%2$s</xliff:g> へのアクセスを許可しますか?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> に <xliff:g id="USB_DEVICE">%2$s</xliff:g>へのアクセスを許可しますか?\nこのアプリに録音権限は付与されていませんが、アクセスを許可すると、この USB デバイスから音声を収集できるようになります。"</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"接続を解除"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"有効化"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"明日自動的に ON に戻す"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"クイック共有、デバイスを探す、デバイスの位置情報などの機能は Bluetooth を使用します"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"バッテリー <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"オーディオ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ヘッドセット"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ウィジェットの追加"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"長押ししてウィジェットをカスタマイズ"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ウィジェットのカスタマイズ"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"無効なウィジェットのアプリアイコン"</string>
     <string name="edit_widget" msgid="9030848101135393954">"ウィジェットを編集"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"削除"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ウィジェットを追加"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"着信音"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"バイブレーション"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"ミュート"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"キャスト"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"着信音がミュートされているため利用できません"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s。タップしてミュートを解除します。"</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s。タップしてバイブレーションに設定します。ユーザー補助機能サービスがミュートされる場合があります。"</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s。タップしてミュートします。ユーザー補助機能サービスがミュートされる場合があります。"</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s。タップしてバイブレーションに設定します。"</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s。タップしてミュートします。"</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"ノイズ コントロール"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"タップすると、着信音のモードを変更できます"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"ミュート"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ミュートを解除"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"電源ボタン メニュー"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"ページ <xliff:g id="ID_1">%1$d</xliff:g>/<xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"ロック画面"</string>
+    <string name="finder_active" msgid="7907846989716941952">"「デバイスを探す」を使うと、電源が OFF の状態でもこのスマートフォンの現在地を確認できます"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"シャットダウン中…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"取り扱いに関する手順をご覧ください"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"取り扱いに関する手順をご覧ください"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"デバイスを電源から外します"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>、<xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"メモ"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"メモ、<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"音声を共有中"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ブロードキャスト"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> のブロードキャストを停止しますか?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> をブロードキャストしたり、出力を変更したりすると、現在のブロードキャストが停止します。"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"高解像度で撮るにはスマートフォンを裏返してください"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"折りたたみ式デバイスが広げられている"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"折りたたみ式デバイスがひっくり返されている"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"折りたたんだ状態"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"広げた状態"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"バッテリー残量 <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"タッチペンを充電器に接続してください"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"タッチペンのバッテリー残量が少なくなっています"</string>
diff --git a/packages/SystemUI/res/values-ja/tiles_states_strings.xml b/packages/SystemUI/res/values-ja/tiles_states_strings.xml
index 31158ca..dd78c5e 100644
--- a/packages/SystemUI/res/values-ja/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ja/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"OFF"</item>
     <item msgid="578444932039713369">"ON"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"使用不可"</item>
+    <item msgid="9061144428113385092">"OFF"</item>
+    <item msgid="2984256114867200368">"ON"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"使用不可"</item>
     <item msgid="8707481475312432575">"OFF"</item>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index d78bc20..e77d137 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ჩართვა"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"ჩართვა"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"არა, გმადლობთ"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"სტანდარტული"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"უკიდურესი"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"ეკრანის ავტოროტაცია"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"მიეცეს <xliff:g id="APPLICATION">%1$s</xliff:g>-ს <xliff:g id="USB_DEVICE">%2$s</xliff:g>-ზე წვდომის უფლება?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"დართავთ <xliff:g id="APPLICATION">%1$s</xliff:g>-ს <xliff:g id="USB_DEVICE">%2$s</xliff:g>-ზე წვდომის ნებას?\nამ აპს არ აქვს მინიჭებული ჩაწერის ნებართვა, მაგრამ შეუძლია ჩაიწეროს აუდიო ამ USB მოწყობილობის მეშვეობით."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"კავშირის გაწყვეტა"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"გააქტიურება"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"ხელახლა ავტომატურად ჩართვა ხვალ"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"ფუნქციები, როგორებიცაა „სწრაფი გაზიარება“, „ჩემი მოწყობილობის პოვნა“ და „მოწყობილობის მდებარეობა“ იყენებენ Bluetooth-ს"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ბატარეა"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"აუდიო"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ყურსაცვამი"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ვიჯეტების დამატება"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ხანგრძლივად დააჭირეთ ვიჯეტების მოსარგებად"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ვიჯეტების მორგება"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"აპის ხატულა გათიშული ვიჯეტისთვის"</string>
     <string name="edit_widget" msgid="9030848101135393954">"ვიჯეტის რედაქტირება"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ამოშლა"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ვიჯეტის დამატება"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"დარეკვა"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"ვიბრაცია"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"დადუმება"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"ტრანსლირება"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"ზარის დადუმების გამო ხელმისაწვდომი არაა"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. შეეხეთ დადუმების გასაუქმებლად."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. შეეხეთ ვიბრაციაზე დასაყენებლად. შეიძლება დადუმდეს მარტივი წვდომის სერვისებიც."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. შეეხეთ დასადუმებლად. შეიძლება დადუმდეს მარტივი წვდომის სერვისებიც."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. შეეხეთ ვიბრაციაზე დასაყენებლად."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. შეეხეთ დასადუმებლად."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"ხმაურის კონტროლი"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"შეეხეთ მრეკავის რეჟიმის შესაცვლელად"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"დადუმება"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"დადუმების მოხსნა"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"ჩართვის მენიუ"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"გვერდი <xliff:g id="ID_1">%1$d</xliff:g> / <xliff:g id="ID_2">%2$d</xliff:g>-დან"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"ჩაკეტილი ეკრანი"</string>
+    <string name="finder_active" msgid="7907846989716941952">"შეგიძლიათ დაადგინოთ ამ ტელეფონის მდებარეობა ფუნქციით „ჩემი მოწყობილობის პოვნა“, მაშინაც კი, როდესაც ის გამორთულია"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"მიმდინარეობს გამორთვა…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"მისაღები ზომების გაცნობა"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"მისაღები ზომების გაცნობა"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"გამოაერᲗეᲗ Თქვენი მოწყობილობა"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"ჩანიშვნა"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"ჩანიშვნა, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"აუდიოს გაზიარება"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"იწყებთ მაუწყებლობას"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"გსურთ <xliff:g id="APP_NAME">%1$s</xliff:g>-ის ტრანსლაციის შეჩერება?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g>-ის ტრანსლაციის შემთხვევაში ან აუდიოს გამოსასვლელის შეცვლისას, მიმდინარე ტრანსლაცია შეჩერდება"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"მაღალი გარჩევადობისთვის ამოაბრუნეთ ტელეფონი"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"დასაკეცი მოწყობილობა იხსნება"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"დასაკეცი მოწყობილობა ტრიალებს"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"დაკეცილი"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"გაშლილი"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"დარჩენილია ბატარეის <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"დააკავშირეთ თქვენი სტილუსი დამტენს"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"სტილუსის ბატარეა დაცლის პირასაა"</string>
diff --git a/packages/SystemUI/res/values-ka/tiles_states_strings.xml b/packages/SystemUI/res/values-ka/tiles_states_strings.xml
index 366030a..2691b69 100644
--- a/packages/SystemUI/res/values-ka/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ka/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"გამორთულია"</item>
     <item msgid="578444932039713369">"ჩართულია"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"მიუწვდომელია"</item>
+    <item msgid="9061144428113385092">"გამორთული"</item>
+    <item msgid="2984256114867200368">"ჩართული"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"მიუწვდომელია"</item>
     <item msgid="8707481475312432575">"გამორთულია"</item>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 407da0b..6daba135 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Қосу"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Қосу"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Жоқ, рақмет"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Стандартты"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Барынша"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Авто айналатын экран"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> қолданбасына <xliff:g id="USB_DEVICE">%2$s</xliff:g> құрылғысына кіруге рұқсат берілсін бе?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> қолданбасына <xliff:g id="USB_DEVICE">%2$s</xliff:g> құрылғысын пайдалануға рұқсат етілсін бе?\nҚолданбаның жазу рұқсаты жоқ, бірақ осы USB құрылғысы арқылы аудио жаза алады."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ажырату"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"іске қосу"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Ертең автоматты түрде қосылсын"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Quick Share, Find My Device сияқты функциялар мен құрылғы локациясы Bluetooth пайдаланады."</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Батарея деңгейі: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Aудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнитура"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Басқа виджеттер қосыңыз."</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Виджеттерді бейімдеу үшін ұзақ басып тұрыңыз."</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Виджеттерді реттеу"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Өшірілген виджеттің қолданба белгішесі"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Виджетті өзгерту"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Өшіру"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Виджет қосу"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Шылдырлау"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Діріл"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Дыбысын өшіру"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Трансляциялау"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Қолжетімді емес, шылдырлату өшірулі."</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Дыбысын қосу үшін түртіңіз."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Діріл режимін орнату үшін түртіңіз. Арнайы мүмкіндік қызметтерінің дыбысы өшуі мүмкін."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Дыбысын өшіру үшін түртіңіз. Арнайы мүмкіндік қызметтерінің дыбысы өшуі мүмкін."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Діріл режимін орнату үшін түртіңіз."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Дыбысын өшіру үшін түртіңіз."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Шуды реттеу"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Қоңырау режимін өзгерту үшін түртіңіз."</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"дыбысын өшіру"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"дыбысын қосу"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Қуат мәзірі"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g> ішінен <xliff:g id="ID_1">%1$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Құлыптаулы экран"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Сіз бұл телефонды, ол тіпті өшірулі тұрса да Find My Device арқылы таба аласыз."</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Өшіріліп жатыр…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Пайдалану нұсқаулығын қараңыз"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Пайдалану нұсқаулығын қараңыз"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Құрылғыны ажыратыңыз"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Ескертпе жазу"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Ескертпе жазу, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Аудио бөлісу"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Таратуда"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасын таратуды тоқтатасыз ба?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> қолданбасын таратсаңыз немесе аудио шығысын өзгертсеңіз, қазіргі тарату сеансы тоқтайды."</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Жоғары ажыратымдылық үшін телефонды айналдырыңыз."</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Бүктемелі құрылғы ашылып жатыр."</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Бүктемелі құрылғы аударылып жатыр."</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"жабық"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ашық"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Қалған батарея заряды: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Стилусты зарядтағышқа жалғаңыз."</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Стилус батареясының заряды аз"</string>
diff --git a/packages/SystemUI/res/values-kk/tiles_states_strings.xml b/packages/SystemUI/res/values-kk/tiles_states_strings.xml
index b8089e4..b37e982 100644
--- a/packages/SystemUI/res/values-kk/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-kk/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Өшірулі"</item>
     <item msgid="578444932039713369">"Қосулы"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Қолжетімді емес"</item>
+    <item msgid="9061144428113385092">"Өшірулі"</item>
+    <item msgid="2984256114867200368">"Қосулы"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Қолжетімсіз"</item>
     <item msgid="8707481475312432575">"Өшірулі"</item>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index eaca657..69adb22 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"បើក"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"បើក"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"ទេ អរគុណ"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"ស្តង់ដារ"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"ខ្លាំងបំផុត"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"បង្វិល​អេក្រង់​ស្វ័យ​ប្រវត្តិ"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"អនុញ្ញាត <xliff:g id="APPLICATION">%1$s</xliff:g> ឱ្យចូលប្រើ <xliff:g id="USB_DEVICE">%2$s</xliff:g> មែនទេ?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"អនុញ្ញាតឱ្យ <xliff:g id="APPLICATION">%1$s</xliff:g> ចូលប្រើ <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nកម្មវិធីនេះ​មិនទាន់បាន​ទទួលសិទ្ធិ​ថតសំឡេងនៅឡើយទេ ប៉ុន្តែ​អាចថត​សំឡេង​តាមរយៈ​ឧបករណ៍ USB នេះបាន។"</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ផ្ដាច់"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"បើកដំណើរការ"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"បើកដោយស្វ័យប្រវត្តិម្ដងទៀតនៅថ្ងៃស្អែក"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"មុខងារដូចជា Quick Share, រកឧបករណ៍របស់ខ្ញុំ និងប៊្លូធូសប្រើប្រាស់ទីតាំងឧបករណ៍"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"ថ្ម <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"សំឡេង"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"កាស"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"បញ្ចូលធាតុ​ក្រាហ្វិកច្រើនទៀត"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ចុច​ឱ្យ​យូរ ដើម្បីប្ដូរធាតុ​ក្រាហ្វិកតាមបំណង"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ប្ដូរ​ធាតុ​ក្រាហ្វិកតាម​បំណង"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"រូបកម្មវិធីសម្រាប់ធាតុ​ក្រាហ្វិកដែលបានបិទ"</string>
     <string name="edit_widget" msgid="9030848101135393954">"កែធាតុ​ក្រាហ្វិក"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ដកចេញ"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"បញ្ចូលធាតុ​ក្រាហ្វិក"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"រោទ៍"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"ញ័រ"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"បិទសំឡេង"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"បញ្ជូន"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"មិន​អាច​ប្រើ​បាន​ទេ​ ព្រោះ​សំឡេង​រោទ៍​ត្រូវ​បាន​បិទ"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s។ ប៉ះដើម្បីបើកសំឡេង។"</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s។ ប៉ះដើម្បីកំណត់ឲ្យញ័រ។ សេវាកម្មលទ្ធភាពប្រើប្រាស់អាចនឹងត្រូវបានបិទសំឡេង។"</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s។ ប៉ះដើម្បីបិទសំឡេង។ សេវាកម្មលទ្ធភាពប្រើប្រាស់អាចនឹងត្រូវបានបិទសំឡេង។"</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s ។ ចុច​ដើម្បី​កំណត់​ឲ្យ​ញ័រ។"</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s ។ ចុច​ដើម្បី​បិទ​សំឡេង។"</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"ការគ្រប់គ្រង​សំឡេងរំខាន"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"ចុច​ដើម្បីប្ដូរ​មុខងារ​រោទ៍"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"បិទ​សំឡេង"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"បើក​សំឡេង"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"ម៉ឺនុយ​ថាមពល"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"ទំព័រ <xliff:g id="ID_1">%1$d</xliff:g> នៃ <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"អេក្រង់​ចាក់សោ"</string>
+    <string name="finder_active" msgid="7907846989716941952">"អ្នកអាចកំណត់ទីតាំងទូរសព្ទនេះដោយប្រើ \"រកឧបករណ៍របស់ខ្ញុំ\" សូម្បីនៅពេលបិទថាមពល"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"កំពុង​បិទ…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"មើលជំហាន​ថែទាំ"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"មើលជំហាន​ថែទាំ"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"ដកឧបករណ៍របស់អ្នក"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"ការកត់ត្រា"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"ការកត់ត្រា, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"កំពុងចែករំលែកសំឡេង"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ការផ្សាយ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"បញ្ឈប់ការផ្សាយ <xliff:g id="APP_NAME">%1$s</xliff:g> ឬ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"ប្រសិនបើអ្នក​ផ្សាយ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ឬប្ដូរឧបករណ៍បញ្ចេញសំឡេង ការផ្សាយបច្ចុប្បន្នរបស់អ្នកនឹង​បញ្ឈប់"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"សម្រាប់កម្រិតគុណភាពកាន់តែខ្ពស់ សូមត្រឡប់ទូរសព្ទ"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ឧបករណ៍អាច​បត់បានកំពុងត្រូវបានលា"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ឧបករណ៍អាច​បត់បានកំពុងត្រូវបានលា"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"ថ្មនៅសល់ <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ភ្ជាប់ប៊ិករបស់អ្នកជាមួយឆ្នាំងសាក"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"ថ្មប៊ិកនៅសល់តិច"</string>
diff --git a/packages/SystemUI/res/values-km/tiles_states_strings.xml b/packages/SystemUI/res/values-km/tiles_states_strings.xml
index 8c5c8d1..0b2d5b3 100644
--- a/packages/SystemUI/res/values-km/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-km/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"បិទ"</item>
     <item msgid="578444932039713369">"បើក"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"មិនអាចប្រើបាន"</item>
+    <item msgid="9061144428113385092">"បិទ"</item>
+    <item msgid="2984256114867200368">"បើក"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"មិនមានទេ"</item>
     <item msgid="8707481475312432575">"បិទ"</item>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 64179ba..f62c2a6 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ಆನ್‌ ಮಾಡಿ"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"ಆನ್ ಮಾಡಿ"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"ಬೇಡ"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"ಪ್ರಮಾಣಿತ"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"ತೀವ್ರ"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"ಪರದೆಯನ್ನು ಸ್ವಯಂ-ತಿರುಗಿಸಿ"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> ಪ್ರವೇಶಿಸಲು <xliff:g id="APPLICATION">%1$s</xliff:g> ಅನ್ನು ಅನುಮತಿಸುವುದೇ?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> ಅನ್ನು ಪ್ರವೇಶಿಸಲು <xliff:g id="APPLICATION">%1$s</xliff:g> ಅನ್ನು ಅನುಮತಿಸುವುದೇ?\nಈ ಆ್ಯಪ್‌ಗೆ ರೆಕಾರ್ಡ್ ಅನುಮತಿಯನ್ನು ನೀಡಲಾಗಿಲ್ಲ, ಆದರೆ ಈ USB ಸಾಧನದ ಮೂಲಕ ಆಡಿಯೊವನ್ನು ಸೆರೆಹಿಡಿಯಬಹುದು."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ಡಿಸ್‌ಕನೆಕ್ಟ್ ಮಾಡಿ"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"ನಾಳೆ ಪುನಃ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಆನ್ ಮಾಡಿ"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"ಕ್ವಿಕ್ ಶೇರ್, Find My Device ನಂತಹ ಫೀಚರ್‌ಗಳು ಮತ್ತು ಸಾಧನದ ಸ್ಥಳ ಬ್ಲೂಟೂತ್ ಬಳಸುತ್ತವೆ"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ಬ್ಯಾಟರಿ"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ಆಡಿಯೋ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ಹೆಡ್‌ಸೆಟ್"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ಹೆಚ್ಚಿನ ವಿಜೆಟ್‌ಗಳನ್ನು ಸೇರಿಸಿ"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ವಿಜೆಟ್‌ಗಳನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಲು ದೀರ್ಘಕಾಲ ಒತ್ತಿರಿ"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ವಿಜೆಟ್‌ಗಳನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಿ"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾದ ವಿಜೆಟ್‌ಗಾಗಿ ಆ್ಯಪ್ ಐಕಾನ್"</string>
     <string name="edit_widget" msgid="9030848101135393954">"ವಿಜೆಟ್ ಅನ್ನು ಎಡಿಟ್ ಮಾಡಿ"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ತೆಗೆದುಹಾಕಿ"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ವಿಜೆಟ್ ಅನ್ನು ಸೇರಿಸಿ"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"ರಿಂಗ್"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"ವೈಬ್ರೇಟ್‌"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"ಮ್ಯೂಟ್"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"ಬಿತ್ತರಿಸಿ"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"ರಿಂಗ್ ಮ್ಯೂಟ್ ಆಗಿರುವ ಕಾರಣ ಲಭ್ಯವಿಲ್ಲ"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. ಅನ್‌ಮ್ಯೂಟ್‌ ಮಾಡುವುದಕ್ಕಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. ಕಂಪನಕ್ಕೆ ಹೊಂದಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ. ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ ಸೇವೆಗಳನ್ನು ಮ್ಯೂಟ್‌ ಮಾಡಬಹುದು."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. ಮ್ಯೂಟ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ. ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ ಸೇವೆಗಳನ್ನು ಮ್ಯೂಟ್‌ ಮಾಡಬಹುದು."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. ವೈಬ್ರೇಟ್ ಮಾಡಲು ಹೊಂದಿಸುವುದಕ್ಕಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. ಮ್ಯೂಟ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"ಗದ್ದಲ ನಿಯಂತ್ರಣ"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"ರಿಂಗರ್ ಮೋಡ್ ಬದಲಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"ಮ್ಯೂಟ್ ಮಾಡಿ"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ಅನ್‌ಮ್ಯೂಟ್ ಮಾಡಿ"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"ಪವರ್ ಮೆನು"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g> ರಲ್ಲಿ <xliff:g id="ID_1">%1$d</xliff:g> ಪುಟ"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"ಲಾಕ್ ಪರದೆ"</string>
+    <string name="finder_active" msgid="7907846989716941952">"ಪವರ್ ಆಫ್ ಆಗಿರುವಾಗಲೂ ನೀವು Find My Device ಮೂಲಕ ಈ ಫೋನ್ ಅನ್ನು ಪತ್ತೆ ಮಾಡಬಹುದು"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"ಶಟ್ ಡೌನ್‌ ಮಾಡಲಾಗುತ್ತಿದೆ…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"ಕಾಳಜಿಯ ಹಂತಗಳನ್ನು ವೀಕ್ಷಿಸಿ"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ಕಾಳಜಿಯ ಹಂತಗಳನ್ನು ವೀಕ್ಷಿಸಿ"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"ನಿಮ್ಮ ಸಾಧನವನ್ನು ಅನ್‌ಪ್ಲಗ್ ಮಾಡಿ"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"ಟಿಪ್ಪಣಿ ಮಾಡಿಕೊಳ್ಳುವಿಕೆ"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"ಟಿಪ್ಪಣಿ ಮಾಡಿಕೊಳ್ಳುವಿಕೆ, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"ಆಡಿಯೊವನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ಪ್ರಸಾರ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ನ ಪ್ರಸಾರವನ್ನು ನಿಲ್ಲಿಸಬೇಕೆ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"ನೀವು <xliff:g id="SWITCHAPP">%1$s</xliff:g> ಅನ್ನು ಪ್ರಸಾರ ಮಾಡಿದರೆ ಅಥವಾ ಔಟ್‌ಪುಟ್ ಅನ್ನು ಬದಲಾಯಿಸಿದರೆ, ನಿಮ್ಮ ಪ್ರಸ್ತುತ ಪ್ರಸಾರವು ಸ್ಥಗಿತಗೊಳ್ಳುತ್ತದೆ"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ಅಧಿಕ ರೆಸಲ್ಯೂಷನ್‌ಗಾಗಿ, ಫೋನ್ ಅನ್ನು ಫ್ಲಿಪ್ ಮಾಡಿ"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ಫೋಲ್ಡ್ ಮಾಡಬಹುದಾದ ಸಾಧನವನ್ನು ಅನ್‌ಫೋಲ್ಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ಫೋಲ್ಡ್ ಮಾಡಬಹುದಾದ ಸಾಧನವನ್ನು ಸುತ್ತಲೂ ತಿರುಗಿಸಲಾಗುತ್ತಿದೆ"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ಬ್ಯಾಟರಿ ಉಳಿದಿದೆ"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ನಿಮ್ಮ ಸ್ಟೈಲಸ್ ಅನ್ನು ಚಾರ್ಜರ್‌ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಿ"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"ಸ್ಟೈಲಸ್ ಬ್ಯಾಟರಿ ಕಡಿಮೆಯಿದೆ"</string>
diff --git a/packages/SystemUI/res/values-kn/tiles_states_strings.xml b/packages/SystemUI/res/values-kn/tiles_states_strings.xml
index 250eb5a..3ae6578 100644
--- a/packages/SystemUI/res/values-kn/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-kn/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"ಆಫ್"</item>
     <item msgid="578444932039713369">"ಆನ್"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"ಲಭ್ಯವಿಲ್ಲ"</item>
+    <item msgid="9061144428113385092">"ಆಫ್"</item>
+    <item msgid="2984256114867200368">"ಆನ್"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="8707481475312432575">"ಆಫ್"</item>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 26825e0..40fb618 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"사용"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"사용"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"사용 안함"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"표준"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"긴급"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"화면 자동 회전"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> 앱이 <xliff:g id="USB_DEVICE">%2$s</xliff:g>에 액세스하도록 허용하시겠습니까?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g>에서 <xliff:g id="USB_DEVICE">%2$s</xliff:g>에 액세스하도록 허용하시겠습니까?\n이 앱에는 녹음 권한이 부여되지 않았지만, 이 USB 기기를 통해 오디오를 녹음할 수 있습니다."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"연결 해제"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"실행"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"내일 다시 자동으로 사용 설정"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Quick Share, 내 기기 찾기, 기기 위치 등의 기능에서 블루투스를 사용합니다."</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"배터리 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"오디오"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"헤드셋"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"더 많은 위젯 추가"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"위젯을 맞춤설정하려면 길게 누르기"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"위젯 맞춤설정"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"사용 중지된 위젯의 앱 아이콘"</string>
     <string name="edit_widget" msgid="9030848101135393954">"위젯 수정"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"삭제"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"위젯 추가"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"벨소리"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"진동"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"음소거"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"전송"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"벨소리가 음소거되어 있으므로 사용할 수 없음"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. 탭하여 음소거를 해제하세요."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. 탭하여 진동으로 설정하세요. 접근성 서비스가 음소거될 수 있습니다."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. 탭하여 음소거로 설정하세요. 접근성 서비스가 음소거될 수 있습니다."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. 탭하여 진동으로 설정하세요."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. 탭하여 음소거로 설정하세요."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"소음 제어"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"탭하여 벨소리 장치 모드 변경"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"음소거"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"음소거 해제"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"전원 메뉴"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g>페이지 중 <xliff:g id="ID_1">%1$d</xliff:g>페이지"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"잠금 화면"</string>
+    <string name="finder_active" msgid="7907846989716941952">"전원이 꺼져 있을 때도 내 기기 찾기로 이 휴대전화를 찾을 수 있습니다."</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"종료 중…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"해결 방법 확인하기"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"해결 방법 확인하기"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"기기 분리하기"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"메모"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"메모, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"오디오 공유 중"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"방송 중"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> 방송을 중지하시겠습니까?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> 앱을 방송하거나 출력을 변경하면 기존 방송이 중단됩니다"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"해상도를 높이려면 후면 카메라를 사용하세요."</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"폴더블 기기를 펼치는 모습"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"폴더블 기기를 뒤집는 모습"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"접은 상태"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"펼친 상태"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"배터리 <xliff:g id="PERCENTAGE">%s</xliff:g> 남음"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"스타일러스를 충전기에 연결하세요"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"스타일러스 배터리 부족"</string>
diff --git a/packages/SystemUI/res/values-ko/tiles_states_strings.xml b/packages/SystemUI/res/values-ko/tiles_states_strings.xml
index 7981d28..002efb2 100644
--- a/packages/SystemUI/res/values-ko/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ko/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"꺼짐"</item>
     <item msgid="578444932039713369">"켜짐"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"사용 불가"</item>
+    <item msgid="9061144428113385092">"사용 안함"</item>
+    <item msgid="2984256114867200368">"사용"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"이용 불가"</item>
     <item msgid="8707481475312432575">"꺼짐"</item>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 7954686..ef213ca 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Күйгүзүү"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Күйгүзүү"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Жок, рахмат"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Кадимки"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Кескин"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Экранды авто буруу"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> колдонмосу <xliff:g id="USB_DEVICE">%2$s</xliff:g> түзмөгүн колдоно берсинби?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> колдонмосу <xliff:g id="USB_DEVICE">%2$s</xliff:g> түзмөгүн колдоно берсинби?\nБул колдонмого жаздырууга уруксат берилген эмес, бирок ушул USB түзмөгү аркылуу үндөрдү жаза алат."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ажыратуу"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"иштетүү"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Эртең автоматтык түрдө кайра күйгүзүү"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Тез Бөлүшүү, \"Түзмөгүм кайда?\" жана түзмөктүн турган жерин аныктоо сыяктуу функциялар Bluetooth\'ду колдонот"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Батареянын деңгээли <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнитура"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Көбүрөөк виджеттерди кошуу"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Виджеттерди ыңгайлаштыруу үчүн кое бербей басып туруңуз"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Виджеттерди ыңгайлаштыруу"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Өчүрүлгөн виджет үчүн колдонмонун сүрөтчөсү"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Виджетти түзөтүү"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Өчүрүү"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Виджет кошуу"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Шыңгыратуу"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Дирилдөө"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Үнсүз"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Тышкы экранга чыгруу"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Үнсүз режимде жеткиликсиз"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Үнүн чыгаруу үчүн таптап коюңуз."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Дирилдөөгө коюу үчүн таптап коюңуз. Атайын мүмкүнчүлүктөр кызматынын үнүн өчүрүп койсо болот."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Үнүн өчүрүү үчүн таптап коюңуз. Атайын мүмкүнчүлүктөр кызматынын үнүн өчүрүп койсо болот."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Дирилдөөгө коюу үчүн басыңыз."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Үнүн өчүрүү үчүн басыңыз."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Ызы-чууну көзөмөлдөө"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Коңгуроо режимин өзгөртүү үчүн басыңыз"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"үнсүз"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"үнүн чыгаруу"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Кубат баскычынын менюсу"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g> ичинен <xliff:g id="ID_1">%1$d</xliff:g>-бет"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Кулпуланган экран"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Бул телефон өчүк болсо да, аны \"Түзмөгүм кайда?\" кызматы аркылуу таба аласыз"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Өчүрүлүүдө…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Тейлөө кадамдарын көрүңүз"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Тейлөө кадамдарын көрүңүз"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Түзмөктү сууруп коюңуз"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Эскертме жазуу"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Эскертме жазуу (<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>)"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Аудиону бөлүшүү"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Кеңири таратуу"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосунда кабарлоо токтотулсунбу?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Эгер <xliff:g id="SWITCHAPP">%1$s</xliff:g> колдонмосунда кабарласаңыз же аудионун чыгуусун өзгөртсөңүз, учурдагы кабарлоо токтотулат"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Жогорку дааналык үчүн телефондун арткы камерасын колдонуңуз"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Ачылып турган бүктөлмө түзмөк"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Оодарылып жаткан бүктөлмө түзмөк"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"бүктөлгөн"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ачылган"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Батареянын кубаты: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Стилусту кубаттаңыз"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Стилустун батареясы отурайын деп калды"</string>
diff --git a/packages/SystemUI/res/values-ky/tiles_states_strings.xml b/packages/SystemUI/res/values-ky/tiles_states_strings.xml
index 0f277f9..bb03d0a 100644
--- a/packages/SystemUI/res/values-ky/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ky/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Өчүк"</item>
     <item msgid="578444932039713369">"Күйүк"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Жеткиликсиз"</item>
+    <item msgid="9061144428113385092">"Өчүк"</item>
+    <item msgid="2984256114867200368">"Күйүк"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Жеткиликсиз"</item>
     <item msgid="8707481475312432575">"Өчүк"</item>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 4c1bd4e..9468502 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ເປີດ​ໃຊ້"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"ເປີດໃຊ້"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"ບໍ່, ຂອບໃຈ"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"ມາດຕະຖານ"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"ສຸດຂີດ"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"ໝຸນໜ້າຈໍອັດຕະໂນມັດ"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"ອະນຸຍາດໃຫ້ <xliff:g id="APPLICATION">%1$s</xliff:g> ເຂົ້າເຖິງ <xliff:g id="USB_DEVICE">%2$s</xliff:g> ໄດ້ບໍ?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"ອະນຸຍາດໃຫ້ <xliff:g id="APPLICATION">%1$s</xliff:g> ເຂົ້າເຖິງ <xliff:g id="USB_DEVICE">%2$s</xliff:g> ໄດ້ບໍ?\nແອັບນີ້ບໍ່ໄດ້ຮັບອະນຸາດໃຫ້ບັນທຶກໄດ້ແຕ່ສາມາດບັນທຶກສຽງໄດ້ຜ່ານອຸປະກອນ USB ນີ້."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ຕັດການເຊື່ອມຕໍ່"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ເປີດນຳໃຊ້"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"ເປີດໃຊ້ໂດຍອັດຕະໂນມັດອີກຄັ້ງມື້ອື່ນ"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"ຄຸນສົມບັດຕ່າງໆເຊັ່ນ: ການແຊຣ໌ດ່ວນ, ຊອກຫາອຸປະກອນຂອງຂ້ອຍ ແລະ ສະຖານທີ່ອຸປະກອນໃຊ້ Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"ແບັດເຕີຣີ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ສຽງ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ຊຸດຫູຟັງ"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ເພີ່ມວິດເຈັດເພີ່ມເຕີມ"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ກົດຄ້າງໄວ້ເພື່ອປັບແຕ່ງວິດເຈັດ"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ປັບແຕ່ງວິດເຈັດ"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ໄອຄອນແອັບສຳລັບວິດເຈັດທີ່ຖືກປິດການນຳໃຊ້"</string>
     <string name="edit_widget" msgid="9030848101135393954">"ແກ້ໄຂວິດເຈັດ"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ລຶບອອກ"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ເພີ່ມວິດເຈັດ"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"​ເຕືອນ​ດ້ວຍ​ສຽງ"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"ສັ່ນເຕືອນ"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"ປິດ"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"ສົ່ງສັນຍານ"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"ບໍ່ມີໃຫ້ໃຊ້ເນື່ອງຈາກມີການປິດສຽງໂທເຂົ້າ"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. ແຕະເພື່ອເຊົາປິດສຽງ."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. ແຕະເພື່ອຕັ້ງເປັນສັ່ນ. ບໍລິການຊ່ວຍເຂົ້າເຖິງອາດຖືກປິດສຽງໄວ້."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. ແຕະເພື່ອປິດສຽງ. ບໍລິການຊ່ວຍເຂົ້າເຖິງອາດຖືກປິດສຽງໄວ້."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. ແຕະເພື່ອຕັ້ງເປັນສັ່ນເຕືອນ."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. ແຕະເພື່ອປິດສຽງ."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"ການຄວບຄຸມສຽງລົບກວນ"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"ແຕະເພື່ອປ່ຽນໂໝດຣິງເກີ"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"ປິດສຽງ"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ເຊົາປິດສຽງ"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"ເມນູເປີດປິດ"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_1">%1$d</xliff:g> ຈາກທັງໝົດ <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"ໜ້າຈໍລັອກ"</string>
+    <string name="finder_active" msgid="7907846989716941952">"ທ່ານສາມາດຊອກຫາສະຖານທີ່ຂອງໂທລະສັບເຄື່ອງນີ້ໄດ້ດ້ວຍແອັບຊອກຫາອຸປະກອນຂອງຂ້ອຍເຖິງແມ່ນວ່າຈະປິດເຄື່ອງຢູ່ກໍຕາມ"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"ກຳລັງປິດເຄື່ອງ…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"ເບິ່ງຂັ້ນຕອນການເບິ່ງແຍງ"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ເບິ່ງຂັ້ນຕອນການເບິ່ງແຍງ"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"ຖອດອຸປະກອນຂອງທ່ານອອກ"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"ການຈົດບັນທຶກ"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"ການຈົດບັນທຶກ <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"ກຳລັງແບ່ງປັນສຽງ"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ກຳລັງອອກອາກາດ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"ຢຸດການອອກອາກາດ <xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"ຫາກທ່ານອອກອາກາດ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ຫຼື ປ່ຽນເອົ້າພຸດ, ການອອກອາກາດປັດຈຸບັນຂອງທ່ານຈະຢຸດ"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ເພື່ອຄວາມລະອຽດທີ່ສູງຂຶ້ນ, ໃຫ້ປີ້ນໂທລະສັບ"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ອຸປະກອນທີ່ພັບໄດ້ກຳລັງກາງອອກ"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ອຸປະກອນທີ່ພັກໄດ້ກຳລັງປີ້ນໄປມາ"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ພັບແລ້ວ"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ກາງອອກແລ້ວ"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"ແບັດເຕີຣີເຫຼືອ <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ເຊື່ອມຕໍ່ປາກກາຂອງທ່ານກັບສາຍສາກ"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"ແບັດເຕີຣີປາກກາເຫຼືອໜ້ອຍ"</string>
diff --git a/packages/SystemUI/res/values-lo/tiles_states_strings.xml b/packages/SystemUI/res/values-lo/tiles_states_strings.xml
index d54cf4d..3c288fc 100644
--- a/packages/SystemUI/res/values-lo/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-lo/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"ປິດ"</item>
     <item msgid="578444932039713369">"ເປີດ"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"ບໍ່ພ້ອມໃຫ້ນຳໃຊ້"</item>
+    <item msgid="9061144428113385092">"ປິດ"</item>
+    <item msgid="2984256114867200368">"ເປີດ"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"ບໍ່ສາມາດໃຊ້ໄດ້"</item>
     <item msgid="8707481475312432575">"ປິດ"</item>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 9a261e0..3fc0dcb 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Įjungti"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Įjungti"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Ne, ačiū"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Įprasta"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ekstremalus"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Automatiškai sukti ekraną"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Leisti „<xliff:g id="APPLICATION">%1$s</xliff:g>“ pasiekti įrenginį (<xliff:g id="USB_DEVICE">%2$s</xliff:g>)?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Leisti „<xliff:g id="APPLICATION">%1$s</xliff:g>“ pasiekti įrenginį (<xliff:g id="USB_DEVICE">%2$s</xliff:g>)?\nŠiai programai nebuvo suteiktas leidimas įrašyti, bet ji gali užfiksuoti garsą per šį USB įrenginį."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"atjungti"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"suaktyvinti"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatiškai vėl įjungti rytoj"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Tokioms funkcijoms kaip „Spartusis bendrinimas“, „Rasti įrenginį“ ir įrenginio vietovė naudojamas „Bluetooth“ ryšys"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akumuliatorius: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Garsas"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Virtualiosios realybės įrenginys"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Pridėkite daugiau valdiklių"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Ilgai paspauskite, kad tinkintumėte valdiklius"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Tinkinti valdiklius"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Išjungto valdiklio programos piktograma"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Redaguoti valdiklį"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Pašalinti"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Pridėti valdiklį"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Skambinti"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibruoti"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Nutildyti"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Perdavimas"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Nepasiekiama, nes skambutis nutildytas"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Palieskite, kad įjungtumėte garsą."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Palieskite, kad nustatytumėte vibravimą. Gali būti nutildytos pritaikymo neįgaliesiems paslaugos."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Palieskite, kad nutildytumėte. Gali būti nutildytos pritaikymo neįgaliesiems paslaugos."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Palieskite, kad nustatytumėte vibravimą."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Palieskite, kad nutildytumėte."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Triukšmo valdymas"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Palieskite, kad pakeistumėte skambučio režimą"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"nutildyti"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"įjungti garsą"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Įjungimo meniu"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_1">%1$d</xliff:g> psl. iš <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Užrakinimo ekranas"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Šį telefoną galite rasti naudodami programą „Rasti įrenginį“, net jei jis išjungtas"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Išjungiama…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Žr. priežiūros veiksmus"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Žr. priežiūros veiksmus"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Atjunkite įrenginį"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Užrašų kūrimas"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Užrašų kūrimas, „<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>“"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Bendrinamas garsas"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Transliavimas"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Sustabdyti „<xliff:g id="APP_NAME">%1$s</xliff:g>“ transliaciją?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Jei transliuosite „<xliff:g id="SWITCHAPP">%1$s</xliff:g>“ arba pakeisite išvestį, dabartinė transliacija bus sustabdyta"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Kad raiška būtų geresnė, apverskite telefoną"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Lankstomasis įrenginys išlankstomas"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Lankstomasis įrenginys apverčiamas"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"sulenkta"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"nesulenkta"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Liko akumuliatoriaus įkrovos: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Prijunkite rašiklį prie kroviklio"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Senka rašiklio akumuliatorius"</string>
diff --git a/packages/SystemUI/res/values-lt/tiles_states_strings.xml b/packages/SystemUI/res/values-lt/tiles_states_strings.xml
index e66f590..8c4515b 100644
--- a/packages/SystemUI/res/values-lt/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-lt/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Išjungta"</item>
     <item msgid="578444932039713369">"Įjungta"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Nepasiekiama"</item>
+    <item msgid="9061144428113385092">"Išjungta"</item>
+    <item msgid="2984256114867200368">"Įjungta"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Nepasiekiama"</item>
     <item msgid="8707481475312432575">"Išjungta"</item>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index a9a1a3c..1a2f8ba 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Ieslēgt"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Ieslēgt"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nē, paldies"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standarta"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Maks. taupīšana"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Automātiska ekrāna pagriešana"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Vai atļaut lietotnei <xliff:g id="APPLICATION">%1$s</xliff:g> piekļūt šai ierīcei: <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Vai atļaut lietotnei <xliff:g id="APPLICATION">%1$s</xliff:g> piekļūt ierīcei “<xliff:g id="USB_DEVICE">%2$s</xliff:g>”?\nŠai lietotnei nav piešķirta ierakstīšanas atļauja, taču tā varētu tvert audio, izmantojot šo USB ierīci."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"atvienot"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivizēt"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automātiski atkal ieslēgt rīt"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Tādas funkcijas kā “Ātrā kopīgošana”, “Atrast ierīci” un ierīces atrašanās vietas noteikšana izmanto tehnoloģiju Bluetooth."</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akumulators: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Austiņas"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Pievienot citus logrīkus"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Nospiediet un turiet, lai pielāgotu logrīkus."</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Pielāgot logrīkus"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Lietotnes ikona atspējotam logrīkam"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Rediģēt logrīku"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Noņemt"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Pievienot logrīku"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Zvanīt"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrēt"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Izslēgt skaņu"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Apraide"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Nevar mainīt, jo izslēgts skaņas signāls"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Pieskarieties, lai ieslēgtu skaņu."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Pieskarieties, lai iestatītu uz vibrozvanu. Var tikt izslēgti pieejamības pakalpojumu signāli."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Pieskarieties, lai izslēgtu skaņu. Var tikt izslēgti pieejamības pakalpojumu signāli."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Pieskarieties, lai iestatītu vibrozvanu."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Pieskarieties, lai izslēgtu skaņu."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Trokšņu kontrole"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Pieskarieties, lai mainītu zvanītāja režīmu."</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"izslēgt skaņu"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ieslēgt skaņu"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Barošanas izvēlne"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_1">%1$d</xliff:g>. lpp. no <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Bloķēšanas ekrāns"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Lietotni “Atrast ierīci” var izmantot šī tālruņa atrašanās vietas noteikšanai arī tad, ja tālrunis ir izslēgts."</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Notiek izslēgšana…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Skatīt apkopes norādījumus"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Skatīt apkopes norādījumus"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Atvienojiet savu ierīci"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Piezīmju pierakstīšana"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Piezīmju pierakstīšana, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Notiek audio kopīgošana"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Notiek apraidīšana"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Vai apturēt lietotnes <xliff:g id="APP_NAME">%1$s</xliff:g> apraidīšanu?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ja sāksiet lietotnes <xliff:g id="SWITCHAPP">%1$s</xliff:g> apraidīšanu vai mainīsiet izvadi, pašreizējā apraide tiks apturēta"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Lai izmantotu augstāku izšķirtspēju, apvērsiet tālruni"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Salokāma ierīce tiek atlocīta"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Salokāma ierīce tiek apgriezta"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Atlikušais uzlādes līmenis: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Pievienojiet skārienekrāna pildspalvu lādētājam"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Zems skārienekrāna pildspalvas akumulatora līmenis"</string>
diff --git a/packages/SystemUI/res/values-lv/tiles_states_strings.xml b/packages/SystemUI/res/values-lv/tiles_states_strings.xml
index d32efec..a75e9d8 100644
--- a/packages/SystemUI/res/values-lv/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-lv/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Izslēgta"</item>
     <item msgid="578444932039713369">"Ieslēgta"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Nav pieejams"</item>
+    <item msgid="9061144428113385092">"Izslēgts"</item>
+    <item msgid="2984256114867200368">"Ieslēgts"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Nav pieejama"</item>
     <item msgid="8707481475312432575">"Izslēgta"</item>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 20a93f7..b6651ad 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Вклучи"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Вклучи"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Не, фала"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Стандарден"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Екстремен"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Автоматско ротирање на екранот"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Ќе дозволите <xliff:g id="APPLICATION">%1$s</xliff:g> да пристапува до <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Дали дозволувате <xliff:g id="APPLICATION">%1$s</xliff:g> да пристапи до <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nНа апликацијава не ѝ е доделена дозвола за снимање, но може да снима аудио преку овој USB-уред."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"прекини врска"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активирај"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Автоматски вклучи повторно утре"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Функциите како „Брзо споделување“, „Најди го мојот уред“ и локација на уредот користат Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> батерија"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Слушалки"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Додајте повеќе виџети"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Притиснете долго за да ги приспособите виџетите"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Приспособете ги виџетите"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Икона за апликација за оневозможен виџет"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Изменување виџети"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Отстранува"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Додајте виџет"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ѕвони"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Вибрации"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Исклучи звук"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Емитување"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Недостапно бидејќи ѕвонењето е исклучено"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Допрете за да вклучите звук."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Допрете за да поставите на вибрации. Можеби ќе се исклучи звукот на услугите за достапност."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Допрете за да исклучите звук. Можеби ќе се исклучи звукот на услугите за достапност."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Допрете за да се постави на вибрации."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Допрете за да се исклучи звукот."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Контрола на бучавата"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Допрете за да го промените режимот на ѕвончето"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"исклучен звук"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"вклучен звук"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Мени на копчето за вклучување"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Страница <xliff:g id="ID_1">%1$d</xliff:g> од <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Заклучен екран"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Може да го лоцирате телефонов со „Најди го мојот уред“ дури и кога е исклучен"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Се исклучува…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Прикажи ги чекорите за грижа за уредот"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Прикажи ги чекорите за грижа за уредот"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Исклучете го уредот од кабел"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Фаќање белешки"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Фаќање белешки, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Се споделува аудио"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Емитување"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Да се прекине емитувањето на <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ако емитувате на <xliff:g id="SWITCHAPP">%1$s</xliff:g> или го промените излезот, тековното емитување ќе запре"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Отворете го телефонот за да добиете повисока резолуција"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Преклопувачки уред се отклопува"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Преклопувачки уред се врти"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Преостаната батерија: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Поврзете го пенкалото со полнач"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Слаба батерија на пенкало"</string>
diff --git a/packages/SystemUI/res/values-mk/tiles_states_strings.xml b/packages/SystemUI/res/values-mk/tiles_states_strings.xml
index 0a42d7c..4dd9e73 100644
--- a/packages/SystemUI/res/values-mk/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-mk/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Исклучено"</item>
     <item msgid="578444932039713369">"Вклучено"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Недостапно"</item>
+    <item msgid="9061144428113385092">"Исклучено"</item>
+    <item msgid="2984256114867200368">"Вклучено"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Недостапно"</item>
     <item msgid="8707481475312432575">"Исклучено"</item>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index fc2bc47..a48d530 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ഓൺ ചെയ്യുക"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"ഓണാക്കുക"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"വേണ്ട"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"സ്റ്റാൻഡേർഡ്"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"എക്‌സ്‌ട്രീം"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"സ്‌ക്രീൻ സ്വയമേ തിരിയുക"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> ആക്‌സസ് ചെയ്യാൻ <xliff:g id="APPLICATION">%1$s</xliff:g>-നെ അനുവദിക്കണോ?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> ആക്‌സസ് ചെയ്യാൻ <xliff:g id="APPLICATION">%1$s</xliff:g> എന്നതിനെ അനുവദിക്കണോ?\nഈ ആപ്പിന് റെക്കോർഡ് അനുമതി നൽകിയിട്ടില്ല, എന്നാൽ ഈ USB ഉപകരണത്തിലൂടെ ഓഡിയോ ക്യാപ്‌ചർ ചെയ്യാനാവും."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"വിച്ഛേദിക്കുക"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"സജീവമാക്കുക"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"നാളെ വീണ്ടും സ്വയമേവ ഓണാക്കുക"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"ക്വിക്ക് ഷെയർ, Find My Device, ഉപകരണ ലൊക്കേഷൻ എന്നിവ പോലുള്ള ഫീച്ചറുകൾ Bluetooth ഉപയോഗിക്കുന്നു"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ബാറ്ററി"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ഓഡിയോ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ഹെഡ്‌സെറ്റ്"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"കൂടുതൽ വിജറ്റുകൾ ചേർക്കുക"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"വിജറ്റുകൾ ഇഷ്ടാനുസൃതമാക്കാൻ ദീർഘനേരം അമർത്തുക"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"വിജറ്റുകൾ ഇഷ്ടാനുസൃതമാക്കുക"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"പ്രവർത്തനരഹിതമാക്കിയ വിജറ്റിനുള്ള ആപ്പ് ഐക്കൺ"</string>
     <string name="edit_widget" msgid="9030848101135393954">"വിജറ്റ് എഡിറ്റ് ചെയ്യുക"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"നീക്കം ചെയ്യുക"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"വിജറ്റ് ചേർക്കുക"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"റിംഗ് ചെയ്യുക"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"വൈബ്രേറ്റ് ചെയ്യുക"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"മ്യൂട്ട് ചെയ്യുക"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"കാസ്റ്റ് ചെയ്യുക"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"റിംഗ് മ്യൂട്ട് ചെയ്തതിനാൽ ലഭ്യമല്ല"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. അൺമ്യൂട്ടുചെയ്യുന്നതിന് ടാപ്പുചെയ്യുക."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. വൈബ്രേറ്റിലേക്ക് സജ്ജമാക്കുന്നതിന് ടാപ്പുചെയ്യുക. ഉപയോഗസഹായി സേവനങ്ങൾ മ്യൂട്ടുചെയ്യപ്പെട്ടേക്കാം."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. മ്യൂട്ടുചെയ്യുന്നതിന് ടാപ്പുചെയ്യുക. ഉപയോഗസഹായി സേവനങ്ങൾ മ്യൂട്ടുചെയ്യപ്പെട്ടേക്കാം."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s വൈബ്രേറ്റിലേക്ക് സജ്ജമാക്കുന്നതിന് ടാപ്പുചെയ്യുക."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s മ്യൂട്ടുചെയ്യുന്നതിന് ടാപ്പുചെയ്യുക."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"നോയ്‌സ് നിയന്ത്രണം"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"റിംഗർ മോഡ് മാറ്റാൻ ടാപ്പ് ചെയ്യുക"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"മ്യൂട്ട് ചെയ്യുക"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"അൺമ്യൂട്ട് ചെയ്യുക"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"പവർ മെനു"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"പേജ് <xliff:g id="ID_1">%1$d</xliff:g> / <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"ലോക്ക് സ്‌ക്രീൻ"</string>
+    <string name="finder_active" msgid="7907846989716941952">"ഓഫായിരിക്കുമ്പോഴും Find My Device ഉപയോഗിച്ച് നിങ്ങൾക്ക് ഈ ഫോൺ കണ്ടെത്താനാകും"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"ഷട്ട്‌ഡൗൺ ചെയ്യുന്നു…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"പരിപാലന നിർദ്ദേശങ്ങൾ കാണുക"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"പരിപാലന നിർദ്ദേശങ്ങൾ കാണുക"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"ഉപകരണം അൺപ്ലഗ് ചെയ്യുക"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"കുറിപ്പ് രേഖപ്പെടുത്തൽ"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"കുറിപ്പ് രേഖപ്പെടുത്തൽ, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"ഓഡിയോ പങ്കിടുന്നു"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"പ്രക്ഷേപണം ചെയ്യുന്നു"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ബ്രോഡ്‌കാസ്റ്റ് ചെയ്യുന്നത് അവസാനിപ്പിക്കണോ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"നിങ്ങൾ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ബ്രോഡ്‌കാസ്റ്റ് ചെയ്യുകയോ ഔട്ട്പുട്ട് മാറ്റുകയോ ചെയ്താൽ നിങ്ങളുടെ നിലവിലുള്ള ബ്രോഡ്‌കാസ്റ്റ് അവസാനിക്കും"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ഉയർന്ന റെസല്യൂഷന്, ഫോൺ ഫ്ലിപ്പ് ചെയ്യുക"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ഫോൾഡ് ചെയ്യാവുന്ന ഉപകരണം അൺഫോൾഡ് ആകുന്നു"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ഫോൾഡ് ചെയ്യാവുന്ന ഉപകരണം, കറങ്ങുന്ന വിധത്തിൽ ഫ്ലിപ്പ് ആകുന്നു"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ഫോൾഡ് ചെയ്തത്"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"അൺഫോൾഡ് ചെയ്തത്"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ബാറ്ററി ചാർജ് ശേഷിക്കുന്നു"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"നിങ്ങളുടെ സ്റ്റൈലസ് ചാർജറുമായി കണക്റ്റ് ചെയ്യുക"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"സ്റ്റൈലസിന്റെ ബാറ്ററി ചാർജ് കുറവാണ്"</string>
diff --git a/packages/SystemUI/res/values-ml/tiles_states_strings.xml b/packages/SystemUI/res/values-ml/tiles_states_strings.xml
index 62bac5c..18e7b29 100644
--- a/packages/SystemUI/res/values-ml/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ml/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"ഓഫാണ്"</item>
     <item msgid="578444932039713369">"ഓണാണ്"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"ലഭ്യമല്ല"</item>
+    <item msgid="9061144428113385092">"ഓഫാണ്"</item>
+    <item msgid="2984256114867200368">"ഓണാണ്"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"ലഭ്യമല്ല"</item>
     <item msgid="8707481475312432575">"ഓഫാണ്"</item>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index f5bf05a..625b8b7 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Асаах"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Асаах"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Үгүй, баярлалаа"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Стандарт"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Экстрим"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Дэлгэцийг автоматаар эргүүлэх"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g>-г <xliff:g id="USB_DEVICE">%2$s</xliff:g>-д хандахыг зөвшөөрөх үү?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g>-д <xliff:g id="USB_DEVICE">%2$s</xliff:g>-д хандахыг зөвшөөрөх үү?\nЭнэ аппад бичих зөвшөөрөл олгогдоогүй ч USB төхөөрөмжөөр дамжуулан аудио бичиж чадсан."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"салгах"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"идэвхжүүлэх"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Маргааш автоматаар дахин асаах"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Түргэн хуваалцах, Миний төхөөрөмжийг олох зэрэг онцлогууд болон төхөөрөмжийн байршил Bluetooth-г ашигладаг"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> батарей"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Чихэвч"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Илүү олон виджет нэмэх"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Виджетүүдийг өөрчлөхийн тулд удаан дарна уу"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Виджетүүдийг өөрчлөх"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Идэвхгүй болгосон виджетийн аппын дүрс тэмдэг"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Виджетийг засах"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Хасах"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Виджет нэмэх"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Хонх дуугаргах"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Чичиргэх"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Хаах"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Дамжуулах"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Хонхны дууг хаасан тул боломжгүй"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Дууг нь нээхийн тулд товшино уу."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Чичиргээнд тохируулахын тулд товшино уу. Хүртээмжийн үйлчилгээний дууг хаасан."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Дууг нь хаахын тулд товшино уу. Хүртээмжийн үйлчилгээний дууг хаасан."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Чичиргээнд тохируулахын тулд товшино уу."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Дууг хаахын тулд товшино уу."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Шуугианы хяналт"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Хонхны горимыг өөрчлөхийн тулд товшино уу"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"дууг хаах"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"дууг нээх"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Асаах/унтраах цэс"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g>-н <xliff:g id="ID_1">%1$d</xliff:g>-р хуудас"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Түгжээтэй дэлгэц"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Та энэ утсыг унтраалттай байсан ч Миний төхөөрөмжийг олохоор байршлыг нь тогтоох боломжтой"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Унтрааж байна…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Хянамж болгоомжийн алхмыг харах"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Хянамж болгоомжийн алхмыг харах"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Төхөөрөмжөө салгана уу"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Тэмдэглэл хөтлөх"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Тэмдэглэл хөтлөх, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Аудио хуваалцаж байна"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Нэвтрүүлэлт"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g>-г нэвтрүүлэхээ зогсоох уу?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Хэрэв та <xliff:g id="SWITCHAPP">%1$s</xliff:g>-г нэвтрүүлсэн эсвэл гаралтыг өөрчилсөн бол таны одоогийн нэвтрүүлэлтийг зогсооно"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Илүү өндөр нягтрал авах бол утсыг хөнтөрнө үү"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Эвхэгддэг төхөөрөмжийг дэлгэж байна"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Эвхэгддэг төхөөрөмжийг хөнтөрч байна"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"эвхсэн"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"дэлгэсэн"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> батарей үлдлээ"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Мэдрэгч үзгээ цэнэглэгчтэй холбоорой"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Мэдрэгч үзэгний батарей бага байна"</string>
diff --git a/packages/SystemUI/res/values-mn/tiles_states_strings.xml b/packages/SystemUI/res/values-mn/tiles_states_strings.xml
index 33f3596..95f835e 100644
--- a/packages/SystemUI/res/values-mn/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-mn/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Унтраалттай"</item>
     <item msgid="578444932039713369">"Асаалттай"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Боломжгүй"</item>
+    <item msgid="9061144428113385092">"Унтраалттай"</item>
+    <item msgid="2984256114867200368">"Асаалттай"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Боломжгүй"</item>
     <item msgid="8707481475312432575">"Унтраалттай"</item>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index f9d4aca..b5c0dc7 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"सुरू करा"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"सुरू करा"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"नाही, नको"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"साधारण"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"एक्स्ट्रीम"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"ऑटो-रोटेट स्क्रीन"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> ला <xliff:g id="USB_DEVICE">%2$s</xliff:g> अ‍ॅक्सेस करण्याची अनुमती द्यायची का?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> ला <xliff:g id="USB_DEVICE">%2$s</xliff:g> अ‍ॅक्सेस करण्याची अनुमती द्यायची का?\nया अ‍ॅपला रेकॉर्ड करण्याची परवानगी दिलेली नाही पण या USB डिव्हाइसद्वारे ऑडिओ कॅप्चर केला जाऊ शकतो."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"डिस्कनेक्ट करा"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ॲक्टिव्हेट करा"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"उद्या पुन्हा आपोआप सुरू करा"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Quick Share, Find My Device आणि डिव्हाइस स्थान यांसारखी वैशिष्ट्ये ब्लूटूथ वापरतात"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> बॅटरी"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ऑडिओ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"हेडसेट"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"आणखी विजेट जोडा"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"विजेट कस्टमाइझ करण्यासाठी प्रेस करून ठेवा"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"विजेट कस्टमाइझ करा"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"बंद केलेल्या विजेटच्या अ‍ॅपचे आयकन"</string>
     <string name="edit_widget" msgid="9030848101135393954">"विजेट संपादित करा"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"काढून टाका"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"विजेट जोडा"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"रिंग करा"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"व्हायब्रेट"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"म्यूट करा"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"कास्ट करा"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"रिंग म्यूट केल्यामुळे उपलब्ध नाही"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. अनम्यूट करण्यासाठी टॅप करा."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. व्हायब्रेट सेट करण्यासाठी टॅप करा. प्रवेशयोग्यता सेवा म्यूट केल्या जाऊ शकतात."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. म्यूट करण्यासाठी टॅप करा. प्रवेशक्षमता सेवा म्यूट केल्या जाऊ शकतात."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. व्हायब्रेट सेट करण्यासाठी टॅप करा."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. म्यूट करण्यासाठी टॅप करा."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"नॉइझ कंट्रोल"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"रिंगर मोड बदलण्यासाठी टॅप करा"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"म्यूट करा"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"म्यूट काढून टाका"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"पॉवर मेनू"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g> पैकी <xliff:g id="ID_1">%1$d</xliff:g> पेज"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"लॉक स्‍क्रीन"</string>
+    <string name="finder_active" msgid="7907846989716941952">"तुम्ही हा फोन बंद असतानादेखील Find My Device वापरून तो शोधू शकता"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"बंद होत आहे…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"काय काळजी घ्यावी ते पहा"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"काय काळजी घ्यावी ते पहा"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"तुमचे डिव्हाइस अनप्लग करा"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"नोंद घेणे"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"नोंद घेणे, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"ऑडिओ शेअर करत आहे"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ब्रॉडकास्ट करत आहे"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> चे प्रसारण थांबवायचे आहे का?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"तुम्ही <xliff:g id="SWITCHAPP">%1$s</xliff:g> चे प्रसारण केल्यास किंवा आउटपुट बदलल्यास, तुमचे सध्याचे प्रसारण बंद होईल"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"उच्च रेझोल्यूशनसाठी, फोन फ्लिप करा"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"फोल्ड करता येण्यासारखे डिव्हाइस अनफोल्ड केले जात आहे"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"फोल्ड करता येण्यासारखे डिव्हाइस आजूबाजूला फ्लिप केले जात आहे"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> बॅटरी शिल्लक आहे"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"तुमचे स्टायलस चार्जरशी कनेक्ट करा"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"स्टायलस बॅटरी कमी आहे"</string>
diff --git a/packages/SystemUI/res/values-mr/tiles_states_strings.xml b/packages/SystemUI/res/values-mr/tiles_states_strings.xml
index 24d3b47..6902a2f 100644
--- a/packages/SystemUI/res/values-mr/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-mr/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"बंद आहे"</item>
     <item msgid="578444932039713369">"सुरू आहे"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"उपलब्ध नाही"</item>
+    <item msgid="9061144428113385092">"बंद आहे"</item>
+    <item msgid="2984256114867200368">"सुरू आहे"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"उपलब्ध नाही"</item>
     <item msgid="8707481475312432575">"बंद आहे"</item>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index dafcfe0..3419e63 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Hidupkan"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Hidupkan"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Tidak perlu"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ekstrem"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Autoputar skrin"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Benarkan <xliff:g id="APPLICATION">%1$s</xliff:g> mengakses <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Benarkan <xliff:g id="APPLICATION">%1$s</xliff:g> mengakses <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nApl ini belum diberikan kebenaran merakam tetapi dapat merakam audio melalui peranti USB ini."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"putuskan sambungan"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktifkan"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Dihidupkan sekali lagi esok secara automatik"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Ciri seperti Quick Share, Find My Device dan lokasi peranti menggunakan Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> bateri"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Set Kepala"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Tambahkan lagi widget"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Tekan lama untuk menyesuaikan widget"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Sesuaikan widget"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikon apl untuk melumpuhkan widget"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Alih keluar"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Tambahkan widget"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Dering"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Getar"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Redam"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Hantar"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Tidak tersedia kerana deringan diredam"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Ketik untuk menyahredam."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Ketik untuk menetapkan pada getar. Perkhidmatan kebolehaksesan mungkin diredamkan."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Ketik untuk meredam. Perkhidmatan kebolehaksesan mungkin diredamkan."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Ketik untuk menetapkan pada getar."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Ketik untuk meredam."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Kawalan Hingar"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Ketik untuk menukar mod pendering"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"redam"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"nyahredam"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menu kuasa"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Halaman <xliff:g id="ID_1">%1$d</xliff:g> daripada <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Kunci skrin"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Anda boleh mengesan telefon ini dengan Find My Device walaupun apabila telefon ini dimatikan kuasa"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Mematikan…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Lihat langkah penjagaan"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Lihat langkah penjagaan"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Cabut palam peranti anda"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Pengambilan nota"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Pengambilan nota, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Perkongsian audio"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Menyiarkan"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Hentikan siaran <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Jika anda siarkan <xliff:g id="SWITCHAPP">%1$s</xliff:g> atau tukarkan output, siaran semasa anda akan berhenti"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Untuk peleraian lebih tinggi, balikkan telefon"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Peranti boleh lipat dibuka"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Peranti boleh lipat diterbalikkan"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"terlipat"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"tidak terlipat"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Bateri tinggal <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Sambungkan stilus anda kepada pengecas"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Bateri stilus lemah"</string>
diff --git a/packages/SystemUI/res/values-ms/tiles_states_strings.xml b/packages/SystemUI/res/values-ms/tiles_states_strings.xml
index 07a8426..a280916 100644
--- a/packages/SystemUI/res/values-ms/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ms/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Mati"</item>
     <item msgid="578444932039713369">"Hidup"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Tidak tersedia"</item>
+    <item msgid="9061144428113385092">"Mati"</item>
+    <item msgid="2984256114867200368">"Hidup"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Tidak tersedia"</item>
     <item msgid="8707481475312432575">"Mati"</item>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index e52b899..36984fc 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ဖွင့်ရန်"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"ဖွင့်ရန်"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"မလိုပါ"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"ပုံမှန်"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"လွန်ကဲ"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"ဖန်သားပြင် အလိုအလျောက်လှည့်ရန်"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> အား ဝင်သုံးရန် <xliff:g id="APPLICATION">%1$s</xliff:g> ကို ခွင့်ပြုပါသလား။"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> အား <xliff:g id="USB_DEVICE">%2$s</xliff:g> ကို သုံးခွင့်ပြုမလား။\nဤအက်ပ်ကို အသံဖမ်းခွင့် ပေးမထားသော်လည်း ၎င်းသည် ဤ USB စက်ပစ္စည်းမှတစ်ဆင့် အသံများကို ဖမ်းယူနိုင်ပါသည်။"</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ချိတ်ဆက်မှုဖြုတ်ရန်"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"စသုံးရန်"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"မနက်ဖြန် အလိုအလျောက် ထပ်ဖွင့်ရန်"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"‘အမြန် မျှဝေပါ’၊ Find My Device နှင့် စက်ပစ္စည်းတည်နေရာကဲ့သို့ တူးလ်များသည် ဘလူးတုသ်သုံးသည်"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ဘက်ထရီ"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"အသံ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"မိုက်ခွက်ပါနားကြပ်"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"နောက်ထပ်ဝိဂျက်များ ထည့်ရန်"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ဝိဂျက်များ စိတ်ကြိုက်လုပ်ရန် ကြာကြာနှိပ်ထားပါ"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ဝိဂျက်များကို စိတ်ကြိုက်လုပ်ရန်"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ပိတ်ထားသော ဝိဂျက်အတွက် အက်ပ်သင်္ကေတ"</string>
     <string name="edit_widget" msgid="9030848101135393954">"ဝိဂျက်ပြင်ရန်"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ဖယ်ရှားရန်"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ဝိဂျက်ထည့်ရန်"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"အသံမြည်သည်"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"တုန်ခါသည်"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"အသံတိတ်သည်"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"ကာစ်လုပ်ရန်"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"ဖုန်းမြည်သံပိတ်ထားသဖြင့် မရနိုင်ပါ"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s။ အသံပြန်ဖွင့်ရန် တို့ပါ။"</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s။ တုန်ခါမှုကို သတ်မှတ်ရန် တို့ပါ။ အများသုံးနိုင်မှု ဝန်ဆောင်မှုများကို အသံပိတ်ထားနိုင်ပါသည်။"</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s။ အသံပိတ်ရန် တို့ပါ။ အများသုံးနိုင်မှု ဝန်ဆောင်မှုများကို အသံပိတ်ထားနိုင်ပါသည်။"</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s။ တုန်ခါခြင်းသို့ သတ်မှတ်ရန်တို့ပါ။"</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s။ အသံပိတ်ရန် တို့ပါ။"</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"ဆူညံသံ ထိန်းချုပ်ရန်"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"ဖုန်းခေါ်သံမုဒ်သို့ ပြောင်းရန် တို့ပါ"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"အသံပိတ်ရန်"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"အသံဖွင့်ရန်"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"ပါဝါမီနူး"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"စာမျက်နှာ <xliff:g id="ID_2">%2$d</xliff:g> အနက်မှ စာမျက်နှာ <xliff:g id="ID_1">%1$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"လော့ခ်ချထားချိန် မျက်နှာပြင်"</string>
+    <string name="finder_active" msgid="7907846989716941952">"ပါဝါပိတ်ထားသော်လည်း Find My Device ဖြင့် ဤဖုန်းကို ရှာနိုင်သည်"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"စက်ပိတ်နေသည်…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"ဂရုပြုစရာ အဆင့်များ ကြည့်ရန်"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ဂရုပြုစရာ အဆင့်များ ကြည့်ရန်"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"သင့်စက်ကို ပလတ်ဖြုတ်ပါ"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>၊ <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"မှတ်စုလိုက်ခြင်း"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"မှတ်စုလိုက်ခြင်း၊ <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"အသံမျှဝေခြင်း"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ထုတ်လွှင့်ခြင်း"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ထုတ်လွှင့်ခြင်းကို ရပ်မလား။"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ကို ထုတ်လွှင့်သောအခါ (သို့) အထွက်ကို ပြောင်းသောအခါ သင့်လက်ရှိထုတ်လွှင့်ခြင်း ရပ်သွားမည်"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ပုံရိပ် ပိုမိုပြတ်သားစေရန် ဖုန်းကို တစ်ဖက်သို့ လှန်လိုက်ပါ"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ခေါက်နိုင်သောစက်ကို ဖြန့်လိုက်သည်"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ခေါက်နိုင်သောစက်ကို တစ်ဘက်သို့ လှန်လိုက်သည်"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ခေါက်ထားသည်"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ဖြန့်ထားသည်"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"ဘက်ထရီ <xliff:g id="PERCENTAGE">%s</xliff:g> ကျန်သေးသည်"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"စတိုင်လပ်စ်ကို အားသွင်းကိရိယာနှင့် ချိတ်ဆက်ခြင်း"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"စတိုင်လပ်စ် ဘက်ထရီ အားနည်းနေသည်"</string>
diff --git a/packages/SystemUI/res/values-my/tiles_states_strings.xml b/packages/SystemUI/res/values-my/tiles_states_strings.xml
index fd375d4..ce10c42 100644
--- a/packages/SystemUI/res/values-my/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-my/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"ပိတ်"</item>
     <item msgid="578444932039713369">"ဖွင့်"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"မရနိုင်ပါ"</item>
+    <item msgid="9061144428113385092">"ပိတ်"</item>
+    <item msgid="2984256114867200368">"ဖွင့်"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"မရနိုင်ပါ"</item>
     <item msgid="8707481475312432575">"ပိတ်"</item>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index ed6e293..e126dea 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Slå på"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Slå på"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nei takk"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ekstrem"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Rotér skjermen automatisk"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Vil du gi <xliff:g id="APPLICATION">%1$s</xliff:g> tilgang til <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Vil du gi <xliff:g id="APPLICATION">%1$s</xliff:g> tilgang til <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nDenne appen har ikke fått tillatelse til å spille inn, men kan ta opp lyd med denne USB-enheten."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"koble fra"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiver"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Slå på igjen i morgen automatisk"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funksjoner som Quick Share, Finn enheten min og enhetsposisjon bruker Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batteri"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Lyd"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Hodetelefoner"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Legg til flere moduler"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Trykk lenge for å tilpasse modulene"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Tilpass moduler"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Appikon for deaktivert modul"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Endre modul"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Fjern"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Legg til modul"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ring"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrer"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Ignorer"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Cast"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Utilgjengelig fordi ringelyden er kuttet"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Trykk for å slå på lyden."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Trykk for å angi vibrasjon. Lyden kan bli slått av for tilgjengelighetstjenestene."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Trykk for å slå av lyden. Lyden kan bli slått av for tilgjengelighetstjenestene."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Trykk for å angi vibrasjon."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Trykk for å slå av lyden."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Støykontroll"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Trykk for å endre ringemodus"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"kutt lyden"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"slå på lyden"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Av/på-meny"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Side <xliff:g id="ID_1">%1$d</xliff:g> av <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Låseskjerm"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Du kan finne denne telefonen med Finn enheten min, selv når den er slått av"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Slår av …"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Se vedlikeholdstrinnene"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Se vedlikeholdstrinnene"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Koble fra enheten"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Notatskriving"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Notatskriving, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Deler lyd"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Kringkaster"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Vil du stoppe kringkastingen av <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Hvis du kringkaster <xliff:g id="SWITCHAPP">%1$s</xliff:g> eller endrer utgangen, stopper den nåværende kringkastingen din"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Brett ut telefonen for å få høyere oppløsning"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"En sammenleggbar enhet blir brettet ut"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"En sammenleggbar enhet blir snudd"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"lagt sammen"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"åpen"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> batteri gjenstår"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Koble pekepennen til en lader"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Det er lite batteri i pekepennen"</string>
diff --git a/packages/SystemUI/res/values-nb/tiles_states_strings.xml b/packages/SystemUI/res/values-nb/tiles_states_strings.xml
index e4a81194..71160d0 100644
--- a/packages/SystemUI/res/values-nb/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-nb/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Av"</item>
     <item msgid="578444932039713369">"På"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Ikke tilgjengelig"</item>
+    <item msgid="9061144428113385092">"Av"</item>
+    <item msgid="2984256114867200368">"På"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Utilgjengelig"</item>
     <item msgid="8707481475312432575">"Av"</item>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index fb7680d..a06c160 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"अन गर्नुहोस्"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"अन गर्नुहोस्"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"पर्दैन"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"डिफल्ट"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"चरम"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"स्वत:घुम्ने स्क्रिन"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> लाई <xliff:g id="USB_DEVICE">%2$s</xliff:g> माथि पहुँच राख्ने अनुमति दिने हो?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> लाई <xliff:g id="USB_DEVICE">%2$s</xliff:g> माथि पहुँच राख्न अनुमति दिने हो?\nयो एपलाई रेकर्ड गर्ने अनुमति प्रदान गरिएको छैन तर यसले USB यन्त्रमार्फत अडियो क्याप्चर गर्न सक्छ।"</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"डिस्कनेक्ट गर्नुहोस्"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"एक्टिभेट गर्नुहोस्"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"भोलि फेरि स्वतः अन गरियोस्"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"क्विक सेयर, Find My Device र डिभाइसको लोकेसन जस्ता सुविधाहरूले ब्लुटुथ प्रयोग गर्छन्"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ब्याट्री"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"अडियो"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"हेडसेट"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"थप विजेटहरू हाल्नुहोस्"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"विजेटहरू कस्टमाइज गर्न केही बेरसम्म थिच्नुहोस्"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"विजेटहरू कस्टमाइज गर्नुहोस्"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"अफ गरिएको विजेटको एप जनाउने आइकन"</string>
     <string name="edit_widget" msgid="9030848101135393954">"विजेट सम्पादन गर्नुहोस्"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"हटाउनुहोस्"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"विजेट हाल्नुहोस्"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"घन्टी"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"कम्पन"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"म्युट गर्नुहोस्"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"कास्ट गर्नुहोस्"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"डिभाइस म्युट गरिएकाले यो सुविधा उपलब्ध छैन"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s। अनम्यूट गर्नाका लागि ट्याप गर्नुहोस्।"</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s। कम्पनमा सेट गर्नाका लागि ट्याप गर्नुहोस्। पहुँच सम्बन्धी सेवाहरू म्यूट हुन सक्छन्।"</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s। म्यूट गर्नाका लागि ट्याप गर्नुहोस्। पहुँच सम्बन्धी सेवाहरू म्यूट हुन सक्छन्।"</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s। कम्पन मोडमा सेट गर्न ट्याप गर्नुहोस्।"</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s। म्यूट गर्न ट्याप गर्नुहोस्।"</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"नोइज कन्ट्रोल"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"रिङ्गर मोड बदल्न ट्याप गर्नुहोस्"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"म्युट गर्नुहोस्"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"अनम्युट गर्नुहोस्"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"पावर मेनु"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g> मध्ये पृष्ठ <xliff:g id="ID_1">%1$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"लक स्क्रिन"</string>
+    <string name="finder_active" msgid="7907846989716941952">"तपाईं Find My Device प्रयोग गरी यो फोन अफ भए पनि यसको लोकेसन पत्ता लगाउन सक्नुहुन्छ"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"सट डाउन गरिँदै छ..."</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"डिभाइसको हेरचाह गर्ने तरिका हेर्नुहोस्"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"डिभाइसको हेरचाह गर्ने तरिका हेर्नुहोस्"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"डिभाइस बिजुलीको स्रोतबाट निकाल्नुहोस्"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"नोट लेख्ने कार्य"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"नोट लेख्ने कार्य, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"अडियो सेयर गरिँदै छ"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"प्रसारण गरिँदै छ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ब्रोडकास्ट गर्न छाड्ने हो?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"तपाईंले <xliff:g id="SWITCHAPP">%1$s</xliff:g> ब्रोडकास्ट गर्नुभयो वा आउटपुट परिवर्तन गर्नुभयो भने तपाईंको हालको ब्रोडकास्ट रोकिने छ"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"उच्च रिजोल्युसनको सेल्फी खिच्न फोन फ्लिप गर्नुहोस्"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"फोल्ड गर्न मिल्ने डिभाइस अनफोल्ड गरेको देखाइएको एनिमेसन"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"फोल्ड गर्न मिल्ने डिभाइस यताउता पल्टाएर देखाइएको एनिमेसन"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ब्याट्री बाँकी छ"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"आफ्नो स्टाइलस चार्जरमा कनेक्ट गर्नुहोस्"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"स्टाइलसको ब्याट्री लो छ"</string>
diff --git a/packages/SystemUI/res/values-ne/tiles_states_strings.xml b/packages/SystemUI/res/values-ne/tiles_states_strings.xml
index 5cf91e5..1977706 100644
--- a/packages/SystemUI/res/values-ne/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ne/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"अफ छ"</item>
     <item msgid="578444932039713369">"अन छ"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"उपलब्ध छैन"</item>
+    <item msgid="9061144428113385092">"अफ छ"</item>
+    <item msgid="2984256114867200368">"अन छ"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"उपलब्ध छैन"</item>
     <item msgid="8707481475312432575">"अफ छ"</item>
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index b6971d3..291f8a2 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -53,6 +53,11 @@
         <item name="android:windowActionBar">false</item>
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowLightStatusBar">false</item>
+
+        <!--
+            TODO(b/309578419): Make activities handle insets properly and then remove this.
+        -->
+        <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
     </style>
 
     <style name="TextAppearance.InternetDialog.Active">
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index a649617..86b2ebe 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Aanzetten"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Aanzetten"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nee, bedankt"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standaard"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extreem"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Scherm automatisch draaien"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> toegang geven tot <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> toegang geven tot <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nDeze app heeft geen opnamerechten gekregen, maar zou audio kunnen vastleggen via dit USB-apparaat."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"loskoppelen"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activeren"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Morgen weer automatisch aanzetten"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Functies zoals Quick Share, Vind mijn apparaat en apparaatlocatie maken gebruik van bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batterijniveau"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Meer widgets toevoegen"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Houd lang ingedrukt om widgets aan te passen"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Widgets aanpassen"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App-icoon voor uitgezette widget"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Widget bewerken"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Verwijderen"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Widget toevoegen"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Bellen"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Trillen"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Geluid staat uit"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Casten"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Niet beschikbaar, belgeluid staat uit"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Tik om dempen op te heffen."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tik om in te stellen op trillen. Het geluid van toegankelijkheidsservices kan hierdoor uitgaan."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Tik om te dempen. Het geluid van toegankelijkheidsservices kan hierdoor uitgaan."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tik om in te stellen op trillen."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Tik om geluid uit te zetten."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Ruisonderdrukking"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Tik om de beltoonmodus te wijzigen"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"geluid uit"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"geluid aanzetten"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Aan/uit-menu"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Pagina <xliff:g id="ID_1">%1$d</xliff:g> van <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Vergrendelscherm"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Je kunt deze telefoon vinden met Vind mijn apparaat, ook als die uitstaat"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Uitzetten…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Onderhoudsstappen bekijken"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Onderhoudsstappen bekijken"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Je apparaat loskoppelen"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Aantekeningen maken"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Aantekeningen maken, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Audio delen"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Uitzending"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Uitzending van <xliff:g id="APP_NAME">%1$s</xliff:g> stopzetten?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Als je <xliff:g id="SWITCHAPP">%1$s</xliff:g> uitzendt of de uitvoer wijzigt, wordt je huidige uitzending gestopt"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Draai de telefoon om voor een hogere resolutie"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Opvouwbaar apparaat wordt uitgevouwen"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Opvouwbaar apparaat wordt gedraaid"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"dichtgevouwen"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"opengevouwen"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Nog <xliff:g id="PERCENTAGE">%s</xliff:g> batterijlading"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Verbind je stylus met een oplader"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Batterij van stylus bijna leeg"</string>
@@ -1236,9 +1259,9 @@
     <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="dismissible_keyguard_swipe" msgid="8377597870094949432">"Swipe omhoog om door te gaan"</string>
-    <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Spiegelen naar extern scherm?"</string>
+    <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirroren naar extern scherm?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Het scherm aan de binnenkant wordt gemirrord. Het scherm aan de voorkant wordt uitgezet."</string>
-    <string name="mirror_display" msgid="2515262008898122928">"Scherm spiegelen"</string>
+    <string name="mirror_display" msgid="2515262008898122928">"Scherm mirroren"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Sluiten"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Scherm verbonden"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfoon en camera"</string>
diff --git a/packages/SystemUI/res/values-nl/tiles_states_strings.xml b/packages/SystemUI/res/values-nl/tiles_states_strings.xml
index 592ecf5..7737794 100644
--- a/packages/SystemUI/res/values-nl/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-nl/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Uit"</item>
     <item msgid="578444932039713369">"Aan"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Niet beschikbaar"</item>
+    <item msgid="9061144428113385092">"Uit"</item>
+    <item msgid="2984256114867200368">"Aan"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Niet beschikbaar"</item>
     <item msgid="8707481475312432575">"Uit"</item>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index b0f5022..afe1e66 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ଚାଲୁ‌ କରନ୍ତୁ"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"ଚାଲୁ କରନ୍ତୁ"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"ନା, ଧନ୍ୟବାଦ"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"ଷ୍ଟାଣ୍ଡାର୍ଡ"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"ଏକ୍ସଟ୍ରିମ୍"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"ଅଟୋ-ରୋଟେଟ ସ୍କ୍ରିନ"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> ଆକ୍ସେସ୍‍ କରିବାକୁ <xliff:g id="APPLICATION">%1$s</xliff:g>କୁ ଅନୁମତି ଦେବେ?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> ଆକ୍ସେସ୍ କରିବାକୁ <xliff:g id="APPLICATION">%1$s</xliff:g>କୁ ଅନୁମତି ଦେବେ କି?\nଏହି ଆପ୍‌କୁ ରେକର୍ଡ କରିବାକୁ ଅନୁମତି ଦିଆଯାଇ ନାହିଁ କିନ୍ତୁ ଏହି USB ଡିଭାଇସ୍ ଜରିଆରେ ଅଡିଓ କ୍ୟାପ୍ଟର୍ କରିପାରିବ।"</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ଡିସକନେକ୍ଟ କରନ୍ତୁ"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ଚାଲୁ କରନ୍ତୁ"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"ଆସନ୍ତାକାଲି ସ୍ୱତଃ ପୁଣି ଚାଲୁ ହେବ"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Quick Share, Find My Device ଏବଂ ଡିଭାଇସ ଲୋକେସନ ପରି ଫିଚରଗୁଡ଼ିକ ବ୍ଲୁଟୁଥ ବ୍ୟବହାର କରେ"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ବ୍ୟାଟେରୀ"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ଅଡିଓ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ହେଡସେଟ୍‍"</string>
@@ -287,7 +292,7 @@
     <string name="quick_settings_media_device_label" msgid="8034019242363789941">"ମିଡିଆ ଡିଭାଇସ୍‌"</string>
     <string name="quick_settings_user_title" msgid="8673045967216204537">"ୟୁଜର"</string>
     <string name="quick_settings_wifi_label" msgid="2879507532983487244">"ୱାଇ-ଫାଇ"</string>
-    <string name="quick_settings_internet_label" msgid="6603068555872455463">"ଇଣ୍ଟରନେଟ"</string>
+    <string name="quick_settings_internet_label" msgid="6603068555872455463">"ଇଣ୍ଟର୍ନେଟ"</string>
     <string name="quick_settings_networks_available" msgid="1875138606855420438">"ନେଟୱାର୍କଗୁଡ଼ିକ ଉପଲବ୍ଧ"</string>
     <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"ନେଟୱାର୍କ ଉପଲବ୍ଧ ନାହିଁ"</string>
     <string name="quick_settings_wifi_detail_empty_text" msgid="483130889414601732">"କୌଣସି ୱାଇ-ଫାଇ ନେଟ୍‌ୱର୍କ ଉପଲବ୍ଧ ନାହିଁ"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ଅଧିକ ୱିଜେଟ ଯୋଗ କରନ୍ତୁ"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ୱିଜେଟଗୁଡ଼ିକୁ କଷ୍ଟମାଇଜ କରିବା ପାଇଁ ଅଧିକ ସମୟ ଦବାନ୍ତୁ"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ୱିଜେଟଗୁଡ଼ିକୁ କଷ୍ଟମାଇଜ କରନ୍ତୁ"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ଅକ୍ଷମ କରାଯାଇଥିବା ୱିଜେଟ ପାଇଁ ଆପ ଆଇକନ"</string>
     <string name="edit_widget" msgid="9030848101135393954">"ୱିଜେଟକୁ ଏଡିଟ କରନ୍ତୁ"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"କାଢ଼ି ଦିଅନ୍ତୁ"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ୱିଜେଟ ଯୋଗ କରନ୍ତୁ"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"ରିଙ୍ଗ"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"ଭାଇବ୍ରେଟ୍‌"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"ମ୍ୟୁଟ"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"କାଷ୍ଟ କରନ୍ତୁ"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"ରିଂକୁ ମ୍ୟୁଟ କରାଯାଇଥିବା ଯୋଗୁଁ ଉପଲବ୍ଧ ନାହିଁ"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s। ଅନମ୍ୟୁଟ୍‍ କରିବା ପାଇଁ ଟାପ୍‍ କରନ୍ତୁ।"</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s। ଭାଇବ୍ରେଟ୍‍ ସେଟ୍‍ କରିବାକୁ ଟାପ୍‍ କରନ୍ତୁ। ଆକ୍ସେସିବିଲିଟୀ ସର୍ଭିସ୍‌ ମ୍ୟୁଟ୍‍ କରାଯାଇପାରେ।"</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s। ମ୍ୟୁଟ୍‍ କରିବାକୁ ଟାପ୍‍ କରନ୍ତୁ। ଆକ୍ସେସିବିଲିଟୀ ସର୍ଭିସ୍‌ ମ୍ୟୁଟ୍‍ କରାଯାଇପାରେ।"</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s। ଭାଇବ୍ରେଟରେ ସେଟ୍‍ କରିବାକୁ ଟାପ୍‍ କରନ୍ତୁ।"</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s। ମ୍ୟୁଟ୍‍ କରିବାକୁ ଟାପ୍‍ କରନ୍ତୁ।"</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"ନଏଜ କଣ୍ଟ୍ରୋଲ"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"ରିଙ୍ଗର୍ ମୋଡ୍ ବଦଳାଇବାକୁ ଟାପ୍ କରନ୍ତୁ"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"ମ୍ୟୁଟ"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ଅନ୍‍-ମ୍ୟୁଟ୍ କରନ୍ତୁ"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"ପାୱାର ମେନୁ"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"ପୃଷ୍ଠା <xliff:g id="ID_1">%1$d</xliff:g> ମୋଟ <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"ଲକ ସ୍କ୍ରିନ"</string>
+    <string name="finder_active" msgid="7907846989716941952">"ପାୱାର ବନ୍ଦ ଥିଲେ ମଧ୍ୟ ଆପଣ Find My Device ମାଧ୍ୟମରେ ଏହି ଫୋନକୁ ଖୋଜିପାରିବେ"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"ବନ୍ଦ କରାଯାଉଛି…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"ଯତ୍ନ ନେବା ପାଇଁ ଷ୍ଟେପଗୁଡ଼ିକ ଦେଖନ୍ତୁ"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ଯତ୍ନ ନେବା ପାଇଁ ଷ୍ଟେପଗୁଡ଼ିକ ଦେଖନ୍ତୁ"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"ଆପଣଙ୍କ ଡିଭାଇସକୁ ଅନପ୍ଲଗ କରନ୍ତୁ"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"ନୋଟ-ଟେକିଂ"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"ନୋଟ-ଟେକିଂ, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"ଅଡିଓ ସେୟାର କରାଯାଉଛି"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ବ୍ରଡକାଷ୍ଟ କରୁଛି"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ବ୍ରଡକାଷ୍ଟ କରିବା ବନ୍ଦ କରିବେ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"ଯଦି ଆପଣ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ବ୍ରଡକାଷ୍ଟ କରନ୍ତି କିମ୍ବା ଆଉଟପୁଟ ବଦଳାନ୍ତି, ତେବେ ଆପଣଙ୍କ ବର୍ତ୍ତମାନର ବ୍ରଡକାଷ୍ଟ ବନ୍ଦ ହୋଇଯିବ"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ଉଚ୍ଚ ରିଜୋଲ୍ୟୁସନ ପାଇଁ ଫୋନକୁ ଫ୍ଲିପ କରନ୍ତୁ"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ଫୋଲ୍ଡ କରାଯାଇପାରୁଥିବା ଡିଭାଇସକୁ ଅନଫୋଲ୍ଡ କରାଯାଉଛି"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ଫୋଲ୍ଡ କରାଯାଇପାରୁଥିବା ଡିଭାଇସକୁ ଫ୍ଲିପ କରାଯାଉଛି"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ବେଟେରୀ ଚାର୍ଜ ବାକି ଅଛି"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ଏକ ଚାର୍ଜର ସହ ଆପଣଙ୍କ ଷ୍ଟାଇଲସକୁ କନେକ୍ଟ କରନ୍ତୁ"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"ଷ୍ଟାଇଲସ ବେଟେରୀର ଚାର୍ଜ କମ ଅଛି"</string>
diff --git a/packages/SystemUI/res/values-or/tiles_states_strings.xml b/packages/SystemUI/res/values-or/tiles_states_strings.xml
index d362c65f..046db2f 100644
--- a/packages/SystemUI/res/values-or/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-or/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"ବନ୍ଦ ଅଛି"</item>
     <item msgid="578444932039713369">"ଚାଲୁ ଅଛି"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"ଅନୁପଲବ୍ଧ"</item>
+    <item msgid="9061144428113385092">"ବନ୍ଦ ଅଛି"</item>
+    <item msgid="2984256114867200368">"ଚାଲୁ ଅଛି"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"ଉପଲବ୍ଧ ନାହିଁ"</item>
     <item msgid="8707481475312432575">"ବନ୍ଦ ଅଛି"</item>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 6916fd2..8fda667 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ਚਾਲੂ ਕਰੋ"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"ਚਾਲੂ ਕਰੋ"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"ਨਹੀਂ ਧੰਨਵਾਦ"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"ਮਿਆਰੀ"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"ਐਕਸਟ੍ਰੀਮ"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"ਸਕ੍ਰੀਨ ਸਵੈ-ਘੁਮਾਓ"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"ਕੀ <xliff:g id="USB_DEVICE">%2$s</xliff:g> ਤੱਕ <xliff:g id="APPLICATION">%1$s</xliff:g> ਨੂੰ ਪਹੁੰਚ ਕਰਨ ਦੇਣੀ ਹੈ?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"ਕੀ <xliff:g id="APPLICATION">%1$s</xliff:g> ਨੂੰ <xliff:g id="USB_DEVICE">%2$s</xliff:g> ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੇਣੀ ਹੈ?\nਇਸ ਐਪ ਨੂੰ ਰਿਕਾਰਡ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਦਿੱਤੀ ਗਈ ਪਰ ਇਹ USB ਡੀਵਾਈਸ ਰਾਹੀਂ ਆਡੀਓ ਕੈਪਚਰ ਕਰ ਸਕਦੀ ਹੈ।"</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ਡਿਸਕਨੈਕਟ ਕਰੋ"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ਕਿਰਿਆਸ਼ੀਲ ਕਰੋ"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"ਕੱਲ੍ਹ ਨੂੰ ਆਪਣੇ ਆਪ ਚਾਲੂ ਹੋ ਜਾਵੇਗਾ"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"ਕਵਿੱਕ ਸ਼ੇਅਰ, Find My Device ਅਤੇ ਡੀਵਾਈਸ ਦਾ ਟਿਕਾਣਾ ਵਰਗੀਆਂ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਬਲੂਟੁੱਥ ਦੀ ਵਰਤੋਂ ਕਰਦੀਆਂ ਹਨ"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ਬੈਟਰੀ"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ਆਡੀਓ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ਹੈੱਡਸੈੱਟ"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ਹੋਰ ਵਿਜੇਟ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ਵਿਜੇਟਾਂ ਨੂੰ ਵਿਉਂਤਬੱਧ ਕਰਨ ਲਈ ਦਬਾਈ ਰੱਖੋ"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ਵਿਜੇਟ ਵਿਉਂਤਬੱਧ ਕਰੋ"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ਬੰਦ ਵਿਜੇਟ ਲਈ ਐਪ ਪ੍ਰਤੀਕ"</string>
     <string name="edit_widget" msgid="9030848101135393954">"ਵਿਜੇਟ ਦਾ ਸੰਪਾਦਨ ਕਰੋ"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ਹਟਾਓ"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ਵਿਜੇਟ ਸ਼ਾਮਲ ਕਰੋ"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"ਘੰਟੀ"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"ਥਰਥਰਾਹਟ"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"ਮਿਊਟ"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"ਕਾਸਟ ਕਰੋ"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"ਉਪਲਬਧ ਨਹੀਂ ਹੈ ਕਿਉਂਕਿ ਘੰਟੀ ਮਿਊਟ ਕੀਤੀ ਹੋਈ ਹੈ"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s। ਅਣਮਿਊਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s। ਥਰਥਰਾਹਟ ਸੈੱਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ। ਪਹੁੰਚਯੋਗਤਾ ਸੇਵਾਵਾਂ ਮਿਊਟ ਹੋ ਸਕਦੀਆਂ ਹਨ।"</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s। ਮਿਊਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ। ਪਹੁੰਚਯੋਗਤਾ ਸੇਵਾਵਾਂ ਮਿਊਟ ਹੋ ਸਕਦੀਆਂ ਹਨ।"</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s। ਥਰਥਰਾਹਟ \'ਤੇ ਸੈੱਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s। ਮਿਊਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"ਸ਼ੋਰ ਨੂੰ ਕੰਟਰੋਲ ਕਰੋ"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"ਰਿੰਗਰ ਮੋਡ ਨੂੰ ਬਦਲਣ ਲਈ ਟੈਪ ਕਰੋ"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"ਮਿਊਟ ਕਰੋ"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ਅਣਮਿਊਟ ਕਰੋ"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"ਪਾਵਰ ਮੀਨੂ"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g> ਦਾ <xliff:g id="ID_1">%1$d</xliff:g> ਪੰਨਾ"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">" ਲਾਕ  ਸਕ੍ਰੀਨ"</string>
+    <string name="finder_active" msgid="7907846989716941952">"ਬੰਦ ਹੋਣ \'ਤੇ ਵੀ, ਤੁਸੀਂ ਇਸ ਫ਼ੋਨ ਨੂੰ Find My Device ਦੀ ਮਦਦ ਨਾਲ ਲੱਭ ਸਕਦੇ ਹੋ"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"ਬੰਦ ਹੋ ਰਿਹਾ ਹੈ…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"ਦੇਖਭਾਲ ਦੇ ਪੜਾਅ ਦੇਖੋ"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ਦੇਖਭਾਲ ਦੇ ਪੜਾਅ ਦੇਖੋ"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"ਆਪਣਾ ਡੀਵਾਈਸ ਅਣਪਲੱਗ ਕਰੋ"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"ਨੋਟ ਬਣਾਉਣਾ"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"ਨੋਟ ਬਣਾਉਣਾ, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"ਆਡੀਓ ਨੂੰ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ਪ੍ਰਸਾਰਨ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"ਕੀ <xliff:g id="APP_NAME">%1$s</xliff:g> ਦੇ ਪ੍ਰਸਾਰਨ ਨੂੰ ਰੋਕਣਾ ਹੈ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"ਜੇ ਤੁਸੀਂ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ਦਾ ਪ੍ਰਸਾਰਨ ਕਰਦੇ ਹੋ ਜਾਂ ਆਊਟਪੁੱਟ ਬਦਲਦੇ ਹੋ, ਤਾਂ ਤੁਹਾਡਾ ਮੌਜੂਦਾ ਪ੍ਰਸਾਰਨ ਰੁਕ ਜਾਵੇਗਾ"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ਉੱਚ ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਲਈ, ਫ਼ੋਨ ਨੂੰ ਫਲਿੱਪ ਕਰੋ"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ਮੋੜਨਯੋਗ ਡੀਵਾਈਸ ਨੂੰ ਖੋਲ੍ਹਿਆ ਜਾ ਰਿਹਾ ਹੈ"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ਮੋੜਨਯੋਗ ਡੀਵਾਈਸ ਨੂੰ ਆਲੇ-ਦੁਆਲੇ ਫਲਿੱਪ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ਬੈਟਰੀ ਬਾਕੀ"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ਆਪਣੇ ਸਟਾਈਲਸ ਨੂੰ ਚਾਰਜਰ ਨਾਲ ਕਨੈਕਟ ਕਰੋ"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"ਸਟਾਈਲਸ ਦੀ ਬੈਟਰੀ ਘੱਟ ਹੈ"</string>
diff --git a/packages/SystemUI/res/values-pa/tiles_states_strings.xml b/packages/SystemUI/res/values-pa/tiles_states_strings.xml
index f249afb..df870cd 100644
--- a/packages/SystemUI/res/values-pa/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-pa/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"ਬੰਦ ਹੈ"</item>
     <item msgid="578444932039713369">"ਚਾਲੂ ਹੈ"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"ਉਪਲਬਧ ਨਹੀਂ"</item>
+    <item msgid="9061144428113385092">"ਬੰਦ"</item>
+    <item msgid="2984256114867200368">"ਚਾਲੂ"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"ਅਣਉਪਲਬਧ ਹੈ"</item>
     <item msgid="8707481475312432575">"ਬੰਦ ਹੈ"</item>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index c04d6da..cfbfa8a 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Włącz"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Włącz"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nie, dziękuję"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standardowe"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Intensywne"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Autoobracanie ekranu"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Zezwolić aplikacji <xliff:g id="APPLICATION">%1$s</xliff:g> na dostęp do: <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Zezwolić aplikacji <xliff:g id="APPLICATION">%1$s</xliff:g> na dostęp do urządzenia <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nTa aplikacja nie ma uprawnień do nagrywania, ale może rejestrować dźwięk za pomocą tego urządzenia USB."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"rozłącz"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktywuj"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatycznie włącz ponownie jutro"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funkcje takie jak Szybkie udostępnianie, Znajdź moje urządzenie i dotyczące lokalizacji urządzenia używają Bluetootha"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> naładowania baterii"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Dźwięk"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Zestaw słuchawkowy"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Dodaj więcej widżetów"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Przytrzymaj, aby dostosować widżety"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Dostosuj widżety"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacji z wyłączonym widżetem"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Edytuj widżet"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Usuń"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodaj widżet"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Dzwonek"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Wibracje"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Wyciszenie"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Przesyłanie"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Niedostępne, bo dzwonek jest wyciszony"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Kliknij, by wyłączyć wyciszenie."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Kliknij, by włączyć wibracje. Ułatwienia dostępu mogą być wyciszone."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Kliknij, by wyciszyć. Ułatwienia dostępu mogą być wyciszone."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Kliknij, by włączyć wibracje."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Kliknij, by wyciszyć."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Tłumienie szumów"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Kliknij, aby zmienić tryb dzwonka"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"wycisz"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"wyłącz wyciszenie"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menu zasilania"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Strona <xliff:g id="ID_1">%1$d</xliff:g> z <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Ekran blokady"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Możesz zlokalizować ten telefon w usłudze Znajdź moje urządzenie, nawet jeśli będzie wyłączony"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Wyłączam…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Zobacz instrukcję postępowania"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Zobacz instrukcję postępowania"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Odłącz urządzenie"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Notatki"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Notatki, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Udostępnia dźwięk"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Transmisja"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Zatrzymaj transmisję aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Jeśli transmitujesz aplikację <xliff:g id="SWITCHAPP">%1$s</xliff:g> lub zmieniasz dane wyjściowe, Twoja obecna transmisja zostanie zakończona"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Odwróć telefon, aby uzyskać wyższą rozdzielczość"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Składane urządzenie jest rozkładane"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Składane urządzenie jest obracane"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"po zamknięciu"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"po otwarciu"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Pozostało <xliff:g id="PERCENTAGE">%s</xliff:g> baterii"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Podłącz rysik do ładowarki"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Słaba bateria w rysiku"</string>
diff --git a/packages/SystemUI/res/values-pl/tiles_states_strings.xml b/packages/SystemUI/res/values-pl/tiles_states_strings.xml
index ea6ad58..c667b99 100644
--- a/packages/SystemUI/res/values-pl/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-pl/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Wyłączony"</item>
     <item msgid="578444932039713369">"Włączony"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Niedostępne"</item>
+    <item msgid="9061144428113385092">"Wyłączono"</item>
+    <item msgid="2984256114867200368">"Włączono"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Niedostępne"</item>
     <item msgid="8707481475312432575">"Wyłączone"</item>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index e82a103..381c3bd 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Ativar"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Ativar"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Agora não"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Padrão"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extrema"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Giro automático da tela"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Permitir que o app <xliff:g id="APPLICATION">%1$s</xliff:g> acesse <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Permitir que <xliff:g id="APPLICATION">%1$s</xliff:g> acesse <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nEsse app não tem permissão de gravação, mas pode capturar áudio pelo dispositivo USB."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ativar"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Ativar automaticamente de novo amanhã"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Recursos como o Quick Share, o Encontre Meu Dispositivo e a localização do dispositivo usam o Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Áudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Fone de ouvido"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Adicione mais widgets"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantenha pressionado para personalizar widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ícone do app para widget desativado"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Remover"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adicionar widget"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Tocar"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrar"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Desativar som"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Transmitir"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Indisponível com o toque foi silenciado"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Toque para ativar o som."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Toque para configurar para vibrar. É possível que os serviços de acessibilidade sejam silenciados."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Toque para silenciar. É possível que os serviços de acessibilidade sejam silenciados."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Toque para configurar para vibrar."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Toque para silenciar."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Controle de ruído"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Toque para mudar o modo da campainha"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"desativar o som"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ativar o som"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menu liga/desliga"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Página <xliff:g id="ID_1">%1$d</xliff:g> de <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Tela de bloqueio"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Localize o smartphone com o Encontre Meu Dispositivo mesmo se ele estiver desligado"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Desligando…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Ver etapas de cuidado"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ver etapas de cuidado"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Desconecte seu dispositivo"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Anotações"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Anotações, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Compartilhando áudio"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Transmitindo"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Interromper a transmissão do app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Se você transmitir o app <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou mudar a saída, a transmissão atual será interrompida"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para uma resolução maior, vire o smartphone"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo dobrável sendo aberto"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo dobrável sendo virado"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"fechado"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"aberto"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Bateria restante: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conecte sua stylus a um carregador"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Bateria da stylus fraca"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/tiles_states_strings.xml b/packages/SystemUI/res/values-pt-rBR/tiles_states_strings.xml
index 28c07f4..ae2bd05 100644
--- a/packages/SystemUI/res/values-pt-rBR/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Desativada"</item>
     <item msgid="578444932039713369">"Ativada"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Indisponível"</item>
+    <item msgid="9061144428113385092">"Desativado"</item>
+    <item msgid="2984256114867200368">"Ativado"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Indisponível"</item>
     <item msgid="8707481475312432575">"Desativado"</item>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 8ba907b..607a4aa 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Ativar"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Ativar"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Não"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Padrão"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extrema"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Rodar ecrã automaticamente"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Permitir que a app <xliff:g id="APPLICATION">%1$s</xliff:g> aceda ao dispositivo <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Permitir que <xliff:g id="APPLICATION">%1$s</xliff:g> aceda a <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nEsta app não recebeu autorização de gravação, mas pode capturar áudio através deste dispositivo USB."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desassociar"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ativar"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Reativar amanhã automaticamente"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"As funcionalidades como Partilha rápida, Localizar o meu dispositivo e localização do dispositivo usam o Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de bateria"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Áudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ausc. c/ mic. integ."</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Adicionar mais widgets"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantenha premido para personalizar os widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ícone da app do widget desativado"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Remover"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adicionar widget"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Toque"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrar"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Desativar som"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Transmitir"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Indisponível porque o toque está desat."</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Toque para reativar o som."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Toque para ativar a vibração. Os serviços de acessibilidade podem ser silenciados."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Toque para desativar o som. Os serviços de acessibilidade podem ser silenciados."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Toque para ativar a vibração."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Toque para desativar o som."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Controlo de ruído"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Toque para alterar o modo de campainha"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"desativar som"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"reativar som"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menu ligar/desligar"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Página <xliff:g id="ID_1">%1$d</xliff:g> de <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Ecrã de bloqueio"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Pode localizar este telemóvel com o serviço Localizar o meu dispositivo mesmo quando está desligado"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"A encerrar…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Veja os passos de manutenção"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Veja os passos de manutenção"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Desligue o dispositivo"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Tomar notas"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Tomar notas, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"A partilhar áudio"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"A transmitir"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Interromper a transmissão da app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Se transmitir a app <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou alterar a saída, a sua transmissão atual é interrompida"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para uma resolução superior, inverta o telemóvel"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo dobrável a ser desdobrado"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo dobrável a ser virado ao contrário"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"fechado"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"aberto"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> de bateria restante"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Ligue a caneta stylus a um carregador"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Bateria da caneta stylus fraca"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/tiles_states_strings.xml b/packages/SystemUI/res/values-pt-rPT/tiles_states_strings.xml
index b58b848..666963b 100644
--- a/packages/SystemUI/res/values-pt-rPT/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Desligado"</item>
     <item msgid="578444932039713369">"Ligado"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Indisponível"</item>
+    <item msgid="9061144428113385092">"Desativado"</item>
+    <item msgid="2984256114867200368">"Ativado"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Indisponível"</item>
     <item msgid="8707481475312432575">"Desligado"</item>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index e82a103..381c3bd 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Ativar"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Ativar"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Agora não"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Padrão"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extrema"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Giro automático da tela"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Permitir que o app <xliff:g id="APPLICATION">%1$s</xliff:g> acesse <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Permitir que <xliff:g id="APPLICATION">%1$s</xliff:g> acesse <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nEsse app não tem permissão de gravação, mas pode capturar áudio pelo dispositivo USB."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ativar"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Ativar automaticamente de novo amanhã"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Recursos como o Quick Share, o Encontre Meu Dispositivo e a localização do dispositivo usam o Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Áudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Fone de ouvido"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Adicione mais widgets"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantenha pressionado para personalizar widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ícone do app para widget desativado"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Remover"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adicionar widget"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Tocar"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrar"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Desativar som"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Transmitir"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Indisponível com o toque foi silenciado"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Toque para ativar o som."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Toque para configurar para vibrar. É possível que os serviços de acessibilidade sejam silenciados."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Toque para silenciar. É possível que os serviços de acessibilidade sejam silenciados."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Toque para configurar para vibrar."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Toque para silenciar."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Controle de ruído"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Toque para mudar o modo da campainha"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"desativar o som"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ativar o som"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menu liga/desliga"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Página <xliff:g id="ID_1">%1$d</xliff:g> de <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Tela de bloqueio"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Localize o smartphone com o Encontre Meu Dispositivo mesmo se ele estiver desligado"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Desligando…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Ver etapas de cuidado"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ver etapas de cuidado"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Desconecte seu dispositivo"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Anotações"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Anotações, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Compartilhando áudio"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Transmitindo"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Interromper a transmissão do app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Se você transmitir o app <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou mudar a saída, a transmissão atual será interrompida"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para uma resolução maior, vire o smartphone"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo dobrável sendo aberto"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo dobrável sendo virado"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"fechado"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"aberto"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Bateria restante: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conecte sua stylus a um carregador"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Bateria da stylus fraca"</string>
diff --git a/packages/SystemUI/res/values-pt/tiles_states_strings.xml b/packages/SystemUI/res/values-pt/tiles_states_strings.xml
index 28c07f4..ae2bd05 100644
--- a/packages/SystemUI/res/values-pt/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-pt/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Desativada"</item>
     <item msgid="578444932039713369">"Ativada"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Indisponível"</item>
+    <item msgid="9061144428113385092">"Desativado"</item>
+    <item msgid="2984256114867200368">"Ativado"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Indisponível"</item>
     <item msgid="8707481475312432575">"Desativado"</item>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 8cd2d77..857a167 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Activează"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Activează"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nu, mulțumesc"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extremă"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Rotire automată a ecranului"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Permiți ca <xliff:g id="APPLICATION">%1$s</xliff:g> să acceseze <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Permiți accesul aplicației <xliff:g id="APPLICATION">%1$s</xliff:g> la <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nPermisiunea de înregistrare nu a fost acordată aplicației, dar aceasta poate să înregistreze conținut audio prin intermediul acestui dispozitiv USB."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"deconectează"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activează"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Activează din nou automat mâine"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funcții precum Quick Share, Găsește-mi dispozitivul și locația dispozitivului folosesc Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Nivelul bateriei: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Căști"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Adaugă mai multe widgeturi"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Apasă lung pentru a personaliza widgeturi"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizează widgeturile"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Pictograma aplicației pentru widgetul dezactivat"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Editează widgetul"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Elimină"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adaugă un widget"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Sonerie"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrații"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Blochează"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Proiectează"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Indisponibil; soneria este dezactivată"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Atinge pentru a activa sunetul."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Atinge pentru a seta vibrarea. Sunetul se poate dezactiva pentru serviciile de accesibilitate."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Atinge pentru a dezactiva sunetul. Sunetul se poate dezactiva pentru serviciile de accesibilitate."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Atinge pentru a seta pe vibrații."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Atinge pentru a dezactiva sunetul."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Controlul zgomotului"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Atinge pentru a schimba modul soneriei"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"dezactivează sunetul"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"activează sunetul"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Meniul de pornire"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Pagina <xliff:g id="ID_1">%1$d</xliff:g> din <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Ecran de blocare"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Poți localiza telefonul folosind aplicația Găsește-mi dispozitivul chiar dacă este închis"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Se închide…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Vezi pașii pentru îngrijire"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Vezi pașii pentru îngrijire"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Deconectează dispozitivul"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Luare de notițe"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Luare de notițe, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Se permite accesul la conținutul audio"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Se difuzează"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Oprești transmisia <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Dacă transmiți <xliff:g id="SWITCHAPP">%1$s</xliff:g> sau schimbi ieșirea, transmisia actuală se va opri"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Pentru o rezoluție mai mare, deschide telefonul"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispozitiv pliabil care este desfăcut"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispozitiv pliabil care este întors"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"închis"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"deschis"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> baterie rămasă"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conectează-ți creionul la un încărcător"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Nivelul bateriei creionului este scăzut"</string>
diff --git a/packages/SystemUI/res/values-ro/tiles_states_strings.xml b/packages/SystemUI/res/values-ro/tiles_states_strings.xml
index 5a5eb9f..ed8285d 100644
--- a/packages/SystemUI/res/values-ro/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ro/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Dezactivată"</item>
     <item msgid="578444932039713369">"Activată"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Indisponibil"</item>
+    <item msgid="9061144428113385092">"Dezactivat"</item>
+    <item msgid="2984256114867200368">"Activat"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Indisponibilă"</item>
     <item msgid="8707481475312432575">"Dezactivată"</item>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 614eb06..87150cf 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Включить"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Включить"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Нет"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Стандартный режим"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Предельное"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Автоповорот экрана"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Предоставить приложению \"<xliff:g id="APPLICATION">%1$s</xliff:g>\" доступ к устройству \"<xliff:g id="USB_DEVICE">%2$s</xliff:g>\"?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Предоставить приложению \"<xliff:g id="APPLICATION">%1$s</xliff:g>\" доступ к устройству \"<xliff:g id="USB_DEVICE">%2$s</xliff:g>\"?\nПриложению не разрешено вести запись, однако с помощью этого USB-устройства оно может записывать звук."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"отключить"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активировать"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Включить завтра автоматически"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Bluetooth используется в сервисе \"Найти устройство\", таких функциях, как Быстрая отправка, и при определении местоположения устройства"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудиоустройство"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнитура"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Добавить виджеты"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Нажмите и удерживайте, чтобы настроить виджеты."</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Настроить виджеты"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Значок приложения для отключенного виджета"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Изменить виджет"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Удалить"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Добавить виджет"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Со звуком"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Вибрация"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Без звука"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Трансляция"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Недоступно, когда отключен звук вызовов"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Нажмите, чтобы включить звук."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Нажмите, чтобы включить вибрацию. Специальные возможности могут прекратить работу."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Нажмите, чтобы выключить звук. Специальные возможности могут прекратить работу."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Нажмите, чтобы включить вибрацию."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Нажмите, чтобы выключить звук."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Контроль уровня шума"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Нажмите, чтобы изменить режим звонка."</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"отключить звук"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"включить звук"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Меню кнопки питания"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Страница <xliff:g id="ID_1">%1$d</xliff:g> из <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Заблокированный экран"</string>
+    <string name="finder_active" msgid="7907846989716941952">"С помощью приложения \"Найти устройство\" вы можете узнать местоположение телефона, даже когда он выключен."</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Выключение…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Подробнее о действиях при перегреве…"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Подробнее о действиях при перегреве…"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Отключите устройство"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Создание заметок"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>: создание заметок"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Передает аудио"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Трансляция"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Остановить трансляцию \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Если вы начнете транслировать \"<xliff:g id="SWITCHAPP">%1$s</xliff:g>\" или смените целевое устройство, текущая трансляция прервется."</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Переверните телефон и используйте основную камеру, чтобы делать снимки с более высоким разрешением."</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Складное устройство в разложенном виде"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Перевернутое складное устройство"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"устройство сложено"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"устройство разложено"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Уровень заряда батареи: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Поставьте стилус на зарядку."</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Низкий заряд батареи стилуса"</string>
diff --git a/packages/SystemUI/res/values-ru/tiles_states_strings.xml b/packages/SystemUI/res/values-ru/tiles_states_strings.xml
index cd14079..48ecf26 100644
--- a/packages/SystemUI/res/values-ru/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ru/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Откл."</item>
     <item msgid="578444932039713369">"Вкл."</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Недоступно"</item>
+    <item msgid="9061144428113385092">"Отключено"</item>
+    <item msgid="2984256114867200368">"Включено"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Функция недоступна"</item>
     <item msgid="8707481475312432575">"Откл."</item>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index d1bcee4..e72f1ce 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ක්‍රියාත්මක කරන්න"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"ක්‍රියාත්මක කරන්න"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"එපා ස්තුතියි"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"සම්මත"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"අතිශය"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"ස්වයංක්‍රීයව-භ්‍රමණය වන තිරය"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> හට <xliff:g id="USB_DEVICE">%2$s</xliff:g> වෙත පිවිසීමට ඉඩ දෙන්නද?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> වෙත ප්‍රවේශ වීමට <xliff:g id="USB_DEVICE">%2$s</xliff:g> හට ඉඩ දෙන්නද?\n මෙම යෙදුමට පටිගත කිරීම් අවසරයක් ලබා දී නොමැති නමුත් මෙම USB උපාංගය හරහා ශ්‍රව්‍ය ග්‍රහණය කර ගත හැකිය."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"විසන්ධි කරන්න"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"සක්‍රිය කරන්න"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"හෙට ස්වයංක්‍රීයව නැවත ක්‍රියාත්මක කරන්න"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"ඉක්මන් බෙදා ගැනීම, මගේ උපාංගය සෙවීම, සහ උපාංග ස්ථානය වැනි විශේෂාංග බ්ලූටූත් භාවිත කරයි"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"බැටරිය <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ශ්‍රව්‍ය"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"හෙඩ්සෙටය"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"තවත් විජට් එක් කරන්න"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"විජට් අභිරුචිකරණය කිරීමට දිගු ඔබන්න"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"විජට්ටු අභිරුචි කරන්න"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"අබල කළ විජට් සඳහා යෙදුම් නිරූපකය"</string>
     <string name="edit_widget" msgid="9030848101135393954">"විජට්ටු සංස්කරණ කරන්න"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ඉවත් කරන්න"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"විජට්ටුව එක් කරන්න"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"නාද කරන්න"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"කම්පනය කරන්න"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"නිහඬ කරන්න"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"විකාශය"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"නාදය නිහඬ කර ඇති නිසා නොලැබේ"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. නිහඬ කිරීම ඉවත් කිරීමට තට්ටු කරන්න."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. කම්පනය කිරීමට තට්ටු කරන්න. ප්‍රවේශ්‍යතා සේවා නිහඬ කළ හැකිය."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. නිහඬ කිරීමට තට්ටු කරන්න. ප්‍රවේශ්‍යතා සේවා නිහඬ කළ හැකිය."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. කම්පනය කිරීමට සකස් කිරීමට තට්ටු කරන්න."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. නිහඬ කිරීමට තට්ටු කරන්න."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"ඝෝෂාව පාලනය"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"නාදකය වෙනස් කිරීමට තට්ටු කරන්න"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"නිහඬ කරන්න"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"නිශ්ශබ්දතාවය ඉවත් කරන්න"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"බල මෙනුව"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g> න් <xliff:g id="ID_1">%1$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"අගුලු තිරය"</string>
+    <string name="finder_active" msgid="7907846989716941952">"බලය ක්‍රියාවිරහිත වූ විට පවා ඔබට මගේ උපාංගය සෙවීම මගින් මෙම දුරකථනය සොයාගත හැක"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"වසා දමමින්…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"රැකවරණ පියවර බලන්න"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"රැකවරණ පියවර බලන්න"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"ඔබේ උපාංගය ගලවන්න"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"සටහන් කර ගැනීම"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"සටහන් කර ගැනීම, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"ශ්‍රව්‍ය බෙදා ගැනීම"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"විකාශනය කරමින්"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> විකාශනය කිරීම නවත්වන්නද?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"ඔබ <xliff:g id="SWITCHAPP">%1$s</xliff:g> විකාශනය කළහොත් හෝ ප්‍රතිදානය වෙනස් කළහොත්, ඔබගේ වත්මන් විකාශනය නවතිනු ඇත."</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ඉහළ විභේදනය සඳහා, දුරකථනය හරවන්න"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"දිග හැරෙමින් පවතින නැමිය හැකි උපාංගය"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"වටා පෙරළෙමින් තිබෙන නැමිය හැකි උපාංගය"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> බැටරිය ඉතිරිව ඇත"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ඔබේ පන්හිඳ චාජරයකට සම්බන්ධ කරන්න"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"පන්හිඳ බැටරිය අඩුයි"</string>
diff --git a/packages/SystemUI/res/values-si/tiles_states_strings.xml b/packages/SystemUI/res/values-si/tiles_states_strings.xml
index fcd768b..cbbc0e7 100644
--- a/packages/SystemUI/res/values-si/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-si/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"අක්‍රියයි"</item>
     <item msgid="578444932039713369">"සක්‍රියයි"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"නොමැත"</item>
+    <item msgid="9061144428113385092">"ක්‍රියාවිරහිතයි"</item>
+    <item msgid="2984256114867200368">"ක්‍රියාත්මකයි"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"නොමැත"</item>
     <item msgid="8707481475312432575">"අක්‍රියයි"</item>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 3e2b554..6614c0d 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Zapnúť"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Zapnúť"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nie, vďaka"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Štandardný"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Super"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Automatické otočenie obrazovky"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Povoliť aplikácii <xliff:g id="APPLICATION">%1$s</xliff:g> prístup k zariadeniu <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Povoliť aplikácii <xliff:g id="APPLICATION">%1$s</xliff:g> pristupovať k zariadeniu <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nTejto aplikácii nebolo udelené povolenie na nahrávanie, môže však snímať zvuk cez toto zariadenie USB."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"odpojiť"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivovať"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automaticky zajtra znova zapnúť"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funkcie, ako sú Quick Share, Nájdi moje zariadenie a poloha zariadenia, používajú Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Batéria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvuk"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Náhlavná súprava"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Pridať ďalšie miniaplikácie"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Miniaplikácie prispôsobíte dlhým stlačením"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prispôsobiť miniaplikácie"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona deaktivovanej miniaplikácie"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Upraviť miniaplikáciu"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Odstrániť"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Pridať miniaplikáciu"</string>
@@ -530,7 +536,7 @@
     <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"Toto zariadenie spravuje tvoj rodič. Vidí a môže spravovať informácie, napríklad aplikácie, ktoré používaš, tvoju polohu a čas používania."</string>
     <string name="legacy_vpn_name" msgid="4174223520162559145">"VPN"</string>
     <string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"Odomknutie udržiava TrustAgent"</string>
-    <string name="kg_prompt_after_adaptive_auth_lock" msgid="1265107698772588299">"Ochrana pred krádež.\nZar. uzamknuté, priveľa pokusov o odomk."</string>
+    <string name="kg_prompt_after_adaptive_auth_lock" msgid="1265107698772588299">"Ochrana pred krádežou\nUzamknuté, priveľa pokusov o odomknutie"</string>
     <string name="zen_mode_and_condition" msgid="5043165189511223718">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
     <string name="accessibility_volume_settings" msgid="1458961116951564784">"Nastavenia zvuku"</string>
     <string name="volume_odi_captions_tip" msgid="8825655463280990941">"Automatické titulkovanie médií"</string>
@@ -573,19 +579,30 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Prezvoniť"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrovať"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Vypnúť zvuk"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Prenos"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Nedostupné, pretože je vypnuté zvonenie"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Klepnutím zapnite zvuk."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Klepnutím aktivujte režim vibrovania. Služby dostupnosti je možné stlmiť."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Klepnutím vypnite zvuk. Služby dostupnosti je možné stlmiť."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Klepnutím nastavíte vibrovanie."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Klepnutím vypnete zvuk."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Ovládanie šumu"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Režim zvonenia zmeníte klepnutím"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"vypnite zvuk"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"zapnite zvuk"</string>
     <string name="volume_ringer_hint_vibrate" msgid="6211609047099337509">"zapnite vibrovanie"</string>
     <string name="volume_dialog_title" msgid="6502703403483577940">"Ovládacie prvky hlasitosti %s"</string>
     <string name="volume_dialog_ringer_guidance_ring" msgid="9143194270463146858">"Hovory a upozornenia spustia zvonenie (<xliff:g id="VOLUME_LEVEL">%1$s</xliff:g>)"</string>
-    <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> sa prehráva v zar."</string>
-    <string name="media_output_title_without_playing" msgid="3825663683169305013">"Zvuk prehrá zariad.:"</string>
+    <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> sa prehráva v:"</string>
+    <string name="media_output_title_without_playing" msgid="3825663683169305013">"Zvuk sa prehrá v:"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Tuner používateľského rozhrania systému"</string>
     <string name="status_bar" msgid="4357390266055077437">"Stavový riadok"</string>
     <string name="demo_mode" msgid="263484519766901593">"Ukážka používateľského rozhrania systému"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Ponuka vypínača"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Strana <xliff:g id="ID_1">%1$d</xliff:g> z <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Uzamknutá obrazovka"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Pomocou funkcie Nájdi moje zariadenie môžete zistiť polohu tohto telefónu, aj keď je vypnutý"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Vypína sa…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Zobraziť opatrenia"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Zobraziť opatrenia"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Odpojte zariadenie"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Zapisovanie poznámok"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Zapisovanie poznámok, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Zdieľa sa zvuk"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Vysiela"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Chcete zastaviť vysielanie aplikácie <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ak vysielate aplikáciu <xliff:g id="SWITCHAPP">%1$s</xliff:g> alebo zmeníte výstup, aktuálne vysielanie bude zastavené"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Ak chcete vyššie rozlíšenie, prevráťte telefón"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Rozloženie skladacieho zariadenia"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Prevrátenie skladacieho zariadenia"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"zložené"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"rozložené"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Zostáva <xliff:g id="PERCENTAGE">%s</xliff:g> batérie"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Pripojte dotykové pero k nabíjačke"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Stav batérie dotykového pera je nízky"</string>
diff --git a/packages/SystemUI/res/values-sk/tiles_states_strings.xml b/packages/SystemUI/res/values-sk/tiles_states_strings.xml
index 660f85d..50f5c25 100644
--- a/packages/SystemUI/res/values-sk/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-sk/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Vypnuté"</item>
     <item msgid="578444932039713369">"Zapnuté"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Nedostupné"</item>
+    <item msgid="9061144428113385092">"Vypnuté"</item>
+    <item msgid="2984256114867200368">"Zapnuté"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Nie je k dispozícii"</item>
     <item msgid="8707481475312432575">"Vypnuté"</item>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 1ea97a1..4a46742 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Vklopi"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Vklopi"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Ne, hvala"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standardno"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Strogo"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Samodejno zasukaj zaslon"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Ali aplikaciji <xliff:g id="APPLICATION">%1$s</xliff:g> dovolite dostop do dodatka USB <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Ali aplikaciji <xliff:g id="APPLICATION">%1$s</xliff:g> dovolite dostop do naprave <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nTa aplikacija sicer nima dovoljenja za snemanje, vendar bi lahko zajemala zvok prek te naprave USB."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekinitev povezave"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiviranje"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Samodejno znova vklopi jutri"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funkcije, kot sta Hitro deljenje in Poišči mojo napravo, ter lokacija naprave, uporabljajo Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Baterija na <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvok"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalke z mikrofonom"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Dodajte več pripomočkov"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pridržite za prilagajanje pripomočkov"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prilagajanje pripomočkov"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacije za onemogočen pripomoček"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Urejanje pripomočka"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Odstrani"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodajanje pripomočka"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Zvonjenje"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibriranje"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Utišano"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Predvajanje"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Ni na voljo, ker je zvonjenje izklopljeno"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Dotaknite se, če želite vklopiti zvok."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Dotaknite se, če želite nastaviti vibriranje. V storitvah za dostopnost bo morda izklopljen zvok."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Dotaknite se, če želite izklopiti zvok. V storitvah za dostopnost bo morda izklopljen zvok."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Dotaknite se, če želite nastaviti vibriranje."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Dotaknite se, če želite izklopiti zvok."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Omejevanje hrupa"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Dotaknite se, če želite spremeniti način zvonjenja."</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"izklop zvoka"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"vklop zvoka"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Meni za vklop/izklop"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_1">%1$d</xliff:g>. stran od <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Zaklenjen zaslon"</string>
+    <string name="finder_active" msgid="7907846989716941952">"S storitvijo Poišči mojo napravo lahko ta telefon poiščete, tudi če je izklopljen"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Zaustavljanje …"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Oglejte si navodila za ukrepanje"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Oglejte si navodila za ukrepanje"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Odklopite napravo"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Ustvarjanje zapiskov"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Ustvarjanje zapiskov, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Deljenje zvoka"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Oddajanje"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Želite ustaviti oddajanje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Če oddajate aplikacijo <xliff:g id="SWITCHAPP">%1$s</xliff:g> ali spremenite izhod, bo trenutno oddajanje ustavljeno."</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Za višjo ločljivost obrnite telefon"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Razpiranje zložljive naprave"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Obračanje zložljive naprave"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"zaprto"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"razprto"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Preostanek energije baterije: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Povežite pisalo s polnilnikom."</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Skoraj prazna baterija pisala"</string>
diff --git a/packages/SystemUI/res/values-sl/tiles_states_strings.xml b/packages/SystemUI/res/values-sl/tiles_states_strings.xml
index d7e62ca..33ba216 100644
--- a/packages/SystemUI/res/values-sl/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-sl/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Izklopljeno"</item>
     <item msgid="578444932039713369">"Vklopljeno"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Ni na voljo"</item>
+    <item msgid="9061144428113385092">"Izklopljeno"</item>
+    <item msgid="2984256114867200368">"Vklopljeno"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Ni na voljo"</item>
     <item msgid="8707481475312432575">"Izklopljeno"</item>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index c3ccd81..1fb6251 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Aktivizo"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Aktivizo"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Jo, faleminderit"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Në kushte ekstreme"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Rrotullimi automatik i ekranit"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Të lejohet <xliff:g id="APPLICATION">%1$s</xliff:g> të ketë qasje te <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Dëshiron të lejosh që <xliff:g id="APPLICATION">%1$s</xliff:g> të ketë qasje te <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nKëtij aplikacioni nuk i është dhënë leje për regjistrim, por mund të regjistrojë audio përmes kësaj pajisjeje USB."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"shkëput"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivizo"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Aktivizoje automatikisht nesër"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Veçoritë si \"Ndarja e shpejtë\", \"Gjej pajisjen time\" dhe vendndodhja e pajisjes përdorin Bluetooth-in"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> bateri"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Kufje me mikrofon"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Shto miniaplikacione të tjera"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Shtyp gjatë për të personalizuar miniaplikacionet"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizo miniaplikacionet"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona e aplikacionit për miniaplikacionin e çaktivizuar"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Modifiko miniaplikacionin"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Hiq"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Shto miniaplikacionin"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Bjeri ziles"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Dridhje"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Pa zë"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Transmeto"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Nuk ofrohet; ziles i është hequr zëri"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Trokit për të aktivizuar."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Trokit për ta caktuar te dridhja. Shërbimet e qasshmërisë mund të çaktivizohen."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Trokit për të çaktivizuar. Shërbimet e qasshmërisë mund të çaktivizohen."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Trokit për ta vendosur në dridhje."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Trokit për ta çaktivizuar."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Kontrolli i zhurmës"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Trokit për të ndryshuar modalitetin e ziles"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"çaktivizo audion"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"aktivizo audion"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menyja e energjisë"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Faqja <xliff:g id="ID_1">%1$d</xliff:g> nga <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Ekrani i kyçjes"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Mund ta gjesh këtë telefon me \"Gjej pajisjen time\" edhe kur është i fikur"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Po fiket…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Shiko hapat për kujdesin"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Shiko hapat për kujdesin"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Shkëpute pajisjen"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Mbajtja e shënimeve"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Mbajtja e shënimeve, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Po ndahet audio"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Po transmeton"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Të ndalohet transmetimi i <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Nëse transmeton <xliff:g id="SWITCHAPP">%1$s</xliff:g> ose ndryshon daljen, transmetimi yt aktual do të ndalojë"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Për rezolucion më të lartë, përmbys telefonin"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Pajisja e palosshme duke u hapur"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Pajisja e palosshme duke u rrotulluar"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Përqindja e mbetur e baterisë: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Lidhe stilolapsin me një karikues"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Bateria e stilolapsit në nivel të ulët"</string>
diff --git a/packages/SystemUI/res/values-sq/tiles_states_strings.xml b/packages/SystemUI/res/values-sq/tiles_states_strings.xml
index b8e1355..fa06795 100644
--- a/packages/SystemUI/res/values-sq/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-sq/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Joaktive"</item>
     <item msgid="578444932039713369">"Aktiv"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Nuk ofrohet"</item>
+    <item msgid="9061144428113385092">"Joaktiv"</item>
+    <item msgid="2984256114867200368">"Aktiv"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Nuk ofrohet"</item>
     <item msgid="8707481475312432575">"Joaktive"</item>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 2309d7d..3444c80 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Укључи"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Укључи"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Не, хвала"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Стандардно"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Екстремно"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Аутоматско ротирање екрана"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Дозвољавате да <xliff:g id="APPLICATION">%1$s</xliff:g> приступа уређају <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Желите ли да дозволите да <xliff:g id="APPLICATION">%1$s</xliff:g> приступа уређају <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nОва апликација нема дозволу за снимање, али би могла да снима звук помоћу овог USB уређаја."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"прекините везу"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активирајте"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Аутоматски поново укључи сутра"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Функције као што су Quick Share, Пронађи мој уређај и локација уређаја користе Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Ниво батерије је <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Слушалице"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Додајте још виџета"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Дуги притисак за прилагођавање виџета"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Прилагоди виџете"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Икона апликације за онемогућен виџет"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Измени виџет"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Уклони"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Додај виџет"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Активирај звоно"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Вибрирај"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Искључи звук"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Пребацивање"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Недоступно јер је звук искључен"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Додирните да бисте укључили звук."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Додирните да бисте подесили на вибрацију. Звук услуга приступачности ће можда бити искључен."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Додирните да бисте искључили звук. Звук услуга приступачности ће можда бити искључен."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Додирните да бисте подесили на вибрацију."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Додирните да бисте искључили звук."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Контрола шума"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Додирните да бисте променили режим звона"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"искључите звук"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"укључите звук"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Мени дугмета за укључивање"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_1">%1$d</xliff:g>. страна од <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Закључан екран"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Можете да лоцирате овај телефон помоћу услуге Пронађи мој уређај чак и када је искључен"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Искључује се…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Погледајте упозорења"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Погледајте упозорења"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Искључите уређај"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Прављење бележака"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Прављење бележака, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Дели се звук"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Емитовање"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Желите да зауставите емитовање апликације <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ако емитујете апликацију <xliff:g id="SWITCHAPP">%1$s</xliff:g> или промените излаз, актуелно емитовање ће се зауставити"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"За већу резолуцију обрните телефон"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Уређај на преклоп се отвара"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Уређај на преклоп се обрће"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"затворено"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"отворено"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Преостало је још<xliff:g id="PERCENTAGE">%s</xliff:g> батерије"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Повежите писаљку са пуњачем"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Низак ниво батерије писаљке"</string>
diff --git a/packages/SystemUI/res/values-sr/tiles_states_strings.xml b/packages/SystemUI/res/values-sr/tiles_states_strings.xml
index c959bfb..55f5a3f 100644
--- a/packages/SystemUI/res/values-sr/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-sr/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Искључено"</item>
     <item msgid="578444932039713369">"Укључено"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Недоступно"</item>
+    <item msgid="9061144428113385092">"Искључено"</item>
+    <item msgid="2984256114867200368">"Укључено"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Недоступно"</item>
     <item msgid="8707481475312432575">"Искључено"</item>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index be80f7a..2ca3d5d 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Aktivera"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Aktivera"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nej tack"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Effektivare läget"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Rotera skärmen automatiskt"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Vill du ge <xliff:g id="APPLICATION">%1$s</xliff:g> åtkomst till <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Vill du ge <xliff:g id="APPLICATION">%1$s</xliff:g> åtkomst till <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nAppen har inte fått inspelningsbehörighet men kan spela in ljud via denna USB-enhet."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"koppla från"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivera"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Aktivera automatiskt igen i morgon"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funktioner som Snabbdelning, Hitta min enhet och enhetens plats använder Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batteri"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ljud"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Lägg till fler widgetar"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Tryck länge för att anpassa widgetar"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Anpassa widgetar"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Appikon för inaktiverad widget"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Redigera widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Ta bort"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Lägg till widget"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ringsignal"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibration"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Dölj"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Casta"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Otillgängligt eftersom ringljudet är av"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Tryck här om du vill slå på ljudet."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tryck här om du vill sätta på vibrationen. Tillgänglighetstjänster kanske inaktiveras."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Tryck här om du vill stänga av ljudet. Tillgänglighetstjänsterna kanske inaktiveras."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tryck här om du vill aktivera vibrationsläget."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Tryck här om du vill stänga av ljudet."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Bruskontroll"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Tryck för att ändra ringsignalens läge"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"stänga av ljudet"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"slå på ljudet"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Startmeny"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Sida <xliff:g id="ID_1">%1$d</xliff:g> av <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Låsskärm"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Du kan hitta den här telefonen med Hitta min enhet även när den är avstängd"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Avslutar …"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Visa alla skötselråd"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Visa alla skötselråd"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Koppla ur enheten"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Anteckna"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Anteckna med <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Delar ljud"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Sänder"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Vill du sluta sända från <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Om en utsändning från <xliff:g id="SWITCHAPP">%1$s</xliff:g> pågår eller om du byter ljudutgång avbryts den nuvarande utsändningen"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Vänd telefonen för högre upplösning"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"En vikbar enhet viks upp"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"En vikbar enhet vänds"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> av batteriet återstår"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Anslut e-pennan till en laddare"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"E-pennans batterinivå är låg"</string>
diff --git a/packages/SystemUI/res/values-sv/tiles_states_strings.xml b/packages/SystemUI/res/values-sv/tiles_states_strings.xml
index 28717df..f921f27 100644
--- a/packages/SystemUI/res/values-sv/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-sv/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Av"</item>
     <item msgid="578444932039713369">"På"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Inte tillgängligt"</item>
+    <item msgid="9061144428113385092">"Av"</item>
+    <item msgid="2984256114867200368">"På"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Inte tillgängligt"</item>
     <item msgid="8707481475312432575">"Av"</item>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 0999820..b48a0f5 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Washa"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Washa"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Hapana"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Kawaida"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Kwa kina"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Skrini ijizungushe kiotomatiki"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Ungependa kuruhusu <xliff:g id="APPLICATION">%1$s</xliff:g> ifikie <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Ungependa kuruhusu <xliff:g id="APPLICATION">%1$s</xliff:g> ifikie <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nProgramu hii haijapewa ruhusa ya kurekodi lakini inaweza kurekodi sauti kupitia kifaa hiki cha USB."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ondoa"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"anza kutumia"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Iwashe tena kesho kiotomatiki"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Vipengele kama vile Kutuma Haraka, Tafuta Kifaa Changu na mahali kifaa kilipo hutumia Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Chaji ya betri ni <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Sauti"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Vifaa vya sauti"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Weka wijeti zingine"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Bonyeza kwa muda mrefu uweke mapendeleo ya wijeti"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Badilisha wijeti upendavyo"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Aikoni ya programu ya wijeti iliyozimwa"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Badilisha wijeti"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Ondoa"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ongeza wijeti"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Piga"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Kutetema"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Zima sauti"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Tuma"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Halipatikani kwa sababu sauti imezimwa"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Gusa ili urejeshe."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Gusa ili uweke mtetemo. Huenda ikakomesha huduma za zana za walio na matatizo ya kuona au kusikia."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Gusa ili ukomeshe. Huenda ikakomesha huduma za zana za walio na matatizo ya kuona au kusikia."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Gusa ili uweke mtetemo."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Gusa ili usitishe."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Kidhibiti cha Kelele"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Gusa ili ubadilishe hali ya programu inayotoa milio ya simu"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"zima sauti"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"washa sauti"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menyu ya kuzima/kuwasha"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Ukurasa wa <xliff:g id="ID_1">%1$d</xliff:g> kati ya <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Skrini iliyofungwa"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Unaweza kutambua mahali ilipo simu hii ukitumia programu ya Tafuta Kifaa Changu hata kama simu hiyo imezimwa"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Inazima…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Angalia hatua za utunzaji"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Angalia hatua za utunzaji"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Chomoa kifaa chako"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Kuandika madokezo"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Kuandika madokezo, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Watu wengine wanasikia sauti"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Inaarifu"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Ungependa kusimamisha utangazaji kwenye <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ikiwa unatangaza kwenye <xliff:g id="SWITCHAPP">%1$s</xliff:g> au unabadilisha maudhui, tangazo lako la sasa litasimamishwa"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Kwa ubora wa juu, geuza simu"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Kifaa kinachokunjwa kikikunjuliwa"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Kifaa kinachokunjwa kikigeuzwa"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"kimekunjwa"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"kimefunguliwa"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Chaji ya betri imesalia <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Unganisha stylus yako kwenye chaja"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Chaji ya betri ya Stylus imepungua"</string>
diff --git a/packages/SystemUI/res/values-sw/tiles_states_strings.xml b/packages/SystemUI/res/values-sw/tiles_states_strings.xml
index 2fe4060..227eee8 100644
--- a/packages/SystemUI/res/values-sw/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-sw/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Kimezimwa"</item>
     <item msgid="578444932039713369">"Kimewashwa"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Haipatikani"</item>
+    <item msgid="9061144428113385092">"Imezimwa"</item>
+    <item msgid="2984256114867200368">"Imewashwa"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Hakipatikani"</item>
     <item msgid="8707481475312432575">"Kimezimwa"</item>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 486d7f9..b954064 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"இயக்கு"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"இயக்கு"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"வேண்டாம்"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"நிலையானது"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"மேம்பட்டது"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"திரையைத் தானாகச் சுழற்று"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="USB_DEVICE">%2$s</xliff:g>ஐ அணுக, <xliff:g id="APPLICATION">%1$s</xliff:g> ஆப்ஸை அனுமதிக்கவா?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="USB_DEVICE">%2$s</xliff:g>ஐப் பயன்படுத்த <xliff:g id="APPLICATION">%1$s</xliff:g>ஐ அனுமதிக்கவா?\nஇந்த ஆப்ஸிற்கு ரெக்கார்டு செய்வதற்கான அனுமதி வழங்கப்படவில்லை, எனினும் இந்த USB சாதனம் மூலம் ஆடியோவைப் பதிவுசெய்யும்."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"இணைப்பு நீக்கும்"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"செயல்படுத்தும்"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"நாளைக்குத் தானாகவே மீண்டும் இயக்கப்படும்"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"விரைவுப் பகிர்தல், Find My Device போன்ற அம்சங்களும் சாதன இருப்பிடமும் புளூடூத்தைப் பயன்படுத்துகின்றன"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> பேட்டரி"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ஆடியோ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ஹெட்செட்"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"கூடுதல் விட்ஜெட்களைச் சேருங்கள்"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"விட்ஜெட்களைப் பிரத்தியேகமாக்க நீண்ட நேரம் அழுத்துக"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"விட்ஜெட்களைப் பிரத்தியேகமாக்குங்கள்"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"முடக்கப்பட்ட விட்ஜெட்டுக்கான ஆப்ஸ் ஐகான்"</string>
     <string name="edit_widget" msgid="9030848101135393954">"விட்ஜெட்டைத் திருத்து"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"அகற்றும்"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"விட்ஜெட்டைச் சேர்"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"ஒலி"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"அதிர்வு"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"அமைதி"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"அலைபரப்பு"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"\'ரிங்\' மியூட்டில் உள்ளதால் கிடைக்கவில்லை"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. ஒலி இயக்க, தட்டவும்."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. அதிர்விற்கு அமைக்க, தட்டவும். அணுகல்தன்மை சேவைகள் ஒலியடக்கப்படக்கூடும்."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. ஒலியடக்க, தட்டவும். அணுகல்தன்மை சேவைகள் ஒலியடக்கப்படக்கூடும்."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. அதிர்விற்கு அமைக்க, தட்டவும்."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. ஒலியடக்க, தட்டவும்."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"இரைச்சல் கட்டுப்பாடு"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"ரிங்கர் பயன்முறையை மாற்ற தட்டவும்"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"ஒலியடக்கும்"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ஒலி இயக்கும்"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"பவர் மெனு"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"பக்கம் <xliff:g id="ID_1">%1$d</xliff:g> / <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"லாக் ஸ்கிரீன்"</string>
+    <string name="finder_active" msgid="7907846989716941952">"மொபைல் பவர் ஆஃப் செய்யப்பட்டிருக்கும்போதும் Find My Device மூலம் அதன் இருப்பிடத்தைக் கண்டறியலாம்"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"ஷட் டவுன் ஆகிறது…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"மேலும் விவரங்களுக்கு இதைப் பார்க்கவும்"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"மேலும் விவரங்களுக்கு இதைப் பார்க்கவும்"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"சாதன இணைப்பைத் துண்டித்தல்"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"குறிப்பெடுத்தல்"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"குறிப்பெடுத்தல், <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"ஆடியோ பகிரப்படுகிறது"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ஒலிபரப்புதல்"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் ஒலிபரப்பப்படுவதை நிறுத்தவா?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"நீங்கள் <xliff:g id="SWITCHAPP">%1$s</xliff:g> ஆப்ஸை ஒலிபரப்பினாலோ அவுட்புட்டை மாற்றினாலோ உங்களின் தற்போதைய ஒலிபரப்பு நிறுத்தப்படும்"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"உயர் தெளிவுத்திறனுக்கு, மொபைலை ஃபிளிப் செய்யுங்கள்"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"மடக்கத்தக்க சாதனம் திறக்கப்படுகிறது"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"மடக்கத்தக்க சாதனம் ஃபிளிப் செய்யப்பட்டு திருப்பப்படுகிறது"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"மடக்கப்பட்டது"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"விரிக்கப்பட்டது"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> பேட்டரி மீதமுள்ளது"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"உங்கள் ஸ்டைலஸைச் சார்ஜருடன் இணையுங்கள்"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"ஸ்டைலஸின் பேட்டரி குறைவாக உள்ளது"</string>
diff --git a/packages/SystemUI/res/values-ta/tiles_states_strings.xml b/packages/SystemUI/res/values-ta/tiles_states_strings.xml
index 5bcc6c7..6043cf2 100644
--- a/packages/SystemUI/res/values-ta/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ta/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"முடக்கப்பட்டுள்ளது"</item>
     <item msgid="578444932039713369">"இயக்கப்பட்டுள்ளது"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"இல்லை"</item>
+    <item msgid="9061144428113385092">"முடக்கப்பட்டுள்ளது"</item>
+    <item msgid="2984256114867200368">"இயக்கப்பட்டுள்ளது"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"கிடைக்கவில்லை"</item>
     <item msgid="8707481475312432575">"முடக்கப்பட்டுள்ளது"</item>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 227ba5a..75f872f 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ఆన్ చేయి"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"ఆన్ చేయండి"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"వద్దు, ధన్యవాదాలు"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"స్టాండర్డ్"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"ఎక్స్‌ట్రీమ్"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"స్క్రీన్ ఆటో-రొటేట్‌"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="USB_DEVICE">%2$s</xliff:g>ని యాక్సెస్ చేయడానికి <xliff:g id="APPLICATION">%1$s</xliff:g>ని అనుమతించాలా?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="USB_DEVICE">%2$s</xliff:g>యాక్సెస్ చేయడానికి <xliff:g id="APPLICATION">%1$s</xliff:g>ను అనుమతించాలా?\nఈ యాప్‌నకు రికార్డ్ చేసే అనుమతి మంజూరు చేయబడలేదు, కానీ ఈ USB పరికరం ద్వారా ఆడియోను క్యాప్చర్ చేయగలదు."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"డిస్‌కనెక్ట్ చేయండి"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"యాక్టివేట్ చేయండి"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"రేపు మళ్లీ ఆటోమేటిక్‌గా ఆన్ చేస్తుంది"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"క్విక్ షేర్, Find My Device, పరికర లొకేషన్ వంటి ఫీచర్‌లు బ్లూటూత్‌ను ఉపయోగిస్తాయి"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> బ్యాటరీ"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ఆడియో"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"హెడ్‌సెట్"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"మరిన్ని విడ్జెట్‌లను జోడించండి"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"విడ్జెట్‌లను అనుకూలీకరించడానికి, నొక్కి, ఉంచండి"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"విడ్జెట్‌లను అనుకూలంగా మార్చండి"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"డిజేబుల్ చేయబడిన విడ్జెట్ కోసం యాప్ చిహ్నం"</string>
     <string name="edit_widget" msgid="9030848101135393954">"విడ్జెట్‌ను ఎడిట్ చేయండి"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"తీసివేయండి"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"విడ్జెట్‌ను జోడించండి"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"రింగ్"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"వైబ్రేట్"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"మ్యూట్"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"ప్రసారం చేయండి"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"వాల్యూమ్ మ్యూట్ అయినందున అందుబాటులో లేదు"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. అన్‌మ్యూట్ చేయడానికి నొక్కండి."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. వైబ్రేషన్‌కు సెట్ చేయడానికి నొక్కండి. యాక్సెస్ సామర్థ్య సేవలు మ్యూట్ చేయబడవచ్చు."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. మ్యూట్ చేయడానికి నొక్కండి. యాక్సెస్ సామర్థ్య సేవలు మ్యూట్ చేయబడవచ్చు."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. వైబ్రేట్ అయ్యేలా సెట్ చేయడం కోసం నొక్కండి."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. మ్యూట్ చేయడానికి నొక్కండి."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"నాయిస్ కంట్రోల్"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"రింగర్ మోడ్‌ను మార్చడానికి ట్యాప్ చేయండి"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"మ్యూట్ చేయి"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"అన్‌మ్యూట్ చేయి"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"పవర్ మెనూ"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g>లో <xliff:g id="ID_1">%1$d</xliff:g>వ పేజీ"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"లాక్ స్క్రీన్"</string>
+    <string name="finder_active" msgid="7907846989716941952">"పవర్ ఆఫ్‌లో ఉన్నప్పుడు కూడా మీరు Find My Deviceతో ఈ ఫోన్‌ను గుర్తించవచ్చు"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"షట్ డౌన్ చేయబడుతోంది…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"తీసుకోవాల్సిన జాగ్రత్తలు ఏమిటో చూడండి"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"తీసుకోవాల్సిన జాగ్రత్తలు ఏమిటో చూడండి"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"మీ పరికరాన్ని అన్‌ప్లగ్ చేయండి"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"నోట్-టేకింగ్"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"నోట్-టేకింగ్, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"ఆడియోను షేర్ చేస్తున్నారు"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ప్రసారం చేస్తోంది"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ప్రసారం చేయడాన్ని ఆపివేయాలా?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"మీరు <xliff:g id="SWITCHAPP">%1$s</xliff:g> ప్రసారం చేస్తే లేదా అవుట్‌పుట్‌ను మార్చినట్లయితే, మీ ప్రస్తుత ప్రసారం ఆగిపోతుంది"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"అధిక రిజల్యూషన్ కోసం, ఫోన్‌ను తిప్పండి"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"మడవగల పరికరం విప్పబడుతోంది"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"మడవగల పరికరం చుట్టూ తిప్పబడుతోంది"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> బ్యాటరీ మిగిలి ఉంది"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"మీ స్టైలస్‌ను ఛార్జర్‌కి కనెక్ట్ చేయండి"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"తక్కువ స్టైలస్ బ్యాటరీ"</string>
diff --git a/packages/SystemUI/res/values-te/tiles_states_strings.xml b/packages/SystemUI/res/values-te/tiles_states_strings.xml
index 9d2b407..370aeb0 100644
--- a/packages/SystemUI/res/values-te/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-te/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"ఆఫ్‌లో ఉంది"</item>
     <item msgid="578444932039713369">"ఆన్‌లో ఉంది"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"అందుబాటులో లేదు"</item>
+    <item msgid="9061144428113385092">"ఆఫ్‌లో ఉంది"</item>
+    <item msgid="2984256114867200368">"ఆన్‌లో ఉంది"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"అందుబాటులో లేదు"</item>
     <item msgid="8707481475312432575">"ఆఫ్‌లో ఉంది"</item>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index d1e186f..94243b03 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"เปิด"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"เปิด"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"ไม่เป็นไร"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"มาตรฐาน"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"สูงสุด"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"หมุนหน้าจออัตโนมัติ"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"อนุญาตให้ <xliff:g id="APPLICATION">%1$s</xliff:g> เข้าถึง <xliff:g id="USB_DEVICE">%2$s</xliff:g> ไหม"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"อนุญาตให้ <xliff:g id="APPLICATION">%1$s</xliff:g> เข้าถึง <xliff:g id="USB_DEVICE">%2$s</xliff:g> ไหม\nแอปนี้ไม่ได้รับอนุญาตให้อัดเสียงแต่อาจเก็บเสียงผ่านอุปกรณ์ USB นี้ได้"</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ยกเลิกการเชื่อมต่อ"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"เปิดใช้งาน"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"เปิดอีกครั้งโดยอัตโนมัติในวันพรุ่งนี้"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"ฟีเจอร์ต่างๆ เช่น Quick Share, หาอุปกรณ์ของฉัน และตำแหน่งของอุปกรณ์ ใช้บลูทูธ"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"แบตเตอรี่ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"เสียง"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ชุดหูฟัง"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"เพิ่มวิดเจ็ตอีก"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"กดค้างเพื่อปรับแต่งวิดเจ็ต"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ปรับแต่งวิดเจ็ต"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ไอคอนแอปสำหรับวิดเจ็ตที่ปิดใช้อยู่"</string>
     <string name="edit_widget" msgid="9030848101135393954">"แก้ไขวิดเจ็ต"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"นำออก"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"เพิ่มวิดเจ็ต"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"ทำให้ส่งเสียง"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"สั่น"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"ปิดเสียง"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"แคสต์"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"เปลี่ยนไม่ได้เนื่องจากปิดเสียงเรียกเข้า"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s แตะเพื่อเปิดเสียง"</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s แตะเพื่อตั้งค่าให้สั่น อาจมีการปิดเสียงบริการการเข้าถึง"</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s แตะเพื่อปิดเสียง อาจมีการปิดเสียงบริการการเข้าถึง"</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s แตะเพื่อตั้งค่าให้สั่น"</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s แตะเพื่อปิดเสียง"</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"การควบคุมเสียง"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"แตะเพื่อเปลี่ยนโหมดเสียงเรียกเข้า"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"ปิดเสียง"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"เปิดเสียง"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"เมนูเปิด/ปิด"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"หน้า <xliff:g id="ID_1">%1$d</xliff:g> จาก <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"หน้าจอล็อก"</string>
+    <string name="finder_active" msgid="7907846989716941952">"คุณจะหาตำแหน่งของโทรศัพท์นี้ได้ด้วยแอปหาอุปกรณ์ของฉันแม้จะปิดเครื่องอยู่ก็ตาม"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"กำลังปิด…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"ดูขั้นตอนในการดูแลรักษา"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ดูขั้นตอนในการดูแลรักษา"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"ถอดปลั๊กอุปกรณ์"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"การจดบันทึก"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"การจดบันทึก <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"กำลังแชร์เสียง"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"กำลังออกอากาศ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"หยุดการออกอากาศ <xliff:g id="APP_NAME">%1$s</xliff:g> ไหม"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"หากคุณออกอากาศ <xliff:g id="SWITCHAPP">%1$s</xliff:g> หรือเปลี่ยนแปลงเอาต์พุต การออกอากาศในปัจจุบันจะหยุดลง"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"พลิกด้านโทรศัพท์เพื่อให้ได้ภาพที่มีความละเอียดมากขึ้น"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"อุปกรณ์ที่พับได้กำลังกางออก"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"อุปกรณ์ที่พับได้กำลังพลิกไปมา"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"พับ"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"กางออก"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"เหลือแบตเตอรี่ <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"เชื่อมต่อสไตลัสกับที่ชาร์จ"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"แบตเตอรี่สไตลัสเหลือน้อย"</string>
diff --git a/packages/SystemUI/res/values-th/tiles_states_strings.xml b/packages/SystemUI/res/values-th/tiles_states_strings.xml
index 69449a7..acaf9f0 100644
--- a/packages/SystemUI/res/values-th/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-th/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"ปิด"</item>
     <item msgid="578444932039713369">"เปิด"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"ไม่พร้อมใช้งาน"</item>
+    <item msgid="9061144428113385092">"ปิด"</item>
+    <item msgid="2984256114867200368">"เปิด"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"ไม่พร้อมใช้งาน"</item>
     <item msgid="8707481475312432575">"ปิด"</item>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index c929e45..99b61e9 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"I-on"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"I-on"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Huwag na lang"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extreme"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"I-auto rotate ang screen"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Payagan ang <xliff:g id="APPLICATION">%1$s</xliff:g> na ma-access ang <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Payagan ang <xliff:g id="APPLICATION">%1$s</xliff:g> na i-access ang <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nHindi nabigyan ng pahintulot ang app na ito para mag-record pero nakakapag-capture ito ng audio sa pamamagitan ng USB device na ito."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"idiskonekta"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"i-activate"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Awtomatikong i-on ulit bukas"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Guamgamit ng Bluetooth ang mga feature tulad ng Quick Share, Hanapin ang Aking Device, at lokasyon ng device"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> na baterya"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Magdagdag ng higit pang widget"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pindutin nang matagal para i-customize ang mga widget"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"I-customize ang mga widget"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icon ng app para sa na-disable na widget"</string>
     <string name="edit_widget" msgid="9030848101135393954">"I-edit ang widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Alisin"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Magdagdag ng widget"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ipa-ring"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"I-vibrate"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"I-mute"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Mag-cast"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Hindi available dahil naka-mute ang ring"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. I-tap upang i-unmute."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. I-tap upang itakda na mag-vibrate. Maaaring i-mute ang mga serbisyo sa Accessibility."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. I-tap upang i-mute. Maaaring i-mute ang mga serbisyo sa Accessibility."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. I-tap upang itakda na mag-vibrate."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. I-tap upang i-mute."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Pagkontrol sa Ingay"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"I-tap para baguhin ang ringer mode"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"i-mute"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"i-unmute"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Power menu"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Page <xliff:g id="ID_1">%1$d</xliff:g> ng <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Lock screen"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Puwede mong hanapin ang teleponong ito gamit ang Hanapin ang Aking Device kahit kapag naka-off ito"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Nagsa-shut down…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Tingnan ang mga hakbang sa pangangalaga"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Tingnan ang mga hakbang sa pangangalaga"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Bunutin sa saksakan ang device"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Pagtatala"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Pagtatala, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Nagse-share ng audio"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Nagbo-broadcast"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Ihinto ang pag-broadcast ng <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Kung magbo-broadcast ka ng <xliff:g id="SWITCHAPP">%1$s</xliff:g> o babaguhin mo ang output, hihinto ang iyong kasalukuyang broadcast"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para sa mas mataas na resolution, i-flip ang telepono"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Ina-unfold na foldable na device"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Fini-flip na foldable na device"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"naka-fold"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"hindi naka-fold"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> baterya na lang ang natitira"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Ikonekta sa charger ang iyong stylus"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Paubos na ang baterya ng stylus"</string>
diff --git a/packages/SystemUI/res/values-tl/tiles_states_strings.xml b/packages/SystemUI/res/values-tl/tiles_states_strings.xml
index 689c2a2..6de62df 100644
--- a/packages/SystemUI/res/values-tl/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-tl/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Naka-off"</item>
     <item msgid="578444932039713369">"Naka-on"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Hindi available"</item>
+    <item msgid="9061144428113385092">"Naka-off"</item>
+    <item msgid="2984256114867200368">"Naka-on"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Hindi available"</item>
     <item msgid="8707481475312432575">"Naka-off"</item>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 9407fbb..c20c62a 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Aç"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Aç"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Hayır, teşekkürler"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standart"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Yüksek"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Ekranı otomatik döndür"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> uygulamasının <xliff:g id="USB_DEVICE">%2$s</xliff:g> cihazına erişmesine izin verilsin mi?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> uygulamasının <xliff:g id="USB_DEVICE">%2$s</xliff:g> cihazına erişmesine izin verilsin mi?\nBu uygulamaya kayıt izni verilmemiş ancak bu USB cihazı aracılığıyla sesleri yakalayabilir."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"bağlantıyı kes"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"etkinleştir"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Yarın otomatik olarak tekrar aç"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Quick Share, Cihazımı Bul ve cihaz konumu gibi özellikler Bluetooth\'u kullanır"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Pil düzeyi <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ses"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Mikrofonlu kulaklık"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Daha fazla widget ekle"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Widget\'ları özelleştirmek için uzun basın"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Widget\'ları özelleştir"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Devre dışı bırakılan widget\'ın uygulama simgesi"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Widget\'ı düzenle"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Kaldır"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Widget ekle"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Zili çaldır"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Titreşim"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Sesi kapat"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Yayınla"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Zil sesi kapatıldığı için kullanılamıyor"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Sesi açmak için dokunun."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Titreşime ayarlamak için dokunun. Erişilebilirlik hizmetlerinin sesi kapatılabilir."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Sesi kapatmak için dokunun. Erişilebilirlik hizmetlerinin sesi kapatılabilir."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Titreşime ayarlamak için dokunun."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Sesi kapatmak için dokunun."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Gürültü Kontrolü"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Telefon zili modunu değiştirmek için dokunun"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"sesi  kapat"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"sesi aç"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Güç menüsü"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Sayfa <xliff:g id="ID_1">%1$d</xliff:g> / <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Kilit ekranı"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Bu telefonu kapalıyken bile Cihazımı Bul işleviyle bulabilirsiniz."</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Kapanıyor…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Bakımla ilgili adımlara bakın"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Bakımla ilgili adımlara bakın"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Cihazınızın fişini çekin"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Not alma"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Not alma, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Ses paylaşılıyor"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Yayınlama"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulamasında anons durdurulsun mu?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> uygulamasında anons yapar veya çıkışı değiştirirseniz mevcut anonsunuz duraklatılır"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Daha yüksek çözünürlük için telefonu çevirin"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Katlanabilir cihaz açılıyor"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Katlanabilir cihaz döndürülüyor"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"katlanmış"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"katlanmamış"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> pil kaldı"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Ekran kaleminizi bir şarj cihazına bağlayın"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Ekran kaleminin pil seviyesi düşük"</string>
diff --git a/packages/SystemUI/res/values-tr/tiles_states_strings.xml b/packages/SystemUI/res/values-tr/tiles_states_strings.xml
index a8c7f78..0c086f8 100644
--- a/packages/SystemUI/res/values-tr/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-tr/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Kapalı"</item>
     <item msgid="578444932039713369">"Açık"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Yok"</item>
+    <item msgid="9061144428113385092">"Kapalı"</item>
+    <item msgid="2984256114867200368">"Açık"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Kullanılamıyor"</item>
     <item msgid="8707481475312432575">"Kapalı"</item>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index b7a4999..3338bce 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Увімкнути"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Увімкнути"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Ні, дякую"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Стандартний режим"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Екстремальний режим"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Автообертання екрана"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Надати додатку <xliff:g id="APPLICATION">%1$s</xliff:g> доступ до такого аксесуара: <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Надати додатку <xliff:g id="APPLICATION">%1$s</xliff:g> доступ до <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nЦей додаток не має дозволу на записування звуку, але може фіксувати його через цей USB-пристрій."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"від’єднати"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активувати"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Автоматично ввімкнути знову завтра"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Такі функції, як швидкий обмін, \"Знайти пристрій\" і визначення місцезнаходження пристрою, використовують Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> заряду акумулятора"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудіопристрій"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнітура"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Додати більше віджетів"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Утримуйте, щоб налаштувати віджети"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Налаштувати віджети"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Значок додатка для вимкненого віджета"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Редагувати віджет"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Видалити"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Додати віджет"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Дзвінок"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Вібросигнал"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Без звуку"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Трансляція"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Недоступно: звук дзвінків вимкнено"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Торкніться, щоб увімкнути звук."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Торкніться, щоб налаштувати вібросигнал. Спеціальні можливості може бути вимкнено."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Торкніться, щоб вимкнути звук. Спеціальні можливості може бути вимкнено."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Торкніться, щоб налаштувати вібросигнал."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Торкніться, щоб вимкнути звук."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Контроль шуму"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Торкніться, щоб змінити режим дзвінка"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"вимкнути звук"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"увімкнути звук"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Меню кнопки живлення"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Сторінка <xliff:g id="ID_1">%1$d</xliff:g> з <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Заблокований екран"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Ви зможете визначити місцеположення цього телефона, навіть коли його вимкнено, за допомогою сервісу Знайти пристрій"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Вимкнення…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Переглянути запобіжні заходи"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Переглянути запобіжні заходи"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Від’єднайте пристрій"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Створення нотаток"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Створення нотаток, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Відтворюється аудіо"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Трансляція"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Зупинити трансляцію з додатка <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Якщо ви зміните додаток (<xliff:g id="SWITCHAPP">%1$s</xliff:g>) або аудіовихід, поточну трансляцію буде припинено"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Для вищої роздільної здатності переверніть телефон"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Розкладний пристрій у розкладеному стані"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Розкладний пристрій обертається"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Заряд акумулятора: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Підключіть стилус до зарядного пристрою"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Низький заряд акумулятора стилуса"</string>
diff --git a/packages/SystemUI/res/values-uk/tiles_states_strings.xml b/packages/SystemUI/res/values-uk/tiles_states_strings.xml
index 4062f1b..fd3fb08 100644
--- a/packages/SystemUI/res/values-uk/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-uk/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Вимкнено"</item>
     <item msgid="578444932039713369">"Увімкнено"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Недоступно"</item>
+    <item msgid="9061144428113385092">"Вимкнено"</item>
+    <item msgid="2984256114867200368">"Увімкнено"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Недоступно"</item>
     <item msgid="8707481475312432575">"Вимкнено"</item>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index f9155a0..bbc7767 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"آن کریں"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"آن کریں"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"نہیں شکریہ"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"معیاری"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"انتہائی"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"سکرین کو خودکار طور پر گھمائیں"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> کو <xliff:g id="USB_DEVICE">%2$s</xliff:g> تک رسائی حاصل کرنے کی اجازت دیں؟"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"‏<xliff:g id="APPLICATION">%1$s</xliff:g> کو <xliff:g id="USB_DEVICE">%2$s</xliff:g> تک رسائی دیں؟\nاس ایپ کو ریکارڈ کی اجازت عطا نہیں کی گئی ہے مگر اس USB آلہ سے کیپچر کر سکتے ہیں۔"</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"غیر منسلک کریں"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"فعال کریں"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"کل دوبارہ خودکار طور پر آن ہوگا"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"فوری اشتراک، میرا آلہ ڈھونڈیں، اور آلہ کے مقام جیسی خصوصیات بلوٹوتھ کا استعمال کرتی ہیں"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> بیٹری"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"آڈیو"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ہیڈ سیٹ"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"مزید ویجٹس شامل کریں"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ویجٹس کو حسب ضرورت بنانے کے لیے لانگ پریس کریں"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ویجیٹس کو حسب ضرورت بنائیں"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"غیر فعال ویجیٹ کے لئے ایپ آئیکن"</string>
     <string name="edit_widget" msgid="9030848101135393954">"ویجیٹ میں ترمیم کریں"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ہٹائیں"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ویجیٹ شامل کریں"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"رِنگ کریں"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"وائبریٹ"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"خاموش کریں"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"کاسٹ"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"دستیاب نہیں ہے کیونکہ رنگ خاموش ہے"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"‏‎%1$s۔ آواز چالو کرنے کیلئے تھپتھپائیں۔"</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"‏‎%1$s۔ ارتعاش پر سیٹ کرنے کیلئے تھپتھپائیں۔ ایکسیسبیلٹی سروسز شاید خاموش ہوں۔"</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"‏‎%1$s۔ خاموش کرنے کیلئے تھپتھپائیں۔ ایکسیسبیلٹی سروسز شاید خاموش ہوں۔"</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"‏‎%1$s۔ ارتعاش پر سیٹ کرنے کیلئے تھپتھپائیں۔"</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"‏‎%1$s۔ خاموش کرنے کیلئے تھپتھپائیں۔"</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"شور کنٹرول"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"رنگر وضع تبدیل کرنے کیلئے تھپتھپائیں"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"خاموش کریں"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"غیر خاموش کریں"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"پاور مینیو"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"صفحہ <xliff:g id="ID_1">%1$d</xliff:g> از <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"مقفل اسکرین"</string>
+    <string name="finder_active" msgid="7907846989716941952">"پاور آف ہونے پر بھی آپ میرا آلہ ڈھونڈیں کے ساتھ اس فون کو تلاش کر سکتے ہیں"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"بند ہو رہا ہے…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"نگہداشت کے اقدامات ملاحظہ کریں"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"نگہداشت کے اقدامات ملاحظہ کریں"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"اپنے آلہ کو ان پلگ کریں"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>، <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"نوٹ لکھنا"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"نوٹ لکھنا، <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"آڈیو کا اشتراک کرنا"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"نشریات"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> براڈکاسٹنگ روکیں؟"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"اگر آپ <xliff:g id="SWITCHAPP">%1$s</xliff:g> براڈکاسٹ کرتے ہیں یا آؤٹ پٹ کو تبدیل کرتے ہیں تو آپ کا موجودہ براڈکاسٹ رک جائے گا"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"زیادہ ریزولوشن کے لیے، فون پلٹائیں"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"فولڈ ہونے والے آلے کو کھولا جا رہا ہے"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"فولڈ ہونے والے آلے کو گھمایا جا رہا ہے"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> بیٹری باقی ہے"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"اپنے اسٹائلس کو چارجر منسلک کریں"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"اسٹائلس بیٹری کم ہے"</string>
diff --git a/packages/SystemUI/res/values-ur/tiles_states_strings.xml b/packages/SystemUI/res/values-ur/tiles_states_strings.xml
index bb27b9f..4957e59 100644
--- a/packages/SystemUI/res/values-ur/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ur/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"آف ہے"</item>
     <item msgid="578444932039713369">"آن ہے"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"دستیاب نہیں ہے"</item>
+    <item msgid="9061144428113385092">"آف ہے"</item>
+    <item msgid="2984256114867200368">"آن ہے"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"دستیاب نہیں ہے"</item>
     <item msgid="8707481475312432575">"آف ہے"</item>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 82b6b46..4967115 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Yoqish"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Yoqish"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Kerak emas"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standart"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Qattiq tejash"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Ekranning avtomatik burilishi"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> ilovasiga <xliff:g id="USB_DEVICE">%2$s</xliff:g> qurilmasidan foydalanishga ruxsat berilsinmi?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> ilovasiga <xliff:g id="USB_DEVICE">%2$s</xliff:g> qurilmasidan foydalanish uchun ruxsat berilsinmi?\nBu ilovaga yozib olish ruxsati berilmagan, lekin shu USB orqali ovozlarni yozib olishi mumkin."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"uzish"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"faollashtirish"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Ertaga yana avtomatik yoqilsin"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Tezkor ulashuv, Qurilmamni top va qurilma geolokatsiyasi kabi funksiyalar Bluetooth ishlatadi"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Batareya quvvati: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Garnitura"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Koʻproq vidjetlar qoʻshish"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Vidjetlarni sozlash uchun bosib turing"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Vidjetlarni moslashtirish"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Faolsizlantirilgan vidjet uchun ilova belgisi"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Vidjetni tahrirlash"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Olib tashlash"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Vidjet kiritish"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Jiringlatish"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Tebranish"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Ovozsiz"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Translatsiya"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Jiringlash ovozsizligi uchun ishlamaydi"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Ovozini yoqish uchun ustiga bosing."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tebranishni yoqish uchun ustiga bosing. Qulayliklar ishlamasligi mumkin."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Ovozini o‘chirish uchun ustiga bosing. Qulayliklar ishlamasligi mumkin."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tebranishni yoqish uchun ustiga bosing."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Ovozsiz qilish uchun ustiga bosing."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Shovqin boshqaruvi"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Jiringlagich rejimini oʻzgartirish uchun bosing"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"ovozsiz qilish"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ovozni yoqish"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Quvvat menyusi"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_1">%1$d</xliff:g>-sahifa, jami: <xliff:g id="ID_2">%2$d</xliff:g> ta sahifa"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Ekran qulfi"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Oʻchiq boʻlsa ham “Qurilmani top” funksiyasi yordamida bu telefonni topish mumkin"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Oʻchirilmoqda…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Batafsil axborot"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Batafsil axborot"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Qurilmani uzing"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Qayd olish"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>: qayd olish"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Audio ulashuvi"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Signal uzatish"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasiga translatsiya toʻxtatilsinmi?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Agar <xliff:g id="SWITCHAPP">%1$s</xliff:g> ilovasiga translatsiya qilsangiz yoki ovoz chiqishini oʻzgartirsangiz, joriy translatsiya toʻxtab qoladi"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Yuqori aniqlik uchun telefonni aylantiring"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Buklanadigan qurilma ochilmoqda"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Buklanadigan qurilma aylantirilmoqda"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"buklangan"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"buklanmagan"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Batareya quvvati: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Stilusni quvvat manbaiga ulang"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Stilus batareyasi kam"</string>
diff --git a/packages/SystemUI/res/values-uz/tiles_states_strings.xml b/packages/SystemUI/res/values-uz/tiles_states_strings.xml
index bd5ee89..670c56c 100644
--- a/packages/SystemUI/res/values-uz/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-uz/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Oʻchiq"</item>
     <item msgid="578444932039713369">"Yoniq"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Mavjud emas"</item>
+    <item msgid="9061144428113385092">"Oʻchiq"</item>
+    <item msgid="2984256114867200368">"Yoniq"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Ishlamaydi"</item>
     <item msgid="8707481475312432575">"Oʻchiq"</item>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 2a8d4cd..0b411b5 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Bật"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Bật"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Không, cảm ơn"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Tiêu chuẩn"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Siêu tiết kiệm"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Tự động xoay màn hình"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Cho phép <xliff:g id="APPLICATION">%1$s</xliff:g> truy cập <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Cho phép <xliff:g id="APPLICATION">%1$s</xliff:g> truy cập vào <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nỨng dụng này chưa được cấp quyền ghi âm nhưng vẫn có thể ghi âm thông qua thiết bị USB này."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ngắt kết nối"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"kích hoạt"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Tự động bật lại vào ngày mai"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Các tính năng như Chia sẻ nhanh, Tìm thiết bị của tôi và dịch vụ vị trí trên thiết bị có sử dụng Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> pin"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Âm thanh"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Tai nghe"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Thêm tiện ích khác"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Nhấn và giữ để tuỳ chỉnh tiện ích"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Tuỳ chỉnh tiện ích"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Biểu tượng ứng dụng của tiện ích đã bị vô hiệu hoá"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Chỉnh sửa tiện ích"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Xoá"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Thêm tiện ích"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Đổ chuông"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Rung"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Tắt tiếng"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Truyền"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Không hoạt động vì chuông bị tắt"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Nhấn để bật tiếng."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Nhấn để đặt chế độ rung. Bạn có thể tắt tiếng dịch vụ trợ năng."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Nhấn để tắt tiếng. Bạn có thể tắt tiếng dịch vụ trợ năng."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Nhấn để đặt chế độ rung."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Nhấn để tắt tiếng."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Kiểm soát tiếng ồn"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Nhấn để thay đổi chế độ chuông"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"tắt tiếng"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"bật tiếng"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Trình đơn nguồn"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Trang <xliff:g id="ID_1">%1$d</xliff:g> / <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Màn hình khóa"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Bạn có thể định vị chiếc điện thoại này bằng ứng dụng Tìm thiết bị của tôi ngay cả khi điện thoại tắt nguồn"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Đang tắt…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Xem các bước chăm sóc"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Xem các bước chăm sóc"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Rút thiết bị ra"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Ghi chú"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Ghi chú, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Đang chia sẻ âm thanh"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Phát sóng"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Dừng phát <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Nếu bạn phát <xliff:g id="SWITCHAPP">%1$s</xliff:g> hoặc thay đổi đầu ra, phiên truyền phát hiện tại sẽ dừng"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Để có độ phân giải cao hơn, hãy lật điện thoại"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Thiết bị có thể gập lại đang được mở ra"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Thiết bị có thể gập lại đang được lật ngược"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"gập"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"mở"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Còn <xliff:g id="PERCENTAGE">%s</xliff:g> pin"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Hãy kết nối bút cảm ứng với bộ sạc"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Bút cảm ứng bị yếu pin"</string>
diff --git a/packages/SystemUI/res/values-vi/tiles_states_strings.xml b/packages/SystemUI/res/values-vi/tiles_states_strings.xml
index 201a45b..4df2d91 100644
--- a/packages/SystemUI/res/values-vi/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-vi/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Đang tắt"</item>
     <item msgid="578444932039713369">"Đang bật"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Không có"</item>
+    <item msgid="9061144428113385092">"Đang tắt"</item>
+    <item msgid="2984256114867200368">"Đang bật"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Không hoạt động"</item>
     <item msgid="8707481475312432575">"Đang tắt"</item>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index ddef35f..9a75884 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"开启"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"开启"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"不用了"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"标准"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"超级"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"自动旋转屏幕"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"要允许<xliff:g id="APPLICATION">%1$s</xliff:g>访问<xliff:g id="USB_DEVICE">%2$s</xliff:g>吗?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"是否允许<xliff:g id="APPLICATION">%1$s</xliff:g>访问<xliff:g id="USB_DEVICE">%2$s</xliff:g>?\n此应用未获得录音权限,但能通过此 USB 设备录制音频。"</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"断开连接"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"启用"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"明天自动重新开启"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"快速分享、查找我的设备、设备位置信息等功能会使用蓝牙"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> 的电量"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音频"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳机"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"添加更多微件"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"长按即可自定义微件"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"自定义微件"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"已停用微件的应用图标"</string>
     <string name="edit_widget" msgid="9030848101135393954">"修改微件"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"移除"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"添加微件"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"响铃"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"振动"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"静音"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"投屏"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"该功能无法使用,因为铃声被静音"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s。点按即可取消静音。"</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s。点按即可设为振动,但可能会同时将无障碍服务设为静音。"</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s。点按即可设为静音,但可能会同时将无障碍服务设为静音。"</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s。点按即可设为振动。"</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s。点按即可设为静音。"</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"噪声控制"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"点按即可更改振铃器模式"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"静音"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"取消静音"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"电源菜单"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"第 <xliff:g id="ID_1">%1$d</xliff:g> 页,共 <xliff:g id="ID_2">%2$d</xliff:g> 页"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"锁定屏幕"</string>
+    <string name="finder_active" msgid="7907846989716941952">"即使手机已关机,您也可以通过“查找我的设备”找到这部手机"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"正在关机…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"查看处理步骤"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"查看处理步骤"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"拔出设备"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>,<xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"记事"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"记事,<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"正在分享音频"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"正在广播"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"要停止广播“<xliff:g id="APP_NAME">%1$s</xliff:g>”的内容吗?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"如果广播“<xliff:g id="SWITCHAPP">%1$s</xliff:g>”的内容或更改输出来源,当前的广播就会停止"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"若要获得更高的分辨率,请翻转手机"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"正在展开可折叠设备"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"正在翻转可折叠设备"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"折叠状态"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"展开状态"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"电池还剩 <xliff:g id="PERCENTAGE">%s</xliff:g> 的电量"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"请将触控笔连接充电器"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"触控笔电池电量低"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml b/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml
index 3ab2d7a..08a1551 100644
--- a/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"已关闭"</item>
     <item msgid="578444932039713369">"已开启"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"不可用"</item>
+    <item msgid="9061144428113385092">"已停用"</item>
+    <item msgid="2984256114867200368">"已启用"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"不可用"</item>
     <item msgid="8707481475312432575">"已关闭"</item>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index dbf2cc0..2cf6da4 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"開啟"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"開啟"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"不用了,謝謝"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"標準"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"超級"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"自動旋轉螢幕"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"要允許「<xliff:g id="APPLICATION">%1$s</xliff:g>」存取「<xliff:g id="USB_DEVICE">%2$s</xliff:g>」嗎?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"要允許「<xliff:g id="APPLICATION">%1$s</xliff:g>」存取「<xliff:g id="USB_DEVICE">%2$s</xliff:g>」嗎?\n此應用程式尚未獲授予錄音權限,但可透過此 USB 裝置記錄音訊。"</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"解除連結"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"啟動"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"明天自動重新開啟"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"「快速共享」、「尋找我的裝置」和裝置位置等功能都會使用藍牙"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"電量:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音訊"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳機"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"新增更多小工具"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"長按即可自訂小工具"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"自訂小工具"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"已停用小工具的應用程式圖示"</string>
     <string name="edit_widget" msgid="9030848101135393954">"編輯小工具"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"移除"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"新增小工具"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"鈴聲"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"震動"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"靜音"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"投放"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"鈴聲已設定為靜音,因此無法使用"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s。輕按即可取消靜音。"</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s。輕按即可設為震動。無障礙功能服務可能已經設為靜音。"</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s。輕按即可設為靜音。無障礙功能服務可能已經設為靜音。"</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s。輕按即可設為震動。"</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s。輕按即可設為靜音。"</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"噪音控制"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"輕按即可變更響鈴模式"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"靜音"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"取消靜音"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"電源選單"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"第 <xliff:g id="ID_1">%1$d</xliff:g> 頁 (共 <xliff:g id="ID_2">%2$d</xliff:g> 頁)"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"螢幕鎖定"</string>
+    <string name="finder_active" msgid="7907846989716941952">"即使手機關機,仍可透過「尋找我的裝置」尋找此手機"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"正在關機…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"查看保養步驟"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"查看保養步驟"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"拔除裝置"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>,<xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"做筆記"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"做筆記,<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"正在分享音訊"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"廣播"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"要停止廣播「<xliff:g id="APP_NAME">%1$s</xliff:g>」的內容嗎?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"如要廣播「<xliff:g id="SWITCHAPP">%1$s</xliff:g>」的內容或變更輸出來源,系統就會停止廣播目前的內容"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"如要提高解像度,請切換至手機後置鏡頭"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"正在展開折疊式裝置"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"正在翻轉折疊式裝置"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"已摺疊"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"已打開"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"剩餘電量:<xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"將觸控筆連接充電器"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"觸控筆電量不足"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/tiles_states_strings.xml b/packages/SystemUI/res/values-zh-rHK/tiles_states_strings.xml
index 89d6628..e29d230 100644
--- a/packages/SystemUI/res/values-zh-rHK/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"已關閉"</item>
     <item msgid="578444932039713369">"已開啟"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"未有提供"</item>
+    <item msgid="9061144428113385092">"關閉"</item>
+    <item msgid="2984256114867200368">"開啟"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"無法使用"</item>
     <item msgid="8707481475312432575">"已關閉"</item>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index d92de1c..12ca94c 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"開啟"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"開啟"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"不用了,謝謝"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"標準"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"超級"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"自動旋轉螢幕"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"要允許「<xliff:g id="APPLICATION">%1$s</xliff:g>」存取「<xliff:g id="USB_DEVICE">%2$s</xliff:g>」嗎?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"要允許「<xliff:g id="APPLICATION">%1$s</xliff:g>」存取「<xliff:g id="USB_DEVICE">%2$s</xliff:g>」嗎?\n這個應用程式未取得錄製權限,但可以透過這部 USB 裝置錄製音訊。"</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"取消連結"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"啟用"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"明天自動重新開啟"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"快速分享、尋找我的裝置和裝置位置資訊等功能都會使用藍牙"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"電量:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音訊"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳機"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"新增更多小工具"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"長按即可自訂小工具"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"自訂小工具"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"所停用小工具的應用程式圖示"</string>
     <string name="edit_widget" msgid="9030848101135393954">"編輯小工具"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"移除"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"新增小工具"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"鈴聲"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"震動"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"靜音"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"投放"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"鈴聲已設為靜音,因此無法使用"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s。輕觸即可取消靜音。"</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s。輕觸即可設為震動,但系統可能會將無障礙服務一併設為靜音。"</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s。輕觸即可設為靜音,但系統可能會將無障礙服務一併設為靜音。"</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s。輕觸即可設為震動。"</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s。輕觸即可設為靜音。"</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"噪音控制"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"輕觸即可變更鈴聲模式"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"靜音"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"取消靜音"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"電源鍵選單"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"第 <xliff:g id="ID_1">%1$d</xliff:g> 頁,共 <xliff:g id="ID_2">%2$d</xliff:g> 頁"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"鎖定畫面"</string>
+    <string name="finder_active" msgid="7907846989716941952">"即使這支手機關機,仍可透過「尋找我的裝置」找出手機位置"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"關機中…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"查看處理步驟"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"查看處理步驟"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"拔除裝置"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>,<xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"做筆記"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"做筆記,<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"分享音訊"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"廣播"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"要停止播送「<xliff:g id="APP_NAME">%1$s</xliff:g>」的內容嗎?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"如果播送「<xliff:g id="SWITCHAPP">%1$s</xliff:g>」的內容或變更輸出來源,系統就會停止播送目前的內容"</string>
@@ -1216,6 +1236,12 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"如要提高解析度,請切換至手機後置鏡頭"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"正在展開的折疊式裝置"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"正在翻轉折疊式裝置"</string>
+    <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+    <skip />
+    <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+    <skip />
+    <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+    <skip />
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"剩餘電量:<xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"將觸控筆接上充電器"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"觸控筆電力不足"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/tiles_states_strings.xml b/packages/SystemUI/res/values-zh-rTW/tiles_states_strings.xml
index a046e33..85e1796 100644
--- a/packages/SystemUI/res/values-zh-rTW/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"已關閉"</item>
     <item msgid="578444932039713369">"已開啟"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"無法使用"</item>
+    <item msgid="9061144428113385092">"已關閉"</item>
+    <item msgid="2984256114867200368">"已開啟"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"無法使用"</item>
     <item msgid="8707481475312432575">"已關閉"</item>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 9cd7082..b6f4a19 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -31,6 +31,8 @@
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Vula"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Vula"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Cha ngiyabonga"</string>
+    <string name="standard_battery_saver_text" msgid="6855876746552374119">"Okujwayelekile"</string>
+    <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Kakhulu"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Ukuzulazula kweskrini okuzenzakalelayo"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Vumela i-<xliff:g id="APPLICATION">%1$s</xliff:g> ukufinyelela i-<xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Vumela i-<xliff:g id="APPLICATION">%1$s</xliff:g> ukuthi ifinyelele ku-<xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nLolu hlelo lokusebenza alunikeziwe imvume yokurekhoda kodwa lingathatha umsindo ngale divayisi ye-USB."</string>
@@ -269,7 +271,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"nqamula"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"yenza kusebenze"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Vula ngokuzenzekela futhi kusasa"</string>
-    <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Izakhi ezifana Nokwabelana Ngokushesha, okuthi Thola Idivayisi Yami, kanye nendawo yedivayisi zisebenzisa i-Bluetooth"</string>
+    <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+    <skip />
+    <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+    <skip />
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ibhethri"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Umsindo"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ihedisethi"</string>
@@ -429,6 +434,7 @@
     <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Engeza amawijethi engeziwe"</string>
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Cindezela isikhathi eside ukuze wenze ngokwezifiso amawijethi"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Yenza ngokwezifiso amawijethi"</string>
+    <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Isithonjana se-app sewijethi evaliwe"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Hlela amawijethi"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Susa"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Engeza iwijethi"</string>
@@ -573,11 +579,22 @@
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Khalisa"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Dlidlizela"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Thulisa"</string>
+    <string name="media_device_cast" msgid="4786241789687569892">"Sakaza"</string>
+    <string name="stream_notification_unavailable" msgid="4313854556205836435">"Ayitholakali ngoba ukukhala kuthulisiwe"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Thepha ukuze ususe ukuthula."</string>
     <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Thepha ukuze usethe ukudlidliza. Amasevisi okufinyelela angathuliswa."</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Thepha ukuze uthulise. Amasevisi okufinyelela angathuliswa."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Thepha ukuze usethele ekudlidlizeni."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Thepha ukuze uthulise."</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Ulawulo Lomsindo"</string>
+    <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+    <skip />
+    <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+    <skip />
     <string name="volume_ringer_change" msgid="3574969197796055532">"Thepha ukuze ushintshe imodi yokukhala"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"thulisa"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"susa ukuthula"</string>
@@ -832,6 +849,8 @@
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Imenyu yamandla"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Ikhasi <xliff:g id="ID_1">%1$d</xliff:g> kwangu-<xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Khiya isikrini"</string>
+    <string name="finder_active" msgid="7907846989716941952">"Ungabeka le foni ngokuthi Thola Ifoni Yami ngisho noma ivaliwe"</string>
+    <string name="shutdown_progress" msgid="5464239146561542178">"Iyacisha…"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Bona izinyathelo zokunakekelwa"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Bona izinyathelo zokunakekelwa"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Khipha idivayisi yakho"</string>
@@ -1186,6 +1205,7 @@
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
     <string name="note_task_button_label" msgid="230135078402003532">"Ukuthatha amanothi"</string>
     <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Ukuthatha amanothi, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
+    <string name="audio_sharing_description" msgid="8849060142768870004">"Yabelana ngomsindo"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Ukusakaza"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Misa ukusakaza i-<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Uma usakaza i-<xliff:g id="SWITCHAPP">%1$s</xliff:g> noma ushintsha okuphumayo, ukusakaza kwakho kwamanje kuzoma"</string>
@@ -1216,6 +1236,9 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Ukuze uthole ukulungiswa okuphezulu, phendula ifoni"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Idivayisi egoqekayo iyembulwa"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Idivayisi egoqekayo iphendulwa nxazonke"</string>
+    <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"kugoqiwe"</string>
+    <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"kuvuliwe"</string>
+    <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
     <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ibhethri elisele"</string>
     <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Xhuma i-stylus yakho kushaja"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Ibhethri le-stylus liphansi"</string>
diff --git a/packages/SystemUI/res/values-zu/tiles_states_strings.xml b/packages/SystemUI/res/values-zu/tiles_states_strings.xml
index e35840b..5c5a67c 100644
--- a/packages/SystemUI/res/values-zu/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-zu/tiles_states_strings.xml
@@ -126,6 +126,11 @@
     <item msgid="8259411607272330225">"Valiwe"</item>
     <item msgid="578444932039713369">"Vuliwe"</item>
   </string-array>
+  <string-array name="tile_states_record_issue">
+    <item msgid="1727196795383575383">"Ayitholakali"</item>
+    <item msgid="9061144428113385092">"Kuvaliwe"</item>
+    <item msgid="2984256114867200368">"Kuvuliwe"</item>
+  </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"Akutholakali"</item>
     <item msgid="8707481475312432575">"Valiwe"</item>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index beaa708..35f6a08 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -101,7 +101,7 @@
 
     <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
     <string name="quick_settings_tiles_stock" translatable="false">
-        internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling
+        internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue
     </string>
 
     <!-- The tiles to display in QuickSettings -->
@@ -345,9 +345,6 @@
          the notification is not swiped enough to dismiss it. -->
     <bool name="config_showNotificationGear">true</bool>
 
-    <!-- Whether or not a background should be drawn behind a notification. -->
-    <bool name="config_drawNotificationBackground">false</bool>
-
     <!-- Whether or the notifications can be shown and dismissed with a drag. -->
     <bool name="config_enableNotificationShadeDrag">true</bool>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 4be1deb..b7eff38 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -164,6 +164,9 @@
     so the width of the icon should be 13.0sp * (12.0 / 20.0) -->
     <dimen name="status_bar_battery_icon_width">7.8sp</dimen>
 
+    <dimen name="status_bar_battery_unified_icon_width">24sp</dimen>
+    <dimen name="status_bar_battery_unified_icon_height">14sp</dimen>
+
     <!-- The battery icon is 13sp tall, but the other system icons are 15sp tall (see
          @*android:dimen/status_bar_system_icon_size) with some top and bottom padding embedded in
          the drawables themselves. So, the battery icon may need an extra 1dp of spacing so that its
@@ -199,6 +202,7 @@
     <!-- Size of the view displaying the mobile signal icon in the status bar. This value should
         match the core/status_bar_system_icon_size and change to sp unit -->
     <dimen name="status_bar_mobile_signal_size">15sp</dimen>
+    <dimen name="status_bar_mobile_signal_size_updated">14sp</dimen>
     <!-- Size of the view displaying the mobile signal icon in the status bar. This value should
         match the viewport height of mobile signal drawables such as ic_lte_mobiledata -->
     <dimen name="status_bar_mobile_type_size">16sp</dimen>
@@ -898,8 +902,8 @@
     <dimen name="communal_enforced_rounded_corner_max_radius">16dp</dimen>
 
     <!-- Width and height used to filter widgets displayed in the communal widget picker -->
-    <dimen name="communal_widget_picker_desired_width">464dp</dimen>
-    <dimen name="communal_widget_picker_desired_height">307dp</dimen>
+    <dimen name="communal_widget_picker_desired_width">424dp</dimen>
+    <dimen name="communal_widget_picker_desired_height">282dp</dimen>
 
     <!-- The width/height of the unlock icon view on keyguard. -->
     <dimen name="keyguard_lock_height">42dp</dimen>
@@ -1742,12 +1746,18 @@
     <dimen name="communal_grid_height">630dp</dimen>
     <!-- Number of columns for each communal card -->
     <integer name="communal_grid_columns_per_card">6</integer>
-    <!-- Width of area on right edge of screen in which swipes will open the communal hub -->
-    <dimen name="communal_right_edge_swipe_region_width">16dp</dimen>
+
+    <!-- The width of the swipe target to initiate opening or closing communal hub. -->
+    <dimen name="communal_gesture_initiation_width">68dp</dimen>
+
+    <!-- TODO(b/322549765): unify with communal_gesture_initiation_width -->
+    <!-- Width of area on right edge of screen in which swipes will open the communal hub when on
+    the lockscreen -->
+    <dimen name="communal_right_edge_swipe_region_width">40dp</dimen>
     <!-- Height of area at top of communal hub where swipes should open the notification shade -->
-    <dimen name="communal_top_edge_swipe_region_height">32dp</dimen>
+    <dimen name="communal_top_edge_swipe_region_height">68dp</dimen>
     <!-- Height of area at bottom of communal hub where swipes should open the bouncer -->
-    <dimen name="communal_bottom_edge_swipe_region_height">32dp</dimen>
+    <dimen name="communal_bottom_edge_swipe_region_height">68dp</dimen>
 
     <dimen name="drag_and_drop_icon_size">70dp</dimen>
 
@@ -1819,9 +1829,6 @@
     <dimen name="dream_overlay_complication_smartspace_padding">24dp</dimen>
     <dimen name="dream_overlay_complication_smartspace_max_width">408dp</dimen>
 
-    <!-- The width of the swipe target to initiate opening communal hub over dreams. -->
-    <dimen name="communal_gesture_initiation_width">48dp</dimen>
-
     <!-- The position of the end guide, which dream overlay complications can align their start with
          if their end is aligned with the parent end. Represented as the percentage over from the
          start of the parent container. -->
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 71ae0d7..035cfdc 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -223,6 +223,7 @@
     <item type="id" name="lock_icon" />
     <item type="id" name="lock_icon_bg" />
     <item type="id" name="burn_in_layer" />
+    <item type="id" name="burn_in_layer_empty_view" />
     <item type="id" name="communal_tutorial_indicator" />
     <item type="id" name="nssl_placeholder_barrier_bottom" />
     <item type="id" name="ambient_indication_container" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4263d94..b8f20f6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -34,6 +34,10 @@
         remaining [CHAR LIMIT=none]-->
     <string name="battery_low_percent_format"><xliff:g id="percentage">%s</xliff:g> remaining</string>
 
+    <!-- SVG path description for the battery frame of the unified battery drawable
+    (BatteryLayersDrawable.kt). Drawn on a 24x14 canvas. Not suitable outside in any other context -->
+    <string name="battery_unified_frame_path_string" translatable="false">M2.75,3C2.75,1.757 3.757,0.75 5,0.75H20C21.795,0.75 23.25,2.205 23.25,4V10C23.25,11.795 21.795,13.25 20,13.25H5C3.757,13.25 2.75,12.243 2.75,11V3Z </string>
+
     <!-- A message that appears when the battery remaining estimate is low in a dialog.  This is
     appended to the subtitle of the low battery alert.  "percentage" is the percentage of battery
     remaining. "time" is the amount of time remaining before the phone runs out of battery [CHAR LIMIT=none]-->
@@ -71,6 +75,11 @@
     <!-- Battery saver notification dismiss action: Do not turn on battery saver. [CHAR LIMIT=NONE]-->
     <string name="battery_saver_dismiss_action">No thanks</string>
 
+    <!-- Secondary label for Battery Saver tile when Battery Saver is enabled. [CHAR LIMIT=20] -->
+    <string name="standard_battery_saver_text">Standard</string>
+    <!-- Secondary label for Battery Saver tile when Extreme Battery Saver is enabled. [CHAR LIMIT=20] -->
+    <string name="extreme_battery_saver_text">Extreme</string>
+
     <!-- Name of the button that links to the Settings app. [CHAR LIMIT=NONE] -->
 
     <!-- Name of the button that links to the Wifi settings screen. [CHAR LIMIT=NONE] -->
@@ -671,8 +680,10 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate">activate</string>
     <!-- QuickSettings: Bluetooth auto on tomorrow [CHAR LIMIT=NONE]-->
     <string name="turn_on_bluetooth_auto_tomorrow">Automatically turn on again tomorrow</string>
-    <!-- QuickSettings: Bluetooth auto on info text [CHAR LIMIT=NONE]-->
-    <string name="turn_on_bluetooth_auto_info">Features like Quick Share, Find My Device, and device location use Bluetooth</string>
+    <!-- QuickSettings: Bluetooth auto on info text when disabled [CHAR LIMIT=NONE]-->
+    <string name="turn_on_bluetooth_auto_info_disabled">Features like Quick Share, Find My Device, and device location use Bluetooth</string>
+    <!-- QuickSettings: Bluetooth auto on info text when enabled [CHAR LIMIT=NONE]-->
+    <string name="turn_on_bluetooth_auto_info_enabled">Bluetooth will turn on tomorrow at 5 AM</string>
 
     <!-- QuickSettings: Bluetooth secondary label for the battery level of a connected device [CHAR LIMIT=20]-->
     <string name="quick_settings_bluetooth_secondary_label_battery_level"><xliff:g id="battery_level_as_percentage">%s</xliff:g> battery</string>
@@ -1114,6 +1125,8 @@
     <string name="popup_on_dismiss_cta_tile_text">Long press to customize widgets</string>
     <!-- Text for the button to configure widgets after long press. [CHAR LIMIT=50] -->
     <string name="button_to_configure_widgets_text">Customize widgets</string>
+    <!-- Description for the App icon of disabled widget. [CHAR LIMIT=NONE] -->
+    <string name="icon_description_for_disabled_widget">App icon for disabled widget</string>
     <!-- Label for the button which configures widgets [CHAR LIMIT=NONE] -->
     <string name="edit_widget">Edit widget</string>
     <!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] -->
@@ -1122,6 +1135,14 @@
     <string name="hub_mode_add_widget_button_text">Add widget</string>
     <!-- Text for the button that exits the hub mode editing mode. [CHAR LIMIT=50] -->
     <string name="hub_mode_editing_exit_button_text">Done</string>
+    <!-- Title for the dialog that redirects users to change allowed widget category in settings. [CHAR LIMIT=NONE] -->
+    <string name="dialog_title_to_allow_any_widget">Allow any widget on lock screen?</string>
+    <!-- Text for the button in the dialog that opens when tapping on disabled widgets. [CHAR LIMIT=NONE] -->
+    <string name="button_text_to_open_settings">Open settings</string>
+    <!-- Title of a dialog. This text is confirming that the user wants to turn on access to their work apps, which the user had previously paused. "Work" is an adjective. [CHAR LIMIT=30] -->
+    <string name="work_mode_off_title">Unpause work apps?</string>
+    <!-- Title for button to unpause on work profile. [CHAR LIMIT=NONE] -->
+    <string name="work_mode_turn_on">Unpause</string>
 
     <!-- Related to user switcher --><skip/>
 
@@ -1515,8 +1536,12 @@
 
     <!-- Media device casting volume slider label [CHAR_LIMIT=20] -->
     <string name="media_device_cast">Cast</string>
-    <!-- A message shown when the notification volume changing is disabled because of the muted ring stream [CHAR_LIMIT=40]-->
+    <!-- A message shown when the notification volume changing is disabled because of the muted ring stream [CHAR_LIMIT=50]-->
     <string name="stream_notification_unavailable">Unavailable because ring is muted</string>
+    <!-- A message shown when the alarm volume changing is disabled because of the don't disturb mode [CHAR_LIMIT=50]-->
+    <string name="stream_alarm_unavailable">Unavailable because Do Not Disturb is on</string>
+    <!-- A message shown when the media volume changing is disabled because of the don't disturb mode [CHAR_LIMIT=50]-->
+    <string name="stream_media_unavailable">Unavailable because Do Not Disturb is on</string>
 
     <!-- Shown in the header of quick settings to indicate to the user that their phone ringer is on vibrate. [CHAR_LIMIT=NONE] -->
     <!-- Shown in the header of quick settings to indicate to the user that their phone ringer is on silent (muted). [CHAR_LIMIT=NONE] -->
@@ -1527,8 +1552,16 @@
     <string name="volume_stream_content_description_vibrate_a11y">%1$s. Tap to set to vibrate.</string>
     <string name="volume_stream_content_description_mute_a11y">%1$s. Tap to mute.</string>
 
-    <!-- Label for button to enabled/disable live caption [CHAR_LIMIT=30] -->
+    <!-- Label for button to enabled/disable active noise cancellation [CHAR_LIMIT=30] -->
     <string name="volume_panel_noise_control_title">Noise Control</string>
+    <!-- Label for button to enabled/disable spatial audio [CHAR_LIMIT=30] -->
+    <string name="volume_panel_spatial_audio_title">Spatial Audio</string>
+    <!-- Label for button to disable spatial audio [CHAR_LIMIT=20] -->
+    <string name="volume_panel_spatial_audio_off">Off</string>
+    <!-- Label for button to enabled spatial audio [CHAR_LIMIT=20] -->
+    <string name="volume_panel_spatial_audio_fixed">Fixed</string>
+    <!-- Label for button to enabled head tracking [CHAR_LIMIT=20] -->
+    <string name="volume_panel_spatial_audio_tracking">Head Tracking</string>
 
     <string name="volume_ringer_change">Tap to change ringer mode</string>
 
@@ -2236,6 +2269,11 @@
     <!-- Tuner string -->
     <!-- Tuner string -->
 
+    <!-- Message shown during shutdown when Find My Device with Dead Battery Finder is active  [CHAR LIMIT=300] -->
+    <string name="finder_active">You can locate this phone with Find My Device even when powered off</string>
+    <!-- Shutdown Progress Dialog. This is shown if the user chooses to power off the phone. [CHAR LIMIT=60] -->
+    <string name="shutdown_progress">Shutting down\u2026</string>
+
     <!-- Text help link for care instructions for overheating devices [CHAR LIMIT=40] -->
     <string name="thermal_shutdown_dialog_help_text">See care steps</string>
     <!-- URL for care instructions for overheating devices -->
@@ -3243,6 +3281,12 @@
     <!-- Text for education page content description for unfolded animation. [CHAR_LIMIT=NONE] -->
     <string name="rear_display_accessibility_unfolded_animation">Foldable device being flipped around</string>
 
+    <!-- QuickSettings: Additional label for the auto-rotation quicksettings tile indicating that the setting corresponds to the folded posture for a foldable device [CHAR LIMIT=32] -->
+    <string name="quick_settings_rotation_posture_folded">folded</string>
+    <!-- QuickSettings: Additional label for the auto-rotation quicksettings tile indicating that the setting corresponds to the unfolded posture for a foldable device [CHAR LIMIT=32] -->
+    <string name="quick_settings_rotation_posture_unfolded">unfolded</string>
+    <!-- QuickSettings: template for rotation tile foldable secondary label [CHAR LIMIT=64] !-->
+    <string name="rotation_tile_with_posture_secondary_label_template">%1$s / %2$s</string>
     <!-- Title for notification of low stylus battery with percentage. "percentage" is
         the value of the battery capacity remaining [CHAR LIMIT=none]-->
     <string name="stylus_battery_low_percentage"><xliff:g id="percentage" example="16%">%s</xliff:g> battery remaining</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 617eadb..59516be 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -965,6 +965,10 @@
         <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
     </style>
 
+    <style name="Widget.SliceView.VolumePanel">
+        <item name="hideHeaderRow">true</item>
+    </style>
+
     <style name="Theme.VolumePanelActivity.Popup" parent="@style/Theme.SystemUI.Dialog">
         <item name="android:dialogCornerRadius">44dp</item>
         <item name="android:colorBackground">?androidprv:attr/materialColorSurfaceContainerHigh
@@ -987,6 +991,11 @@
         <item name="android:windowActionBar">false</item>
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowLightStatusBar">true</item>
+
+        <!--
+            TODO(b/309578419): Make activities handle insets properly and then remove this.
+        -->
+        <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
     </style>
 
     <style name="Theme.EditWidgetsActivity"
@@ -1581,4 +1590,8 @@
     <style name="Theme.PrivacyDialog" parent="@style/Theme.SystemUI.Dialog">
         <item name="android:colorBackground">?androidprv:attr/materialColorSurfaceContainer</item>
     </style>
+
+    <style name="Theme.SystemUI.Dialog.StickyKeys" parent="@style/Theme.SystemUI.Dialog">
+        <item name="android:colorBackground">@color/transparent</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/res/values/tiles_states_strings.xml b/packages/SystemUI/res/values/tiles_states_strings.xml
index 7020d54..9036a35 100644
--- a/packages/SystemUI/res/values/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values/tiles_states_strings.xml
@@ -222,6 +222,16 @@
         <item>On</item>
     </string-array>
 
+    <!-- State names for record_issue tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_record_issue">
+        <item>Unavailable</item>
+        <item>Off</item>
+        <item>On</item>
+    </string-array>
+
     <!-- State names for reverse (charging) tile: unavailable, off, on.
          This subtitle is shown when the tile is in that particular state but does not set its own
          subtitle, so some of these may never appear on screen. They should still be translated as
diff --git a/packages/SystemUI/res/xml/fileprovider.xml b/packages/SystemUI/res/xml/fileprovider.xml
index b67378e..71cc05d 100644
--- a/packages/SystemUI/res/xml/fileprovider.xml
+++ b/packages/SystemUI/res/xml/fileprovider.xml
@@ -19,4 +19,5 @@
     <cache-path name="leak" path="leak/"/>
     <external-path name="screenrecord" path="."/>
     <cache-path name="multi_user" path="multi_user/" />
-</paths>
\ No newline at end of file
+    <root-path name="traces" path="/data/local/traces"/>
+</paths>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 6e611fe..42ba05c 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -35,6 +35,9 @@
     srcs: [
         ":statslog-SystemUI-java-gen",
     ],
+    libs: [
+        "androidx.annotation_annotation",
+    ],
 }
 
 android_library {
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 10393cf..ca63483 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
@@ -196,7 +196,7 @@
             final RecentsAnimationListener animationHandler, final Consumer<Boolean> resultCallback,
             Handler resultCallbackHandler) {
         boolean result = startRecentsActivity(intent, eventTime, animationHandler);
-        if (resultCallback != null) {
+        if (resultCallback != null && resultCallbackHandler != null) {
             resultCallbackHandler.post(new Runnable() {
                 @Override
                 public void run() {
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index 1cfa816..b9b8fbe 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -17,9 +17,9 @@
 package com.android.keyguard;
 
 import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ACTIVE_DATA_SUB_CHANGED;
-import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ON_SIM_STATE_CHANGED;
 import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ON_TELEPHONY_CAPABLE;
 import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_REFRESH_CARRIER_INFO;
+import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_SIM_ERROR_STATE_CHANGED;
 
 import android.content.Context;
 import android.content.Intent;
@@ -123,12 +123,15 @@
                 return;
             }
 
-            mLogger.logUpdateCarrierTextForReason(REASON_ON_SIM_STATE_CHANGED);
+
+            mLogger.logSimStateChangedCallback(subId, slotId, simState);
             if (getStatusForIccState(simState) == CarrierTextManager.StatusMode.SimIoError) {
                 mSimErrorState[slotId] = true;
+                mLogger.logUpdateCarrierTextForReason(REASON_SIM_ERROR_STATE_CHANGED);
                 updateCarrierText();
             } else if (mSimErrorState[slotId]) {
                 mSimErrorState[slotId] = false;
+                mLogger.logUpdateCarrierTextForReason(REASON_SIM_ERROR_STATE_CHANGED);
                 updateCarrierText();
             }
         }
@@ -206,6 +209,9 @@
                 // This will set/remove the listeners appropriately. Note that it will never double
                 // add the listeners.
                 handleSetListening(mCarrierTextCallback);
+                mainExecutor.execute(() -> {
+                    mKeyguardUpdateMonitor.registerCallback(mCallback);
+                });
             }
         });
     }
@@ -273,7 +279,6 @@
             if (mNetworkSupported.get()) {
                 // Keyguard update monitor expects callbacks from main thread
                 mMainExecutor.execute(() -> {
-                    mKeyguardUpdateMonitor.registerCallback(mCallback);
                     mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
                 });
                 mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener);
@@ -286,7 +291,6 @@
         } else {
             mCarrierTextCallback = null;
             mMainExecutor.execute(() -> {
-                mKeyguardUpdateMonitor.removeCallback(mCallback);
                 mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
             });
             mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener);
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 05e07a7..8a2245d 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -15,8 +15,6 @@
  */
 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
@@ -43,14 +41,16 @@
 import com.android.systemui.flags.Flags.REGION_SAMPLING
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.log.core.Logger
+import com.android.systemui.plugins.clocks.AlarmData
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockFaceController
 import com.android.systemui.plugins.clocks.ClockMessageBuffers
 import com.android.systemui.plugins.clocks.ClockTickRate
-import com.android.systemui.plugins.clocks.AlarmData
 import com.android.systemui.plugins.clocks.WeatherData
 import com.android.systemui.plugins.clocks.ZenData
 import com.android.systemui.plugins.clocks.ZenData.ZenMode
@@ -61,16 +61,18 @@
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.ZenModeController
 import com.android.systemui.util.concurrency.DelayableExecutor
+import java.util.Locale
+import java.util.TimeZone
+import java.util.concurrent.Executor
+import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.launch
-import java.util.Locale
-import java.util.TimeZone
-import java.util.concurrent.Executor
-import javax.inject.Inject
 
 /**
  * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
@@ -93,11 +95,13 @@
     private val featureFlags: FeatureFlagsClassic,
     private val zenModeController: ZenModeController,
 ) {
-    var loggers = listOf(
-        clockBuffers.infraMessageBuffer,
-        clockBuffers.smallClockMessageBuffer,
-        clockBuffers.largeClockMessageBuffer
-    ).map { Logger(it, TAG) }
+    var loggers =
+        listOf(
+                clockBuffers.infraMessageBuffer,
+                clockBuffers.smallClockMessageBuffer,
+                clockBuffers.largeClockMessageBuffer
+            )
+            .map { Logger(it, TAG) }
 
     var clock: ClockController? = null
         get() = field
@@ -108,11 +112,12 @@
         }
 
     private fun disconnectClock(clock: ClockController?) {
-        if (clock == null) { return; }
+        if (clock == null) {
+            return
+        }
         smallClockOnAttachStateChangeListener?.let {
             clock.smallClock.view.removeOnAttachStateChangeListener(it)
-            smallClockFrame?.viewTreeObserver
-                    ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
+            smallClockFrame?.viewTreeObserver?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
         }
         largeClockOnAttachStateChangeListener?.let {
             clock.largeClock.view.removeOnAttachStateChangeListener(it)
@@ -120,7 +125,9 @@
     }
 
     private fun connectClock(clock: ClockController?) {
-        if (clock == null) { return; }
+        if (clock == null) {
+            return
+        }
         val clockStr = clock.toString()
         loggers.forEach { it.d({ "New Clock: $str1" }) { str1 = clockStr } }
 
@@ -129,23 +136,27 @@
         if (!regionSamplingEnabled) {
             updateColors()
         } else {
-            smallRegionSampler = createRegionSampler(
-                clock.smallClock.view,
-                mainExecutor,
-                bgExecutor,
-                regionSamplingEnabled,
-                isLockscreen = true,
-                ::updateColors
-            ).apply { startRegionSampler() }
+            smallRegionSampler =
+                createRegionSampler(
+                        clock.smallClock.view,
+                        mainExecutor,
+                        bgExecutor,
+                        regionSamplingEnabled,
+                        isLockscreen = true,
+                        ::updateColors
+                    )
+                    .apply { startRegionSampler() }
 
-            largeRegionSampler = createRegionSampler(
-                clock.largeClock.view,
-                mainExecutor,
-                bgExecutor,
-                regionSamplingEnabled,
-                isLockscreen = true,
-                ::updateColors
-            ).apply { startRegionSampler() }
+            largeRegionSampler =
+                createRegionSampler(
+                        clock.largeClock.view,
+                        mainExecutor,
+                        bgExecutor,
+                        regionSamplingEnabled,
+                        isLockscreen = true,
+                        ::updateColors
+                    )
+                    .apply { startRegionSampler() }
 
             updateColors()
         }
@@ -158,49 +169,49 @@
             }
             clock.events.onWeatherDataChanged(it)
         }
-        zenData?.let {
-            clock.events.onZenDataChanged(it)
-        }
-        alarmData?.let {
-            clock.events.onAlarmDataChanged(it)
-        }
+        zenData?.let { clock.events.onZenDataChanged(it) }
+        alarmData?.let { clock.events.onAlarmDataChanged(it) }
 
-        smallClockOnAttachStateChangeListener = object : OnAttachStateChangeListener {
-            var pastVisibility: Int? = null
-            override fun onViewAttachedToWindow(view: View) {
-                clock.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
-                // Match the asing for view.parent's layout classes.
-                smallClockFrame = (view.parent as ViewGroup)?.also { frame ->
-                    pastVisibility = frame.visibility
-                    onGlobalLayoutListener = OnGlobalLayoutListener {
-                        val currentVisibility = frame.visibility
-                        if (pastVisibility != currentVisibility) {
-                            pastVisibility = currentVisibility
-                            // when small clock is visible,
-                            // recalculate bounds and sample
-                            if (currentVisibility == View.VISIBLE) {
-                                smallRegionSampler?.stopRegionSampler()
-                                smallRegionSampler?.startRegionSampler()
+        smallClockOnAttachStateChangeListener =
+            object : OnAttachStateChangeListener {
+                var pastVisibility: Int? = null
+                override fun onViewAttachedToWindow(view: View) {
+                    clock.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+                    // Match the asing for view.parent's layout classes.
+                    smallClockFrame =
+                        (view.parent as ViewGroup)?.also { frame ->
+                            pastVisibility = frame.visibility
+                            onGlobalLayoutListener = OnGlobalLayoutListener {
+                                val currentVisibility = frame.visibility
+                                if (pastVisibility != currentVisibility) {
+                                    pastVisibility = currentVisibility
+                                    // when small clock is visible,
+                                    // recalculate bounds and sample
+                                    if (currentVisibility == View.VISIBLE) {
+                                        smallRegionSampler?.stopRegionSampler()
+                                        smallRegionSampler?.startRegionSampler()
+                                    }
+                                }
                             }
+                            frame.viewTreeObserver.addOnGlobalLayoutListener(onGlobalLayoutListener)
                         }
-                    }
-                    frame.viewTreeObserver.addOnGlobalLayoutListener(onGlobalLayoutListener)
+                }
+
+                override fun onViewDetachedFromWindow(p0: View) {
+                    smallClockFrame
+                        ?.viewTreeObserver
+                        ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
                 }
             }
-
-            override fun onViewDetachedFromWindow(p0: View) {
-                smallClockFrame?.viewTreeObserver
-                        ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
-            }
-        }
         clock.smallClock.view.addOnAttachStateChangeListener(smallClockOnAttachStateChangeListener)
 
-        largeClockOnAttachStateChangeListener = object : OnAttachStateChangeListener {
-            override fun onViewAttachedToWindow(p0: View) {
-                clock.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+        largeClockOnAttachStateChangeListener =
+            object : OnAttachStateChangeListener {
+                override fun onViewAttachedToWindow(p0: View) {
+                    clock.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+                }
+                override fun onViewDetachedFromWindow(p0: View) {}
             }
-            override fun onViewDetachedFromWindow(p0: View) {}
-        }
         clock.largeClock.view.addOnAttachStateChangeListener(largeClockOnAttachStateChangeListener)
     }
 
@@ -263,7 +274,9 @@
             bgExecutor,
             regionSamplingEnabled,
             isLockscreen,
-        ) { updateColors() }
+        ) {
+            updateColors()
+        }
     }
 
     var smallRegionSampler: RegionSampler? = null
@@ -364,35 +377,38 @@
             }
         }
 
-    private val zenModeCallback = object : ZenModeController.Callback {
-        override fun onZenChanged(zen: Int) {
-            var mode = ZenMode.fromInt(zen)
-            if (mode == null) {
-                Log.e(TAG, "Failed to get zen mode from int: $zen")
-                return
-            }
-
-            zenData = ZenData(
-                mode,
-                if (mode == ZenMode.OFF) SysuiR.string::dnd_is_off.name
-                    else SysuiR.string::dnd_is_on.name
-            ).also { data ->
-                mainExecutor.execute {
-                    clock?.run { events.onZenDataChanged(data) }
+    private val zenModeCallback =
+        object : ZenModeController.Callback {
+            override fun onZenChanged(zen: Int) {
+                var mode = ZenMode.fromInt(zen)
+                if (mode == null) {
+                    Log.e(TAG, "Failed to get zen mode from int: $zen")
+                    return
                 }
-            }
-        }
 
-        override fun onNextAlarmChanged() {
-            val nextAlarmMillis = zenModeController.getNextAlarm()
-            alarmData = AlarmData(
-                if (nextAlarmMillis > 0) nextAlarmMillis else null,
-                SysuiR.string::status_bar_alarm.name
-            ).also { data ->
-                clock?.run { events.onAlarmDataChanged(data) }
+                zenData =
+                    ZenData(
+                            mode,
+                            if (mode == ZenMode.OFF) SysuiR.string::dnd_is_off.name
+                            else SysuiR.string::dnd_is_on.name
+                        )
+                        .also { data ->
+                            mainExecutor.execute { clock?.run { events.onZenDataChanged(data) } }
+                        }
+            }
+
+            override fun onNextAlarmChanged() {
+                val nextAlarmMillis = zenModeController.getNextAlarm()
+                alarmData =
+                    AlarmData(
+                            if (nextAlarmMillis > 0) nextAlarmMillis else null,
+                            SysuiR.string::status_bar_alarm.name
+                        )
+                        .also { data ->
+                            mainExecutor.execute { clock?.run { events.onAlarmDataChanged(data) } }
+                        }
             }
         }
-    }
 
     fun registerListeners(parent: View) {
         if (isRegistered) {
@@ -445,12 +461,15 @@
         largeRegionSampler?.stopRegionSampler()
         smallTimeListener?.stop()
         largeTimeListener?.stop()
-        clock?.smallClock?.view
-                ?.removeOnAttachStateChangeListener(smallClockOnAttachStateChangeListener)
-        smallClockFrame?.viewTreeObserver
-                ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
-        clock?.largeClock?.view
-                ?.removeOnAttachStateChangeListener(largeClockOnAttachStateChangeListener)
+        clock
+            ?.smallClock
+            ?.view
+            ?.removeOnAttachStateChangeListener(smallClockOnAttachStateChangeListener)
+        smallClockFrame?.viewTreeObserver?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
+        clock
+            ?.largeClock
+            ?.view
+            ?.removeOnAttachStateChangeListener(largeClockOnAttachStateChangeListener)
     }
 
     /**
@@ -474,12 +493,10 @@
         largeTimeListener = null
 
         clock?.let {
-            smallTimeListener = TimeListener(it.smallClock, mainExecutor).apply {
-                update(shouldTimeListenerRun)
-            }
-            largeTimeListener = TimeListener(it.largeClock, mainExecutor).apply {
-                update(shouldTimeListenerRun)
-            }
+            smallTimeListener =
+                TimeListener(it.smallClock, mainExecutor).apply { update(shouldTimeListenerRun) }
+            largeTimeListener =
+                TimeListener(it.largeClock, mainExecutor).apply { update(shouldTimeListenerRun) }
         }
     }
 
@@ -518,7 +535,13 @@
     @VisibleForTesting
     internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
         return scope.launch {
-            keyguardTransitionInteractor.dozeAmountTransition.collect { handleDoze(it.value) }
+            merge(
+                    keyguardTransitionInteractor.aodToLockscreenTransition.map { step ->
+                        step.copy(value = 1f - step.value)
+                    },
+                    keyguardTransitionInteractor.lockscreenToAodTransition,
+                )
+                .collect { handleDoze(it.value) }
         }
     }
 
@@ -528,7 +551,8 @@
     @VisibleForTesting
     internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job {
         return scope.launch {
-            keyguardTransitionInteractor.transitionStepsToState(AOD)
+            keyguardTransitionInteractor
+                .transitionStepsToState(AOD)
                 .filter { it.transitionState == TransitionState.STARTED }
                 .filter { it.from != LOCKSCREEN }
                 .collect { handleDoze(1f) }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
index fc4e122..4c3f623 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
@@ -103,7 +103,7 @@
     public static boolean isEsimLocked(Context context, int subId) {
         EuiccManager euiccManager =
                 (EuiccManager) context.getSystemService(Context.EUICC_SERVICE);
-        if (!euiccManager.isEnabled()) {
+        if (euiccManager == null || !euiccManager.isEnabled()) {
             return false;
         }
         SubscriptionInfo sub = SubscriptionManager.from(context).getActiveSubscriptionInfo(subId);
@@ -118,6 +118,10 @@
 
     @Override
     public void onClick(View v) {
+        if (mEuiccManager == null) {
+            Log.e(TAG, "EuiccManager not present");
+            return;
+        }
         SubscriptionInfo sub = SubscriptionManager.from(mContext)
                 .getActiveSubscriptionInfo(mSubscriptionId);
         if (sub == null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
index 363dd01..f528ec8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -18,11 +18,16 @@
 
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
+import android.hardware.biometrics.BiometricSourceType;
+import android.os.SystemClock;
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
+import android.util.Log;
+import android.util.Pair;
 import android.view.View;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -40,6 +45,16 @@
 public class KeyguardMessageAreaController<T extends KeyguardMessageArea>
         extends ViewController<T> {
     /**
+     * Pair representing:
+     *   first - BiometricSource the currently displayed message is associated with.
+     *   second - Timestamp the biometric message came in uptimeMillis.
+     * This Pair can be null if the message is not associated with a biometric.
+     */
+    @Nullable
+    private Pair<BiometricSourceType, Long> mMessageBiometricSource = null;
+    private static final Long SKIP_SHOWING_FACE_MESSAGE_AFTER_FP_MESSAGE_MS = 3500L;
+
+    /**
      * Delay before speaking an accessibility announcement. Used to prevent
      * lift-to-type from interrupting itself.
      */
@@ -149,12 +164,42 @@
      * Sets a message to the underlying text view.
      */
     public void setMessage(CharSequence s, boolean animate) {
+        setMessage(s, animate, null);
+    }
+
+    /**
+     * Sets a message to the underlying text view.
+     */
+    public void setMessage(CharSequence s, BiometricSourceType biometricSourceType) {
+        setMessage(s, true, biometricSourceType);
+    }
+
+    private void setMessage(
+            CharSequence s,
+            boolean animate,
+            BiometricSourceType biometricSourceType) {
+        final long uptimeMillis = SystemClock.uptimeMillis();
+        if (skipShowingFaceMessage(biometricSourceType, uptimeMillis)) {
+            Log.d("KeyguardMessageAreaController", "Skip showing face message \"" + s + "\"");
+            return;
+        }
+        mMessageBiometricSource =  new Pair<>(biometricSourceType, uptimeMillis);
         if (mView.isDisabled()) {
             return;
         }
         mView.setMessage(s, animate);
     }
 
+    private boolean skipShowingFaceMessage(
+            BiometricSourceType biometricSourceType, Long currentUptimeMillis
+    ) {
+        return mMessageBiometricSource != null
+                && biometricSourceType == BiometricSourceType.FACE
+                && mMessageBiometricSource.first == BiometricSourceType.FINGERPRINT
+                && (currentUptimeMillis - mMessageBiometricSource.second)
+                    < SKIP_SHOWING_FACE_MESSAGE_AFTER_FP_MESSAGE_MS;
+    }
+
     public void setMessage(int resId) {
         String message = resId != 0 ? mView.getResources().getString(resId) : null;
         setMessage(message);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 84c8ea7..26e91b6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -122,7 +122,7 @@
             case PROMPT_REASON_USER_REQUEST:
                 return R.string.kg_prompt_after_user_lockdown_password;
             case PROMPT_REASON_PREPARE_FOR_UPDATE:
-                return R.string.kg_prompt_reason_timeout_password;
+                return R.string.kg_prompt_added_security_password;
             case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
                 return R.string.kg_prompt_reason_timeout_password;
             case PROMPT_REASON_TRUSTAGENT_EXPIRED:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 7473e0c6..490ad5c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -86,8 +86,9 @@
     };
 
     private final View.OnKeyListener mKeyListener = (v, keyCode, keyEvent) -> {
+        // Ignore SPACE as a confirm key to allow the space character within passwords.
         final boolean isKeyboardEnterKey = keyEvent != null
-                && KeyEvent.isConfirmKey(keyCode)
+                && KeyEvent.isConfirmKey(keyCode) && keyCode != KeyEvent.KEYCODE_SPACE
                 && keyEvent.getAction() == KeyEvent.ACTION_UP;
         if (isKeyboardEnterKey) {
             verifyPasswordAndUnlock();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index bf8900d..caa74780 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -323,7 +323,7 @@
                 resId = R.string.kg_prompt_after_user_lockdown_pattern;
                 break;
             case PROMPT_REASON_PREPARE_FOR_UPDATE:
-                resId = R.string.kg_prompt_reason_timeout_pattern;
+                resId = R.string.kg_prompt_added_security_pattern;
                 break;
             case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
                 resId = R.string.kg_prompt_reason_timeout_pattern;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index bcab6f0..fbe9edf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -134,7 +134,7 @@
             case PROMPT_REASON_USER_REQUEST:
                 return R.string.kg_prompt_after_user_lockdown_pin;
             case PROMPT_REASON_PREPARE_FOR_UPDATE:
-                return R.string.kg_prompt_reason_timeout_pin;
+                return R.string.kg_prompt_added_security_pin;
             case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
                 return R.string.kg_prompt_reason_timeout_pin;
             case PROMPT_REASON_TRUSTAGENT_EXPIRED:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 9421f15..c0ae4a1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -53,9 +53,6 @@
 import com.android.systemui.animation.ViewHierarchyAnimator;
 import com.android.systemui.dump.DumpManager;
 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.clocks.ClockController;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.power.shared.model.ScreenPowerState;
@@ -104,7 +101,6 @@
     private final Rect mClipBounds = new Rect();
     private final KeyguardInteractor mKeyguardInteractor;
     private final PowerInteractor mPowerInteractor;
-    private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     private final DozeParameters mDozeParameters;
 
     private View mStatusArea = null;
@@ -112,7 +108,6 @@
 
     private Boolean mSplitShadeEnabled = false;
     private Boolean mStatusViewCentered = true;
-    private boolean mGoneToAodTransitionRunning = false;
     private DumpManager mDumpManager;
 
     private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener =
@@ -181,7 +176,6 @@
             KeyguardLogger logger,
             InteractionJankMonitor interactionJankMonitor,
             KeyguardInteractor keyguardInteractor,
-            KeyguardTransitionInteractor keyguardTransitionInteractor,
             DumpManager dumpManager,
             PowerInteractor powerInteractor) {
         super(keyguardStatusView);
@@ -197,7 +191,6 @@
         mDumpManager = dumpManager;
         mKeyguardInteractor = keyguardInteractor;
         mPowerInteractor = powerInteractor;
-        mKeyguardTransitionInteractor = keyguardTransitionInteractor;
     }
 
     @Override
@@ -232,6 +225,7 @@
         mDumpManager.registerDumpable(getInstanceName(), this);
         if (migrateClocksToBlueprint()) {
             startCoroutines(EmptyCoroutineContext.INSTANCE);
+            mView.setVisibility(View.GONE);
         }
     }
 
@@ -247,15 +241,6 @@
                         dozeTimeTick();
                     }
                 }, context);
-
-        collectFlow(mView, mKeyguardTransitionInteractor.getGoneToAodTransition(),
-                (TransitionStep step) -> {
-                    if (step.getTransitionState() == TransitionState.RUNNING) {
-                        mGoneToAodTransitionRunning = true;
-                    } else {
-                        mGoneToAodTransitionRunning = false;
-                    }
-                }, context);
     }
 
     public KeyguardStatusView getView() {
@@ -326,7 +311,7 @@
      * Set keyguard status view alpha.
      */
     public void setAlpha(float alpha) {
-        if (!mKeyguardVisibilityHelper.isVisibilityAnimating() && !mGoneToAodTransitionRunning) {
+        if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
             mView.setAlpha(alpha);
         }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 38c2829e..8c51a4e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -34,6 +34,7 @@
 import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
 import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
 import static android.os.BatteryManager.CHARGING_POLICY_DEFAULT;
+import static android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING;
 
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
@@ -437,7 +438,8 @@
         }
     };
 
-    private final OnSubscriptionsChangedListener mSubscriptionListener =
+    @VisibleForTesting
+    final OnSubscriptionsChangedListener mSubscriptionListener =
             new OnSubscriptionsChangedListener() {
                 @Override
                 public void onSubscriptionsChanged() {
@@ -586,16 +588,14 @@
     private void handleSimSubscriptionInfoChanged() {
         Assert.isMainThread();
         mLogger.v("onSubscriptionInfoChanged()");
-        List<SubscriptionInfo> sil = mSubscriptionManager
-                .getCompleteActiveSubscriptionInfoList();
-        if (sil != null) {
-            for (SubscriptionInfo subInfo : sil) {
+        List<SubscriptionInfo> subscriptionInfos = getSubscriptionInfo(true /* forceReload */);
+        if (!subscriptionInfos.isEmpty()) {
+            for (SubscriptionInfo subInfo : subscriptionInfos) {
                 mLogger.logSubInfo(subInfo);
             }
         } else {
             mLogger.v("onSubscriptionInfoChanged: list is null");
         }
-        List<SubscriptionInfo> subscriptionInfos = getSubscriptionInfo(true /* forceReload */);
 
         // Hack level over 9000: Because the subscription id is not yet valid when we see the
         // first update in handleSimStateChange, we need to force refresh all SIM states
@@ -658,18 +658,18 @@
 
     /**
      * @return List of SubscriptionInfo records, maybe empty but never null.
+     *
+     * Note that this method will filter out any subscription which is PROFILE_CLASS_PROVISIONING
      */
     public List<SubscriptionInfo> getSubscriptionInfo(boolean forceReload) {
         List<SubscriptionInfo> sil = mSubscriptionInfo;
         if (sil == null || forceReload) {
-            sil = mSubscriptionManager.getCompleteActiveSubscriptionInfoList();
+            mSubscriptionInfo = mSubscriptionManager.getCompleteActiveSubscriptionInfoList()
+                    .stream()
+                    .filter(subInfo -> subInfo.getProfileClass() != PROFILE_CLASS_PROVISIONING)
+                    .toList();
         }
-        if (sil == null) {
-            // getCompleteActiveSubscriptionInfoList was null callers expect an empty list.
-            mSubscriptionInfo = new ArrayList<>();
-        } else {
-            mSubscriptionInfo = sil;
-        }
+
         return new ArrayList<>(mSubscriptionInfo);
     }
 
@@ -3313,6 +3313,7 @@
 
         becameAbsent |= ABSENT_SIM_STATE_LIST.contains(state);
 
+        // TODO(b/327476182): Preserve SIM_STATE_CARD_IO_ERROR sims in a separate data source.
         SimData data = mSimDatas.get(subId);
         final boolean changed;
         if (data == null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index 2000028..f5a6cb3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -88,6 +88,10 @@
             boolean keyguardFadingAway,
             boolean goingToFullShade,
             int oldStatusBarState) {
+        if (migrateClocksToBlueprint()) {
+            log("Ignoring KeyguardVisibilityelper, migrateClocksToBlueprint flag on");
+            return;
+        }
         Assert.isMainThread();
         PropertyAnimator.cancelAnimation(mView, AnimatableProperty.ALPHA);
         boolean isOccluded = mKeyguardStateController.isOccluded();
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
index d02b72f..cb474d3 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
@@ -94,6 +94,20 @@
         )
     }
 
+    fun logSimStateChangedCallback(subId: Int, slotId: Int, simState: Int) {
+        buffer.log(
+            TAG,
+            LogLevel.VERBOSE,
+            {
+                // subId is always a very small int, and we've run out of integers for log buffer
+                long1 = subId.toLong()
+                int1 = slotId
+                int2 = simState
+            },
+            { "onSimStateChangedCallback: subId=$long1 slotId=$int1 simState=$int2" }
+        )
+    }
+
     /**
      * Used to log the starting point for _why_ the carrier text is updating. In order to keep us
      * from holding on to too many objects, we'll just use simple ints for reasons here
@@ -113,7 +127,7 @@
     companion object {
         const val REASON_REFRESH_CARRIER_INFO = 1
         const val REASON_ON_TELEPHONY_CAPABLE = 2
-        const val REASON_ON_SIM_STATE_CHANGED = 3
+        const val REASON_SIM_ERROR_STATE_CHANGED = 3
         const val REASON_ACTIVE_DATA_SUB_CHANGED = 4
 
         @Retention(AnnotationRetention.SOURCE)
@@ -122,7 +136,7 @@
                 [
                     REASON_REFRESH_CARRIER_INFO,
                     REASON_ON_TELEPHONY_CAPABLE,
-                    REASON_ON_SIM_STATE_CHANGED,
+                    REASON_SIM_ERROR_STATE_CHANGED,
                     REASON_ACTIVE_DATA_SUB_CHANGED,
                 ]
         )
@@ -132,7 +146,7 @@
             when (this) {
                 REASON_REFRESH_CARRIER_INFO -> "REFRESH_CARRIER_INFO"
                 REASON_ON_TELEPHONY_CAPABLE -> "ON_TELEPHONY_CAPABLE"
-                REASON_ON_SIM_STATE_CHANGED -> "SIM_STATE_CHANGED"
+                REASON_SIM_ERROR_STATE_CHANGED -> "SIM_ERROR_STATE_CHANGED"
                 REASON_ACTIVE_DATA_SUB_CHANGED -> "ACTIVE_DATA_SUB_CHANGED"
                 else -> "unknown"
             }
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index d2ad096..ce4032a 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -16,6 +16,7 @@
 
 package com.android.keyguard.logging
 
+import android.hardware.biometrics.BiometricSourceType
 import com.android.systemui.biometrics.AuthRippleController
 import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController
 import com.android.systemui.log.LogBuffer
@@ -117,6 +118,26 @@
         )
     }
 
+    fun logDropNonFingerprintMessage(
+        message: CharSequence,
+        followUpMessage: CharSequence?,
+        biometricSourceType: BiometricSourceType?,
+    ) {
+        buffer.log(
+            KeyguardIndicationController.TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = message.toString()
+                str2 = followUpMessage?.toString()
+                str3 = biometricSourceType?.name
+            },
+            {
+                "droppingNonFingerprintMessage message=$str1 " +
+                    "followUpMessage:$str2 biometricSourceType:$str3"
+            }
+        )
+    }
+
     fun logUpdateBatteryIndication(
         powerIndication: String,
         pluggedIn: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 8853589..33f14d4 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -22,17 +22,13 @@
 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.KeyguardUpdateMonitor;
-import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
-import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
 import com.android.systemui.animation.DialogTransitionAnimator;
 import com.android.systemui.assist.AssistManager;
 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.FeatureFlags;
 import com.android.systemui.fragments.FragmentService;
@@ -47,7 +43,6 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -58,7 +53,6 @@
 import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.policy.BluetoothController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.FlashlightController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
 import com.android.systemui.tuner.TunerService;
@@ -70,6 +64,7 @@
 import javax.inject.Inject;
 import javax.inject.Named;
 
+
 /**
  * Class to handle ugly dependencies throughout sysui until we determine the
  * long-term dependency injection solution.
@@ -96,10 +91,6 @@
      * Key for getting a Handler for receiving time tick broadcasts on.
      */
     public static final String TIME_TICK_HANDLER_NAME = "time_tick_handler";
-    /**
-     * Generic handler on the main thread.
-     */
-    private static final String MAIN_HANDLER_NAME = "main_handler";
 
     /**
      * An email address to send memory leak reports to by default.
@@ -121,11 +112,6 @@
      */
     public static final DependencyKey<Handler> TIME_TICK_HANDLER =
             new DependencyKey<>(TIME_TICK_HANDLER_NAME);
-    /**
-     * Generic handler on the main thread.
-     */
-    public static final DependencyKey<Handler> MAIN_HANDLER =
-            new DependencyKey<>(MAIN_HANDLER_NAME);
 
     private final ArrayMap<Object, Object> mDependencies = new ArrayMap<>();
     private final ArrayMap<Object, LazyDependencyCreator> mProviders = new ArrayMap<>();
@@ -134,7 +120,6 @@
 
     @Inject Lazy<BroadcastDispatcher> mBroadcastDispatcher;
     @Inject Lazy<BluetoothController> mBluetoothController;
-    @Inject Lazy<FlashlightController> mFlashlightController;
     @Inject Lazy<KeyguardUpdateMonitor> mKeyguardUpdateMonitor;
     @Inject Lazy<DeviceProvisionedController> mDeviceProvisionedController;
     @Inject Lazy<PluginManager> mPluginManager;
@@ -150,15 +135,10 @@
     @Inject Lazy<LightBarController> mLightBarController;
     @Inject Lazy<OverviewProxyService> mOverviewProxyService;
     @Inject Lazy<NavigationModeController> mNavBarModeController;
-    @Inject Lazy<AccessibilityButtonModeObserver> mAccessibilityButtonModeObserver;
-    @Inject Lazy<AccessibilityButtonTargetsObserver> mAccessibilityButtonListController;
-    @Inject Lazy<IStatusBarService> mIStatusBarService;
-    @Inject Lazy<NotificationRemoteInputManager.Callback> mNotificationRemoteInputManagerCallback;
     @Inject Lazy<NavigationBarController> mNavigationBarController;
     @Inject Lazy<StatusBarStateController> mStatusBarStateController;
     @Inject Lazy<NotificationMediaManager> mNotificationMediaManager;
     @Inject @Background Lazy<Looper> mBgLooper;
-    @Inject @Main Lazy<Handler> mMainHandler;
     @Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler;
     @Inject Lazy<SysUiState> mSysUiStateFlagsContainer;
     @Inject Lazy<CommandQueue> mCommandQueue;
@@ -187,10 +167,8 @@
         // on imports.
         mProviders.put(TIME_TICK_HANDLER, mTimeTickHandler::get);
         mProviders.put(BG_LOOPER, mBgLooper::get);
-        mProviders.put(MAIN_HANDLER, mMainHandler::get);
         mProviders.put(BroadcastDispatcher.class, mBroadcastDispatcher::get);
         mProviders.put(BluetoothController.class, mBluetoothController::get);
-        mProviders.put(FlashlightController.class, mFlashlightController::get);
         mProviders.put(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor::get);
         mProviders.put(DeviceProvisionedController.class, mDeviceProvisionedController::get);
         mProviders.put(PluginManager.class, mPluginManager::get);
@@ -205,13 +183,6 @@
         mProviders.put(LightBarController.class, mLightBarController::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(IStatusBarService.class, mIStatusBarService::get);
-        mProviders.put(NotificationRemoteInputManager.Callback.class,
-                mNotificationRemoteInputManagerCallback::get);
         mProviders.put(NavigationBarController.class, mNavigationBarController::get);
         mProviders.put(StatusBarStateController.class, mStatusBarStateController::get);
         mProviders.put(NotificationMediaManager.class, mNotificationMediaManager::get);
diff --git a/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt b/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt
index 22d0bc9..af810ea 100644
--- a/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt
@@ -20,6 +20,7 @@
 import android.content.Context
 import android.view.ContextThemeWrapper
 import com.android.settingslib.Utils
+import com.android.settingslib.flags.Flags.newStatusBarIcons
 import com.android.systemui.res.R
 
 /**
@@ -53,14 +54,26 @@
                 Utils.getThemeAttr(context, R.attr.darkIconTheme))
         val dualToneLightTheme = ContextThemeWrapper(context,
                 Utils.getThemeAttr(context, R.attr.lightIconTheme))
-        darkColor = Color(
-                Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.singleToneColor),
+        if (newStatusBarIcons()) {
+            darkColor = Color(
+                android.graphics.Color.BLACK,
                 Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.iconBackgroundColor),
                 Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.fillColor))
-        lightColor = Color(
-                Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.singleToneColor),
+
+            lightColor = Color(
+                android.graphics.Color.WHITE,
                 Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.iconBackgroundColor),
                 Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.fillColor))
+        } else {
+            darkColor = Color(
+                    Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.singleToneColor),
+                    Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.iconBackgroundColor),
+                    Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.fillColor))
+            lightColor = Color(
+                    Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.singleToneColor),
+                    Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.iconBackgroundColor),
+                    Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.fillColor))
+        }
     }
 
     private fun getColorForDarkIntensity(darkIntensity: Float, lightColor: Int, darkColor: Int) =
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 6299739..577bbc0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -22,6 +22,8 @@
 import android.content.ComponentCallbacks;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -435,7 +437,14 @@
         }
         mMenuAnimationController.flingMenuThenSpringToEdge(
                 getMenuPosition().x, 100f, 0f);
-        mContext.startActivity(getIntentForEditScreen());
+
+        Intent intent = getIntentForEditScreen();
+        PackageManager packageManager = getContext().getPackageManager();
+        List<ResolveInfo> activities = packageManager.queryIntentActivities(intent,
+                PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY));
+        if (!activities.isEmpty()) {
+            mContext.startActivity(intent);
+        }
     }
 
     void incrementTexMetricForAllTargets(String metric) {
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 6d4baf4..cd3b8a6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -17,6 +17,7 @@
 package com.android.systemui.accessibility.floatingmenu;
 
 import static android.view.WindowInsets.Type.ime;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
 
 import static androidx.core.view.WindowInsetsCompat.Type;
@@ -48,6 +49,7 @@
 import android.os.Looper;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.ArraySet;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewTreeObserver;
@@ -64,6 +66,7 @@
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
 
+import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.dialog.AccessibilityTarget;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.messages.nano.SystemMessageProto;
@@ -162,35 +165,45 @@
     final Runnable mDismissMenuAction = new Runnable() {
         @Override
         public void run() {
-            mSecureSettings.putStringForUser(
-                    Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "",
-                    UserHandle.USER_CURRENT);
+            if (android.view.accessibility.Flags.a11yQsShortcut()) {
+                mAccessibilityManager.enableShortcutsForTargets(
+                        /* enable= */ false,
+                        ShortcutConstants.UserShortcutType.SOFTWARE,
+                        new ArraySet<>(mAccessibilityManager.getAccessibilityShortcutTargets(
+                                ACCESSIBILITY_BUTTON)),
+                        mSecureSettings.getRealUserHandle(UserHandle.USER_CURRENT)
+                );
+            } else {
+                mSecureSettings.putStringForUser(
+                        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "",
+                        UserHandle.USER_CURRENT);
 
-            final List<ComponentName> hardwareKeyShortcutComponents =
-                    mAccessibilityManager.getAccessibilityShortcutTargets(
-                                    ACCESSIBILITY_SHORTCUT_KEY)
-                            .stream()
-                            .map(ComponentName::unflattenFromString)
-                            .toList();
+                final List<ComponentName> hardwareKeyShortcutComponents =
+                        mAccessibilityManager.getAccessibilityShortcutTargets(
+                                        ACCESSIBILITY_SHORTCUT_KEY)
+                                .stream()
+                                .map(ComponentName::unflattenFromString)
+                                .toList();
 
-            // Should disable the corresponding service when the fragment type is
-            // INVISIBLE_TOGGLE, which will enable service when the shortcut is on.
-            final List<AccessibilityServiceInfo> serviceInfoList =
-                    mAccessibilityManager.getEnabledAccessibilityServiceList(
-                            AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
-            serviceInfoList.forEach(info -> {
-                if (getAccessibilityServiceFragmentType(info) != INVISIBLE_TOGGLE) {
-                    return;
-                }
+                // Should disable the corresponding service when the fragment type is
+                // INVISIBLE_TOGGLE, which will enable service when the shortcut is on.
+                final List<AccessibilityServiceInfo> serviceInfoList =
+                        mAccessibilityManager.getEnabledAccessibilityServiceList(
+                                AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+                serviceInfoList.forEach(info -> {
+                    if (getAccessibilityServiceFragmentType(info) != INVISIBLE_TOGGLE) {
+                        return;
+                    }
 
-                final ComponentName serviceComponentName = info.getComponentName();
-                if (hardwareKeyShortcutComponents.contains(serviceComponentName)) {
-                    return;
-                }
+                    final ComponentName serviceComponentName = info.getComponentName();
+                    if (hardwareKeyShortcutComponents.contains(serviceComponentName)) {
+                        return;
+                    }
 
-                setAccessibilityServiceState(getContext(), serviceComponentName, /* enabled= */
-                        false);
-            });
+                    setAccessibilityServiceState(getContext(), serviceComponentName, /* enabled= */
+                            false);
+                });
+            }
 
             mFloatingMenu.hide();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index b1a153a..01c2cc4 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -17,6 +17,7 @@
 
 import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
 
+import static com.android.settingslib.flags.Flags.newStatusBarIcons;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -25,6 +26,7 @@
 import android.animation.ObjectAnimator;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -47,6 +49,9 @@
 
 import com.android.app.animation.Interpolators;
 import com.android.systemui.DualToneHandler;
+import com.android.systemui.battery.unified.BatteryColors;
+import com.android.systemui.battery.unified.BatteryDrawableState;
+import com.android.systemui.battery.unified.BatteryLayersDrawable;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.res.R;
@@ -78,6 +83,7 @@
     private boolean mShowPercentAvailable;
     private String mEstimateText = null;
     private boolean mPluggedIn;
+    private boolean mPowerSaveEnabled;
     private boolean mIsBatteryDefender;
     private boolean mIsIncompatibleCharging;
     private boolean mDisplayShieldEnabled;
@@ -91,6 +97,12 @@
 
     private BatteryEstimateFetcher mBatteryEstimateFetcher;
 
+    // for Flags.newStatusBarIcons. The unified battery icon can show percent inside
+    @Nullable private BatteryLayersDrawable mUnifiedBattery;
+    private BatteryColors mUnifiedBatteryColors = BatteryColors.LIGHT_THEME_COLORS;
+    private BatteryDrawableState mUnifiedBatteryState =
+            BatteryDrawableState.Companion.getDefaultInitialState();
+
     public BatteryMeterView(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
@@ -106,6 +118,7 @@
         final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,
                 context.getColor(com.android.settingslib.R.color.meter_background_color));
         mPercentageStyleId = atts.getResourceId(R.styleable.BatteryMeterView_textAppearance, 0);
+
         mDrawable = new AccessorizedBatteryDrawable(context, frameColor);
         atts.recycle();
 
@@ -115,13 +128,26 @@
         setupLayoutTransition();
 
         mBatteryIconView = new ImageView(context);
-        mBatteryIconView.setImageDrawable(mDrawable);
-        final MarginLayoutParams mlp = new MarginLayoutParams(
-                getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_width),
-                getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_height));
-        mlp.setMargins(0, 0, 0,
-                getResources().getDimensionPixelOffset(R.dimen.battery_margin_bottom));
-        addView(mBatteryIconView, mlp);
+        if (newStatusBarIcons()) {
+            mUnifiedBattery = BatteryLayersDrawable.Companion
+                    .newBatteryDrawable(context, mUnifiedBatteryState);
+            mBatteryIconView.setImageDrawable(mUnifiedBattery);
+
+            final MarginLayoutParams mlp = new MarginLayoutParams(
+                    getResources().getDimensionPixelSize(
+                            R.dimen.status_bar_battery_unified_icon_width),
+                    getResources().getDimensionPixelSize(
+                            R.dimen.status_bar_battery_unified_icon_height));
+            addView(mBatteryIconView, mlp);
+        } else {
+            mBatteryIconView.setImageDrawable(mDrawable);
+            final MarginLayoutParams mlp = new MarginLayoutParams(
+                    getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_width),
+                    getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_height));
+            mlp.setMargins(0, 0, 0,
+                    getResources().getDimensionPixelOffset(R.dimen.battery_margin_bottom));
+            addView(mBatteryIconView, mlp);
+        }
 
         updateShowPercent();
         mDualToneHandler = new DualToneHandler(context);
@@ -132,6 +158,14 @@
         setClipToPadding(false);
     }
 
+
+    private void setBatteryDrawableState(BatteryDrawableState newState) {
+        if (!newStatusBarIcons()) return;
+
+        mUnifiedBatteryState = newState;
+        mUnifiedBattery.setBatteryState(mUnifiedBatteryState);
+    }
+
     private void setupLayoutTransition() {
         LayoutTransition transition = new LayoutTransition();
         transition.setDuration(200);
@@ -200,25 +234,94 @@
      * @param pluggedIn whether the device is plugged in or not
      */
     public void onBatteryLevelChanged(@IntRange(from = 0, to = 100) int level, boolean pluggedIn) {
+        boolean wasCharging = isCharging();
         mPluggedIn = pluggedIn;
         mLevel = level;
-        mDrawable.setCharging(isCharging());
+        boolean isCharging = isCharging();
+        mDrawable.setCharging(isCharging);
         mDrawable.setBatteryLevel(level);
         updatePercentText();
+
+        if (newStatusBarIcons()) {
+            Drawable attr = mUnifiedBatteryState.getAttribution();
+            if (isCharging != wasCharging) {
+                attr = getBatteryAttribution(isCharging);
+            }
+
+            BatteryDrawableState newState =
+                    new BatteryDrawableState(
+                            level,
+                            mUnifiedBatteryState.getShowPercent(),
+                            level <= 20,
+                            attr
+                    );
+
+            setBatteryDrawableState(newState);
+        }
+    }
+
+    // Potentially reloads any attribution. Should not be called if the state hasn't changed
+    private Drawable getBatteryAttribution(boolean isCharging) {
+        if (!newStatusBarIcons()) return null;
+
+        int resId = 0;
+        if (mPowerSaveEnabled) {
+            resId = R.drawable.battery_unified_attr_powersave;
+        } else if (mIsBatteryDefender && mDisplayShieldEnabled) {
+            resId = R.drawable.battery_unified_attr_defend;
+        } else if (isCharging) {
+            resId = R.drawable.battery_unified_attr_charging;
+        }
+
+        Drawable attr = null;
+        if (resId > 0) {
+            attr = mContext.getDrawable(resId);
+        }
+
+        return attr;
     }
 
     void onPowerSaveChanged(boolean isPowerSave) {
-        mDrawable.setPowerSaveEnabled(isPowerSave);
+        if (isPowerSave == mPowerSaveEnabled) {
+            return;
+        }
+        mPowerSaveEnabled = isPowerSave;
+        if (!newStatusBarIcons()) {
+            mDrawable.setPowerSaveEnabled(isPowerSave);
+        } else {
+            setBatteryDrawableState(
+                    new BatteryDrawableState(
+                            mUnifiedBatteryState.getLevel(),
+                            mUnifiedBatteryState.getShowPercent(),
+                            mUnifiedBatteryState.getShowErrorState(),
+                            getBatteryAttribution(isCharging())
+                    )
+            );
+        }
     }
 
     void onIsBatteryDefenderChanged(boolean isBatteryDefender) {
         boolean valueChanged = mIsBatteryDefender != isBatteryDefender;
         mIsBatteryDefender = isBatteryDefender;
-        if (valueChanged) {
-            updateContentDescription();
+
+        if (!valueChanged) {
+            return;
+        }
+
+        updateContentDescription();
+        if (!newStatusBarIcons()) {
             // The battery drawable is a different size depending on whether it's currently
             // overheated or not, so we need to re-scale the view when overheated changes.
             scaleBatteryMeterViews();
+        } else {
+            setBatteryDrawableState(
+                    new BatteryDrawableState(
+                            mUnifiedBatteryState.getLevel(),
+                            mUnifiedBatteryState.getShowPercent(),
+                            mUnifiedBatteryState.getShowErrorState(),
+                            getBatteryAttribution(isCharging())
+                    )
+            );
         }
     }
 
@@ -226,16 +329,41 @@
         boolean valueChanged = mIsIncompatibleCharging != isIncompatibleCharging;
         mIsIncompatibleCharging = isIncompatibleCharging;
         if (valueChanged) {
-            mDrawable.setCharging(isCharging());
+            if (newStatusBarIcons()) {
+                setBatteryDrawableState(
+                        new BatteryDrawableState(
+                                mUnifiedBatteryState.getLevel(),
+                                mUnifiedBatteryState.getShowPercent(),
+                                mUnifiedBatteryState.getShowErrorState(),
+                                getBatteryAttribution(isCharging())
+                        )
+                );
+            } else {
+                mDrawable.setCharging(isCharging());
+            }
             updateContentDescription();
         }
     }
 
-    private TextView loadPercentView() {
+    private TextView inflatePercentView() {
         return (TextView) LayoutInflater.from(getContext())
                 .inflate(R.layout.battery_percentage_view, null);
     }
 
+    private void addPercentView(TextView inflatedPercentView) {
+        mBatteryPercentView = inflatedPercentView;
+
+        if (mPercentageStyleId != 0) { // Only set if specified as attribute
+            mBatteryPercentView.setTextAppearance(mPercentageStyleId);
+        }
+        float fontHeight = mBatteryPercentView.getPaint().getFontMetricsInt(null);
+        mBatteryPercentView.setLineHeight(TypedValue.COMPLEX_UNIT_PX, fontHeight);
+        if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
+        addView(mBatteryPercentView, new LayoutParams(
+                LayoutParams.WRAP_CONTENT,
+                (int) Math.ceil(fontHeight)));
+    }
+
     /**
      * Updates percent view by removing old one and reinflating if necessary
      */
@@ -260,6 +388,44 @@
     }
 
     void updatePercentText() {
+        if (!newStatusBarIcons()) {
+            updatePercentTextLegacy();
+            return;
+        }
+
+        // The unified battery can show the percent inside, so we only need to handle
+        // the estimated time remaining case
+        if (mShowPercentMode == MODE_ESTIMATE
+                && mBatteryEstimateFetcher != null
+                && !isCharging()
+        ) {
+            mBatteryEstimateFetcher.fetchBatteryTimeRemainingEstimate(
+                    (String estimate) -> {
+                        if (mBatteryPercentView == null) {
+                            // Similar to the legacy behavior, inflate and add the view. We will
+                            // only use it for the estimate text
+                            addPercentView(inflatePercentView());
+                        }
+                        if (estimate != null && mShowPercentMode == MODE_ESTIMATE) {
+                            mEstimateText = estimate;
+                            mBatteryPercentView.setText(estimate);
+                            updateContentDescription();
+                        } else {
+                            mEstimateText = null;
+                            mBatteryPercentView.setText(null);
+                            updateContentDescription();
+                        }
+                    });
+        } else {
+            if (mBatteryPercentView != null) {
+                mEstimateText = null;
+                mBatteryPercentView.setText(null);
+            }
+            updateContentDescription();
+        }
+    }
+
+    void updatePercentTextLegacy() {
         if (mBatteryStateUnknown) {
             return;
         }
@@ -334,6 +500,42 @@
     }
 
     void updateShowPercent() {
+        if (!newStatusBarIcons()) {
+            updateShowPercentLegacy();
+            return;
+        }
+
+        if (!mShowPercentAvailable || mUnifiedBattery == null) return;
+
+        boolean shouldShow = mShowPercentMode == MODE_ON || mShowPercentMode == MODE_ESTIMATE;
+        if (!mBatteryStateUnknown && !shouldShow && (mShowPercentMode != MODE_OFF)) {
+            // Slow case: fall back to the system setting
+            // TODO(b/140051051)
+            shouldShow = 0 != whitelistIpcs(() -> Settings.System
+                    .getIntForUser(getContext().getContentResolver(),
+                    SHOW_BATTERY_PERCENT, getContext().getResources().getBoolean(
+                    com.android.internal.R.bool.config_defaultBatteryPercentageSetting)
+                    ? 1 : 0, UserHandle.USER_CURRENT));
+        }
+
+        setBatteryDrawableState(
+                new BatteryDrawableState(
+                        mUnifiedBatteryState.getLevel(),
+                        shouldShow,
+                        mUnifiedBatteryState.getShowErrorState(),
+                        mUnifiedBatteryState.getAttribution()
+                )
+        );
+
+        // The legacy impl used the percent view for the estimate and the percent text. The modern
+        // version only uses it for estimate. It can be safely removed here
+        if (mShowPercentMode != MODE_ESTIMATE) {
+            removeView(mBatteryPercentView);
+            mBatteryPercentView = null;
+        }
+    }
+
+    private void updateShowPercentLegacy() {
         final boolean showing = mBatteryPercentView != null;
         // TODO(b/140051051)
         final boolean systemSetting = 0 != whitelistIpcs(() -> Settings.System
@@ -349,17 +551,8 @@
 
         if (shouldShow) {
             if (!showing) {
-                mBatteryPercentView = loadPercentView();
-                if (mPercentageStyleId != 0) { // Only set if specified as attribute
-                    mBatteryPercentView.setTextAppearance(mPercentageStyleId);
-                }
-                float fontHeight = mBatteryPercentView.getPaint().getFontMetricsInt(null);
-                mBatteryPercentView.setLineHeight(TypedValue.COMPLEX_UNIT_PX, fontHeight);
-                if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
+                addPercentView(inflatePercentView());
                 updatePercentText();
-                addView(mBatteryPercentView, new LayoutParams(
-                        LayoutParams.WRAP_CONTENT,
-                        (int) Math.ceil(fontHeight)));
             }
         } else {
             if (showing) {
@@ -395,10 +588,39 @@
         updateShowPercent();
     }
 
+    void scaleBatteryMeterViews() {
+        if (!newStatusBarIcons()) {
+            scaleBatteryMeterViewsLegacy();
+            return;
+        }
+
+        // For simplicity's sake, copy the general pattern in the legacy method and use the new
+        // resources, excluding what we don't need
+        Resources res = getContext().getResources();
+        TypedValue typedValue = new TypedValue();
+
+        res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
+        float iconScaleFactor = typedValue.getFloat();
+
+        float mainBatteryHeight =
+                res.getDimensionPixelSize(
+                        R.dimen.status_bar_battery_unified_icon_height) * iconScaleFactor;
+        float mainBatteryWidth =
+                res.getDimensionPixelSize(
+                        R.dimen.status_bar_battery_unified_icon_width) * iconScaleFactor;
+
+        LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams(
+                Math.round(mainBatteryWidth),
+                Math.round(mainBatteryHeight));
+
+        mBatteryIconView.setLayoutParams(scaledLayoutParams);
+        mBatteryIconView.invalidateDrawable(mUnifiedBattery);
+    }
+
     /**
      * Looks up the scale factor for status bar icons and scales the battery view by that amount.
      */
-    void scaleBatteryMeterViews() {
+    void scaleBatteryMeterViewsLegacy() {
         Resources res = getContext().getResources();
         TypedValue typedValue = new TypedValue();
 
@@ -445,6 +667,32 @@
     @Override
     public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
         if (mIsStaticColor) return;
+
+        if (!newStatusBarIcons()) {
+            onDarkChangedLegacy(areas, darkIntensity, tint);
+            return;
+        }
+
+        if (mUnifiedBattery == null) {
+            return;
+        }
+
+        if (DarkIconDispatcher.isInAreas(areas, this)) {
+            if (darkIntensity < 0.5) {
+                mUnifiedBatteryColors = BatteryColors.DARK_THEME_COLORS;
+            } else {
+                mUnifiedBatteryColors = BatteryColors.LIGHT_THEME_COLORS;
+            }
+
+            mUnifiedBattery.setColors(mUnifiedBatteryColors);
+        } else  {
+            // Same behavior as the legacy code when not isInArea
+            mUnifiedBatteryColors = BatteryColors.DARK_THEME_COLORS;
+            mUnifiedBattery.setColors(mUnifiedBatteryColors);
+        }
+    }
+
+    private void onDarkChangedLegacy(ArrayList<Rect> areas, float darkIntensity, int tint) {
         float intensity = DarkIconDispatcher.isInAreas(areas, this) ? darkIntensity : 0;
         int nonAdaptedSingleToneColor = mDualToneHandler.getSingleColor(intensity);
         int nonAdaptedForegroundColor = mDualToneHandler.getFillColor(intensity);
@@ -478,7 +726,16 @@
         }
     }
 
-    private boolean isCharging() {
+    /** For newStatusBarIcons(), we use a BatteryColors object to declare the theme */
+    public void setUnifiedBatteryColors(BatteryColors colors) {
+        if (!newStatusBarIcons()) return;
+
+        mUnifiedBatteryColors = colors;
+        mUnifiedBattery.setColors(mUnifiedBatteryColors);
+    }
+
+    @VisibleForTesting
+    boolean isCharging() {
         return mPluggedIn && !mIsIncompatibleCharging;
     }
 
@@ -505,6 +762,16 @@
         return mBatteryPercentView.getText();
     }
 
+    @VisibleForTesting
+    TextView getBatteryPercentView() {
+        return mBatteryPercentView;
+    }
+
+    @VisibleForTesting
+    BatteryDrawableState getUnifiedBatteryState() {
+        return mUnifiedBatteryState;
+    }
+
     /** An interface that will fetch the estimated time remaining for the user's battery. */
     public interface BatteryEstimateFetcher {
         void fetchBatteryTimeRemainingEstimate(
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt b/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt
index 8c9ae62..10a0d95 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt
@@ -1,9 +1,21 @@
 package com.android.systemui.battery
 
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.qs.tiles.BatterySaverTile
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileDataInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.impl.battery.ui.BatterySaverTileMapper
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.res.R
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
 import dagger.multibindings.IntoMap
 import dagger.multibindings.StringKey
 
@@ -15,4 +27,39 @@
     @IntoMap
     @StringKey(BatterySaverTile.TILE_SPEC)
     fun bindBatterySaverTile(batterySaverTile: BatterySaverTile): QSTileImpl<*>
+
+    companion object {
+        private const val BATTERY_SAVER_TILE_SPEC = "battery"
+
+        @Provides
+        @IntoMap
+        @StringKey(BATTERY_SAVER_TILE_SPEC)
+        fun provideBatterySaverTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+            QSTileConfig(
+                tileSpec = TileSpec.create(BATTERY_SAVER_TILE_SPEC),
+                uiConfig =
+                    QSTileUIConfig.Resource(
+                        iconRes = R.drawable.qs_battery_saver_icon_off,
+                        labelRes = R.string.battery_detail_switch_title,
+                    ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+            )
+
+        /** Inject BatterySaverTile into tileViewModelMap in QSModule */
+        @Provides
+        @IntoMap
+        @StringKey(BATTERY_SAVER_TILE_SPEC)
+        fun provideBatterySaverTileViewModel(
+            factory: QSTileViewModelFactory.Static<BatterySaverTileModel>,
+            mapper: BatterySaverTileMapper,
+            stateInteractor: BatterySaverTileDataInteractor,
+            userActionInteractor: BatterySaverTileUserActionInteractor
+        ): QSTileViewModel =
+            factory.create(
+                TileSpec.create(BATTERY_SAVER_TILE_SPEC),
+                userActionInteractor,
+                stateInteractor,
+                mapper,
+            )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt
new file mode 100644
index 0000000..f3652b8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.battery.unified
+
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.PixelFormat
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.DrawableWrapper
+import android.view.Gravity
+import kotlin.math.ceil
+import kotlin.math.min
+import kotlin.math.roundToInt
+
+/**
+ * A battery attribution is defined as a drawable that can display either alongside the percent text
+ * or solely in the center of the battery frame.
+ *
+ * Attributions are given an explicit canvas of 18x8, or 6x6 depending on the display mode (centered
+ * or right-aligned). The size is configured in [BatteryLayersDrawable] by changing this drawable
+ * wrapper's bounds, and optionally setting the [gravity]
+ */
+@Suppress("RtlHardcoded")
+class BatteryAttributionDrawable(dr: Drawable?) : DrawableWrapper(dr) {
+    /** One of [CENTER, LEFT]. Note that number text does not RTL. */
+    var gravity = Gravity.CENTER
+        set(value) {
+            field = value
+            updateBoundsInner()
+        }
+
+    // Must be called if bounds change, gravity changes, or the wrapped drawable changes
+    private fun updateBoundsInner() {
+        val dr = drawable ?: return
+
+        val hScale = bounds.width().toFloat() / dr.intrinsicWidth.toFloat()
+        val vScale = bounds.height().toFloat() / dr.intrinsicHeight.toFloat()
+        val scale = min(hScale, vScale)
+
+        val dw = scale * dr.intrinsicWidth
+        val dh = scale * dr.intrinsicHeight
+
+        if (gravity == Gravity.CENTER) {
+            val padLeft = (bounds.width() - dw) / 2
+            val padTop = (bounds.height() - dh) / 2
+            dr.setBounds(
+                (bounds.left + padLeft).roundToInt(),
+                (bounds.top + padTop).roundToInt(),
+                (bounds.left + padLeft + dw).roundToInt(),
+                (bounds.top + padTop + dh).roundToInt()
+            )
+        } else if (gravity == Gravity.LEFT) {
+            dr.setBounds(
+                bounds.left,
+                bounds.top,
+                ceil(bounds.left + dw).toInt(),
+                ceil(bounds.top + dh).toInt()
+            )
+        }
+    }
+
+    override fun setDrawable(dr: Drawable?) {
+        super.setDrawable(dr)
+        updateBoundsInner()
+    }
+
+    override fun onBoundsChange(bounds: Rect) {
+        updateBoundsInner()
+    }
+
+    /**
+     * DrawableWrapper allows for a null constructor, but this method assumes that the drawable is
+     * non-null. It is called by LayerDrawable on init, so we have to handle null here specifically
+     */
+    override fun getChangingConfigurations(): Int = drawable?.changingConfigurations ?: 0
+
+    override fun draw(canvas: Canvas) {
+        drawable?.draw(canvas)
+    }
+
+    // Deprecated, but needed for Drawable implementation
+    override fun getOpacity() = PixelFormat.OPAQUE
+
+    // We don't use this
+    override fun setAlpha(alpha: Int) {}
+
+    override fun setColorFilter(colorFilter: ColorFilter?) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt
new file mode 100644
index 0000000..9f13e6d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.battery.unified
+
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+
+/**
+ * Encapsulates all drawing information needed by BatteryMeterDrawable to render properly. Rendered
+ * state will be equivalent to the most recent state passed in.
+ */
+data class BatteryDrawableState(
+    /** [0-100] description of the battery level */
+    val level: Int,
+    /** Whether or not to render the percent as a foreground text layer */
+    val showPercent: Boolean,
+    /**
+     * In an error state, the drawable will use the error colors and removes the third layer. If
+     * [showPercent] is false, then the fill will be rendered in the foreground error color. Else
+     * the fill is not rendered.
+     */
+    val showErrorState: Boolean,
+
+    /**
+     * An attribution is a drawable that shows either alongside the percent, or centered in the
+     * foreground of the overall drawable.
+     *
+     * When space sharing with the percent text, the default rect is 6x6, positioned directly next
+     * to the percent and left-aligned.
+     *
+     * When the attribution is the only foreground layer, then we use a 16x8 canvas and center this
+     * drawable.
+     *
+     * In both cases, we use a FIT_CENTER style scaling. Note that for now the attributions will
+     * have to configure their own padding inside of their vector definitions. Future versions
+     * should abstract the side- and center- canvases and allow attributions to be defined with
+     * separate designs for each case.
+     */
+    val attribution: Drawable?
+) {
+    fun hasForegroundContent() = showPercent || attribution != null
+
+    companion object {
+        val DefaultInitialState =
+            BatteryDrawableState(
+                level = 50,
+                showPercent = false,
+                showErrorState = false,
+                attribution = null,
+            )
+    }
+}
+
+sealed interface BatteryColors {
+    /** The color for the frame and any foreground attributions for the battery */
+    val fg: Int
+    /**
+     * Default color for the frame background. Configured to be a transparent white or black that
+     * matches the current mode (white for light theme, black for dark theme) and provides extra
+     * contrast for the drawable
+     */
+    val bg: Int
+
+    /** Color for the level fill when there is an attribution on top */
+    val fill: Int
+    /**
+     * When there is no attribution, [fillOnlyColor] describes an opaque color with more contrast
+     */
+    val fillOnly: Int
+
+    /** Error colors are used for low battery states typically */
+    val errorForeground: Int
+    val errorBackground: Int
+
+    /** Currently unused */
+    val warnBackground: Int
+
+    /** Color scheme appropriate for light mode (dark icons) */
+    data object LightThemeColors : BatteryColors {
+        override val fg = Color.BLACK
+        // 22% alpha white
+        override val bg: Int = Color.valueOf(1f, 1f, 1f, 0.22f).toArgb()
+
+        // 18% alpha black
+        override val fill = Color.valueOf(0f, 0f, 0f, 0.18f).toArgb()
+        // GM Gray 700
+        override val fillOnly = Color.parseColor("#5F6368")
+
+        // GM Red 600
+        override val errorForeground = Color.parseColor("#D93025")
+        // GM Red 100
+        override val errorBackground = Color.parseColor("#FAD2CF")
+
+        // GM Yellow 500
+        override val warnBackground = Color.parseColor("#FBBC04")
+    }
+
+    /** Color scheme appropriate for dark mode (light icons) */
+    data object DarkThemeColors : BatteryColors {
+        override val fg = Color.WHITE
+        // 18% alpha black
+        override val bg: Int = Color.valueOf(0f, 0f, 0f, 0.18f).toArgb()
+
+        // 22% alpha white
+        override val fill = Color.valueOf(1f, 1f, 1f, 0.22f).toArgb()
+        // GM Gray 400
+        override val fillOnly = Color.parseColor("#BDC1C6")
+
+        // GM Red 600
+        override val errorForeground = Color.parseColor("#D93025")
+        // GM Red 200
+        override val errorBackground = Color.parseColor("#F6AEA9")
+        // GM Yellow
+        override val warnBackground = Color.parseColor("#FBBC04")
+    }
+
+    companion object {
+        /** For use from java */
+        @JvmField val LIGHT_THEME_COLORS = LightThemeColors
+
+        @JvmField val DARK_THEME_COLORS = DarkThemeColors
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt
new file mode 100644
index 0000000..5e34d29
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.battery.unified
+
+import android.graphics.BlendMode
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.Matrix
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.PixelFormat
+import android.graphics.Rect
+import android.graphics.RectF
+import android.graphics.drawable.Drawable
+import android.view.View
+import com.android.systemui.battery.unified.BatteryLayersDrawable.Companion.Metrics
+import kotlin.math.floor
+import kotlin.math.roundToInt
+
+/**
+ * Draws a right-to-left fill inside of the given [framePath]. This fill is designed to exactly fill
+ * the usable space inside of [framePath], given that the stroke width of the path is 1.5, and we
+ * want an extra 0.25 (canvas units) of a gap between the fill and the stroke
+ */
+class BatteryFillDrawable(private val framePath: Path) : Drawable() {
+    private var hScale = 1f
+    private val scaleMatrix = Matrix().also { it.setScale(1f, 1f) }
+    private val scaledPath = Path()
+    private val scaledFillRect = RectF()
+    private var scaledLeftOffset = 0f
+    private var scaledRightInset = 0f
+
+    // Drawable.level cannot be overloaded
+    var batteryLevel = 0
+        set(value) {
+            field = value
+            invalidateSelf()
+        }
+
+    var fillColor: Int = 0
+        set(value) {
+            field = value
+            fillPaint.color = value
+            invalidateSelf()
+        }
+
+    private val clearPaint =
+        Paint(Paint.ANTI_ALIAS_FLAG).also { p ->
+            p.style = Paint.Style.STROKE
+            p.strokeWidth = 5f
+            p.blendMode = BlendMode.CLEAR
+        }
+
+    private val fillPaint =
+        Paint(Paint.ANTI_ALIAS_FLAG).also { p ->
+            p.style = Paint.Style.FILL
+            p.color = fillColor
+        }
+
+    override fun onBoundsChange(bounds: Rect) {
+        super.onBoundsChange(bounds)
+
+        hScale = bounds.right / Metrics.ViewportWidth
+
+        if (bounds.isEmpty) {
+            scaleMatrix.setScale(1f, 1f)
+        } else {
+            scaleMatrix.setScale(
+                (bounds.right / Metrics.ViewportWidth),
+                (bounds.bottom / Metrics.ViewportHeight)
+            )
+        }
+
+        updateScale()
+    }
+
+    private fun updateScale() {
+        framePath.transform(/* matrix = */ scaleMatrix, /* dst = */ scaledPath)
+        scaleMatrix.mapRect(/* dst = */ scaledFillRect, /* src = */ FillRect)
+
+        scaledLeftOffset = LeftFillOffset * hScale
+        scaledRightInset = RightFillInset * hScale
+    }
+
+    override fun draw(canvas: Canvas) {
+        if (batteryLevel == 0) {
+            return
+        }
+
+        // saveLayer is needed here so we don't clip the other layers of our drawable
+        canvas.saveLayer(null, null)
+
+        // Fill from the opposite direction in rtl mode
+        if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {
+            canvas.scale(-1f, 1f, bounds.width() / 2f, bounds.height() / 2f)
+        }
+
+        // We need to use 3 draw commands:
+        // 1. Clip to the current level
+        // 2. Clip anything outside of the path
+        // 3. render the fill as a rect the correct size to fit the inner space
+        // 4. Clip out the padding between the frame and the fill
+
+        val fillLeft: Int =
+            if (batteryLevel == 100) {
+                0
+            } else {
+                val fillFraction = batteryLevel / 100f
+                floor(scaledFillRect.width() * (1 - fillFraction)).roundToInt()
+            }
+
+        // Clip to the fill level
+        canvas.clipOutRect(
+            scaledLeftOffset,
+            bounds.top.toFloat(),
+            scaledLeftOffset + fillLeft,
+            bounds.height().toFloat()
+        )
+        // Clip everything outside of the path
+        canvas.clipPath(scaledPath)
+
+        // Draw the fill
+        canvas.drawRect(scaledFillRect, fillPaint)
+
+        // Clear around the fill
+        canvas.drawPath(scaledPath, clearPaint)
+
+        // Finally, restore the layer
+        canvas.restore()
+    }
+
+    override fun setColorFilter(colorFilter: ColorFilter?) {
+        clearPaint.setColorFilter(colorFilter)
+        fillPaint.setColorFilter(colorFilter)
+    }
+
+    // unused
+    override fun getOpacity(): Int = PixelFormat.OPAQUE
+
+    // unused
+    override fun setAlpha(alpha: Int) {}
+
+    companion object {
+        // 3.75f =
+        //       2.75 (left-most edge of the frame path)
+        //     + 0.75 (1/2 of the stroke width)
+        //     + 0.25 (padding between stroke and fill edge)
+        private const val LeftFillOffset = 3.75f
+
+        // 1.75, calculated the same way, but from the right edge (without the battery cap), which
+        // consumes 2 units of width.
+        private const val RightFillInset = 1.75f
+
+        /** Scale this to the viewport so we fill correctly! */
+        private val FillRect =
+            RectF(
+                LeftFillOffset,
+                0f,
+                Metrics.ViewportWidth - RightFillInset,
+                Metrics.ViewportHeight
+            )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt
new file mode 100644
index 0000000..706b9ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.battery.unified
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Matrix
+import android.graphics.Rect
+import android.graphics.RectF
+import android.graphics.Typeface
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.LayerDrawable
+import android.util.PathParser
+import android.view.Gravity
+import android.view.View
+import com.android.systemui.res.R
+import kotlin.math.ceil
+import kotlin.math.floor
+import kotlin.math.roundToInt
+
+/**
+ * Custom [Drawable] that manages a list of other drawables which, together, achieve an appropriate
+ * view for [BatteryDrawableState].
+ *
+ * The main elements managed by this drawable are:
+ *
+ *      1. A battery frame background, which may show a solid fill color
+ *      2. The battery frame itself
+ *      3. A custom [BatteryFillDrawable], which renders a fill level, appropriately scale and
+ *         clipped to the battery percent
+ *      4. Percent text
+ *      5. An attribution
+ *
+ * Layers (1) and (2) are loaded directly from xml, as they are static assets. Layer (3) contains a
+ * custom [Drawable.draw] implementation and uses the same path as the battery shape to achieve an
+ * appropriate fill shape.
+ *
+ * The text and attribution layers have the following behaviors:
+ *
+ *      - When text-only or attribute-only, the foreground layer is centered and the maximum size
+ *      - When sharing space between the attribute and the text:
+ *          - The internal space is divided into 12x10 and 6x6 rectangles
+ *          - The attribution is aligned left
+ *          - The percent text is scaled based on the number of characters (1,2, or 3) in the string
+ *
+ * When [BatteryDrawableState.showErrorState] is true, we will only show either the percent text OR
+ * the battery fill, in order to maximize contrast when using the error colors.
+ */
+@Suppress("RtlHardcoded")
+class BatteryLayersDrawable(
+    private val frameBg: Drawable,
+    private val frame: Drawable,
+    private val fill: BatteryFillDrawable,
+    private val textOnly: BatteryPercentTextOnlyDrawable,
+    private val spaceSharingText: BatterySpaceSharingPercentTextDrawable,
+    private val attribution: BatteryAttributionDrawable,
+    batteryState: BatteryDrawableState,
+) : LayerDrawable(arrayOf(frameBg, frame, fill, textOnly, spaceSharingText, attribution)) {
+
+    private val scaleMatrix = Matrix().also { it.setScale(1f, 1f) }
+
+    private val attrFullCanvas = RectF()
+    private val attrRightCanvas = RectF()
+    private val scaledAttrFullCanvas = RectF()
+    private val scaledAttrRightCanvas = RectF()
+
+    var batteryState = batteryState
+        set(value) {
+            if (field != value) {
+                // Update before we set the backing field so we can diff
+                handleUpdateState(field, value)
+                field = value
+                invalidateSelf()
+            }
+        }
+
+    var colors: BatteryColors = BatteryColors.LightThemeColors
+        set(value) {
+            field = value
+            updateColors(batteryState.showErrorState, value)
+        }
+
+    init {
+        isAutoMirrored = true
+        // Initialize the canvas rects since they are not static
+        setAttrRects(layoutDirection == View.LAYOUT_DIRECTION_RTL)
+    }
+
+    private fun handleUpdateState(old: BatteryDrawableState, new: BatteryDrawableState) {
+        if (new.showErrorState != old.showErrorState) {
+            updateColors(new.showErrorState, colors)
+        }
+
+        if (new.level != old.level) {
+            fill.batteryLevel = new.level
+            textOnly.batteryLevel = new.level
+            spaceSharingText.batteryLevel = new.level
+        }
+
+        if (new.attribution != null && new.attribution != attribution.drawable) {
+            attribution.drawable = new.attribution
+            updateColors(new.showErrorState, colors)
+        }
+
+        if (new.hasForegroundContent() != old.hasForegroundContent()) {
+            setFillColor(new.hasForegroundContent(), new.showErrorState, colors)
+        }
+    }
+
+    /** In error states, we don't draw fill unless there is no foreground content (e.g., percent) */
+    private fun updateColors(showErrorState: Boolean, colorInfo: BatteryColors) {
+        frameBg.setTint(if (showErrorState) colorInfo.errorBackground else colorInfo.bg)
+        frame.setTint(colorInfo.fg)
+        attribution.setTint(if (showErrorState) colorInfo.errorForeground else colorInfo.fg)
+        textOnly.setTint(if (showErrorState) colorInfo.errorForeground else colorInfo.fg)
+        spaceSharingText.setTint(if (showErrorState) colorInfo.errorForeground else colorInfo.fg)
+        setFillColor(batteryState.hasForegroundContent(), showErrorState, colorInfo)
+    }
+
+    /**
+     * If there is a foreground layer, then we draw the fill with the low opacity
+     * [BatteryColors.fill] color. Otherwise, if there is no other foreground layer, we will use
+     * either the error or fillOnly colors for more contrast
+     */
+    private fun setFillColor(
+        hasFg: Boolean,
+        error: Boolean,
+        colorInfo: BatteryColors,
+    ) {
+        if (hasFg) {
+            fill.fillColor = colorInfo.fill
+        } else {
+            fill.fillColor = if (error) colorInfo.errorForeground else colorInfo.fillOnly
+        }
+    }
+
+    override fun onBoundsChange(bounds: Rect) {
+        super.onBoundsChange(bounds)
+
+        scaleMatrix.setScale(
+            bounds.width() / Metrics.ViewportWidth,
+            bounds.height() / Metrics.ViewportHeight
+        )
+
+        scaleAttributionBounds()
+    }
+
+    override fun onLayoutDirectionChanged(layoutDirection: Int): Boolean {
+        setAttrRects(layoutDirection == View.LAYOUT_DIRECTION_RTL)
+        scaleAttributionBounds()
+
+        return super.onLayoutDirectionChanged(layoutDirection)
+    }
+
+    private fun setAttrRects(rtl: Boolean) {
+        // Local refs make the math easier to parse
+        val full = Metrics.AttrFullCanvasInsets
+        val side = Metrics.AttrRightCanvasInsets
+        val sideRtl = Metrics.AttrRightCanvasInsetsRtl
+        val vh = Metrics.ViewportHeight
+        val vw = Metrics.ViewportWidth
+
+        attrFullCanvas.set(
+            if (rtl) full.right else full.left,
+            full.top,
+            vw - if (rtl) full.left else full.right,
+            vh - full.bottom,
+        )
+        attrRightCanvas.set(
+            if (rtl) sideRtl.left else side.left,
+            side.top,
+            vw - (if (rtl) sideRtl.right else side.right),
+            vh - side.bottom,
+        )
+    }
+
+    /** If bounds (i.e., scale), or RTL properties change, we have to recalculate the attr bounds */
+    private fun scaleAttributionBounds() {
+        scaleMatrix.mapRect(scaledAttrFullCanvas, attrFullCanvas)
+        scaleMatrix.mapRect(scaledAttrRightCanvas, attrRightCanvas)
+    }
+
+    override fun draw(canvas: Canvas) {
+        // 1. Draw the frame bg
+        frameBg.draw(canvas)
+        // 2. Then the frame itself
+        frame.draw(canvas)
+
+        // 3. Fill it the appropriate amount if non-error state or error + no attribute
+        if (!batteryState.showErrorState || !batteryState.hasForegroundContent()) {
+            fill.draw(canvas)
+        }
+        // 4. Decide what goes inside
+        if (batteryState.showPercent && batteryState.attribution != null) {
+            // 4a. percent & attribution. Implies space-sharing
+
+            // Configure the attribute to draw in a smaller bounding box and align left and use
+            // floor/ceil math to make sure we get every available pixel
+            attribution.gravity = Gravity.LEFT
+            attribution.setBounds(
+                floor(scaledAttrRightCanvas.left).toInt(),
+                floor(scaledAttrRightCanvas.top).toInt(),
+                ceil(scaledAttrRightCanvas.right).toInt(),
+                ceil(scaledAttrRightCanvas.bottom).toInt(),
+            )
+            attribution.draw(canvas)
+
+            spaceSharingText.draw(canvas)
+        } else if (batteryState.showPercent) {
+            // 4b. Percent only
+            textOnly.draw(canvas)
+        } else if (batteryState.attribution != null) {
+            // 4c. Attribution only
+            attribution.gravity = Gravity.CENTER
+            attribution.setBounds(
+                scaledAttrFullCanvas.left.roundToInt(),
+                scaledAttrFullCanvas.top.roundToInt(),
+                scaledAttrFullCanvas.right.roundToInt(),
+                scaledAttrFullCanvas.bottom.roundToInt(),
+            )
+            attribution.draw(canvas)
+        }
+    }
+
+    /**
+     * This drawable relies on [BatteryColors] to encode all alpha in their values, so we ignore
+     * externally-set alpha
+     */
+    override fun setAlpha(alpha: Int) {}
+
+    /**
+     * Interface that describes relevant top-level metrics for the proper rendering of this icon.
+     * The overall canvas is defined as ViewportWidth x ViewportHeight, which is hard coded to 24x14
+     * points.
+     *
+     * The attr canvas insets are rect inset definitions. That is, they are defined as l,t,r,b
+     * points from the nearest edge. Note that for RTL, we don't actually flip the text since
+     * numbers do not reverse for RTL locales.
+     */
+    interface M {
+        val ViewportWidth: Float
+        val ViewportHeight: Float
+
+        /**
+         * Insets, oriented in the above viewport in LTR, that define the full canvas for a single
+         * foreground element. The element will be fit-center and center-aligned on this canvas
+         *
+         * 18x8 point size
+         */
+        val AttrFullCanvasInsets: RectF
+
+        /**
+         * Insets, oriented in the above viewport in LTR, that define the partial canvas for a
+         * foreground element that shares space with the percent text. The element will be
+         * fit-center and left-aligned on this canvas.
+         *
+         * 6x6 point size
+         */
+        val AttrRightCanvasInsets: RectF
+
+        /**
+         * Insets, oriented in the above viewport in RTL, that define the partial canvas for a
+         * foreground element that shares space with the percent text. The element will be
+         * fit-center and left-aligned on this canvas.
+         *
+         * 6x6 point size
+         */
+        val AttrRightCanvasInsetsRtl: RectF
+    }
+
+    companion object {
+        private val PercentFont = Typeface.create("google-sans", Typeface.BOLD)
+
+        /**
+         * Think of this like the `android:<attr>` values in a drawable.xml file. [Metrics] defines
+         * relevant canvas and size information for us to layout this cluster of drawables
+         */
+        val Metrics =
+            object : M {
+                override val ViewportWidth: Float = 24f
+                override val ViewportHeight: Float = 14f
+
+                override val AttrFullCanvasInsets = RectF(4f, 3f, 2f, 3f)
+                override val AttrRightCanvasInsets = RectF(16f, 4f, 2f, 4f)
+                override val AttrRightCanvasInsetsRtl = RectF(14f, 4f, 4f, 4f)
+            }
+
+        /**
+         * Create all of the layers needed by [BatteryLayersDrawable]. This class relies on the
+         * following resources to exist in order to properly render:
+         * - R.drawable.battery_unified_frame_bg
+         * - R.drawable.battery_unified_frame
+         * - R.string.battery_unified_frame_path_string
+         * - GoogleSans bold font
+         *
+         * See [BatteryDrawableState] for how to set the properties of the resulting class
+         */
+        fun newBatteryDrawable(
+            context: Context,
+            initialState: BatteryDrawableState = BatteryDrawableState.DefaultInitialState,
+        ): BatteryLayersDrawable {
+            val framePath =
+                PathParser.createPathFromPathData(
+                    context.getString(R.string.battery_unified_frame_path_string)
+                )
+
+            val frameBg =
+                context.getDrawable(R.drawable.battery_unified_frame_bg)
+                    ?: throw IllegalStateException("Missing battery_unified_frame_bg.xml")
+            val frame =
+                context.getDrawable(R.drawable.battery_unified_frame)
+                    ?: throw IllegalStateException("Missing battery_unified_frame.xml")
+            val fill = BatteryFillDrawable(framePath)
+            val textOnly = BatteryPercentTextOnlyDrawable(PercentFont)
+            val spaceSharingText = BatterySpaceSharingPercentTextDrawable(PercentFont)
+            val attribution = BatteryAttributionDrawable(null)
+
+            return BatteryLayersDrawable(
+                frameBg = frameBg,
+                frame = frame,
+                fill = fill,
+                textOnly = textOnly,
+                spaceSharingText = spaceSharingText,
+                attribution = attribution,
+                batteryState = initialState,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt
new file mode 100644
index 0000000..aa0e373
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.battery.unified
+
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.Paint
+import android.graphics.PixelFormat
+import android.graphics.Rect
+import android.graphics.Typeface
+import android.graphics.drawable.Drawable
+import android.view.View
+import com.android.systemui.battery.unified.BatteryLayersDrawable.Companion.Metrics
+
+/**
+ * (Names are hard) this drawable calculates the percent text for inside of the
+ * [BatteryLayersDrawable], assuming that there is no other attribution in the foreground. In this
+ * case, we can use the maximum font size and center the text in the full render area inside of the
+ * frame. After accounting for the stroke width and the insets from there, our rendering area is
+ * 18x10 points.
+ *
+ * See [BatterySpaceSharingPercentTextDrawable] (names are still hard) for the space-sharing
+ * approach.
+ *
+ * Note that these drawing metrics are only tested to work with google-sans BOLD
+ */
+class BatteryPercentTextOnlyDrawable(font: Typeface) : Drawable() {
+    private var hScale = 1f
+    private var vScale = 1f
+
+    // range 0-100
+    var batteryLevel: Int = 100
+        set(value) {
+            field = value
+            percentText = "$value"
+            invalidateSelf()
+        }
+
+    private var percentText = "$batteryLevel"
+
+    private val textPaint =
+        Paint().also { p ->
+            p.textSize = 10f
+            p.typeface = font
+        }
+
+    override fun onBoundsChange(bounds: Rect) {
+        super.onBoundsChange(bounds)
+
+        vScale = bounds.bottom / Metrics.ViewportHeight
+        hScale = bounds.right / Metrics.ViewportWidth
+
+        updateScale()
+    }
+
+    private fun updateScale() {
+        textPaint.textSize = TextSize * vScale
+    }
+
+    override fun draw(canvas: Canvas) {
+        val rtl = layoutDirection == View.LAYOUT_DIRECTION_RTL
+        val totalAvailableHeight = CanvasHeight * vScale
+
+        // Distribute the vertical whitespace around the text. This is a simplified version of
+        // the equation ((C - T) / 2) + T - V, where C == canvas height, T == text height, and V
+        // is the vertical nudge.
+        val offsetY = (totalAvailableHeight + textPaint.textSize) / 2 - (VerticalNudge * vScale)
+
+        val totalAvailableWidth = CanvasWidth * hScale
+        val textWidth = textPaint.measureText(percentText)
+        val offsetX = (totalAvailableWidth - textWidth) / 2
+        val startOffset = if (rtl) ViewportInsetRight else ViewportInsetLeft
+
+        // Draw the text centered in the available area
+        canvas.drawText(
+            percentText,
+            (startOffset * hScale) + offsetX,
+            (ViewportInsetTop * vScale) + offsetY,
+            textPaint
+        )
+    }
+
+    override fun setTint(tintColor: Int) {
+        textPaint.color = tintColor
+        super.setTint(tintColor)
+    }
+
+    override fun getOpacity() = PixelFormat.OPAQUE
+
+    override fun setAlpha(alpha: Int) {}
+
+    override fun setColorFilter(colorFilter: ColorFilter?) {}
+
+    companion object {
+        // Based on the 24x14 canvas, we can render in an 18x10 canvas, inset like so:
+        const val ViewportInsetLeft = 4f
+        const val ViewportInsetRight = 2f
+        const val ViewportInsetTop = 2f
+        const val CanvasHeight = 10f
+        const val CanvasWidth = 18f
+
+        // raise the text up by a smidgen so that it is more centered. Experimentally determined
+        const val VerticalNudge = 1.5f
+
+        // Experimentally-determined value
+        const val TextSize = 10f
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt
new file mode 100644
index 0000000..3b4c779
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.battery.unified
+
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.Paint
+import android.graphics.PixelFormat
+import android.graphics.Rect
+import android.graphics.Typeface
+import android.graphics.drawable.Drawable
+import android.view.View
+import com.android.systemui.battery.unified.BatteryLayersDrawable.Companion.Metrics
+
+/**
+ * A variant of [BatteryPercentTextOnlyDrawable] with the following differences:
+ * 1. It is defined on a canvas of 12x10 (shortened by 6 points horizontally)
+ * 2. Because of this, we scale the font according to the number of characters
+ *
+ * Note that these drawing metrics are only tested to work with google-sans BOLD
+ */
+class BatterySpaceSharingPercentTextDrawable(font: Typeface) : Drawable() {
+    private var verticalNudge = 0f
+    private var hScale = 1f
+    private var vScale = 1f
+
+    // range 0-100
+    var batteryLevel: Int = 88
+        set(value) {
+            field = value
+            percentText = "$value"
+            invalidateSelf()
+        }
+
+    private var percentText = "$batteryLevel"
+        set(value) {
+            field = value
+            numberOfCharacters = percentText.length
+        }
+
+    private var numberOfCharacters = percentText.length
+        set(value) {
+            if (field != value) {
+                field = value
+                updateFontSize()
+            }
+        }
+
+    private val textPaint =
+        Paint().also { p ->
+            p.textSize = 10f
+            p.typeface = font
+        }
+
+    private fun updateFontSize() {
+        // These values are determined experimentally
+        when (numberOfCharacters) {
+            3 -> {
+                verticalNudge = 1f
+                textPaint.textSize = 6f * hScale
+            }
+            // 1, 2
+            else -> {
+                verticalNudge = 1.25f
+                textPaint.textSize = 9f * hScale
+            }
+        }
+    }
+
+    private fun updateScale() {
+        updateFontSize()
+    }
+
+    override fun onBoundsChange(bounds: Rect) {
+        super.onBoundsChange(bounds)
+
+        hScale = bounds.right / Metrics.ViewportWidth
+        vScale = bounds.bottom / Metrics.ViewportHeight
+
+        updateScale()
+    }
+
+    override fun draw(canvas: Canvas) {
+        val rtl = layoutDirection == View.LAYOUT_DIRECTION_RTL
+        val totalAvailableHeight = CanvasHeight * vScale
+
+        // Distribute the vertical whitespace around the text. This is a simplified version of
+        // the equation ((C - T) / 2) + T - V, where C == canvas height, T == text height, and V
+        // is the vertical nudge.
+        val offsetY = (totalAvailableHeight + textPaint.textSize) / 2 - (verticalNudge * vScale)
+
+        val totalAvailableWidth = CanvasWidth * hScale
+        val textWidth = textPaint.measureText(percentText)
+        val offsetX = (totalAvailableWidth - textWidth) / 2
+
+        canvas.drawText(
+            percentText,
+            ((if (rtl) ViewportInsetLeftRtl else ViewportInsetLeft) * hScale) + offsetX,
+            (ViewportInsetTop * vScale) + offsetY,
+            textPaint
+        )
+    }
+
+    override fun setTint(tintColor: Int) {
+        textPaint.color = tintColor
+        super.setTint(tintColor)
+    }
+
+    override fun getOpacity() = PixelFormat.OPAQUE
+
+    override fun setAlpha(p0: Int) {}
+
+    override fun setColorFilter(colorFilter: ColorFilter?) {
+        textPaint.colorFilter = colorFilter
+    }
+
+    companion object {
+        private const val ViewportInsetLeft = 4f
+        private const val ViewportInsetLeftRtl = 2f
+        private const val ViewportInsetTop = 2f
+
+        private const val CanvasWidth = 12f
+        private const val CanvasHeight = 10f
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 0bd44f0..9de71c1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -17,7 +17,7 @@
 package com.android.systemui.biometrics;
 
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
-import static android.hardware.biometrics.Flags.customBiometricPrompt;
+import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_BIOMETRIC_PROMPT_TRANSITION;
@@ -33,6 +33,7 @@
 import android.hardware.biometrics.BiometricAuthenticator.Modality;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager.Authenticators;
+import android.hardware.biometrics.Flags;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -43,6 +44,7 @@
 import android.os.UserManager;
 import android.util.Log;
 import android.view.Display;
+import android.view.DisplayInfo;
 import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -160,6 +162,8 @@
     private final ImageView mBackgroundView;
     private final ScrollView mBiometricScrollView;
     private final View mPanelView;
+    private final List<FingerprintSensorPropertiesInternal> mFpProps;
+    private final List<FaceSensorPropertiesInternal> mFaceProps;
     private final float mTranslationY;
     @VisibleForTesting @ContainerState int mContainerState = STATE_UNKNOWN;
     private final Set<Integer> mFailedModalities = new HashSet<Integer>();
@@ -374,6 +378,8 @@
         mPromptSelectorInteractorProvider = promptSelectorInteractorProvider;
         mCredentialViewModelProvider = credentialViewModelProvider;
         mPromptViewModel = promptViewModel;
+        mFpProps = fpProps;
+        mFaceProps = faceProps;
 
         showPrompt(config, layoutInflater, promptViewModel,
                 Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
@@ -402,7 +408,12 @@
             @Nullable FaceSensorPropertiesInternal faceProps,
             @NonNull VibratorHelper vibratorHelper
     ) {
-        if (Utils.isBiometricAllowed(config.mPromptInfo) || customBiometricPrompt()) {
+        // Set this value before showing either of the prompt.
+        mPromptSelectorInteractorProvider.get().setShouldShowBpWithoutIconForCredential(
+                config.mPromptInfo);
+
+        if (Utils.isBiometricAllowed(config.mPromptInfo)
+                || mPromptViewModel.getShowBpWithoutIconForCredential().getValue()) {
             addBiometricView(config, layoutInflater, viewModel, fpProps, faceProps, vibratorHelper);
         } else if (constraintBp() && Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) {
             addCredentialView(true, false);
@@ -411,7 +422,6 @@
         }
     }
 
-
     private void addBiometricView(@NonNull Config config, @NonNull LayoutInflater layoutInflater,
             @NonNull PromptViewModel viewModel,
             @Nullable FingerprintSensorPropertiesInternal fpProps,
@@ -517,7 +527,7 @@
     @Override
     public void onOrientationChanged() {
         if (!constraintBp()) {
-            maybeUpdatePositionForUdfps(true /* invalidate */);
+            updatePositionByCapability(true /* invalidate */);
         }
     }
 
@@ -534,7 +544,8 @@
                 () -> animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED));
         if (constraintBp()) {
             // Do nothing on attachment with constraintLayout
-        } else if (Utils.isBiometricAllowed(mConfig.mPromptInfo) || customBiometricPrompt()) {
+        } else if (Utils.isBiometricAllowed(mConfig.mPromptInfo)
+                || mPromptViewModel.getShowBpWithoutIconForCredential().getValue()) {
             mBiometricScrollView.addView(mBiometricView.asView());
         } else if (Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) {
             addCredentialView(true /* animatePanel */, false /* animateContents */);
@@ -544,7 +555,7 @@
         }
 
         if (!constraintBp()) {
-            maybeUpdatePositionForUdfps(false /* invalidate */);
+            updatePositionByCapability(false /* invalidate */);
         }
 
         if (mConfig.mSkipIntro) {
@@ -610,6 +621,22 @@
         };
     }
 
+    private void updatePositionByCapability(boolean forceInvalidate) {
+        final FingerprintSensorPropertiesInternal fpProp = Utils.findFirstSensorProperties(
+                mFpProps, mConfig.mSensorIds);
+        final FaceSensorPropertiesInternal faceProp = Utils.findFirstSensorProperties(
+                mFaceProps, mConfig.mSensorIds);
+        if (fpProp != null && fpProp.isAnyUdfpsType()) {
+            maybeUpdatePositionForUdfps(forceInvalidate /* invalidate */);
+        }
+        if (faceProp != null && mBiometricView.isFaceOnly()) {
+            alwaysUpdatePositionAtScreenBottom(forceInvalidate /* invalidate */);
+        }
+        if (fpProp != null && fpProp.sensorType == TYPE_POWER_BUTTON) {
+            alwaysUpdatePositionAtScreenBottom(forceInvalidate /* invalidate */);
+        }
+    }
+
     private static boolean shouldUpdatePositionForUdfps(@NonNull View view) {
         if (view instanceof BiometricPromptLayout) {
             // this will force the prompt to align itself on the edge of the screen
@@ -627,11 +654,14 @@
         if (display == null) {
             return false;
         }
+
+        final DisplayInfo cachedDisplayInfo = new DisplayInfo();
+        display.getDisplayInfo(cachedDisplayInfo);
         if (mBiometricView == null || !shouldUpdatePositionForUdfps(mBiometricView.asView())) {
             return false;
         }
 
-        final int displayRotation = display.getRotation();
+        final int displayRotation = cachedDisplayInfo.rotation;
         switch (displayRotation) {
             case Surface.ROTATION_0:
                 mPanelController.setPosition(AuthPanelController.POSITION_BOTTOM);
@@ -663,6 +693,38 @@
         return true;
     }
 
+    private boolean alwaysUpdatePositionAtScreenBottom(boolean invalidate) {
+        final Display display = getDisplay();
+        if (display == null) {
+            return false;
+        }
+        if (mBiometricView == null || !shouldUpdatePositionForUdfps(mBiometricView.asView())) {
+            return false;
+        }
+
+        final int displayRotation = display.getRotation();
+        switch (displayRotation) {
+            case Surface.ROTATION_0:
+            case Surface.ROTATION_90:
+            case Surface.ROTATION_270:
+            case Surface.ROTATION_180:
+                mPanelController.setPosition(AuthPanelController.POSITION_BOTTOM);
+                setScrollViewGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
+                break;
+            default:
+                Log.e(TAG, "Unsupported display rotation: " + displayRotation);
+                mPanelController.setPosition(AuthPanelController.POSITION_BOTTOM);
+                setScrollViewGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
+                break;
+        }
+
+        if (invalidate) {
+            mPanelView.invalidateOutline();
+        }
+
+        return true;
+    }
+
     private void setScrollViewGravity(int gravity) {
         final FrameLayout.LayoutParams params =
                 (FrameLayout.LayoutParams) mBiometricScrollView.getLayoutParams();
@@ -819,6 +881,9 @@
 
         final Runnable endActionRunnable = () -> {
             setVisibility(View.INVISIBLE);
+            if (Flags.customBiometricPrompt()) {
+                mPromptSelectorInteractorProvider.get().resetPrompt();
+            }
             removeWindowIfAttached();
         };
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 921e395..16865ca 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -31,6 +31,7 @@
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
 import android.os.Build
 import android.os.RemoteException
+import android.os.Trace
 import android.provider.Settings
 import android.util.Log
 import android.util.RotationUtils
@@ -58,9 +59,9 @@
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.power.shared.model.WakefulnessState
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -125,11 +126,15 @@
         private val powerInteractor: PowerInteractor,
         @Application private val scope: CoroutineScope,
 ) {
-    private val isFinishedGoingToSleep: Flow<Unit> =
-        powerInteractor.detailedWakefulness
-            .filter { it.internalWakefulnessState == WakefulnessState.ASLEEP }
+    private val currentStateUpdatedToOffAodOrDozing: Flow<Unit> =
+        transitionInteractor.currentKeyguardState
+            .filter {
+                it == KeyguardState.OFF ||
+                    it == KeyguardState.AOD ||
+                    it == KeyguardState.DOZING
+            }
             .map { } // map to Unit
-    private var listenForAsleepJob: Job? = null
+    private var listenForCurrentKeyguardState: Job? = null
     private var addViewRunnable: Runnable? = null
     private var overlayViewLegacy: UdfpsView? = null
         private set
@@ -280,18 +285,19 @@
     private fun addViewNowOrLater(view: View, animation: UdfpsAnimationViewController<*>?) {
         if (udfpsViewPerformance()) {
             addViewRunnable = kotlinx.coroutines.Runnable {
+                Trace.setCounter("UdfpsAddView", 1)
                 windowManager.addView(
                         view,
                         coreLayoutParams.updateDimensions(animation)
                 )
             }
-            if (powerInteractor.detailedWakefulness.value.internalWakefulnessState
-                    != WakefulnessState.STARTING_TO_SLEEP) {
+            if (powerInteractor.detailedWakefulness.value.isAwake()) {
+                // Device is awake, so we add the view immediately.
                 addViewIfPending()
             } else {
-                listenForAsleepJob?.cancel()
-                listenForAsleepJob = scope.launch {
-                    isFinishedGoingToSleep.collect {
+                listenForCurrentKeyguardState?.cancel()
+                listenForCurrentKeyguardState = scope.launch {
+                    currentStateUpdatedToOffAodOrDozing.collect {
                         addViewIfPending()
                     }
                 }
@@ -306,7 +312,7 @@
 
     private fun addViewIfPending() {
         addViewRunnable?.let {
-            listenForAsleepJob?.cancel()
+            listenForCurrentKeyguardState?.cancel()
             it.run()
         }
         addViewRunnable = null
@@ -412,7 +418,14 @@
             udfpsDisplayModeProvider.disable(null)
         }
         getTouchOverlay()?.apply {
-            windowManager.removeView(this)
+            if (udfpsViewPerformance()) {
+                if (this.parent != null) {
+                    windowManager.removeView(this)
+                }
+                Trace.setCounter("UdfpsAddView", 0)
+            } else {
+                windowManager.removeView(this)
+            }
             setOnTouchListener(null)
             setOnHoverListener(null)
             overlayTouchListener?.let {
@@ -423,7 +436,7 @@
         overlayViewLegacy = null
         overlayTouchView = null
         overlayTouchListener = null
-        listenForAsleepJob?.cancel()
+        listenForCurrentKeyguardState?.cancel()
 
         return wasShowing
     }
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 c8fb044..8e5a97b 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
@@ -17,6 +17,7 @@
 package com.android.systemui.biometrics.data.repository
 
 import android.content.Context
+import android.util.DisplayMetrics
 import android.util.Size
 import android.view.DisplayInfo
 import com.android.systemui.biometrics.shared.model.DisplayRotation
@@ -29,8 +30,10 @@
 import com.android.systemui.display.data.repository.DisplayRepository
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
@@ -53,6 +56,9 @@
 
     /** Provides the current display size */
     val currentDisplaySize: StateFlow<Size>
+
+    /** Provides whether the current display is large screen */
+    val isLargeScreen: Flow<Boolean>
 }
 
 @SysUISingleton
@@ -121,6 +127,15 @@
                     ),
             )
 
+    override val isLargeScreen: Flow<Boolean> =
+        currentDisplayInfo
+            .map {
+                // TODO: This works, but investigate better way to handle this
+                it.logicalWidth * 160 / it.logicalDensityDpi > DisplayMetrics.DENSITY_XXXHIGH &&
+                    it.logicalHeight * 160 / it.logicalDensityDpi > DisplayMetrics.DENSITY_XXHIGH
+            }
+            .distinctUntilChanged()
+
     companion object {
         const val TAG = "DisplayStateRepositoryImpl"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index 0c0ed77..40d38dd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -51,6 +51,8 @@
  * There is never more than one instance of the FingerprintProperty at any given time.
  */
 interface FingerprintPropertyRepository {
+    /** Whether the fingerprint properties have been initialized yet. */
+    val propertiesInitialized: StateFlow<Boolean>
 
     /** The id of fingerprint sensor. */
     val sensorId: Flow<Int>
@@ -59,7 +61,7 @@
     val strength: Flow<SensorStrength>
 
     /** The types of fingerprint sensor (rear, ultrasonic, optical, etc.). */
-    val sensorType: Flow<FingerprintSensorType>
+    val sensorType: StateFlow<FingerprintSensorType>
 
     /** The sensor location relative to each physical display. */
     val sensorLocations: Flow<Map<String, SensorLocationInternal>>
@@ -105,15 +107,30 @@
             .stateIn(
                 applicationScope,
                 started = SharingStarted.Eagerly,
-                initialValue = DEFAULT_PROPS,
+                initialValue = UNINITIALIZED_PROPS,
+            )
+
+    override val propertiesInitialized: StateFlow<Boolean> =
+        props
+            .map { it != UNINITIALIZED_PROPS }
+            .stateIn(
+                applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = props.value != UNINITIALIZED_PROPS,
             )
 
     override val sensorId: Flow<Int> = props.map { it.sensorId }
 
     override val strength: Flow<SensorStrength> = props.map { it.sensorStrength.toSensorStrength() }
 
-    override val sensorType: Flow<FingerprintSensorType> =
-        props.map { it.sensorType.toSensorType() }
+    override val sensorType: StateFlow<FingerprintSensorType> =
+        props
+            .map { it.sensorType.toSensorType() }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = props.value.sensorType.toSensorType(),
+            )
 
     override val sensorLocations: Flow<Map<String, SensorLocationInternal>> =
         props.map {
@@ -124,6 +141,17 @@
 
     companion object {
         private const val TAG = "FingerprintPropertyRepositoryImpl"
+        private val UNINITIALIZED_PROPS =
+            FingerprintSensorPropertiesInternal(
+                -2 /* sensorId */,
+                SensorProperties.STRENGTH_CONVENIENCE,
+                0 /* maxEnrollmentsPerUser */,
+                listOf<ComponentInfoInternal>(),
+                FingerprintSensorProperties.TYPE_UNKNOWN,
+                false /* halControlsIllumination */,
+                true /* resetLockoutRequiresHardwareAuthToken */,
+                listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT)
+            )
         private val DEFAULT_PROPS =
             FingerprintSensorPropertiesInternal(
                 -1 /* sensorId */,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
index ad7bb0e..b87fadf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
@@ -16,8 +16,11 @@
 
 package com.android.systemui.biometrics.data.repository
 
+import android.hardware.biometrics.Flags
 import android.hardware.biometrics.PromptInfo
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.Utils
+import com.android.systemui.biometrics.Utils.isDeviceCredentialAllowed
 import com.android.systemui.biometrics.shared.model.PromptKind
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -65,6 +68,18 @@
      */
     val isConfirmationRequired: Flow<Boolean>
 
+    /**
+     * If biometric prompt without icon needs to show for displaying content prior to credential
+     * view.
+     */
+    val showBpWithoutIconForCredential: StateFlow<Boolean>
+
+    /**
+     * Update whether biometric prompt without icon needs to show for displaying content prior to
+     * credential view, which should be set before [setPrompt].
+     */
+    fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo)
+
     /** Update the prompt configuration, which should be set before [isShowing]. */
     fun setPrompt(
         promptInfo: PromptInfo,
@@ -129,6 +144,19 @@
             }
             .distinctUntilChanged()
 
+    private val _showBpWithoutIconForCredential: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    override val showBpWithoutIconForCredential = _showBpWithoutIconForCredential.asStateFlow()
+
+    override fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) {
+        val hasCredentialViewShown = kind.value !is PromptKind.Biometric
+        val showBpForCredential =
+            Flags.customBiometricPrompt() &&
+                !Utils.isBiometricAllowed(promptInfo) &&
+                isDeviceCredentialAllowed(promptInfo) &&
+                promptInfo.contentView != null
+        _showBpWithoutIconForCredential.value = showBpForCredential && !hasCredentialViewShown
+    }
+
     override fun setPrompt(
         promptInfo: PromptInfo,
         userId: Int,
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 427361d..7d67219 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
@@ -68,6 +68,8 @@
 
     /** Called on configuration changes, used to keep the display state in sync */
     fun onConfigurationChanged(newConfig: Configuration)
+
+    val isLargeScreen: Flow<Boolean>
 }
 
 /** Encapsulates logic for interacting with the display state. */
@@ -138,6 +140,8 @@
             .sample(defaultDisplay)
             .map { it?.state == Display.STATE_OFF }
 
+    override val isLargeScreen: Flow<Boolean> = displayStateRepository.isLargeScreen
+
     companion object {
         private const val TAG = "DisplayStateInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
index ff9cdbd..5ae2ff0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
@@ -24,22 +24,35 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
 
 @SysUISingleton
 class FingerprintPropertyInteractor
 @Inject
 constructor(
+    @Application private val applicationScope: CoroutineScope,
     @Application private val context: Context,
     repository: FingerprintPropertyRepository,
     configurationInteractor: ConfigurationInteractor,
     displayStateInteractor: DisplayStateInteractor,
 ) {
-    val isUdfps: Flow<Boolean> = repository.sensorType.map { it.isUdfps() }
+    val propertiesInitialized: StateFlow<Boolean> = repository.propertiesInitialized
+    val isUdfps: StateFlow<Boolean> =
+        repository.sensorType
+            .map { it.isUdfps() }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = repository.sensorType.value.isUdfps(),
+            )
 
     /**
      * Devices with multiple physical displays use unique display ids to determine which sensor is
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
index e3facff..94cea57 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
@@ -30,6 +30,7 @@
 import kotlinx.coroutines.CoroutineDispatcher
 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
@@ -59,6 +60,13 @@
     /** If the prompt is currently showing. */
     val isShowing: Flow<Boolean> = biometricPromptRepository.isShowing
 
+    /**
+     * If biometric prompt without icon needs to show for displaying content prior to credential
+     * view.
+     */
+    val showBpWithoutIconForCredential: StateFlow<Boolean> =
+        biometricPromptRepository.showBpWithoutIconForCredential
+
     /** Metadata about the current credential prompt, including app-supplied preferences. */
     val prompt: Flow<BiometricPromptRequest.Credential?> =
         combine(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
index b3f9574..45816c1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
@@ -70,6 +71,18 @@
     /** Fingerprint sensor type */
     val sensorType: Flow<FingerprintSensorType>
 
+    /**
+     * If biometric prompt without icon needs to show for displaying content prior to credential
+     * view.
+     */
+    val showBpWithoutIconForCredential: StateFlow<Boolean>
+
+    /**
+     * Update whether biometric prompt without icon needs to show for displaying content prior to
+     * credential view, which should be set before [PromptRepository.setPrompt].
+     */
+    fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo)
+
     /** Use biometrics for authentication. */
     fun useBiometricsForAuthentication(
         promptInfo: PromptInfo,
@@ -154,6 +167,12 @@
 
     override val sensorType: Flow<FingerprintSensorType> = fingerprintPropertyRepository.sensorType
 
+    override val showBpWithoutIconForCredential = promptRepository.showBpWithoutIconForCredential
+
+    override fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) {
+        promptRepository.setShouldShowBpWithoutIconForCredential(promptInfo)
+    }
+
     override fun useBiometricsForAuthentication(
         promptInfo: PromptInfo,
         userId: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
index 6133a51c..6f079e2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
@@ -17,6 +17,7 @@
     val title: String,
     val subtitle: String,
     val description: String,
+    val contentView: PromptContentView?,
     val userInfo: BiometricUserInfo,
     val operationInfo: BiometricOperationInfo,
     val showEmergencyCallButton: Boolean,
@@ -33,11 +34,11 @@
             title = info.title?.toString() ?: "",
             subtitle = info.subtitle?.toString() ?: "",
             description = info.description?.toString() ?: "",
+            contentView = info.contentView,
             userInfo = userInfo,
             operationInfo = operationInfo,
             showEmergencyCallButton = info.isShowEmergencyCallButton
         ) {
-        val contentView: PromptContentView? = info.contentView
         val logoRes: Int = info.logoRes
         val logoBitmap: Bitmap? = info.logoBitmap
         val logoDescription: String? = info.logoDescription
@@ -54,6 +55,7 @@
             title = (info.deviceCredentialTitle ?: info.title)?.toString() ?: "",
             subtitle = (info.deviceCredentialSubtitle ?: info.subtitle)?.toString() ?: "",
             description = (info.deviceCredentialDescription ?: info.description)?.toString() ?: "",
+            contentView = info.contentView,
             userInfo = userInfo,
             operationInfo = operationInfo,
             showEmergencyCallButton = info.isShowEmergencyCallButton
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
index 96582cb..e58c8ff 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
@@ -31,7 +31,6 @@
 import android.view.LayoutInflater
 import android.view.View
 import android.widget.LinearLayout
-import android.widget.ScrollView
 import android.widget.Space
 import android.widget.TextView
 import androidx.lifecycle.Lifecycle
@@ -46,7 +45,7 @@
 
 /** Sub-binder for [BiometricPromptLayout.customized_view_container]. */
 object BiometricCustomizedViewBinder {
-    fun bind(customizedViewContainer: ScrollView, spaceAbove: Space, viewModel: PromptViewModel) {
+    fun bind(customizedViewContainer: LinearLayout, spaceAbove: Space, viewModel: PromptViewModel) {
         customizedViewContainer.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.CREATED) {
                 launch {
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 cd5b124..e48f05d 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
@@ -22,7 +22,6 @@
 import android.hardware.biometrics.BiometricAuthenticator
 import android.hardware.biometrics.BiometricConstants
 import android.hardware.biometrics.BiometricPrompt
-import android.hardware.biometrics.Flags.customBiometricPrompt
 import android.hardware.face.FaceManager
 import android.text.method.ScrollingMovementMethod
 import android.util.Log
@@ -33,7 +32,7 @@
 import android.view.accessibility.AccessibilityManager
 import android.widget.Button
 import android.widget.ImageView
-import android.widget.ScrollView
+import android.widget.LinearLayout
 import android.widget.TextView
 import androidx.lifecycle.DefaultLifecycleObserver
 import androidx.lifecycle.Lifecycle
@@ -101,7 +100,7 @@
         val subtitleView = view.requireViewById<TextView>(R.id.subtitle)
         val descriptionView = view.requireViewById<TextView>(R.id.description)
         val customizedViewContainer =
-            view.requireViewById<ScrollView>(R.id.customized_view_container)
+            view.requireViewById<LinearLayout>(R.id.customized_view_container)
 
         // set selected to enable marquee unless a screen reader is enabled
         logoView.isSelected =
@@ -119,7 +118,7 @@
 
         val iconSizeOverride =
             if (constraintBp()) {
-                viewModel.fingerprintAffordanceSize
+                null
             } else {
                 (view as BiometricPromptLayout).updatedFingerprintAffordanceSize
             }
@@ -151,17 +150,6 @@
             // these do not change and need to be set before any size transitions
             val modalities = viewModel.modalities.first()
 
-            // If there is no biometrics available, biometric prompt is showing just for displaying
-            // content, no authentication needed.
-            if (!(customBiometricPrompt() && modalities.isEmpty)) {
-                PromptIconViewBinder.bind(
-                    iconView,
-                    iconOverlayView,
-                    iconSizeOverride,
-                    viewModel,
-                )
-            }
-
             if (modalities.hasFingerprint) {
                 /**
                  * Load the given [rawResources] immediately so they are cached for use in the
@@ -233,6 +221,19 @@
                 )
             }
 
+            lifecycleScope.launch {
+                viewModel.showBpWithoutIconForCredential.collect {
+                    if (!it) {
+                        PromptIconViewBinder.bind(
+                            iconView,
+                            iconOverlayView,
+                            iconSizeOverride,
+                            viewModel,
+                        )
+                    }
+                }
+            }
+
             // TODO(b/251476085): migrate legacy icon controllers and remove
             // The fingerprint sensor is started by the legacy
             // AuthContainerView#onDialogAnimatedIn in all cases but the implicit coex flow
@@ -651,6 +652,8 @@
 
     fun isCoex() = modalities.hasFaceAndFingerprint
 
+    fun isFaceOnly() = modalities.hasFaceOnly
+
     fun asView() = view
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index a37d916..478ef8f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -21,7 +21,6 @@
 import android.animation.ValueAnimator
 import android.graphics.Outline
 import android.graphics.Rect
-import android.hardware.biometrics.Flags
 import android.transition.AutoTransition
 import android.transition.TransitionManager
 import android.view.Surface
@@ -55,6 +54,7 @@
 import com.android.systemui.biometrics.ui.viewmodel.isRight
 import com.android.systemui.biometrics.ui.viewmodel.isSmall
 import com.android.systemui.biometrics.ui.viewmodel.isTop
+import com.android.systemui.keyguard.ui.view.layout.sections.setVisibility
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
 import kotlin.math.abs
@@ -100,6 +100,8 @@
             val leftGuideline = view.requireViewById<Guideline>(R.id.leftGuideline)
             val rightGuideline = view.requireViewById<Guideline>(R.id.rightGuideline)
             val bottomGuideline = view.requireViewById<Guideline>(R.id.bottomGuideline)
+            val topGuideline = view.requireViewById<Guideline>(R.id.topGuideline)
+            val midGuideline = view.findViewById<Guideline>(R.id.midGuideline)
 
             val iconHolderView = view.requireViewById<View>(R.id.biometric_icon)
             val panelView = view.requireViewById<View>(R.id.panel)
@@ -111,18 +113,30 @@
 
             val smallConstraintSet = ConstraintSet()
             smallConstraintSet.clone(mediumConstraintSet)
-            viewsToHideWhenSmall.forEach { smallConstraintSet.setVisibility(it.id, View.GONE) }
 
             val largeConstraintSet = ConstraintSet()
             largeConstraintSet.clone(mediumConstraintSet)
-            viewsToHideWhenSmall.forEach { largeConstraintSet.setVisibility(it.id, View.GONE) }
-            largeConstraintSet.setVisibility(iconHolderView.id, View.GONE)
-            largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
-            largeConstraintSet.setVisibility(R.id.indicator, View.GONE)
             largeConstraintSet.setGuidelineBegin(leftGuideline.id, 0)
             largeConstraintSet.setGuidelineEnd(rightGuideline.id, 0)
             largeConstraintSet.setGuidelineEnd(bottomGuideline.id, 0)
 
+            // TODO: Investigate better way to handle 180 rotations
+            val flipConstraintSet = ConstraintSet()
+            flipConstraintSet.clone(mediumConstraintSet)
+            flipConstraintSet.connect(
+                R.id.scrollView,
+                ConstraintSet.START,
+                R.id.midGuideline,
+                ConstraintSet.START
+            )
+            flipConstraintSet.connect(
+                R.id.scrollView,
+                ConstraintSet.END,
+                R.id.rightGuideline,
+                ConstraintSet.END
+            )
+            flipConstraintSet.setHorizontalBias(R.id.biometric_icon, .2f)
+
             // Round the panel outline
             panelView.outlineProvider =
                 object : ViewOutlineProvider() {
@@ -138,70 +152,51 @@
                         .getInsets(WindowInsets.Type.navigationBars())
                         .bottom
 
+                // TODO: Move to viewmodel
                 fun measureBounds(position: PromptPosition) {
-                    val width = min(windowBounds.height(), windowBounds.width())
+                    val density = windowManager.currentWindowMetrics.density
+                    val width = min((640 * density).toInt(), windowBounds.width())
 
                     var left = -1
-                    var top = -1
                     var right = -1
                     var bottom = -1
+                    var mid = -1
 
                     when {
                         position.isTop -> {
                             left = windowBounds.centerX() - width / 2 + viewModel.promptMargin
-                            top = viewModel.promptMargin
                             right = windowBounds.centerX() - width / 2 + viewModel.promptMargin
                             bottom = iconHolderView.centerY() * 2 - iconHolderView.centerY() / 4
                         }
                         position.isBottom -> {
-                            if (view.isLandscape()) {
-                                left = windowBounds.centerX() - width / 2 + viewModel.promptMargin
-                                top = iconHolderView.centerY()
-                                right = windowBounds.centerX() - width / 2 + viewModel.promptMargin
-                                bottom = bottomInset + viewModel.promptMargin
-                            } else {
-                                left = windowBounds.centerX() - width / 2 + viewModel.promptMargin
-                                top =
-                                    windowBounds.height() -
-                                        (windowBounds.height() - iconHolderView.centerY()) * 2 +
-                                        viewModel.promptMargin
-                                right = windowBounds.centerX() - width / 2 + viewModel.promptMargin
-                                bottom = viewModel.promptMargin
-                            }
+                            left = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+                            right = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+                            bottom = viewModel.promptMargin
                         }
-
-                        // For Udfps exclusive left and right, measure guideline to center
-                        // icon in BP
                         position.isLeft -> {
                             left = viewModel.promptMargin
-                            top =
-                                windowBounds.height() -
-                                    (windowBounds.height() - iconHolderView.centerY()) * 2 +
-                                    viewModel.promptMargin
-                            right =
+                            mid =
                                 abs(
                                     windowBounds.width() - iconHolderView.centerX() * 2 +
                                         viewModel.promptMargin
                                 )
+                            right = windowBounds.width() - (windowBounds.width() * .85).toInt()
                             bottom = bottomInset + viewModel.promptMargin
                         }
                         position.isRight -> {
-                            left =
+                            left = windowBounds.width() - (windowBounds.width() * .85).toInt()
+                            right = viewModel.promptMargin
+                            bottom = bottomInset + viewModel.promptMargin
+                            mid =
                                 abs(
                                     iconHolderView.centerX() -
                                         (windowBounds.width() - iconHolderView.centerX()) -
                                         viewModel.promptMargin
                                 )
-                            top =
-                                windowBounds.height() -
-                                    (windowBounds.height() - iconHolderView.centerY()) * 2 +
-                                    viewModel.promptMargin
-                            right = viewModel.promptMargin
-                            bottom = bottomInset + viewModel.promptMargin
                         }
                     }
 
-                    val bounds = Rect(left, top, right, bottom)
+                    val bounds = Rect(left, mid, right, bottom)
                     if (bounds.shouldAdjustLeftGuideline()) {
                         leftGuideline.setGuidelineBegin(bounds.left)
                         smallConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
@@ -217,15 +212,32 @@
                         smallConstraintSet.setGuidelineEnd(bottomGuideline.id, bounds.bottom)
                         mediumConstraintSet.setGuidelineEnd(bottomGuideline.id, bounds.bottom)
                     }
+
+                    if (position.isBottom) {
+                        topGuideline.setGuidelinePercent(.25f)
+                        mediumConstraintSet.setGuidelinePercent(topGuideline.id, .25f)
+                    } else {
+                        topGuideline.setGuidelinePercent(0f)
+                        mediumConstraintSet.setGuidelinePercent(topGuideline.id, 0f)
+                    }
+
+                    if (mid != -1 && midGuideline != null) {
+                        midGuideline.setGuidelineBegin(mid)
+                    }
                 }
 
-                view.repeatWhenAttached {
-                    var currentSize: PromptSize? = null
-                    val modalities = viewModel.modalities.first()
-                    // TODO(b/288175072): Move all visibility settings together.
-                    //  If there is no biometrics available, biometric prompt is showing just for
-                    // displaying content, no authentication needed.
-                    if (Flags.customBiometricPrompt() && modalities.isEmpty) {
+                fun setConstraintSetVisibility() {
+                    viewsToHideWhenSmall.forEach {
+                        mediumConstraintSet.setVisibility(it.id, it.showContentOrHide())
+                        largeConstraintSet.setVisibility(it.id, View.GONE)
+                        smallConstraintSet.setVisibility(it.id, View.GONE)
+                    }
+
+                    largeConstraintSet.setVisibility(iconHolderView.id, View.GONE)
+                    largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
+                    largeConstraintSet.setVisibility(R.id.indicator, View.GONE)
+
+                    if (viewModel.showBpWithoutIconForCredential.value) {
                         smallConstraintSet.setVisibility(iconHolderView.id, View.GONE)
                         smallConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
                         smallConstraintSet.setVisibility(R.id.indicator, View.GONE)
@@ -233,12 +245,23 @@
                         mediumConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
                         mediumConstraintSet.setVisibility(R.id.indicator, View.GONE)
                     }
+                }
+
+                view.repeatWhenAttached {
+                    var currentSize: PromptSize? = null
+
                     lifecycleScope.launch {
                         combine(viewModel.position, viewModel.size, ::Pair).collect {
                             (position, size) ->
                             view.doOnAttach {
-                                measureBounds(position)
+                                if (position.isLeft) {
+                                    flipConstraintSet.applyTo(view)
+                                } else if (position.isRight) {
+                                    mediumConstraintSet.applyTo(view)
+                                }
 
+                                measureBounds(position)
+                                setConstraintSetVisibility()
                                 when {
                                     size.isSmall -> {
                                         val ratio =
@@ -313,7 +336,6 @@
                 // TODO(b/251476085): migrate the legacy panel controller and simplify this
                 view.repeatWhenAttached {
                     var currentSize: PromptSize? = null
-                    val modalities = viewModel.modalities.first()
                     lifecycleScope.launch {
                         /**
                          * View is only set visible in BiometricViewSizeBinder once PromptSize is
@@ -331,11 +353,13 @@
 
                             // prepare for animated size transitions
                             for (v in viewsToHideWhenSmall) {
-                                v.showContentOrHide(forceHide = size.isSmall)
+                                v.visibility = v.showContentOrHide(forceHide = size.isSmall)
                             }
-                            if (Flags.customBiometricPrompt() && modalities.isEmpty) {
+
+                            if (viewModel.showBpWithoutIconForCredential.value) {
                                 iconHolderView.visibility = View.GONE
                             }
+
                             if (currentSize == null && size.isSmall) {
                                 iconHolderView.alpha = 0f
                             }
@@ -344,9 +368,9 @@
                             }
 
                             // TODO(b/302735104): Fix wrong height due to the delay of
-                            // PromptContentView. addOnLayoutChangeListener() will cause crash when
-                            // showing credential view, since |PromptIconViewModel| won't release
-                            // the flow.
+                            // PromptContentView. addOnLayoutChangeListener() will cause crash
+                            // when showing credential view, since |PromptIconViewModel| won't
+                            // release the flow.
                             // propagate size changes to legacy panel controller and animate
                             // transitions
                             view.doOnLayout {
@@ -456,19 +480,24 @@
 }
 
 private fun View.isLandscape(): Boolean {
-    val r = context.display?.rotation
-    return r == Surface.ROTATION_90 || r == Surface.ROTATION_270
+    val r = context.display.rotation
+    return if (
+        context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
+    ) {
+        r == Surface.ROTATION_0 || r == Surface.ROTATION_180
+    } else {
+        r == Surface.ROTATION_90 || r == Surface.ROTATION_270
+    }
 }
 
-private fun View.showContentOrHide(forceHide: Boolean = false) {
+private fun View.showContentOrHide(forceHide: Boolean = false): Int {
     val isTextViewWithBlankText = this is TextView && this.text.isBlank()
     val isImageViewWithoutImage = this is ImageView && this.drawable == null
-    visibility =
-        if (forceHide || isTextViewWithBlankText || isImageViewWithoutImage) {
-            View.GONE
-        } else {
-            View.VISIBLE
-        }
+    return if (forceHide || isTextViewWithBlankText || isImageViewWithoutImage) {
+        View.GONE
+    } else {
+        View.VISIBLE
+    }
 }
 
 private fun View.centerX(): Int {
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
index 2e47375..3469cfa 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -37,6 +37,7 @@
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
 
 /** Sub-binder for [BiometricPromptLayout.iconView]. */
@@ -62,6 +63,12 @@
 
                     iconOverlayView.layoutParams.width = iconViewLayoutParamSizeOverride.first
                     iconOverlayView.layoutParams.height = iconViewLayoutParamSizeOverride.second
+                } else {
+                    iconView.layoutParams.width = viewModel.fingerprintIconWidth.first()
+                    iconView.layoutParams.height = viewModel.fingerprintIconWidth.first()
+
+                    iconOverlayView.layoutParams.width = viewModel.fingerprintIconWidth.first()
+                    iconOverlayView.layoutParams.height = viewModel.fingerprintIconWidth.first()
                 }
 
                 var faceIcon: AnimatedVectorDrawable? = null
@@ -77,15 +84,12 @@
                     }
 
                 launch {
-                    var width: Int
-                    var height: Int
+                    var width = 0
+                    var height = 0
                     viewModel.activeAuthType.collect { activeAuthType ->
                         when (activeAuthType) {
                             AuthType.Fingerprint,
                             AuthType.Coex -> {
-                                width = viewModel.fingerprintIconWidth
-                                height = viewModel.fingerprintIconHeight
-
                                 /**
                                  * View is only set visible in BiometricViewSizeBinder once
                                  * PromptSize is determined that accounts for iconView size, to
@@ -113,7 +117,7 @@
                             }
                         }
 
-                        if (iconViewLayoutParamSizeOverride == null) {
+                        if (width != 0 && height != 0) {
                             iconView.layoutParams.width = width
                             iconView.layoutParams.height = height
                             iconOverlayView.layoutParams.width = width
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
index 03c5c53..46be8c7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
@@ -4,13 +4,13 @@
 import android.graphics.drawable.Drawable
 import android.text.InputType
 import com.android.internal.widget.LockPatternView
-import com.android.systemui.res.R
 import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.domain.interactor.CredentialStatus
 import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor
 import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
 import com.android.systemui.biometrics.shared.model.BiometricUserInfo
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
 import javax.inject.Inject
 import kotlin.reflect.KClass
 import kotlinx.coroutines.flow.Flow
@@ -32,14 +32,16 @@
 
     /** Top level information about the prompt. */
     val header: Flow<CredentialHeaderViewModel> =
-        credentialInteractor.prompt.filterIsInstance<BiometricPromptRequest.Credential>().map {
-            request ->
+        combine(
+            credentialInteractor.prompt.filterIsInstance<BiometricPromptRequest.Credential>(),
+            credentialInteractor.showBpWithoutIconForCredential
+        ) { request, showBpWithoutIconForCredential ->
             BiometricPromptHeaderViewModelImpl(
                 request,
                 user = request.userInfo,
                 title = request.title,
-                subtitle = request.subtitle,
-                description = request.description,
+                subtitle = if (showBpWithoutIconForCredential) "" else request.subtitle,
+                description = if (showBpWithoutIconForCredential) "" else request.description,
                 icon = applicationContext.asLockIcon(request.userInfo.deviceCredentialOwnerId),
                 showEmergencyCallButton = request.showEmergencyCallButton
             )
@@ -145,7 +147,7 @@
                 .createLaunchEmergencyDialerIntent(null)
                 .setFlags(
                     android.content.Intent.FLAG_ACTIVITY_NEW_TASK or
-                            android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP
+                        android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP
                 )
         context.startActivity(intent)
     }
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
index b7cffaf..257eb4a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
@@ -159,8 +159,8 @@
     val lastPulseLightToDark: Flow<Boolean> = _lastPulseLightToDark.asStateFlow()
 
     /** Layout params for fingerprint iconView */
-    val fingerprintIconWidth: Int = promptViewModel.fingerprintIconWidth
-    val fingerprintIconHeight: Int = promptViewModel.fingerprintIconHeight
+    val fingerprintIconWidth: Flow<Int> = promptViewModel.fingerprintSensorDiameter
+    val fingerprintIconHeight: Flow<Int> = promptViewModel.fingerprintSensorDiameter
 
     /** Layout params for face iconView */
     val faceIconWidth: Int = promptViewModel.faceIconWidth
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 c933e0e..61aeffe 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
@@ -47,6 +47,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
@@ -84,14 +85,15 @@
     val faceIconHeight: Int =
         context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
 
-    val fingerprintSensorDiameter: Int =
-        (udfpsOverlayInteractor.udfpsOverlayParams.value.sensorBounds.width() *
-                udfpsOverlayInteractor.udfpsOverlayParams.value.scaleFactor)
-            .toInt()
-    val fingerprintAffordanceSize: Pair<Int, Int>? =
-        if (fingerprintSensorDiameter != 0)
-            Pair(fingerprintSensorDiameter, fingerprintSensorDiameter)
-        else null
+    val fingerprintSensorDiameter: Flow<Int> =
+        combine(modalities, udfpsOverlayInteractor.udfpsOverlayParams) { modalities, overlayParams
+            ->
+            if (modalities.hasUdfps) {
+                overlayParams.sensorBounds.width()
+            } else {
+                fingerprintIconWidth
+            }
+        }
 
     private val _accessibilityHint = MutableSharedFlow<String>()
 
@@ -123,6 +125,9 @@
     /** The kind of credential the user has. */
     val credentialKind: Flow<PromptKind> = promptSelectorInteractor.credentialKind
 
+    val showBpWithoutIconForCredential: StateFlow<Boolean> =
+        promptSelectorInteractor.showBpWithoutIconForCredential
+
     /** The label to use for the cancel button. */
     val negativeButtonText: Flow<String> =
         promptSelectorInteractor.prompt.map { it?.negativeButtonText ?: "" }
@@ -153,12 +158,13 @@
 
     /** The current position of the prompt */
     val position: Flow<PromptPosition> =
-        combine(_forceLargeSize, modalities, displayStateInteractor.currentRotation) {
-                forceLarge,
-                modalities,
-                rotation ->
+        combine(
+                _forceLargeSize,
+                displayStateInteractor.isLargeScreen,
+                displayStateInteractor.currentRotation
+            ) { forceLarge, isLargeScreen, rotation ->
                 when {
-                    forceLarge || !modalities.hasUdfps -> PromptPosition.Bottom
+                    forceLarge || isLargeScreen -> PromptPosition.Bottom
                     rotation == DisplayRotation.ROTATION_90 -> PromptPosition.Right
                     rotation == DisplayRotation.ROTATION_270 -> PromptPosition.Left
                     rotation == DisplayRotation.ROTATION_180 -> PromptPosition.Top
@@ -340,22 +346,22 @@
     /** If the indicator (help, error) message should be shown. */
     val isIndicatorMessageVisible: Flow<Boolean> =
         combine(
-                size,
-                message,
-            ) { size, message ->
-                size.isNotSmall && message.message.isNotBlank()
-            }
-            .distinctUntilChanged()
+            size,
+            position,
+            message,
+        ) { size, _, message ->
+            size.isNotSmall && message.message.isNotBlank()
+        }
 
     /** If the auth is pending confirmation and the confirm button should be shown. */
     val isConfirmButtonVisible: Flow<Boolean> =
         combine(
-                size,
-                isPendingConfirmation,
-            ) { size, isPendingConfirmation ->
-                size.isNotSmall && isPendingConfirmation
-            }
-            .distinctUntilChanged()
+            size,
+            position,
+            isPendingConfirmation,
+        ) { size, _, isPendingConfirmation ->
+            size.isNotSmall && isPendingConfirmation
+        }
 
     /** If the icon can be used as a confirmation button. */
     val isIconConfirmButton: Flow<Boolean> = size.map { it.isNotSmall }.distinctUntilChanged()
@@ -363,28 +369,25 @@
     /** If the negative button should be shown. */
     val isNegativeButtonVisible: Flow<Boolean> =
         combine(
-                size,
-                isAuthenticated,
-                promptSelectorInteractor.isCredentialAllowed,
-            ) { size, authState, credentialAllowed ->
-                size.isNotSmall && authState.isNotAuthenticated && !credentialAllowed
-            }
-            .distinctUntilChanged()
+            size,
+            position,
+            isAuthenticated,
+            promptSelectorInteractor.isCredentialAllowed,
+        ) { size, _, authState, credentialAllowed ->
+            size.isNotSmall && authState.isNotAuthenticated && !credentialAllowed
+        }
 
     /** If the cancel button should be shown (. */
     val isCancelButtonVisible: Flow<Boolean> =
         combine(
-                size,
-                isAuthenticated,
-                isNegativeButtonVisible,
-                isConfirmButtonVisible,
-            ) { size, authState, showNegativeButton, showConfirmButton ->
-                size.isNotSmall &&
-                    authState.isAuthenticated &&
-                    !showNegativeButton &&
-                    showConfirmButton
-            }
-            .distinctUntilChanged()
+            size,
+            position,
+            isAuthenticated,
+            isNegativeButtonVisible,
+            isConfirmButtonVisible,
+        ) { size, _, authState, showNegativeButton, showConfirmButton ->
+            size.isNotSmall && authState.isAuthenticated && !showNegativeButton && showConfirmButton
+        }
 
     private val _canTryAgainNow = MutableStateFlow(false)
     /**
@@ -393,35 +396,34 @@
      */
     val canTryAgainNow: Flow<Boolean> =
         combine(
-                _canTryAgainNow,
-                size,
-                isAuthenticated,
-                isRetrySupported,
-            ) { readyToTryAgain, size, authState, supportsRetry ->
-                readyToTryAgain && size.isNotSmall && supportsRetry && authState.isNotAuthenticated
-            }
-            .distinctUntilChanged()
+            _canTryAgainNow,
+            size,
+            position,
+            isAuthenticated,
+            isRetrySupported,
+        ) { readyToTryAgain, size, _, authState, supportsRetry ->
+            readyToTryAgain && size.isNotSmall && supportsRetry && authState.isNotAuthenticated
+        }
 
     /** If the try again button show be shown (only the button, see [canTryAgainNow]). */
     val isTryAgainButtonVisible: Flow<Boolean> =
         combine(
-                canTryAgainNow,
-                modalities,
-            ) { tryAgainIsPossible, modalities ->
-                tryAgainIsPossible && modalities.hasFaceOnly
-            }
-            .distinctUntilChanged()
+            canTryAgainNow,
+            modalities,
+        ) { tryAgainIsPossible, modalities ->
+            tryAgainIsPossible && modalities.hasFaceOnly
+        }
 
     /** If the credential fallback button show be shown. */
     val isCredentialButtonVisible: Flow<Boolean> =
         combine(
-                size,
-                isAuthenticated,
-                promptSelectorInteractor.isCredentialAllowed,
-            ) { size, authState, credentialAllowed ->
-                size.isNotSmall && authState.isNotAuthenticated && credentialAllowed
-            }
-            .distinctUntilChanged()
+            size,
+            position,
+            isAuthenticated,
+            promptSelectorInteractor.isCredentialAllowed,
+        ) { size, _, authState, credentialAllowed ->
+            size.isNotSmall && authState.isNotAuthenticated && credentialAllowed
+        }
 
     private val history = PromptHistoryImpl()
     private var messageJob: Job? = null
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
index 6af0fa0..66aeda6 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
@@ -42,7 +42,7 @@
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.media.controls.util.MediaDataUtils;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 
@@ -69,7 +69,7 @@
 
     private final Context mContext;
     private final UiEventLogger mUiEventLogger;
-    private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+    private final MediaOutputDialogManager mMediaOutputDialogManager;
     private final LocalBluetoothManager mLocalBluetoothManager;
     private final BroadcastSender mBroadcastSender;
     private final SystemUIDialog.Factory mSystemUIDialogFactory;
@@ -157,7 +157,7 @@
     @AssistedInject
     BroadcastDialogDelegate(
             Context context,
-            MediaOutputDialogFactory mediaOutputDialogFactory,
+            MediaOutputDialogManager mediaOutputDialogManager,
             @Nullable LocalBluetoothManager localBluetoothManager,
             UiEventLogger uiEventLogger,
             @Background Executor bgExecutor,
@@ -166,7 +166,7 @@
             @Assisted(CURRENT_BROADCAST_APP) String currentBroadcastApp,
             @Assisted(OUTPUT_PKG_NAME) String outputPkgName) {
         mContext = context;
-        mMediaOutputDialogFactory = mediaOutputDialogFactory;
+        mMediaOutputDialogManager = mediaOutputDialogManager;
         mLocalBluetoothManager = localBluetoothManager;
         mSystemUIDialogFactory = systemUIDialogFactory;
         mCurrentBroadcastApp = currentBroadcastApp;
@@ -218,7 +218,7 @@
                 R.string.bt_le_audio_broadcast_dialog_switch_app, switchBroadcastApp), null);
         mSwitchBroadcast.setOnClickListener((view) -> startSwitchBroadcast());
         changeOutput.setOnClickListener((view) -> {
-            mMediaOutputDialogFactory.create(mOutputPackageName, true, null);
+            mMediaOutputDialogManager.createAndShow(mOutputPackageName, true, null);
             dialog.dismiss();
         });
         cancelBtn.setOnClickListener((view) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt
index 269878b..b9e1c55 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt
@@ -92,7 +92,7 @@
     keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val subscriptionManager: SubscriptionManagerProxy,
     broadcastDispatcher: BroadcastDispatcher,
-    euiccManager: EuiccManager,
+    euiccManager: EuiccManager?,
 ) : SimBouncerRepository {
     private val isPukScreenAvailable: Boolean =
         resources.getBoolean(com.android.internal.R.bool.config_enable_puk_unlock_screen)
@@ -163,7 +163,9 @@
     @SuppressLint("MissingPermission")
     override val isLockedEsim: StateFlow<Boolean?> =
         activeSubscriptionInfo
-            .map { info -> info?.let { euiccManager.isEnabled && info.isEmbedded } }
+            .map { info ->
+                info?.let { euiccManager != null && euiccManager.isEnabled && info.isEmbedded }
+            }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.Eagerly,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorModule.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorModule.kt
index efa7792..0f61eef 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorModule.kt
@@ -50,7 +50,7 @@
     }
 
     @Provides
-    fun provideEuiccManager(@Application applicationContext: Context): EuiccManager {
-        return applicationContext.getSystemService(Context.EUICC_SERVICE) as EuiccManager
+    fun provideEuiccManager(@Application applicationContext: Context): EuiccManager? {
+        return applicationContext.getSystemService(Context.EUICC_SERVICE) as EuiccManager?
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
index c25e748..3092def 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
@@ -23,10 +23,12 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.Flags
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.biometrics.data.repository.FacePropertyRepository
 import com.android.systemui.biometrics.shared.model.SensorStrength
 import com.android.systemui.bouncer.data.repository.BouncerMessageRepository
 import com.android.systemui.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.bouncer.shared.model.BouncerMessageStrings
 import com.android.systemui.bouncer.shared.model.Message
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -35,46 +37,6 @@
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.res.R.string.bouncer_face_not_recognized
-import com.android.systemui.res.R.string.keyguard_enter_password
-import com.android.systemui.res.R.string.keyguard_enter_pattern
-import com.android.systemui.res.R.string.keyguard_enter_pin
-import com.android.systemui.res.R.string.kg_bio_too_many_attempts_password
-import com.android.systemui.res.R.string.kg_bio_too_many_attempts_pattern
-import com.android.systemui.res.R.string.kg_bio_too_many_attempts_pin
-import com.android.systemui.res.R.string.kg_bio_try_again_or_password
-import com.android.systemui.res.R.string.kg_bio_try_again_or_pattern
-import com.android.systemui.res.R.string.kg_bio_try_again_or_pin
-import com.android.systemui.res.R.string.kg_face_locked_out
-import com.android.systemui.res.R.string.kg_fp_not_recognized
-import com.android.systemui.res.R.string.kg_primary_auth_locked_out_password
-import com.android.systemui.res.R.string.kg_primary_auth_locked_out_pattern
-import com.android.systemui.res.R.string.kg_primary_auth_locked_out_pin
-import com.android.systemui.res.R.string.kg_prompt_after_adaptive_auth_lock
-import com.android.systemui.res.R.string.kg_prompt_after_dpm_lock
-import com.android.systemui.res.R.string.kg_prompt_after_update_password
-import com.android.systemui.res.R.string.kg_prompt_after_update_pattern
-import com.android.systemui.res.R.string.kg_prompt_after_update_pin
-import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_password
-import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_pattern
-import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_pin
-import com.android.systemui.res.R.string.kg_prompt_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_password_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_pattern_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_pin_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_reason_restart_password
-import com.android.systemui.res.R.string.kg_prompt_reason_restart_pattern
-import com.android.systemui.res.R.string.kg_prompt_reason_restart_pin
-import com.android.systemui.res.R.string.kg_prompt_unattended_update
-import com.android.systemui.res.R.string.kg_too_many_failed_attempts_countdown
-import com.android.systemui.res.R.string.kg_trust_agent_disabled
-import com.android.systemui.res.R.string.kg_unlock_with_password_or_fp
-import com.android.systemui.res.R.string.kg_unlock_with_pattern_or_fp
-import com.android.systemui.res.R.string.kg_unlock_with_pin_or_fp
-import com.android.systemui.res.R.string.kg_wrong_input_try_fp_suggestion
-import com.android.systemui.res.R.string.kg_wrong_password_try_again
-import com.android.systemui.res.R.string.kg_wrong_pattern_try_again
-import com.android.systemui.res.R.string.kg_wrong_pin_try_again
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.kotlin.Quint
 import javax.inject.Inject
@@ -130,17 +92,22 @@
                 repository.setMessage(
                     when (biometricSourceType) {
                         BiometricSourceType.FINGERPRINT ->
-                            incorrectFingerprintInput(currentSecurityMode)
+                            BouncerMessageStrings.incorrectFingerprintInput(
+                                    currentSecurityMode.toAuthModel()
+                                )
+                                .toMessage()
                         BiometricSourceType.FACE ->
-                            incorrectFaceInput(
-                                currentSecurityMode,
-                                isFingerprintAuthCurrentlyAllowed.value
-                            )
+                            BouncerMessageStrings.incorrectFaceInput(
+                                    currentSecurityMode.toAuthModel(),
+                                    isFingerprintAuthCurrentlyAllowed.value
+                                )
+                                .toMessage()
                         else ->
-                            defaultMessage(
-                                currentSecurityMode,
-                                isFingerprintAuthCurrentlyAllowed.value
-                            )
+                            BouncerMessageStrings.defaultMessage(
+                                    currentSecurityMode.toAuthModel(),
+                                    isFingerprintAuthCurrentlyAllowed.value
+                                )
+                                .toMessage()
                     }
                 )
             }
@@ -189,45 +156,79 @@
                     trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterReboot
                 ) {
                     if (wasRebootedForMainlineUpdate) {
-                        authRequiredForMainlineUpdate(currentSecurityMode)
+                        BouncerMessageStrings.authRequiredForMainlineUpdate(
+                                currentSecurityMode.toAuthModel()
+                            )
+                            .toMessage()
                     } else {
-                        authRequiredAfterReboot(currentSecurityMode)
+                        BouncerMessageStrings.authRequiredAfterReboot(
+                                currentSecurityMode.toAuthModel()
+                            )
+                            .toMessage()
                     }
                 } else if (trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterTimeout) {
-                    authRequiredAfterPrimaryAuthTimeout(currentSecurityMode)
+                    BouncerMessageStrings.authRequiredAfterPrimaryAuthTimeout(
+                            currentSecurityMode.toAuthModel()
+                        )
+                        .toMessage()
                 } else if (flags.isPrimaryAuthRequiredAfterDpmLockdown) {
-                    authRequiredAfterAdminLockdown(currentSecurityMode)
+                    BouncerMessageStrings.authRequiredAfterAdminLockdown(
+                            currentSecurityMode.toAuthModel()
+                        )
+                        .toMessage()
                 } else if (
                     trustOrBiometricsAvailable && flags.primaryAuthRequiredForUnattendedUpdate
                 ) {
-                    authRequiredForUnattendedUpdate(currentSecurityMode)
+                    BouncerMessageStrings.authRequiredForUnattendedUpdate(
+                            currentSecurityMode.toAuthModel()
+                        )
+                        .toMessage()
                 } else if (fpLockedOut) {
-                    class3AuthLockedOut(currentSecurityMode)
+                    BouncerMessageStrings.class3AuthLockedOut(currentSecurityMode.toAuthModel())
+                        .toMessage()
                 } else if (faceLockedOut) {
                     if (isFaceAuthClass3) {
-                        class3AuthLockedOut(currentSecurityMode)
+                        BouncerMessageStrings.class3AuthLockedOut(currentSecurityMode.toAuthModel())
+                            .toMessage()
                     } else {
-                        faceLockedOut(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+                        BouncerMessageStrings.faceLockedOut(
+                                currentSecurityMode.toAuthModel(),
+                                isFingerprintAuthCurrentlyAllowed.value
+                            )
+                            .toMessage()
                     }
                 } else if (flags.isSomeAuthRequiredAfterAdaptiveAuthRequest) {
-                    authRequiredAfterAdaptiveAuthRequest(
-                        currentSecurityMode,
-                        isFingerprintAuthCurrentlyAllowed.value
-                    )
+                    BouncerMessageStrings.authRequiredAfterAdaptiveAuthRequest(
+                            currentSecurityMode.toAuthModel(),
+                            isFingerprintAuthCurrentlyAllowed.value
+                        )
+                        .toMessage()
                 } else if (
                     trustOrBiometricsAvailable &&
                         flags.strongerAuthRequiredAfterNonStrongBiometricsTimeout
                 ) {
-                    nonStrongAuthTimeout(
-                        currentSecurityMode,
-                        isFingerprintAuthCurrentlyAllowed.value
-                    )
+                    BouncerMessageStrings.nonStrongAuthTimeout(
+                            currentSecurityMode.toAuthModel(),
+                            isFingerprintAuthCurrentlyAllowed.value
+                        )
+                        .toMessage()
                 } else if (isTrustUsuallyManaged && flags.someAuthRequiredAfterUserRequest) {
-                    trustAgentDisabled(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+                    BouncerMessageStrings.trustAgentDisabled(
+                            currentSecurityMode.toAuthModel(),
+                            isFingerprintAuthCurrentlyAllowed.value
+                        )
+                        .toMessage()
                 } else if (isTrustUsuallyManaged && flags.someAuthRequiredAfterTrustAgentExpired) {
-                    trustAgentDisabled(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+                    BouncerMessageStrings.trustAgentDisabled(
+                            currentSecurityMode.toAuthModel(),
+                            isFingerprintAuthCurrentlyAllowed.value
+                        )
+                        .toMessage()
                 } else if (trustOrBiometricsAvailable && flags.isInUserLockdown) {
-                    authRequiredAfterUserLockdown(currentSecurityMode)
+                    BouncerMessageStrings.authRequiredAfterUserLockdown(
+                            currentSecurityMode.toAuthModel()
+                        )
+                        .toMessage()
                 } else {
                     defaultMessage
                 }
@@ -244,7 +245,11 @@
 
                 override fun onTick(millisUntilFinished: Long) {
                     val secondsRemaining = (millisUntilFinished / 1000.0).roundToInt()
-                    val message = primaryAuthLockedOut(currentSecurityMode)
+                    val message =
+                        BouncerMessageStrings.primaryAuthLockedOut(
+                                currentSecurityMode.toAuthModel()
+                            )
+                            .toMessage()
                     message.message?.animate = false
                     message.message?.formatterArgs =
                         mutableMapOf<String, Any>(Pair("count", secondsRemaining))
@@ -258,7 +263,11 @@
         if (!Flags.revampedBouncerMessages()) return
 
         repository.setMessage(
-            incorrectSecurityInput(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+            BouncerMessageStrings.incorrectSecurityInput(
+                    currentSecurityMode.toAuthModel(),
+                    isFingerprintAuthCurrentlyAllowed.value
+                )
+                .toMessage()
         )
     }
 
@@ -285,7 +294,12 @@
     }
 
     private val defaultMessage: BouncerMessageModel
-        get() = defaultMessage(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+        get() =
+            BouncerMessageStrings.defaultMessage(
+                    currentSecurityMode.toAuthModel(),
+                    isFingerprintAuthCurrentlyAllowed.value
+                )
+                .toMessage()
 
     fun onPrimaryBouncerUserInput() {
         if (!Flags.revampedBouncerMessages()) return
@@ -354,283 +368,35 @@
     return BouncerMessageModel(
         message =
             Message(
-                messageResId = defaultMessage(securityMode, fpAuthIsAllowed).message?.messageResId,
+                messageResId =
+                    BouncerMessageStrings.defaultMessage(
+                            securityMode.toAuthModel(),
+                            fpAuthIsAllowed
+                        )
+                        .toMessage()
+                        .message
+                        ?.messageResId,
                 animate = false
             ),
         secondaryMessage = Message(message = secondaryMessage, animate = false)
     )
 }
 
-private fun defaultMessage(
-    securityMode: SecurityMode,
-    fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
-    return if (fpAuthIsAllowed) {
-        defaultMessageWithFingerprint(securityMode)
-    } else
-        when (securityMode) {
-            SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-            SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-            SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-            else -> Pair(0, 0)
-        }.toMessage()
-}
-
-private fun defaultMessageWithFingerprint(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, 0)
-        SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, 0)
-        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, 0)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun incorrectSecurityInput(
-    securityMode: SecurityMode,
-    fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
-    return if (fpAuthIsAllowed) {
-        incorrectSecurityInputWithFingerprint(securityMode)
-    } else
-        when (securityMode) {
-            SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, 0)
-            SecurityMode.Password -> Pair(kg_wrong_password_try_again, 0)
-            SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, 0)
-            else -> Pair(0, 0)
-        }.toMessage()
-}
-
-private fun incorrectSecurityInputWithFingerprint(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, kg_wrong_input_try_fp_suggestion)
-        SecurityMode.Password -> Pair(kg_wrong_password_try_again, kg_wrong_input_try_fp_suggestion)
-        SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, kg_wrong_input_try_fp_suggestion)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun incorrectFingerprintInput(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pattern)
-        SecurityMode.Password -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_password)
-        SecurityMode.PIN -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pin)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun incorrectFaceInput(
-    securityMode: SecurityMode,
-    fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
-    return if (fpAuthIsAllowed) incorrectFaceInputWithFingerprintAllowed(securityMode)
-    else
-        when (securityMode) {
-            SecurityMode.Pattern -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pattern)
-            SecurityMode.Password -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_password)
-            SecurityMode.PIN -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pin)
-            else -> Pair(0, 0)
-        }.toMessage()
-}
-
-private fun incorrectFaceInputWithFingerprintAllowed(
-    securityMode: SecurityMode
-): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, bouncer_face_not_recognized)
-        SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, bouncer_face_not_recognized)
-        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, bouncer_face_not_recognized)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun biometricLockout(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern)
-        SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_bio_too_many_attempts_pin)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun authRequiredAfterReboot(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_reason_restart_pattern)
-        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_reason_restart_password)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun authRequiredAfterAdminLockdown(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_dpm_lock)
-        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_dpm_lock)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun authRequiredAfterAdaptiveAuthRequest(
-    securityMode: SecurityMode,
-    fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
-    return if (fpAuthIsAllowed) authRequiredAfterAdaptiveAuthRequestFingerprintAllowed(securityMode)
-    else
-        return when (securityMode) {
-            SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_adaptive_auth_lock)
-            SecurityMode.Password ->
-                Pair(keyguard_enter_password, kg_prompt_after_adaptive_auth_lock)
-            SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_adaptive_auth_lock)
-            else -> Pair(0, 0)
-        }.toMessage()
-}
-
-private fun authRequiredAfterAdaptiveAuthRequestFingerprintAllowed(
-    securityMode: SecurityMode
-): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern ->
-            Pair(kg_unlock_with_pattern_or_fp, kg_prompt_after_adaptive_auth_lock)
-        SecurityMode.Password ->
-            Pair(kg_unlock_with_password_or_fp, kg_prompt_after_adaptive_auth_lock)
-        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_after_adaptive_auth_lock)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun authRequiredAfterUserLockdown(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_user_lockdown_pattern)
-        SecurityMode.Password ->
-            Pair(keyguard_enter_password, kg_prompt_after_user_lockdown_password)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun authRequiredForUnattendedUpdate(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_unattended_update)
-        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_unattended_update)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_unattended_update)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun authRequiredForMainlineUpdate(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_update_pattern)
-        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_update_password)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_update_pin)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun authRequiredAfterPrimaryAuthTimeout(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_pattern_auth_timeout)
-        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_password_auth_timeout)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun nonStrongAuthTimeout(
-    securityMode: SecurityMode,
-    fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
-    return if (fpAuthIsAllowed) {
-        nonStrongAuthTimeoutWithFingerprintAllowed(securityMode)
-    } else
-        when (securityMode) {
-            SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_auth_timeout)
-            SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_auth_timeout)
-            SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_auth_timeout)
-            else -> Pair(0, 0)
-        }.toMessage()
-}
-
-fun nonStrongAuthTimeoutWithFingerprintAllowed(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_prompt_auth_timeout)
-        SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_prompt_auth_timeout)
-        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_auth_timeout)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun faceLockedOut(
-    securityMode: SecurityMode,
-    fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
-    return if (fpAuthIsAllowed) faceLockedOutButFingerprintAvailable(securityMode)
-    else
-        when (securityMode) {
-            SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_face_locked_out)
-            SecurityMode.Password -> Pair(keyguard_enter_password, kg_face_locked_out)
-            SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_face_locked_out)
-            else -> Pair(0, 0)
-        }.toMessage()
-}
-
-private fun faceLockedOutButFingerprintAvailable(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_face_locked_out)
-        SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_face_locked_out)
-        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_face_locked_out)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun class3AuthLockedOut(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern)
-        SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_bio_too_many_attempts_pin)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun trustAgentDisabled(
-    securityMode: SecurityMode,
-    fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
-    return if (fpAuthIsAllowed) trustAgentDisabledWithFingerprintAllowed(securityMode)
-    else
-        when (securityMode) {
-            SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_trust_agent_disabled)
-            SecurityMode.Password -> Pair(keyguard_enter_password, kg_trust_agent_disabled)
-            SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_trust_agent_disabled)
-            else -> Pair(0, 0)
-        }.toMessage()
-}
-
-private fun trustAgentDisabledWithFingerprintAllowed(
-    securityMode: SecurityMode
-): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_trust_agent_disabled)
-        SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_trust_agent_disabled)
-        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_trust_agent_disabled)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
-private fun primaryAuthLockedOut(securityMode: SecurityMode): BouncerMessageModel {
-    return when (securityMode) {
-        SecurityMode.Pattern ->
-            Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pattern)
-        SecurityMode.Password ->
-            Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_password)
-        SecurityMode.PIN ->
-            Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pin)
-        else -> Pair(0, 0)
-    }.toMessage()
-}
-
 private fun Pair<Int, Int>.toMessage(): BouncerMessageModel {
     return BouncerMessageModel(
         message = Message(messageResId = this.first, animate = false),
         secondaryMessage = Message(messageResId = this.second, animate = false)
     )
 }
+
+private fun SecurityMode.toAuthModel(): AuthenticationMethodModel {
+    return when (this) {
+        SecurityMode.Invalid -> AuthenticationMethodModel.None
+        SecurityMode.None -> AuthenticationMethodModel.None
+        SecurityMode.Pattern -> AuthenticationMethodModel.Pattern
+        SecurityMode.Password -> AuthenticationMethodModel.Password
+        SecurityMode.PIN -> AuthenticationMethodModel.Pin
+        SecurityMode.SimPin -> AuthenticationMethodModel.Sim
+        SecurityMode.SimPuk -> AuthenticationMethodModel.Sim
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
index 99d1f13..c3d4cb3 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
@@ -61,7 +61,7 @@
     private val telephonyManager: TelephonyManager,
     @Main private val resources: Resources,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-    private val euiccManager: EuiccManager,
+    private val euiccManager: EuiccManager?,
     // TODO(b/307977401): Replace this with `MobileConnectionsInteractor` when available.
     mobileConnectionsRepository: MobileConnectionsRepository,
 ) {
@@ -141,11 +141,13 @@
                 UserHandle.SYSTEM
             )
         applicationScope.launch(backgroundDispatcher) {
-            euiccManager.switchToSubscription(
-                INVALID_SUBSCRIPTION_ID,
-                activeSubscription.portIndex,
-                callbackIntent,
-            )
+            if (euiccManager != null) {
+                euiccManager.switchToSubscription(
+                    INVALID_SUBSCRIPTION_ID,
+                    activeSubscription.portIndex,
+                    callbackIntent,
+                )
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
new file mode 100644
index 0000000..e789475
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.shared.flag
+
+import com.android.systemui.Flags
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import dagger.Module
+import dagger.Provides
+
+interface ComposeBouncerFlags {
+
+    /**
+     * Returns `true` if the Compose bouncer is enabled or if the scene container framework is
+     * enabled; `false` otherwise.
+     */
+    fun isComposeBouncerOrSceneContainerEnabled(): Boolean
+
+    /**
+     * Returns `true` if only compose bouncer is enabled and scene container framework is not
+     * enabled.
+     */
+    @Deprecated(
+        "Avoid using this, this is meant to be used only by the glue code " +
+            "that includes compose bouncer in legacy keyguard.",
+        replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()")
+    )
+    fun isOnlyComposeBouncerEnabled(): Boolean
+}
+
+class ComposeBouncerFlagsImpl(private val sceneContainerFlags: SceneContainerFlags) :
+    ComposeBouncerFlags {
+
+    override fun isComposeBouncerOrSceneContainerEnabled(): Boolean {
+        return sceneContainerFlags.isEnabled() || Flags.composeBouncer()
+    }
+
+    @Deprecated(
+        "Avoid using this, this is meant to be used only by the glue code " +
+            "that includes compose bouncer in legacy keyguard.",
+        replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()")
+    )
+    override fun isOnlyComposeBouncerEnabled(): Boolean {
+        return !sceneContainerFlags.isEnabled() && Flags.composeBouncer()
+    }
+}
+
+@Module
+object ComposeBouncerFlagsModule {
+    @Provides
+    @SysUISingleton
+    fun impl(sceneContainerFlags: SceneContainerFlags): ComposeBouncerFlags {
+        return ComposeBouncerFlagsImpl(sceneContainerFlags)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt
new file mode 100644
index 0000000..cb12ce5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.shared.model
+
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin
+import com.android.systemui.res.R
+
+typealias BouncerMessagePair = Pair<Int, Int>
+
+val BouncerMessagePair.primaryMessage: Int
+    get() = this.first
+
+val BouncerMessagePair.secondaryMessage: Int
+    get() = this.second
+
+object BouncerMessageStrings {
+    private val EmptyMessage = Pair(0, 0)
+
+    fun defaultMessage(
+        securityMode: AuthenticationMethodModel,
+        fpAuthIsAllowed: Boolean
+    ): BouncerMessagePair {
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), 0)
+            Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), 0)
+            Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), 0)
+            else -> EmptyMessage
+        }
+    }
+
+    fun incorrectSecurityInput(
+        securityMode: AuthenticationMethodModel,
+        fpAuthIsAllowed: Boolean
+    ): BouncerMessagePair {
+        val secondaryMessage = incorrectSecurityInputSecondaryMessage(fpAuthIsAllowed)
+        return when (securityMode) {
+            Pattern -> Pair(R.string.kg_wrong_pattern_try_again, secondaryMessage)
+            Password -> Pair(R.string.kg_wrong_password_try_again, secondaryMessage)
+            Pin -> Pair(R.string.kg_wrong_pin_try_again, secondaryMessage)
+            else -> EmptyMessage
+        }
+    }
+
+    private fun incorrectSecurityInputSecondaryMessage(fpAuthIsAllowed: Boolean): Int {
+        return if (fpAuthIsAllowed) R.string.kg_wrong_input_try_fp_suggestion else 0
+    }
+
+    fun incorrectFingerprintInput(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+        val primaryMessage = R.string.kg_fp_not_recognized
+        return when (securityMode) {
+            Pattern -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pattern)
+            Password -> Pair(primaryMessage, R.string.kg_bio_try_again_or_password)
+            Pin -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pin)
+            else -> EmptyMessage
+        }
+    }
+
+    fun incorrectFaceInput(
+        securityMode: AuthenticationMethodModel,
+        fpAuthIsAllowed: Boolean
+    ): BouncerMessagePair {
+        return if (fpAuthIsAllowed) incorrectFaceInputWithFingerprintAllowed(securityMode)
+        else {
+            val primaryMessage = R.string.bouncer_face_not_recognized
+            when (securityMode) {
+                Pattern -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pattern)
+                Password -> Pair(primaryMessage, R.string.kg_bio_try_again_or_password)
+                Pin -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pin)
+                else -> EmptyMessage
+            }
+        }
+    }
+
+    private fun incorrectFaceInputWithFingerprintAllowed(
+        securityMode: AuthenticationMethodModel
+    ): BouncerMessagePair {
+        val secondaryMsg = R.string.bouncer_face_not_recognized
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(true), secondaryMsg)
+            Password -> Pair(passwordDefaultMessage(true), secondaryMsg)
+            Pin -> Pair(pinDefaultMessage(true), secondaryMsg)
+            else -> EmptyMessage
+        }
+    }
+
+    fun authRequiredAfterReboot(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_reason_restart_pattern)
+            Password ->
+                Pair(passwordDefaultMessage(false), R.string.kg_prompt_reason_restart_password)
+            Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_reason_restart_pin)
+            else -> EmptyMessage
+        }
+    }
+
+    fun authRequiredAfterAdminLockdown(
+        securityMode: AuthenticationMethodModel
+    ): BouncerMessagePair {
+        val secondaryMsg = R.string.kg_prompt_after_dpm_lock
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(false), secondaryMsg)
+            Password -> Pair(passwordDefaultMessage(false), secondaryMsg)
+            Pin -> Pair(pinDefaultMessage(false), secondaryMsg)
+            else -> EmptyMessage
+        }
+    }
+
+    fun authRequiredAfterAdaptiveAuthRequest(
+        securityMode: AuthenticationMethodModel,
+        fpAuthIsAllowed: Boolean
+    ): BouncerMessagePair {
+        val secondaryMsg = R.string.kg_prompt_after_adaptive_auth_lock
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            else -> EmptyMessage
+        }
+    }
+
+    fun authRequiredAfterUserLockdown(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+        return when (securityMode) {
+            Pattern ->
+                Pair(patternDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_pattern)
+            Password ->
+                Pair(passwordDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_password)
+            Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_pin)
+            else -> EmptyMessage
+        }
+    }
+
+    fun authRequiredForUnattendedUpdate(
+        securityMode: AuthenticationMethodModel
+    ): BouncerMessagePair {
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_added_security_pattern)
+            Password ->
+                Pair(passwordDefaultMessage(false), R.string.kg_prompt_added_security_password)
+            Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_added_security_pin)
+            else -> EmptyMessage
+        }
+    }
+
+    fun authRequiredForMainlineUpdate(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_after_update_pattern)
+            Password ->
+                Pair(passwordDefaultMessage(false), R.string.kg_prompt_after_update_password)
+            Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_after_update_pin)
+            else -> EmptyMessage
+        }
+    }
+
+    fun authRequiredAfterPrimaryAuthTimeout(
+        securityMode: AuthenticationMethodModel
+    ): BouncerMessagePair {
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_pattern_auth_timeout)
+            Password ->
+                Pair(passwordDefaultMessage(false), R.string.kg_prompt_password_auth_timeout)
+            Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_pin_auth_timeout)
+            else -> EmptyMessage
+        }
+    }
+
+    fun nonStrongAuthTimeout(
+        securityMode: AuthenticationMethodModel,
+        fpAuthIsAllowed: Boolean
+    ): BouncerMessagePair {
+        val secondaryMsg = R.string.kg_prompt_auth_timeout
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            else -> EmptyMessage
+        }
+    }
+
+    fun faceLockedOut(
+        securityMode: AuthenticationMethodModel,
+        fpAuthIsAllowed: Boolean
+    ): BouncerMessagePair {
+        val secondaryMsg = R.string.kg_face_locked_out
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            else -> EmptyMessage
+        }
+    }
+
+    fun class3AuthLockedOut(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(false), R.string.kg_bio_too_many_attempts_pattern)
+            Password ->
+                Pair(passwordDefaultMessage(false), R.string.kg_bio_too_many_attempts_password)
+            Pin -> Pair(pinDefaultMessage(false), R.string.kg_bio_too_many_attempts_pin)
+            else -> EmptyMessage
+        }
+    }
+
+    fun trustAgentDisabled(
+        securityMode: AuthenticationMethodModel,
+        fpAuthIsAllowed: Boolean
+    ): BouncerMessagePair {
+        val secondaryMsg = R.string.kg_trust_agent_disabled
+        return when (securityMode) {
+            Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+            else -> EmptyMessage
+        }
+    }
+
+    fun primaryAuthLockedOut(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+        return when (securityMode) {
+            Pattern ->
+                Pair(
+                    R.string.kg_too_many_failed_attempts_countdown,
+                    R.string.kg_primary_auth_locked_out_pattern
+                )
+            Password ->
+                Pair(
+                    R.string.kg_too_many_failed_attempts_countdown,
+                    R.string.kg_primary_auth_locked_out_password
+                )
+            Pin ->
+                Pair(
+                    R.string.kg_too_many_failed_attempts_countdown,
+                    R.string.kg_primary_auth_locked_out_pin
+                )
+            else -> EmptyMessage
+        }
+    }
+
+    private fun patternDefaultMessage(fingerprintAllowed: Boolean): Int {
+        return if (fingerprintAllowed) R.string.kg_unlock_with_pattern_or_fp
+        else R.string.keyguard_enter_pattern
+    }
+
+    private fun pinDefaultMessage(fingerprintAllowed: Boolean): Int {
+        return if (fingerprintAllowed) R.string.kg_unlock_with_pin_or_fp
+        else R.string.keyguard_enter_pin
+    }
+
+    private fun passwordDefaultMessage(fingerprintAllowed: Boolean): Int {
+        return if (fingerprintAllowed) R.string.kg_unlock_with_password_or_fp
+        else R.string.keyguard_enter_password
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
index dd253a8..f1a0e5e 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
@@ -4,14 +4,13 @@
 import com.android.keyguard.KeyguardMessageAreaController
 import com.android.keyguard.ViewMediatorCallback
 import com.android.keyguard.dagger.KeyguardBouncerComponent
-import com.android.systemui.Flags
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
 import com.android.systemui.bouncer.ui.BouncerDialogFactory
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
-import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.Flags.COMPOSE_BOUNCER_ENABLED
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
@@ -55,13 +54,12 @@
 class BouncerViewBinder
 @Inject
 constructor(
+    private val composeBouncerFlags: ComposeBouncerFlags,
     private val legacyBouncerDependencies: Lazy<LegacyBouncerDependencies>,
     private val composeBouncerDependencies: Lazy<ComposeBouncerDependencies>,
 ) {
     fun bind(view: ViewGroup) {
-        if (
-            ComposeFacade.isComposeAvailable() && Flags.composeBouncer() && COMPOSE_BOUNCER_ENABLED
-        ) {
+        if (COMPOSE_BOUNCER_ENABLED && composeBouncerFlags.isOnlyComposeBouncerEnabled()) {
             val deps = composeBouncerDependencies.get()
             ComposeBouncerViewBinder.bind(
                 view,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
index 7b05395..179fa87 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
@@ -1,15 +1,17 @@
 package com.android.systemui.bouncer.ui.binder
 
 import android.view.ViewGroup
+import androidx.compose.ui.platform.ComposeView
 import androidx.core.view.isVisible
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.compose.theme.PlatformTheme
 import com.android.keyguard.ViewMediatorCallback
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.bouncer.ui.BouncerDialogFactory
+import com.android.systemui.bouncer.ui.composable.BouncerContent
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
-import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import kotlinx.coroutines.flow.collectLatest
@@ -27,12 +29,11 @@
         viewMediatorCallback: ViewMediatorCallback?,
     ) {
         view.addView(
-            ComposeFacade.createBouncer(
-                view.context,
-                viewModel,
-                dialogFactory,
-            )
+            ComposeView(view.context).apply {
+                setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } }
+            }
         )
+
         view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.CREATED) {
                 launch {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 4466cbb..6287578 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
 import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
@@ -35,7 +36,6 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.inputmethod.domain.interactor.InputMethodInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.user.ui.viewmodel.UserActionViewModel
 import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
@@ -72,7 +72,7 @@
     private val simBouncerInteractor: SimBouncerInteractor,
     private val authenticationInteractor: AuthenticationInteractor,
     private val selectedUserInteractor: SelectedUserInteractor,
-    flags: SceneContainerFlags,
+    flags: ComposeBouncerFlags,
     selectedUser: Flow<UserViewModel>,
     users: Flow<List<UserViewModel>>,
     userSwitcherMenu: Flow<List<UserActionViewModel>>,
@@ -233,7 +233,7 @@
     private var lockoutCountdownJob: Job? = null
 
     init {
-        if (flags.isEnabled()) {
+        if (flags.isComposeBouncerOrSceneContainerEnabled()) {
             // Keeps the lockout dialog up-to-date.
             applicationScope.launch {
                 bouncerInteractor.onLockoutStarted.collect {
@@ -478,7 +478,7 @@
         actionButtonInteractor: BouncerActionButtonInteractor,
         authenticationInteractor: AuthenticationInteractor,
         selectedUserInteractor: SelectedUserInteractor,
-        flags: SceneContainerFlags,
+        flags: ComposeBouncerFlags,
         userSwitcherViewModel: UserSwitcherViewModel,
         clock: SystemClock,
         devicePolicyManager: DevicePolicyManager,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 1c8b84d..b42eda1 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -73,6 +73,14 @@
                 initialValue = isInputEnabled.value && !isTextFieldFocused.value,
             )
 
+    /** The ID of the currently-selected user. */
+    val selectedUserId: StateFlow<Int> =
+        selectedUserInteractor.selectedUser.stateIn(
+            scope = viewModelScope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = selectedUserInteractor.getSelectedUserId(),
+        )
+
     override fun onHidden() {
         super.onHidden()
         isTextFieldFocused.value = false
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraRotationModule.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraRotationModule.kt
new file mode 100644
index 0000000..f123828
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraRotationModule.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.camera
+
+import com.android.systemui.camera.data.repository.CameraAutoRotateRepository
+import com.android.systemui.camera.data.repository.CameraAutoRotateRepositoryImpl
+import com.android.systemui.camera.data.repository.CameraSensorPrivacyRepository
+import com.android.systemui.camera.data.repository.CameraSensorPrivacyRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+/** Module for repositories that provide data regarding camera rotation state. */
+@Module
+interface CameraRotationModule {
+
+    @Binds
+    fun bindsPrivacyRepoImpl(impl: CameraSensorPrivacyRepositoryImpl): CameraSensorPrivacyRepository
+    @Binds fun bindsRotateRepoImpl(impl: CameraAutoRotateRepositoryImpl): CameraAutoRotateRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepository.kt b/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepository.kt
new file mode 100644
index 0000000..023fd28
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepository.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.camera.data.repository
+
+import android.os.UserHandle
+import android.provider.Settings
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+interface CameraAutoRotateRepository {
+    /** @return true if camera auto rotate setting is enabled */
+    fun isCameraAutoRotateSettingEnabled(userHandle: UserHandle): StateFlow<Boolean>
+}
+
+@SysUISingleton
+class CameraAutoRotateRepositoryImpl
+@Inject
+constructor(
+    private val secureSettings: SecureSettings,
+    @Background private val bgCoroutineContext: CoroutineContext,
+    @Application private val applicationScope: CoroutineScope,
+) : CameraAutoRotateRepository {
+    private val userMap = mutableMapOf<Int, StateFlow<Boolean>>()
+
+    override fun isCameraAutoRotateSettingEnabled(userHandle: UserHandle): StateFlow<Boolean> {
+        return userMap.getOrPut(userHandle.identifier) {
+            secureSettings
+                .observerFlow(userHandle.identifier, Settings.Secure.CAMERA_AUTOROTATE)
+                .map { isAutoRotateSettingEnabled(userHandle.identifier) }
+                .onStart { emit(isAutoRotateSettingEnabled(userHandle.identifier)) }
+                .flowOn(bgCoroutineContext)
+                .stateIn(applicationScope, SharingStarted.WhileSubscribed(), false)
+        }
+    }
+
+    private fun isAutoRotateSettingEnabled(userId: Int) =
+        secureSettings.getIntForUser(SETTING_NAME, DISABLED, userId) == ENABLED
+
+    private companion object {
+        const val SETTING_NAME = Settings.Secure.CAMERA_AUTOROTATE
+        const val DISABLED = 0
+        const val ENABLED = 1
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepository.kt b/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepository.kt
new file mode 100644
index 0000000..7816a14
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepository.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.camera.data.repository
+
+import android.hardware.SensorPrivacyManager
+import android.hardware.SensorPrivacyManager.Sensors.CAMERA
+import android.os.UserHandle
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+interface CameraSensorPrivacyRepository {
+    /** Tracks whether camera sensor privacy is enabled. */
+    fun isEnabled(userHandle: UserHandle): StateFlow<Boolean>
+}
+
+@SysUISingleton
+class CameraSensorPrivacyRepositoryImpl
+@Inject
+constructor(
+    @Background private val bgCoroutineContext: CoroutineContext,
+    @Application private val scope: CoroutineScope,
+    private val privacyManager: SensorPrivacyManager,
+) : CameraSensorPrivacyRepository {
+    private val userMap = mutableMapOf<Int, StateFlow<Boolean>>()
+
+    /** Whether camera sensor privacy is enabled */
+    override fun isEnabled(userHandle: UserHandle): StateFlow<Boolean> =
+        userMap.getOrPut(userHandle.identifier) {
+            privacyManager
+                .isEnabled(userHandle)
+                .flowOn(bgCoroutineContext)
+                .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+        }
+}
+
+fun SensorPrivacyManager.isEnabled(userHandle: UserHandle): Flow<Boolean> {
+    return conflatedCallbackFlow {
+            val privacyCallback =
+                SensorPrivacyManager.OnSensorPrivacyChangedListener { sensor, enabled ->
+                    if (sensor == CAMERA) {
+                        trySend(enabled)
+                    }
+                }
+            addSensorPrivacyListener(CAMERA, userHandle.identifier, privacyCallback)
+            awaitClose { removeSensorPrivacyListener(privacyCallback) }
+        }
+        .onStart { emit(isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, CAMERA)) }
+        .distinctUntilChanged()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
new file mode 100644
index 0000000..9e7fb4e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal
+
+import android.annotation.SuppressLint
+import android.app.DreamManager
+import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.communalHub
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.Utils.Companion.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+/**
+ * A [CoreStartable] responsible for automatically starting the dream when the communal hub is
+ * shown, to support the user swiping away the hub to enter the dream.
+ */
+@SysUISingleton
+class CommunalDreamStartable
+@Inject
+constructor(
+    private val powerInteractor: PowerInteractor,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val dreamManager: DreamManager,
+    @Background private val bgScope: CoroutineScope,
+) : CoreStartable {
+    @SuppressLint("MissingPermission")
+    override fun start() {
+        if (!communalHub()) {
+            return
+        }
+
+        // Restart the dream underneath the hub in order to support the ability to swipe
+        // away the hub to enter the dream.
+        keyguardTransitionInteractor.finishedKeyguardState
+            .sample(powerInteractor.isAwake, keyguardInteractor.isDreaming)
+            .onEach { (finishedState, isAwake, dreaming) ->
+                if (
+                    finishedState == KeyguardState.GLANCEABLE_HUB &&
+                        !dreaming &&
+                        dreamManager.canStartDreaming(isAwake)
+                ) {
+                    dreamManager.startDream()
+                }
+            }
+            .launchIn(bgScope)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index 8397372..c3c7411 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -16,25 +16,23 @@
 
 package com.android.systemui.communal
 
+import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.CoreStartable
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dock.DockManager
-import com.android.systemui.dock.retrieveIsDocked
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.FlowPreview
 import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.debounce
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.mapLatest
@@ -65,38 +63,40 @@
             .onEach { nextScene -> communalInteractor.onSceneChanged(nextScene) }
             .launchIn(applicationScope)
 
+        // TODO(b/322787129): re-enable once custom animations are in place
         // Handle automatically switching to communal when docked.
-        dockManager
-            .retrieveIsDocked()
-            // Allow some time after docking to ensure the dream doesn't start. If the dream
-            // starts, then we don't want to automatically transition to glanceable hub.
-            .debounce(DOCK_DEBOUNCE_DELAY)
-            .sample(keyguardTransitionInteractor.startedKeyguardState, ::Pair)
-            .onEach { (docked, lastStartedState) ->
-                if (docked && lastStartedState == KeyguardState.LOCKSCREEN) {
-                    communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
-                }
-            }
-            .launchIn(bgScope)
+        //        dockManager
+        //            .retrieveIsDocked()
+        //            // Allow some time after docking to ensure the dream doesn't start. If the
+        // dream
+        //            // starts, then we don't want to automatically transition to glanceable hub.
+        //            .debounce(DOCK_DEBOUNCE_DELAY)
+        //            .sample(keyguardTransitionInteractor.startedKeyguardState, ::Pair)
+        //            .onEach { (docked, lastStartedState) ->
+        //                if (docked && lastStartedState == KeyguardState.LOCKSCREEN) {
+        //                    communalInteractor.onSceneChanged(CommunalScenes.Communal)
+        //                }
+        //            }
+        //            .launchIn(bgScope)
     }
 
     private suspend fun determineSceneAfterTransition(
         lastStartedTransition: TransitionStep,
-    ): CommunalSceneKey? {
+    ): SceneKey? {
         val to = lastStartedTransition.to
         val from = lastStartedTransition.from
         val docked = dockManager.isDocked
 
         return when {
-            docked && to == KeyguardState.LOCKSCREEN && from != KeyguardState.GLANCEABLE_HUB -> {
-                CommunalSceneKey.Communal
+            docked && to == KeyguardState.LOCKSCREEN && from == KeyguardState.DREAMING -> {
+                CommunalScenes.Communal
             }
-            to == KeyguardState.GONE -> CommunalSceneKey.Blank
+            to == KeyguardState.GONE -> CommunalScenes.Blank
             !docked && !KeyguardState.deviceIsAwakeInState(to) -> {
                 // If the user taps the screen and wakes the device within this timeout, we don't
                 // want to dismiss the hub
                 delay(AWAKE_DEBOUNCE_DELAY)
-                CommunalSceneKey.Blank
+                CommunalScenes.Blank
             }
             else -> null
         }
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 f4a3bcb..201ce83 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
@@ -16,8 +16,9 @@
 
 package com.android.systemui.communal.data.repository
 
-import com.android.systemui.communal.shared.model.CommunalSceneKey
-import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.scene.data.repository.SceneContainerRepository
@@ -40,20 +41,20 @@
      * Target scene as requested by the underlying [SceneTransitionLayout] or through
      * [setDesiredScene].
      */
-    val desiredScene: StateFlow<CommunalSceneKey>
+    val desiredScene: StateFlow<SceneKey>
 
     /** Exposes the transition state of the communal [SceneTransitionLayout]. */
-    val transitionState: StateFlow<ObservableCommunalTransitionState>
+    val transitionState: StateFlow<ObservableTransitionState>
 
     /** Updates the requested scene. */
-    fun setDesiredScene(desiredScene: CommunalSceneKey)
+    fun setDesiredScene(desiredScene: SceneKey)
 
     /**
      * Updates the transition state of the hub [SceneTransitionLayout].
      *
      * Note that you must call is with `null` when the UI is done or risk a memory leak.
      */
-    fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?)
+    fun setTransitionState(transitionState: Flow<ObservableTransitionState>?)
 }
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -66,14 +67,12 @@
     sceneContainerRepository: SceneContainerRepository,
 ) : CommunalRepository {
 
-    private val _desiredScene: MutableStateFlow<CommunalSceneKey> =
-        MutableStateFlow(CommunalSceneKey.DEFAULT)
-    override val desiredScene: StateFlow<CommunalSceneKey> = _desiredScene.asStateFlow()
+    private val _desiredScene: MutableStateFlow<SceneKey> = MutableStateFlow(CommunalScenes.Default)
+    override val desiredScene: StateFlow<SceneKey> = _desiredScene.asStateFlow()
 
-    private val defaultTransitionState =
-        ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT)
-    private val _transitionState = MutableStateFlow<Flow<ObservableCommunalTransitionState>?>(null)
-    override val transitionState: StateFlow<ObservableCommunalTransitionState> =
+    private val defaultTransitionState = ObservableTransitionState.Idle(CommunalScenes.Default)
+    private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null)
+    override val transitionState: StateFlow<ObservableTransitionState> =
         _transitionState
             .flatMapLatest { innerFlowOrNull -> innerFlowOrNull ?: flowOf(defaultTransitionState) }
             .stateIn(
@@ -82,7 +81,7 @@
                 initialValue = defaultTransitionState,
             )
 
-    override fun setDesiredScene(desiredScene: CommunalSceneKey) {
+    override fun setDesiredScene(desiredScene: SceneKey) {
         _desiredScene.value = desiredScene
     }
 
@@ -91,7 +90,7 @@
      *
      * Note that you must call is with `null` when the UI is done or risk a memory leak.
      */
-    override fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?) {
+    override fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
         _transitionState.value = transitionState
     }
 }
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 5d52541..940b48c 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,19 +18,26 @@
 
 import android.app.smartspace.SmartspaceTarget
 import android.content.ComponentName
+import android.content.Intent
+import android.content.IntentFilter
 import android.os.UserHandle
+import android.os.UserManager
+import android.provider.Settings
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.communal.data.repository.CommunalMediaRepository
 import com.android.systemui.communal.data.repository.CommunalPrefsRepository
 import com.android.systemui.communal.data.repository.CommunalRepository
 import com.android.systemui.communal.data.repository.CommunalWidgetRepository
 import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.domain.model.CommunalContentModel.WidgetContent
 import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.shared.model.CommunalContentSize.FULL
 import com.android.systemui.communal.shared.model.CommunalContentSize.HALF
 import com.android.systemui.communal.shared.model.CommunalContentSize.THIRD
-import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
-import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
 import com.android.systemui.communal.widgets.WidgetConfigurator
@@ -43,14 +50,16 @@
 import com.android.systemui.log.dagger.CommunalTableLog
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.smartspace.data.repository.SmartspaceRepository
 import com.android.systemui.util.kotlin.BooleanFlowOperators.and
 import com.android.systemui.util.kotlin.BooleanFlowOperators.not
 import com.android.systemui.util.kotlin.BooleanFlowOperators.or
+import com.android.systemui.util.kotlin.emitOnStart
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -76,6 +85,7 @@
 @Inject
 constructor(
     @Application applicationScope: CoroutineScope,
+    broadcastDispatcher: BroadcastDispatcher,
     private val communalRepository: CommunalRepository,
     private val widgetRepository: CommunalWidgetRepository,
     private val communalPrefsRepository: CommunalPrefsRepository,
@@ -86,6 +96,8 @@
     private val appWidgetHost: CommunalAppWidgetHost,
     private val editWidgetsActivityStarter: EditWidgetsActivityStarter,
     private val userTracker: UserTracker,
+    private val activityStarter: ActivityStarter,
+    private val userManager: UserManager,
     sceneInteractor: SceneInteractor,
     sceneContainerFlags: SceneContainerFlags,
     @CommunalLog logBuffer: LogBuffer,
@@ -130,34 +142,33 @@
      * Target scene as requested by the underlying [SceneTransitionLayout] or through
      * [onSceneChanged].
      *
-     * If [isCommunalAvailable] is false, will return [CommunalSceneKey.Blank]
+     * If [isCommunalAvailable] is false, will return [CommunalScenes.Blank]
      */
-    val desiredScene: Flow<CommunalSceneKey> =
+    val desiredScene: Flow<SceneKey> =
         communalRepository.desiredScene.combine(isCommunalAvailable) { scene, available ->
-            if (available) scene else CommunalSceneKey.Blank
+            if (available) scene else CommunalScenes.Blank
         }
 
     /** Transition state of the hub mode. */
-    val transitionState: StateFlow<ObservableCommunalTransitionState> =
-        communalRepository.transitionState
+    val transitionState: StateFlow<ObservableTransitionState> = communalRepository.transitionState
 
     /**
      * Updates the transition state of the hub [SceneTransitionLayout].
      *
      * Note that you must call is with `null` when the UI is done or risk a memory leak.
      */
-    fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?) {
+    fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
         communalRepository.setTransitionState(transitionState)
     }
 
     /** Returns a flow that tracks the progress of transitions to the given scene from 0-1. */
-    fun transitionProgressToScene(targetScene: CommunalSceneKey) =
+    fun transitionProgressToScene(targetScene: SceneKey) =
         transitionState
             .flatMapLatest { state ->
                 when (state) {
-                    is ObservableCommunalTransitionState.Idle ->
+                    is ObservableTransitionState.Idle ->
                         flowOf(CommunalTransitionProgress.Idle(state.scene))
-                    is ObservableCommunalTransitionState.Transition ->
+                    is ObservableTransitionState.Transition ->
                         if (state.toScene == targetScene) {
                             state.progress.map {
                                 CommunalTransitionProgress.Transition(
@@ -175,7 +186,7 @@
 
     /**
      * Flow that emits a boolean if the communal UI is the target scene, ie. the [desiredScene] is
-     * the [CommunalSceneKey.Communal].
+     * the [CommunalScenes.Communal].
      *
      * This will be true as soon as the desired scene is set programmatically or at whatever point
      * during a fling that SceneTransitionLayout determines that the end state will be the communal
@@ -190,9 +201,9 @@
         flow { emit(sceneContainerFlags.isEnabled()) }
             .flatMapLatest { sceneContainerEnabled ->
                 if (sceneContainerEnabled) {
-                    sceneInteractor.currentScene.map { it == SceneKey.Communal }
+                    sceneInteractor.currentScene.map { it == Scenes.Communal }
                 } else {
-                    desiredScene.map { it == CommunalSceneKey.Communal }
+                    desiredScene.map { it == CommunalScenes.Communal }
                 }
             }
             .distinctUntilChanged()
@@ -219,7 +230,7 @@
      */
     val isIdleOnCommunal: Flow<Boolean> =
         communalRepository.transitionState.map {
-            it is ObservableCommunalTransitionState.Idle && it.scene == CommunalSceneKey.Communal
+            it is ObservableTransitionState.Idle && it.scene == CommunalScenes.Communal
         }
 
     /**
@@ -229,11 +240,11 @@
      */
     val isCommunalVisible: Flow<Boolean> =
         communalRepository.transitionState.map {
-            !(it is ObservableCommunalTransitionState.Idle && it.scene == CommunalSceneKey.Blank)
+            !(it is ObservableTransitionState.Idle && it.scene == CommunalScenes.Blank)
         }
 
     /** Callback received whenever the [SceneTransitionLayout] finishes a scene transition. */
-    fun onSceneChanged(newScene: CommunalSceneKey) {
+    fun onSceneChanged(newScene: SceneKey) {
         communalRepository.setDesiredScene(newScene)
     }
 
@@ -246,6 +257,18 @@
         editWidgetsActivityStarter.startActivity(preselectedKey)
     }
 
+    /**
+     * Navigates to communal widget setting after user has unlocked the device. Currently, this
+     * setting resides within the Hub Mode settings screen.
+     */
+    fun navigateToCommunalWidgetSettings() {
+        activityStarter.postStartActivityDismissingKeyguard(
+            Intent(Settings.ACTION_COMMUNAL_SETTING)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP),
+            /* delay= */ 0,
+        )
+    }
+
     /** Dismiss the CTA tile from the hub in view mode. */
     suspend fun dismissCtaTile() = communalPrefsRepository.setCtaDismissedForCurrentUser()
 
@@ -271,6 +294,33 @@
     fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) =
         widgetRepository.updateWidgetOrder(widgetIdToPriorityMap)
 
+    /** Request to unpause work profile that is currently in quiet mode. */
+    fun unpauseWorkProfile() {
+        userTracker.userProfiles
+            .find { it.isManagedProfile }
+            ?.userHandle
+            ?.let { userHandle ->
+                userManager.requestQuietModeEnabled(/* enableQuietMode */ false, userHandle)
+            }
+    }
+
+    /** Returns true if work profile is in quiet mode (disabled) for user handle. */
+    private fun isQuietModeEnabled(userHandle: UserHandle): Boolean =
+        userManager.isManagedProfile(userHandle.identifier) &&
+            userManager.isQuietModeEnabled(userHandle)
+
+    /** Emits whenever a work profile pause or unpause broadcast is received. */
+    private val updateOnWorkProfileBroadcastReceived: Flow<Unit> =
+        broadcastDispatcher
+            .broadcastFlow(
+                filter =
+                    IntentFilter().apply {
+                        addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
+                        addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
+                    },
+            )
+            .emitOnStart()
+
     /** All widgets present in db. */
     val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
         isCommunalAvailable.flatMapLatest { available ->
@@ -278,14 +328,27 @@
         }
 
     /** A list of widget content to be displayed in the communal hub. */
-    val widgetContent: Flow<List<CommunalContentModel.Widget>> =
-        widgetRepository.communalWidgets.map { widgets ->
-            filterWidgetsByExistingUsers(widgets).map Widget@{ widget ->
-                return@Widget CommunalContentModel.Widget(
-                    appWidgetId = widget.appWidgetId,
-                    providerInfo = widget.providerInfo,
-                    appWidgetHost = appWidgetHost,
-                )
+    val widgetContent: Flow<List<WidgetContent>> =
+        combine(
+            widgetRepository.communalWidgets.map { filterWidgetsByExistingUsers(it) },
+            communalSettingsInteractor.communalWidgetCategories,
+            updateOnWorkProfileBroadcastReceived,
+        ) { widgets, allowedCategories, _ ->
+            widgets.map { widget ->
+                if (widget.providerInfo.widgetCategory and allowedCategories != 0) {
+                    // At least one category this widget specified is allowed, so show it
+                    WidgetContent.Widget(
+                        appWidgetId = widget.appWidgetId,
+                        providerInfo = widget.providerInfo,
+                        appWidgetHost = appWidgetHost,
+                        inQuietMode = isQuietModeEnabled(widget.providerInfo.profile)
+                    )
+                } else {
+                    WidgetContent.DisabledWidget(
+                        appWidgetId = widget.appWidgetId,
+                        providerInfo = widget.providerInfo,
+                    )
+                }
             }
         }
 
@@ -410,7 +473,7 @@
 /** Simplified transition progress data class for tracking a single transition between scenes. */
 sealed class CommunalTransitionProgress {
     /** No transition/animation is currently running. */
-    data class Idle(val scene: CommunalSceneKey) : CommunalTransitionProgress()
+    data class Idle(val scene: SceneKey) : CommunalTransitionProgress()
 
     /** There is a transition animating to the expected scene. */
     data class Transition(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index ae019a1..5fabd3c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -18,6 +18,7 @@
 
 import android.appwidget.AppWidgetProviderInfo
 import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
+import android.content.pm.ApplicationInfo
 import android.widget.RemoteViews
 import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.widgets.CommunalAppWidgetHost
@@ -42,20 +43,38 @@
         val createdTimestampMillis: Long
     }
 
-    data class Widget(
-        val appWidgetId: Int,
-        val providerInfo: AppWidgetProviderInfo,
-        val appWidgetHost: CommunalAppWidgetHost,
-    ) : CommunalContentModel {
-        override val key = KEY.widget(appWidgetId)
-        // Widget size is always half.
-        override val size = CommunalContentSize.HALF
+    sealed interface WidgetContent : CommunalContentModel {
+        val appWidgetId: Int
+        val providerInfo: AppWidgetProviderInfo
 
-        /** Whether this widget can be reconfigured after it has already been added. */
-        val reconfigurable: Boolean
-            get() =
-                (providerInfo.widgetFeatures and WIDGET_FEATURE_RECONFIGURABLE != 0) &&
-                    providerInfo.configure != null
+        data class Widget(
+            override val appWidgetId: Int,
+            override val providerInfo: AppWidgetProviderInfo,
+            val appWidgetHost: CommunalAppWidgetHost,
+            val inQuietMode: Boolean,
+        ) : WidgetContent {
+            override val key = KEY.widget(appWidgetId)
+            // Widget size is always half.
+            override val size = CommunalContentSize.HALF
+
+            /** Whether this widget can be reconfigured after it has already been added. */
+            val reconfigurable: Boolean
+                get() =
+                    (providerInfo.widgetFeatures and WIDGET_FEATURE_RECONFIGURABLE != 0) &&
+                        providerInfo.configure != null
+        }
+
+        data class DisabledWidget(
+            override val appWidgetId: Int,
+            override val providerInfo: AppWidgetProviderInfo
+        ) : WidgetContent {
+            override val key = KEY.disabledWidget(appWidgetId)
+            // Widget size is always half.
+            override val size = CommunalContentSize.HALF
+
+            val appInfo: ApplicationInfo?
+                get() = providerInfo.providerInfo?.applicationInfo
+        }
     }
 
     /** A placeholder item representing a new widget being added */
@@ -111,6 +130,10 @@
                 return "widget_$id"
             }
 
+            fun disabledWidget(id: Int): String {
+                return "disabled_widget_$id"
+            }
+
             fun widgetPlaceholder(): String {
                 return "widget_placeholder_${UUID.randomUUID()}"
             }
@@ -129,5 +152,7 @@
         }
     }
 
-    fun isWidget() = this is Widget
+    fun isWidgetContent() = this is WidgetContent
+
+    fun isSmartspace() = this is Smartspace
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt
index 889023e..f2b4738 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt
@@ -16,12 +16,12 @@
 
 package com.android.systemui.communal.log
 
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.CoreStartable
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.shared.log.CommunalUiEvent
-import com.android.systemui.communal.shared.model.CommunalSceneKey
-import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.util.kotlin.pairwise
@@ -87,25 +87,25 @@
 }
 
 /** Whether currently in communal scene. */
-private fun ObservableCommunalTransitionState.isOnCommunal(): Boolean {
-    return this is ObservableCommunalTransitionState.Idle && scene == CommunalSceneKey.Communal
+private fun ObservableTransitionState.isOnCommunal(): Boolean {
+    return this is ObservableTransitionState.Idle && scene == CommunalScenes.Communal
 }
 
 /** Whether currently in a scene other than communal. */
-private fun ObservableCommunalTransitionState.isNotOnCommunal(): Boolean {
-    return this is ObservableCommunalTransitionState.Idle && scene != CommunalSceneKey.Communal
+private fun ObservableTransitionState.isNotOnCommunal(): Boolean {
+    return this is ObservableTransitionState.Idle && scene != CommunalScenes.Communal
 }
 
 /** Whether currently transitioning from another scene to communal. */
-private fun ObservableCommunalTransitionState.isSwipingToCommunal(): Boolean {
-    return this is ObservableCommunalTransitionState.Transition &&
-        toScene == CommunalSceneKey.Communal &&
+private fun ObservableTransitionState.isSwipingToCommunal(): Boolean {
+    return this is ObservableTransitionState.Transition &&
+        toScene == CommunalScenes.Communal &&
         isInitiatedByUserInput
 }
 
 /** Whether currently transitioning from communal to another scene. */
-private fun ObservableCommunalTransitionState.isSwipingFromCommunal(): Boolean {
-    return this is ObservableCommunalTransitionState.Transition &&
-        fromScene == CommunalSceneKey.Communal &&
+private fun ObservableTransitionState.isSwipingFromCommunal(): Boolean {
+    return this is ObservableTransitionState.Transition &&
+        fromScene == CommunalScenes.Communal &&
         isInitiatedByUserInput
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalSceneKey.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalScenes.kt
similarity index 74%
rename from packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalSceneKey.kt
rename to packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalScenes.kt
index c68dd4f..d5a56c1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalSceneKey.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalScenes.kt
@@ -16,21 +16,15 @@
 
 package com.android.systemui.communal.shared.model
 
+import com.android.compose.animation.scene.SceneKey
+
 /** Definition of the possible scenes for the communal UI. */
-sealed class CommunalSceneKey(
-    private val loggingName: String,
-) {
-    /** The communal scene containing the hub UI. */
-    object Communal : CommunalSceneKey("communal")
-
+object CommunalScenes {
     /** The default scene, shows nothing and is only there to allow swiping to communal. */
-    object Blank : CommunalSceneKey("blank")
+    @JvmField val Blank = SceneKey("blank")
 
-    override fun toString(): String {
-        return loggingName
-    }
+    /** The communal scene containing the hub UI. */
+    @JvmField val Communal = SceneKey("communal")
 
-    companion object {
-        val DEFAULT = Blank
-    }
+    @JvmField val Default = Blank
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/ObservableCommunalTransitionState.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/ObservableCommunalTransitionState.kt
deleted file mode 100644
index d834715..0000000
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/ObservableCommunalTransitionState.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.communal.shared.model
-
-import kotlinx.coroutines.flow.Flow
-
-/**
- * This is a fork of the `com.android.compose.animation.scene.ObservableTransitionState` class.
- *
- * TODO(b/315490861): remove this fork, once we can compile Compose into System UI.
- */
-sealed class ObservableCommunalTransitionState {
-    /** No transition/animation is currently running. */
-    data class Idle(val scene: CommunalSceneKey) : ObservableCommunalTransitionState()
-
-    /** There is a transition animating between two scenes. */
-    data class Transition(
-        val fromScene: CommunalSceneKey,
-        val toScene: CommunalSceneKey,
-        val progress: Flow<Float>,
-
-        /**
-         * Whether the transition was originally triggered by user input rather than being
-         * programmatic. If this value is initially true, it will remain true until the transition
-         * fully completes, even if the user input that triggered the transition has ended. Any
-         * sub-transitions launched by this one will inherit this value. For example, if the user
-         * drags a pointer but does not exceed the threshold required to transition to another
-         * 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>,
-    ) : ObservableCommunalTransitionState()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 3ec9a26..85f3c20 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -18,10 +18,10 @@
 
 import android.content.ComponentName
 import android.os.UserHandle
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
-import com.android.systemui.communal.shared.model.CommunalSceneKey
-import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.media.controls.ui.view.MediaHost
 import kotlinx.coroutines.flow.Flow
@@ -34,7 +34,7 @@
     private val communalInteractor: CommunalInteractor,
     val mediaHost: MediaHost,
 ) {
-    val currentScene: Flow<CommunalSceneKey> = communalInteractor.desiredScene
+    val currentScene: Flow<SceneKey> = communalInteractor.desiredScene
 
     /** Whether widgets are currently being re-ordered. */
     open val reorderingWidgets: StateFlow<Boolean> = MutableStateFlow(false)
@@ -45,7 +45,7 @@
     val selectedKey: StateFlow<String?>
         get() = _selectedKey
 
-    fun onSceneChanged(scene: CommunalSceneKey) {
+    fun onSceneChanged(scene: SceneKey) {
         communalInteractor.onSceneChanged(scene)
     }
 
@@ -54,7 +54,7 @@
      *
      * Note that you must call is with `null` when the UI is done or risk a memory leak.
      */
-    fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?) {
+    fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
         communalInteractor.setTransitionState(transitionState)
     }
 
@@ -70,6 +70,10 @@
         communalInteractor.addWidget(componentName, user, priority, configurator)
     }
 
+    open fun onOpenEnableWidgetDialog() {}
+
+    open fun onOpenEnableWorkProfileDialog() {}
+
     /** A list of all the communal content to be displayed in the communal hub. */
     abstract val communalContent: Flow<List<CommunalContentModel>>
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index fc9a7df..6e69ed7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
@@ -46,6 +47,7 @@
 import kotlinx.coroutines.launch
 
 /** The default view model used for showing the communal hub. */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class CommunalViewModel
 @Inject
@@ -54,6 +56,7 @@
     private val communalInteractor: CommunalInteractor,
     tutorialInteractor: CommunalTutorialInteractor,
     shadeInteractor: ShadeInteractor,
+    deviceEntryInteractor: DeviceEntryInteractor,
     @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
     @CommunalLog logBuffer: LogBuffer,
 ) : BaseCommunalViewModel(communalInteractor, mediaHost) {
@@ -84,9 +87,19 @@
     override val isPopupOnDismissCtaShowing: Flow<Boolean> =
         _isPopupOnDismissCtaShowing.asStateFlow()
 
+    private val _isEnableWidgetDialogShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    val isEnableWidgetDialogShowing: Flow<Boolean> = _isEnableWidgetDialogShowing.asStateFlow()
+
+    private val _isEnableWorkProfileDialogShowing: MutableStateFlow<Boolean> =
+        MutableStateFlow(false)
+    val isEnableWorkProfileDialogShowing: Flow<Boolean> =
+        _isEnableWorkProfileDialogShowing.asStateFlow()
+
     /** Whether touches should be disabled in communal */
     val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded)
 
+    val deviceUnlocked: Flow<Boolean> = deviceEntryInteractor.isUnlocked
+
     init {
         // Initialize our media host for the UMO. This only needs to happen once and must be done
         // before the MediaHierarchyManager attempts to move the UMO to the hub.
@@ -115,6 +128,40 @@
         setPopupOnDismissCtaVisibility(false)
     }
 
+    override fun onOpenEnableWidgetDialog() {
+        setIsEnableWidgetDialogShowing(true)
+    }
+
+    fun onEnableWidgetDialogConfirm() {
+        communalInteractor.navigateToCommunalWidgetSettings()
+        setIsEnableWidgetDialogShowing(false)
+    }
+
+    fun onEnableWidgetDialogCancel() {
+        setIsEnableWidgetDialogShowing(false)
+    }
+
+    override fun onOpenEnableWorkProfileDialog() {
+        setIsEnableWorkProfileDialogShowing(true)
+    }
+
+    fun onEnableWorkProfileDialogConfirm() {
+        communalInteractor.unpauseWorkProfile()
+        setIsEnableWorkProfileDialogShowing(false)
+    }
+
+    fun onEnableWorkProfileDialogCancel() {
+        setIsEnableWorkProfileDialogShowing(false)
+    }
+
+    private fun setIsEnableWidgetDialogShowing(isVisible: Boolean) {
+        _isEnableWidgetDialogShowing.value = isVisible
+    }
+
+    private fun setIsEnableWorkProfileDialogShowing(isVisible: Boolean) {
+        _isEnableWorkProfileDialogShowing.value = isVisible
+    }
+
     private fun setPopupOnDismissCtaVisibility(isVisible: Boolean) {
         _isPopupOnDismissCtaShowing.value = isVisible
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt
index 080dbed..93e2b37 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt
@@ -21,6 +21,7 @@
 import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL
 import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
 import android.content.ComponentName
+import android.os.Bundle
 import android.os.UserHandle
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
@@ -56,6 +57,7 @@
             return widgetInfo.configure != null && !configurationOptional
         }
     }
+
     private val logger = Logger(logBuffer, TAG)
 
     /**
@@ -84,9 +86,16 @@
 
     private fun bindWidget(widgetId: Int, user: UserHandle, provider: ComponentName): Boolean {
         if (appWidgetManager.isPresent) {
+            val options =
+                Bundle().apply {
+                    putInt(
+                        AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
+                        AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
+                    )
+                }
             return appWidgetManager
                 .get()
-                .bindAppWidgetIdIfAllowed(widgetId, user, provider, /* options */ null)
+                .bindAppWidgetIdIfAllowed(widgetId, user, provider, options)
         }
         return false
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index a5a390d..b6ad26b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -25,14 +25,21 @@
 import android.view.IWindowManager
 import android.view.WindowInsets
 import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
 import androidx.activity.result.ActivityResultLauncher
 import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.ui.Modifier
+import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.compose.theme.PlatformTheme
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.communal.shared.log.CommunalUiEvent
-import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.ui.compose.CommunalHub
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
 import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent
-import com.android.systemui.compose.ComposeFacade.setCommunalEditWidgetActivityContent
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
@@ -110,56 +117,68 @@
         val preselectedKey = intent.getStringExtra(EXTRA_PRESELECTED_KEY)
         communalViewModel.setSelectedKey(preselectedKey)
 
-        setCommunalEditWidgetActivityContent(
-            activity = this,
-            viewModel = communalViewModel,
-            widgetConfigurator = widgetConfigurator,
-            onOpenWidgetPicker = {
-                val intent =
-                    Intent(Intent.ACTION_MAIN).also { it.addCategory(Intent.CATEGORY_HOME) }
-                packageManager
-                    .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
-                    ?.activityInfo
-                    ?.packageName
-                    ?.let { packageName ->
-                        try {
-                            addWidgetActivityLauncher.launch(
-                                Intent(Intent.ACTION_PICK).apply {
-                                    setPackage(packageName)
-                                    putExtra(
-                                        EXTRA_DESIRED_WIDGET_WIDTH,
-                                        resources.getDimensionPixelSize(
-                                            R.dimen.communal_widget_picker_desired_width
-                                        )
-                                    )
-                                    putExtra(
-                                        EXTRA_DESIRED_WIDGET_HEIGHT,
-                                        resources.getDimensionPixelSize(
-                                            R.dimen.communal_widget_picker_desired_height
-                                        )
-                                    )
-                                    putExtra(
-                                        AppWidgetManager.EXTRA_CATEGORY_FILTER,
-                                        communalViewModel.getCommunalWidgetCategories
-                                    )
-                                }
-                            )
-                        } catch (e: Exception) {
-                            Log.e(TAG, "Failed to launch widget picker activity", e)
-                        }
-                    }
-                    ?: run { Log.e(TAG, "Couldn't resolve launcher package name") }
-            },
-            onEditDone = {
-                try {
-                    communalViewModel.onSceneChanged(CommunalSceneKey.Communal)
-                    checkNotNull(windowManagerService).lockNow(/* options */ null)
-                    finish()
-                } catch (e: RemoteException) {
-                    Log.e(TAG, "Couldn't lock the device as WindowManager is dead.")
+        setContent {
+            PlatformTheme {
+                Box(
+                    modifier =
+                        Modifier.fillMaxSize()
+                            .background(LocalAndroidColorScheme.current.outlineVariant),
+                ) {
+                    CommunalHub(
+                        viewModel = communalViewModel,
+                        onOpenWidgetPicker = ::onOpenWidgetPicker,
+                        widgetConfigurator = widgetConfigurator,
+                        onEditDone = ::onEditDone,
+                    )
                 }
             }
-        )
+        }
+    }
+
+    private fun onOpenWidgetPicker() {
+        val intent = Intent(Intent.ACTION_MAIN).also { it.addCategory(Intent.CATEGORY_HOME) }
+        packageManager
+            .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
+            ?.activityInfo
+            ?.packageName
+            ?.let { packageName ->
+                try {
+                    addWidgetActivityLauncher.launch(
+                        Intent(Intent.ACTION_PICK).apply {
+                            setPackage(packageName)
+                            putExtra(
+                                EXTRA_DESIRED_WIDGET_WIDTH,
+                                resources.getDimensionPixelSize(
+                                    R.dimen.communal_widget_picker_desired_width
+                                )
+                            )
+                            putExtra(
+                                EXTRA_DESIRED_WIDGET_HEIGHT,
+                                resources.getDimensionPixelSize(
+                                    R.dimen.communal_widget_picker_desired_height
+                                )
+                            )
+                            putExtra(
+                                AppWidgetManager.EXTRA_CATEGORY_FILTER,
+                                communalViewModel.getCommunalWidgetCategories
+                            )
+                        }
+                    )
+                } catch (e: Exception) {
+                    Log.e(TAG, "Failed to launch widget picker activity", e)
+                }
+            }
+            ?: run { Log.e(TAG, "Couldn't resolve launcher package name") }
+    }
+
+    private fun onEditDone() {
+        try {
+            communalViewModel.onSceneChanged(CommunalScenes.Communal)
+            checkNotNull(windowManagerService).lockNow(/* options */ null)
+            finish()
+        } catch (e: RemoteException) {
+            Log.e(TAG, "Couldn't lock the device as WindowManager is dead.")
+        }
     }
 
     override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
deleted file mode 100644
index a0aaa90..0000000
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ /dev/null
@@ -1,130 +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.compose
-
-import android.content.Context
-import android.view.View
-import android.view.WindowInsets
-import androidx.activity.ComponentActivity
-import androidx.lifecycle.LifecycleOwner
-import com.android.systemui.bouncer.ui.BouncerDialogFactory
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
-import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
-import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
-import com.android.systemui.communal.widgets.WidgetConfigurator
-import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
-import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
-import com.android.systemui.people.ui.viewmodel.PeopleViewModel
-import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
-import com.android.systemui.scene.shared.model.Scene
-import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
-import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.StateFlow
-
-/**
- * A facade to interact with Compose, when it is available.
- *
- * You should access this facade by calling the static methods on
- * [com.android.systemui.compose.ComposeFacade] directly.
- */
-interface BaseComposeFacade {
-    /**
-     * Whether Compose is currently available. This function should be checked before calling any
-     * other functions on this facade.
-     *
-     * This value will never change at runtime.
-     */
-    fun isComposeAvailable(): Boolean
-
-    /**
-     * Return the [ComposeInitializer] to make Compose usable in windows outside normal activities.
-     */
-    fun composeInitializer(): ComposeInitializer
-
-    /** Bind the content of [activity] to [viewModel]. */
-    fun setPeopleSpaceActivityContent(
-        activity: ComponentActivity,
-        viewModel: PeopleViewModel,
-        onResult: (PeopleViewModel.Result) -> Unit,
-    )
-
-    /** Bind the content of [activity] to [viewModel]. */
-    fun setCommunalEditWidgetActivityContent(
-        activity: ComponentActivity,
-        viewModel: BaseCommunalViewModel,
-        widgetConfigurator: WidgetConfigurator,
-        onOpenWidgetPicker: () -> Unit,
-        onEditDone: () -> Unit,
-    )
-
-    fun setVolumePanelActivityContent(
-        activity: ComponentActivity,
-        viewModel: VolumePanelViewModel,
-        onDismiss: () -> Unit,
-    )
-
-    /** Create a [View] to represent [viewModel] on screen. */
-    fun createFooterActionsView(
-        context: Context,
-        viewModel: FooterActionsViewModel,
-        qsVisibilityLifecycleOwner: LifecycleOwner,
-    ): View
-
-    /** Create a [View] to represent [viewModel] on screen. */
-    fun createSceneContainerView(
-        scope: CoroutineScope,
-        context: Context,
-        viewModel: SceneContainerViewModel,
-        windowInsets: StateFlow<WindowInsets?>,
-        sceneByKey: Map<SceneKey, Scene>,
-        dataSourceDelegator: SceneDataSourceDelegator,
-    ): View
-
-    /** Creates sticky key indicator content presenting provided [viewModel] */
-    fun createStickyKeysIndicatorContent(
-        context: Context,
-        viewModel: StickyKeysIndicatorViewModel
-    ): View
-
-    /** Create a [View] to represent [viewModel] on screen. */
-    fun createCommunalView(
-        context: Context,
-        viewModel: BaseCommunalViewModel,
-    ): View
-
-    /** Create a [View] to represent the [BouncerViewModel]. */
-    fun createBouncer(
-        context: Context,
-        viewModel: BouncerViewModel,
-        dialogFactory: BouncerDialogFactory,
-    ): View
-
-    /** Creates a container that hosts the communal UI and handles gesture transitions. */
-    fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View
-
-    /** Creates a [View] that represents the Lockscreen. */
-    fun createLockscreen(
-        context: Context,
-        viewModel: LockscreenContentViewModel,
-        blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>,
-    ): View
-}
diff --git a/packages/SystemUI/src/com/android/systemui/compose/ComposeInitializer.kt b/packages/SystemUI/src/com/android/systemui/compose/ComposeInitializer.kt
index 90dc3a0..813e0e0 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/ComposeInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/ComposeInitializer.kt
@@ -17,6 +17,13 @@
 package com.android.systemui.compose
 
 import android.view.View
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.findViewTreeLifecycleOwner
+import androidx.lifecycle.setViewTreeLifecycleOwner
+import androidx.savedstate.SavedStateRegistryController
+import androidx.savedstate.SavedStateRegistryOwner
+import com.android.compose.animation.ViewTreeSavedStateRegistryOwner
+import com.android.systemui.lifecycle.ViewLifecycleOwner
 
 /**
  * An initializer to use Compose outside of an Activity, e.g. inside a window added directly using
@@ -39,10 +46,55 @@
  *    }
  * ```
  */
-interface ComposeInitializer {
+object ComposeInitializer {
     /** Function to be called on your window root view's [View.onAttachedToWindow] function. */
-    fun onAttachedToWindow(root: View)
+    fun onAttachedToWindow(root: View) {
+        if (root.findViewTreeLifecycleOwner() != null) {
+            error("root $root already has a LifecycleOwner")
+        }
+
+        val parent = root.parent
+        if (parent is View && parent.id != android.R.id.content) {
+            error(
+                "ComposeInitializer.onAttachedToWindow(View) must be called on the content child." +
+                    "Outside of activities and dialogs, this is usually the top-most View of a " +
+                    "window."
+            )
+        }
+
+        // The lifecycle owner, which is STARTED when [root] is visible and RESUMED when [root] is
+        // both visible and focused.
+        val lifecycleOwner = ViewLifecycleOwner(root)
+
+        // We create a trivial implementation of [SavedStateRegistryOwner] that does not do any save
+        // or restore because SystemUI process is always running and top-level windows using this
+        // initializer are created once, when the process is started.
+        val savedStateRegistryOwner =
+            object : SavedStateRegistryOwner {
+                private val savedStateRegistryController =
+                    SavedStateRegistryController.create(this).apply { performRestore(null) }
+
+                override val savedStateRegistry = savedStateRegistryController.savedStateRegistry
+
+                override val lifecycle: Lifecycle
+                    get() = lifecycleOwner.lifecycle
+            }
+
+        // We must call [ViewLifecycleOwner.onCreate] after creating the [SavedStateRegistryOwner]
+        // because `onCreate` might move the lifecycle state to STARTED which will make
+        // [SavedStateRegistryController.performRestore] throw.
+        lifecycleOwner.onCreate()
+
+        // Set the owners on the root. They will be reused by any ComposeView inside the root
+        // hierarchy.
+        root.setViewTreeLifecycleOwner(lifecycleOwner)
+        ViewTreeSavedStateRegistryOwner.set(root, savedStateRegistryOwner)
+    }
 
     /** Function to be called on your window root view's [View.onDetachedFromWindow] function. */
-    fun onDetachedFromWindow(root: View)
+    fun onDetachedFromWindow(root: View) {
+        (root.findViewTreeLifecycleOwner() as ViewLifecycleOwner).onDestroy()
+        root.setViewTreeLifecycleOwner(null)
+        ViewTreeSavedStateRegistryOwner.set(root, null)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
index e8a8444..7f8103e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
@@ -18,7 +18,7 @@
 
 import android.app.Activity
 import android.app.ActivityOptions
-import android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+import android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
 import android.app.Dialog
 import android.app.PendingIntent
 import android.content.ComponentName
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 6d9994f..9fa3e5f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -71,6 +71,7 @@
 import android.media.projection.IMediaProjectionManager;
 import android.media.projection.MediaProjectionManager;
 import android.media.session.MediaSessionManager;
+import android.nearby.NearbyManager;
 import android.net.ConnectivityManager;
 import android.net.NetworkScoreManager;
 import android.net.wifi.WifiManager;
@@ -441,6 +442,12 @@
 
     @Provides
     @Singleton
+    static NearbyManager provideNearbyManager(Context context) {
+        return context.getSystemService(NearbyManager.class);
+    }
+
+    @Provides
+    @Singleton
     static NetworkScoreManager provideNetworkScoreManager(Context context) {
         return context.getSystemService(NetworkScoreManager.class);
     }
@@ -636,6 +643,7 @@
 
     @Provides
     @Singleton
+    @Nullable
     static CarrierConfigManager provideCarrierConfigManager(Context context) {
         return context.getSystemService(CarrierConfigManager.class);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
index a90980f..a431a59 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.dagger;
 
-import com.android.systemui.globalactions.ShutdownUiModule;
 import com.android.systemui.keyguard.CustomizationProvider;
 import com.android.systemui.statusbar.NotificationInsetsModule;
 import com.android.systemui.statusbar.QsFrameTranslateModule;
@@ -32,7 +31,6 @@
         DependencyProvider.class,
         NotificationInsetsModule.class,
         QsFrameTranslateModule.class,
-        ShutdownUiModule.class,
         SystemUIBinder.class,
         SystemUIModule.class,
         SystemUICoreStartableModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index f7bc5cdc..a4011fd 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -43,6 +43,7 @@
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsImplementation;
 import com.android.systemui.rotationlock.RotationLockModule;
+import com.android.systemui.rotationlock.RotationLockNewModule;
 import com.android.systemui.scene.SceneContainerFrameworkModule;
 import com.android.systemui.screenshot.ReferenceScreenshotModule;
 import com.android.systemui.settings.MultiUserUtilsModule;
@@ -110,6 +111,7 @@
         RearDisplayModule.class,
         ReferenceScreenshotModule.class,
         RotationLockModule.class,
+        RotationLockNewModule.class,
         ScreenDecorationsModule.class,
         SystemActionsModule.class,
         ShadeModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index a3d6ad4..21ee5bd 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.back.domain.interactor.BackActionInteractor
 import com.android.systemui.biometrics.BiometricNotificationService
 import com.android.systemui.clipboardoverlay.ClipboardListener
+import com.android.systemui.communal.CommunalDreamStartable
 import com.android.systemui.communal.CommunalSceneStartable
 import com.android.systemui.communal.log.CommunalLoggerStartable
 import com.android.systemui.communal.widgets.CommunalAppWidgetHostStartable
@@ -328,6 +329,11 @@
 
     @Binds
     @IntoMap
+    @ClassKey(CommunalDreamStartable::class)
+    abstract fun bindCommunalDreamStartable(impl: CommunalDreamStartable): CoreStartable
+
+    @Binds
+    @IntoMap
     @ClassKey(CommunalAppWidgetHostStartable::class)
     abstract fun bindCommunalAppWidgetHostStartable(
         impl: CommunalAppWidgetHostStartable
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 1157d97..19af371 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -51,6 +51,7 @@
 import com.android.systemui.controls.dagger.ControlsModule;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.SystemUser;
+import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.demomode.dagger.DemoModeModule;
 import com.android.systemui.deviceentry.DeviceEntryModule;
 import com.android.systemui.display.DisplayModule;
@@ -365,7 +366,8 @@
             SysUiState sysUiState,
             FeatureFlags featureFlags,
             NotifPipelineFlags notifPipelineFlags,
-            @Main Executor sysuiMainExecutor) {
+            @Main Executor sysuiMainExecutor,
+            @UiBackground Executor sysuiUiBgExecutor) {
         return Optional.ofNullable(BubblesManager.create(context,
                 bubblesOptional,
                 notificationShadeWindowController,
@@ -384,7 +386,8 @@
                 sysUiState,
                 featureFlags,
                 notifPipelineFlags,
-                sysuiMainExecutor));
+                sysuiMainExecutor,
+                sysuiUiBgExecutor));
     }
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index cf7d601..baae986 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -62,6 +62,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
@@ -83,10 +87,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
@@ -431,11 +431,11 @@
             override fun onAuthenticationFailed() {
                 _isAuthenticated.value = false
                 faceAuthLogger.authenticationFailed()
+                _authenticationStatus.value = FailedFaceAuthenticationStatus()
                 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()
+                    // Skip onFaceAuthRequestCompleted in such a scenario.
                     onFaceAuthRequestCompleted()
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractor.kt
index 6bfe8d9..846013c 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractor.kt
@@ -21,9 +21,12 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceFailureMessage
+import com.android.systemui.deviceentry.shared.model.FaceLockoutMessage
 import com.android.systemui.deviceentry.shared.model.FaceMessage
 import com.android.systemui.deviceentry.shared.model.FaceTimeoutMessage
 import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FingerprintFailureMessage
 import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage
 import com.android.systemui.deviceentry.shared.model.FingerprintMessage
 import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
@@ -97,11 +100,7 @@
         fingerprintAuthInteractor.fingerprintHelp
             .sample(biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed, ::Pair)
             .filter { (_, fingerprintAuthAllowed) -> fingerprintAuthAllowed }
-            .map { (helpStatus, _) ->
-                FingerprintMessage(
-                    helpStatus.msg,
-                )
-            }
+            .map { (helpStatus, _) -> FingerprintMessage(helpStatus.msg) }
 
     private val fingerprintFailMessage: Flow<FingerprintMessage> =
         fingerprintPropertyInteractor.isUdfps.flatMapLatest { isUdfps ->
@@ -109,7 +108,7 @@
                 .sample(biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed)
                 .filter { fingerprintAuthAllowed -> fingerprintAuthAllowed }
                 .map {
-                    FingerprintMessage(
+                    FingerprintFailureMessage(
                         if (isUdfps) {
                             resources.getString(
                                 com.android.internal.R.string.fingerprint_udfps_error_not_match
@@ -118,7 +117,7 @@
                             resources.getString(
                                 com.android.internal.R.string.fingerprint_error_not_match
                             )
-                        },
+                        }
                     )
                 }
         }
@@ -154,7 +153,7 @@
         faceFailure
             .sample(biometricSettingsInteractor.faceAuthCurrentlyAllowed)
             .filter { faceAuthCurrentlyAllowed -> faceAuthCurrentlyAllowed }
-            .map { FaceMessage(resources.getString(R.string.keyguard_face_failed)) }
+            .map { FaceFailureMessage(resources.getString(R.string.keyguard_face_failed)) }
 
     private val faceErrorMessage: Flow<FaceMessage> =
         faceError
@@ -173,6 +172,7 @@
                             FaceTimeoutMessage(status.msg)
                         }
                     }
+                    status.isLockoutError() -> FaceLockoutMessage(status.msg)
                     else -> FaceMessage(status.msg)
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 21fd87c..029a4f3 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.keyguard.data.repository.TrustRepository
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -81,9 +81,9 @@
     val isDeviceEntered: StateFlow<Boolean> =
         sceneInteractor.currentScene
             .filter { currentScene ->
-                currentScene == SceneKey.Gone || currentScene == SceneKey.Lockscreen
+                currentScene == Scenes.Gone || currentScene == Scenes.Lockscreen
             }
-            .map { it == SceneKey.Gone }
+            .map { it == Scenes.Gone }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.Eagerly,
@@ -148,12 +148,12 @@
         applicationScope.launch {
             if (isAuthenticationRequired()) {
                 sceneInteractor.changeScene(
-                    toScene = SceneKey.Bouncer,
+                    toScene = Scenes.Bouncer,
                     loggingReason = "request to unlock device while authentication required",
                 )
             } else {
                 sceneInteractor.changeScene(
-                    toScene = SceneKey.Gone,
+                    toScene = Scenes.Gone,
                     loggingReason = "request to unlock device while authentication isn't required",
                 )
             }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt
index 72b9da6..80b52ed 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt
@@ -16,17 +16,17 @@
 
 package com.android.systemui.deviceentry.domain.interactor
 
-import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
 
 /** Encapsulates business logic for device entry under-display fingerprint state changes. */
 @ExperimentalCoroutinesApi
@@ -34,14 +34,13 @@
 class DeviceEntryUdfpsInteractor
 @Inject
 constructor(
+    fingerprintPropertyInteractor: FingerprintPropertyInteractor,
     // TODO (b/309655554): create & use interactors for these repositories
-    fingerprintPropertyRepository: FingerprintPropertyRepository,
     fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
     biometricSettingsRepository: BiometricSettingsRepository,
 ) {
     /** Whether the device supports an under display fingerprint sensor. */
-    val isUdfpsSupported: Flow<Boolean> =
-        fingerprintPropertyRepository.sensorType.map { it.isUdfps() }
+    val isUdfpsSupported: StateFlow<Boolean> = fingerprintPropertyInteractor.isUdfps
 
     /** Whether the under-display fingerprint sensor is enrolled and enabled for device entry. */
     val isUdfpsEnrolledAndEnabled: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
index 79455eb..289dbd9 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
@@ -24,9 +24,12 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.shared.model.BiometricMessage
 import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -60,17 +63,23 @@
     private val context: Context,
     activityStarter: ActivityStarter,
     powerInteractor: PowerInteractor,
+    keyguardTransitionInteractor: KeyguardTransitionInteractor,
 ) {
     private val keyguardOccludedByApp: Flow<Boolean> =
-        combine(
-                keyguardInteractor.isKeyguardOccluded,
-                keyguardInteractor.isKeyguardShowing,
-                primaryBouncerInteractor.isShowing,
-                alternateBouncerInteractor.isVisible,
-            ) { occluded, showing, primaryBouncerShowing, alternateBouncerVisible ->
-                occluded && showing && !primaryBouncerShowing && !alternateBouncerVisible
-            }
-            .distinctUntilChanged()
+        if (KeyguardWmStateRefactor.isEnabled) {
+            keyguardTransitionInteractor.currentKeyguardState.map { it == KeyguardState.OCCLUDED }
+        } else {
+            combine(
+                    keyguardInteractor.isKeyguardOccluded,
+                    keyguardInteractor.isKeyguardShowing,
+                    primaryBouncerInteractor.isShowing,
+                    alternateBouncerInteractor.isVisible,
+                ) { occluded, showing, primaryBouncerShowing, alternateBouncerVisible ->
+                    occluded && showing && !primaryBouncerShowing && !alternateBouncerVisible
+                }
+                .distinctUntilChanged()
+        }
+
     private val fingerprintUnlockSuccessEvents: Flow<Unit> =
         fingerprintAuthRepository.authenticationStatus
             .ifKeyguardOccludedByApp()
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/BiometricMessageModels.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/BiometricMessageModels.kt
index 59c3f7f..2ced8c41 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/BiometricMessageModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/BiometricMessageModels.kt
@@ -32,9 +32,15 @@
     private val faceTimeoutMessage: String?,
 ) : FaceMessage(faceTimeoutMessage)
 
+data class FaceLockoutMessage(private val msg: String?) : FaceMessage(msg)
+
+data class FaceFailureMessage(private val msg: String) : FaceMessage(msg)
+
 /** Fingerprint biometric message */
 open class FingerprintMessage(fingerprintMessage: String?) : BiometricMessage(fingerprintMessage)
 
 data class FingerprintLockoutMessage(
     private val fingerprintLockoutMessage: String?,
 ) : FingerprintMessage(fingerprintLockoutMessage)
+
+data class FingerprintFailureMessage(private val msg: String?) : FingerprintMessage(msg)
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 c0a873a..989b0de 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
@@ -18,6 +18,7 @@
 import android.content.Context
 import android.os.Bundle
 import android.view.View
+import android.view.WindowInsets
 import android.widget.TextView
 import androidx.core.view.updatePadding
 import com.android.systemui.res.R
@@ -44,7 +45,10 @@
     private lateinit var mirrorButton: TextView
     private lateinit var dismissButton: TextView
     private lateinit var dualDisplayWarning: TextView
+    private lateinit var bottomSheet: View
     private var enabledPressed = false
+    private val defaultDialogBottomInset =
+        context.resources.getDimensionPixelSize(R.dimen.dialog_bottom_padding)
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -63,6 +67,8 @@
                 visibility = if (showConcurrentDisplayInfo) View.VISIBLE else View.GONE
             }
 
+        bottomSheet = requireViewById(R.id.cd_bottom_sheet)
+
         setOnDismissListener {
             if (!enabledPressed) {
                 onCancelMirroring.onClick(null)
@@ -71,15 +77,17 @@
         setupInsets()
     }
 
-    private fun setupInsets() {
+    private fun setupInsets(navbarInsets: Int = navbarBottomInsetsProvider()) {
         // This avoids overlap between dialog content and navigation bars.
-        requireViewById<View>(R.id.cd_bottom_sheet).apply {
-            val navbarInsets = navbarBottomInsetsProvider()
-            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, defaultDialogBottomInset))
+        // 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.
+        bottomSheet.updatePadding(bottom = max(navbarInsets, defaultDialogBottomInset))
+    }
+
+    override fun onInsetsChanged(changedTypes: Int, insets: WindowInsets) {
+        val navbarType = WindowInsets.Type.navigationBars()
+        if (changedTypes and navbarType != 0) {
+            setupInsets(insets.getInsets(navbarType).bottom)
         }
     }
 
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 190062c..fbf0538 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
@@ -32,9 +32,12 @@
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
 import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.FlowPreview
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.debounce
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.launch
@@ -57,6 +60,7 @@
     private var dialog: Dialog? = null
 
     /** Starts listening for pending displays. */
+    @OptIn(FlowPreview::class)
     override fun start() {
         val pendingDisplayFlow = connectedDisplayInteractor.pendingDisplay
         val concurrentDisplaysInProgessFlow =
@@ -66,6 +70,13 @@
                 flow { emit(false) }
             }
         pendingDisplayFlow
+            // Let's debounce for 2 reasons:
+            // - prevent fast dialog flashes in case pending displays are available for just a few
+            // millis
+            // - Prevent jumps related to inset changes: when in 3 buttons navigation, device
+            // unlock triggers a change in insets that might result in a jump of the dialog (if a
+            // display was connected while on the lockscreen).
+            .debounce(200.milliseconds)
             .combine(concurrentDisplaysInProgessFlow) { pendingDisplay, concurrentDisplaysInProgress
                 ->
                 if (pendingDisplay == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index b97bace..f860893 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -33,7 +33,7 @@
 import com.android.systemui.complication.ComplicationLayoutParams.POSITION_TOP
 import com.android.systemui.complication.ComplicationLayoutParams.Position
 import com.android.systemui.dreams.dagger.DreamOverlayModule
-import com.android.systemui.dreams.ui.viewmodel.DreamOverlayViewModel
+import com.android.systemui.dreams.ui.viewmodel.DreamViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
@@ -53,7 +53,7 @@
     private val mStatusBarViewController: DreamOverlayStatusBarViewController,
     private val mOverlayStateController: DreamOverlayStateController,
     @Named(DreamOverlayModule.DREAM_BLUR_RADIUS) private val mDreamBlurRadius: Int,
-    private val dreamOverlayViewModel: DreamOverlayViewModel,
+    private val dreamViewModel: DreamViewModel,
     @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
     private val mDreamInBlurAnimDurationMs: Long,
     @Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
@@ -87,7 +87,7 @@
         view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.CREATED) {
                 launch {
-                    dreamOverlayViewModel.dreamOverlayTranslationY.collect { px ->
+                    dreamViewModel.dreamOverlayTranslationY.collect { px ->
                         ComplicationLayoutParams.iteratePositions(
                             { position: Int -> setElementsTranslationYAtPosition(px, position) },
                             POSITION_TOP or POSITION_BOTTOM
@@ -96,7 +96,7 @@
                 }
 
                 launch {
-                    dreamOverlayViewModel.dreamOverlayTranslationX.collect { px ->
+                    dreamViewModel.dreamOverlayTranslationX.collect { px ->
                         ComplicationLayoutParams.iteratePositions(
                             { position: Int -> setElementsTranslationXAtPosition(px, position) },
                             POSITION_TOP or POSITION_BOTTOM
@@ -105,7 +105,7 @@
                 }
 
                 launch {
-                    dreamOverlayViewModel.dreamOverlayAlpha.collect { alpha ->
+                    dreamViewModel.dreamOverlayAlpha.collect { alpha ->
                         ComplicationLayoutParams.iteratePositions(
                             { position: Int ->
                                 setElementsAlphaAtPosition(
@@ -120,7 +120,7 @@
                 }
 
                 launch {
-                    dreamOverlayViewModel.transitionEnded.collect { _ ->
+                    dreamViewModel.transitionEnded.collect { _ ->
                         mOverlayStateController.setExitAnimationsRunning(false)
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
index e74814a..376d312 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.dreams.homecontrols
 
 import android.content.Intent
+import android.os.PowerManager
 import android.service.controls.ControlsProviderService
 import android.service.dreams.DreamService
 import android.window.TaskFragmentInfo
@@ -27,6 +28,8 @@
 import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor.Companion.MAX_UPDATE_CORRELATION_DELAY
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.dagger.DreamLog
+import com.android.systemui.util.wakelock.WakeLock
+import com.android.systemui.util.wakelock.WakeLock.Builder.NO_TIMEOUT
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineDispatcher
@@ -42,14 +45,23 @@
     private val controlsSettingsRepository: ControlsSettingsRepository,
     private val taskFragmentFactory: TaskFragmentComponent.Factory,
     private val homeControlsComponentInteractor: HomeControlsComponentInteractor,
+    private val wakeLockBuilder: WakeLock.Builder,
     private val dreamActivityProvider: DreamActivityProvider,
     @Background private val bgDispatcher: CoroutineDispatcher,
     @DreamLog logBuffer: LogBuffer
 ) : DreamService() {
+
     private val serviceJob = SupervisorJob()
     private val serviceScope = CoroutineScope(bgDispatcher + serviceJob)
-    private val logger = DreamLogger(logBuffer, "HomeControlsDreamService")
+    private val logger = DreamLogger(logBuffer, TAG)
     private lateinit var taskFragmentComponent: TaskFragmentComponent
+    private val wakeLock: WakeLock by lazy {
+        wakeLockBuilder
+            .setMaxTimeout(NO_TIMEOUT)
+            .setTag(TAG)
+            .setLevelsAndFlags(PowerManager.SCREEN_BRIGHT_WAKE_LOCK)
+            .build()
+    }
 
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
@@ -72,6 +84,8 @@
                     hide = { finish() }
                 )
                 .apply { createTaskFragment() }
+
+        wakeLock.acquire(TAG)
     }
 
     private fun onTaskFragmentInfoChanged(taskFragmentInfo: TaskFragmentInfo) {
@@ -100,6 +114,7 @@
 
     override fun onDetachedFromWindow() {
         super.onDetachedFromWindow()
+        wakeLock.release(TAG)
         taskFragmentComponent.destroy()
         serviceScope.launch {
             delay(CANCELLATION_DELAY_AFTER_DETACHED)
@@ -115,5 +130,6 @@
          * complete.
          */
         val CANCELLATION_DELAY_AFTER_DETACHED = 5.seconds
+        const val TAG = "HomeControlsDreamService"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt
deleted file mode 100644
index bd99f4b..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dreams.ui.viewmodel
-
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
-import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel
-import com.android.systemui.res.R
-import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.merge
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SysUISingleton
-class DreamOverlayViewModel
-@Inject
-constructor(
-    configurationInteractor: ConfigurationInteractor,
-    toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
-    fromGlanceableHubTransitionInteractor: GlanceableHubToDreamingTransitionViewModel,
-    private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
-) {
-
-    val dreamOverlayTranslationX: Flow<Float> =
-        merge(
-            toGlanceableHubTransitionViewModel.dreamOverlayTranslationX,
-            fromGlanceableHubTransitionInteractor.dreamOverlayTranslationX,
-        )
-
-    val dreamOverlayTranslationY: Flow<Float> =
-        configurationInteractor
-            .dimensionPixelSize(R.dimen.dream_overlay_exit_y_offset)
-            .flatMapLatest { px: Int ->
-                toLockscreenTransitionViewModel.dreamOverlayTranslationY(px)
-            }
-
-    val dreamOverlayAlpha: Flow<Float> =
-        merge(
-            toLockscreenTransitionViewModel.dreamOverlayAlpha,
-            toGlanceableHubTransitionViewModel.dreamOverlayAlpha,
-            fromGlanceableHubTransitionInteractor.dreamOverlayAlpha,
-        )
-
-    val transitionEnded = toLockscreenTransitionViewModel.transitionEnded
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
new file mode 100644
index 0000000..0cb57fb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.ui.viewmodel
+
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dock.DockManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel
+import com.android.systemui.res.R
+import com.android.systemui.settings.UserTracker
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.merge
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class DreamViewModel
+@Inject
+constructor(
+    configurationInteractor: ConfigurationInteractor,
+    keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    fromGlanceableHubTransitionInteractor: GlanceableHubToDreamingTransitionViewModel,
+    private val toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
+    private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+    private val dockManager: DockManager,
+    private val communalInteractor: CommunalInteractor,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val userTracker: UserTracker,
+) {
+
+    fun startTransitionFromDream() {
+        val showGlanceableHub =
+            dockManager.isDocked &&
+                communalInteractor.isCommunalEnabled.value &&
+                !keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId)
+        if (showGlanceableHub) {
+            toGlanceableHubTransitionViewModel.startTransition()
+            communalInteractor.onSceneChanged(CommunalScenes.Communal)
+        } else {
+            toLockscreenTransitionViewModel.startTransition()
+        }
+    }
+
+    val dreamOverlayTranslationX: Flow<Float> =
+        merge(
+                toGlanceableHubTransitionViewModel.dreamOverlayTranslationX,
+                fromGlanceableHubTransitionInteractor.dreamOverlayTranslationX,
+            )
+            .distinctUntilChanged()
+
+    val dreamOverlayTranslationY: Flow<Float> =
+        configurationInteractor
+            .dimensionPixelSize(R.dimen.dream_overlay_exit_y_offset)
+            .flatMapLatest { px: Int ->
+                toLockscreenTransitionViewModel.dreamOverlayTranslationY(px)
+            }
+
+    val dreamAlpha: Flow<Float> =
+        merge(
+                toLockscreenTransitionViewModel.dreamOverlayAlpha,
+                toGlanceableHubTransitionViewModel.dreamAlpha,
+            )
+            .distinctUntilChanged()
+
+    val dreamOverlayAlpha: Flow<Float> =
+        merge(
+                toLockscreenTransitionViewModel.dreamOverlayAlpha,
+                toGlanceableHubTransitionViewModel.dreamOverlayAlpha,
+                fromGlanceableHubTransitionInteractor.dreamOverlayAlpha,
+            )
+            .distinctUntilChanged()
+
+    val transitionEnded =
+        keyguardTransitionInteractor.fromDreamingTransition.filter { step ->
+            step.transitionState == TransitionState.FINISHED ||
+                step.transitionState == TransitionState.CANCELED
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 7a24d76..298da13 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -22,8 +22,10 @@
 import com.android.server.notification.Flags.crossAppPoliteNotifications
 import com.android.server.notification.Flags.politeNotifications
 import com.android.server.notification.Flags.vibrateWhileUnlocked
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
 import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
+import com.android.systemui.Flags.communalHub
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.dagger.SysUISingleton
@@ -58,6 +60,9 @@
         // ComposeLockscreen dependencies
         ComposeLockscreen.token dependsOn keyguardBottomAreaRefactor
         ComposeLockscreen.token dependsOn migrateClocksToBlueprint
+
+        // CommunalHub dependencies
+        communalHub dependsOn migrateClocksToBlueprint
     }
 
     private inline val politeNotifications
@@ -70,4 +75,6 @@
         get() = FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor())
     private inline val migrateClocksToBlueprint
         get() = FlagToken(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, migrateClocksToBlueprint())
+    private inline val communalHub
+        get() = FlagToken(FLAG_COMMUNAL_HUB, communalHub())
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 33a69bf..a199fea 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -76,23 +76,6 @@
     val NOTIFICATION_MEMORY_LOGGING_ENABLED =
             releasedFlag("notification_memory_logging_enabled")
 
-    // TODO(b/260335638): Tracking Bug
-    @JvmField
-    val NOTIFICATION_INLINE_REPLY_ANIMATION = releasedFlag("notification_inline_reply_animation")
-
-    // TODO(b/288326013): Tracking Bug
-    @JvmField
-    val NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION =
-        unreleasedFlag("notification_async_hybrid_view_inflation", teamfood = false)
-
-    @JvmField
-    val ANIMATED_NOTIFICATION_SHADE_INSETS =
-        releasedFlag("animated_notification_shade_insets")
-
-    // TODO(b/268005230): Tracking Bug
-    @JvmField
-    val SENSITIVE_REVEAL_ANIM = releasedFlag("sensitive_reveal_anim")
-
     // TODO(b/280783617): Tracking Bug
     @Keep
     @JvmField
@@ -369,12 +352,6 @@
     @Keep
     val WM_BUBBLE_BAR = sysPropBooleanFlag("persist.wm.debug.bubble_bar", default = false)
 
-    // TODO(b/260271148): Tracking bug
-    @Keep
-    @JvmField
-    val WM_DESKTOP_WINDOWING_2 =
-        sysPropBooleanFlag("persist.wm.debug.desktop_mode_2", default = false)
-
     // TODO(b/254513207): Tracking Bug to delete
     @Keep
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java
index 51978ec..ccd69ca 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java
@@ -22,7 +22,10 @@
 import android.annotation.StringRes;
 import android.app.Dialog;
 import android.content.Context;
+import android.nearby.NearbyManager;
+import android.net.platform.flags.Flags;
 import android.os.PowerManager;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
@@ -38,6 +41,8 @@
 import com.android.systemui.statusbar.BlurUtils;
 import com.android.systemui.statusbar.phone.ScrimController;
 
+import javax.inject.Inject;
+
 /**
  * Provides the UI shown during system shutdown.
  */
@@ -45,9 +50,13 @@
 
     private Context mContext;
     private BlurUtils mBlurUtils;
-    public ShutdownUi(Context context, BlurUtils blurUtils) {
+    private NearbyManager mNearbyManager;
+
+    @Inject
+    public ShutdownUi(Context context, BlurUtils blurUtils, NearbyManager nearbyManager) {
         mContext = context;
         mBlurUtils = blurUtils;
+        mNearbyManager = nearbyManager;
     }
 
     /**
@@ -132,12 +141,28 @@
     /**
      * Returns the layout resource to use for UI while shutting down.
      * @param isReboot Whether this is a reboot or a shutdown.
-     * @return
      */
-    public int getShutdownDialogContent(boolean isReboot) {
-        return R.layout.shutdown_dialog;
+    @VisibleForTesting int getShutdownDialogContent(boolean isReboot) {
+        if (!Flags.poweredOffFindingPlatform()) {
+            return R.layout.shutdown_dialog;
+        }
+        int finderActive = mNearbyManager.getPoweredOffFindingMode();
+        if (finderActive == NearbyManager.POWERED_OFF_FINDING_MODE_DISABLED
+                || finderActive == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) {
+            // inactive or unsupported, use regular shutdown dialog
+            return R.layout.shutdown_dialog;
+        } else if (finderActive == NearbyManager.POWERED_OFF_FINDING_MODE_ENABLED) {
+            // active, use dialog with finder info if shutting down
+            return isReboot ? R.layout.shutdown_dialog :
+                    com.android.systemui.res.R.layout.shutdown_dialog_finder_active;
+        } else {
+            // that's weird? default to regular dialog
+            Log.w("ShutdownUi", "Unexpected value for finder active: " + finderActive);
+            return R.layout.shutdown_dialog;
+        }
     }
 
+
     @StringRes
     @VisibleForTesting int getRebootMessage(boolean isReboot, @Nullable String reason) {
         if (reason != null && reason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUiModule.kt b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUiModule.kt
deleted file mode 100644
index b7285da..0000000
--- a/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUiModule.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.globalactions
-
-import android.content.Context
-import com.android.systemui.statusbar.BlurUtils
-import dagger.Module
-import dagger.Provides
-
-/** Provides the UI shown during system shutdown. */
-@Module
-class ShutdownUiModule {
-    /** Shutdown UI provider. */
-    @Provides
-    fun provideShutdownUi(context: Context?, blurUtils: BlurUtils?): ShutdownUi {
-        return ShutdownUi(context, blurUtils)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/LongPressHapticBuilder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/LongPressHapticBuilder.kt
new file mode 100644
index 0000000..0143b85
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/LongPressHapticBuilder.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.qs
+
+import android.os.VibrationEffect
+import android.util.Log
+import kotlin.math.max
+
+object LongPressHapticBuilder {
+
+    const val INVALID_DURATION = 0 /* in ms */
+
+    private const val TAG = "LongPressHapticBuilder"
+    private const val SPIN_SCALE = 0.2f
+    private const val CLICK_SCALE = 0.5f
+    private const val LOW_TICK_SCALE = 0.08f
+    private const val WARMUP_TIME = 75 /* in ms */
+    private const val DAMPING_TIME = 24 /* in ms */
+
+    /** Create the signal that indicates that a long-press action is available. */
+    fun createLongPressHint(
+        lowTickDuration: Int,
+        spinDuration: Int,
+        effectDuration: Int
+    ): VibrationEffect? {
+        if (lowTickDuration == 0 || spinDuration == 0) {
+            Log.d(
+                TAG,
+                "The LOW_TICK and/or SPIN primitives are not supported. No signal created.",
+            )
+            return null
+        }
+        if (effectDuration < WARMUP_TIME + spinDuration + DAMPING_TIME) {
+            Log.d(
+                TAG,
+                "Cannot fit long-press hint signal in the effect duration. No signal created",
+            )
+            return null
+        }
+
+        val nLowTicks = WARMUP_TIME / lowTickDuration
+        val rampDownLowTicks = DAMPING_TIME / lowTickDuration
+        val composition = VibrationEffect.startComposition()
+
+        // Warmup low ticks
+        repeat(nLowTicks) {
+            composition.addPrimitive(
+                VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+                LOW_TICK_SCALE,
+                0,
+            )
+        }
+
+        // Spin effect
+        composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, SPIN_SCALE, 0)
+
+        // Damping low ticks
+        repeat(rampDownLowTicks) { i ->
+            composition.addPrimitive(
+                VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+                LOW_TICK_SCALE / (i + 1),
+                0,
+            )
+        }
+
+        return composition.compose()
+    }
+
+    /** Create a "snapping" effect that triggers at the end of a long-press gesture */
+    fun createSnapEffect(): VibrationEffect? =
+        VibrationEffect.startComposition()
+            .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, CLICK_SCALE, 0)
+            .compose()
+
+    /** Creates a signal that indicates the reversal of the long-press animation. */
+    fun createReversedEffect(
+        pausedProgress: Float,
+        lowTickDuration: Int,
+        effectDuration: Int,
+    ): VibrationEffect? {
+        val duration = pausedProgress * effectDuration
+        if (duration == 0f) return null
+
+        if (lowTickDuration == 0) {
+            Log.d(TAG, "Cannot play reverse haptics because LOW_TICK is not supported")
+            return null
+        }
+
+        val nLowTicks = (duration / lowTickDuration).toInt()
+        if (nLowTicks == 0) return null
+
+        val composition = VibrationEffect.startComposition()
+        var scale: Float
+        val step = LOW_TICK_SCALE / nLowTicks
+        repeat(nLowTicks) { i ->
+            scale = max(LOW_TICK_SCALE - step * i, 0f)
+            composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, scale, 0)
+        }
+        return composition.compose()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
new file mode 100644
index 0000000..ec72a14
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.qs
+
+import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.os.VibrationEffect
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewConfiguration
+import android.view.animation.AccelerateDecelerateInterpolator
+import androidx.annotation.VisibleForTesting
+import androidx.core.animation.doOnCancel
+import androidx.core.animation.doOnEnd
+import androidx.core.animation.doOnStart
+import com.android.systemui.statusbar.VibratorHelper
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+
+/**
+ * A class that handles the long press visuo-haptic effect for a QS tile.
+ *
+ * The class is also a [View.OnTouchListener] to handle the touch events, clicks and long-press
+ * gestures of the tile. The class also provides a [State] that can be used to determine the current
+ * state of the long press effect.
+ *
+ * @property[vibratorHelper] The [VibratorHelper] to deliver haptic effects.
+ * @property[effectDuration] The duration of the effect in ms.
+ */
+class QSLongPressEffect(
+    private val vibratorHelper: VibratorHelper?,
+    private val effectDuration: Int,
+) : View.OnTouchListener {
+
+    /** Current state */
+    var state = State.IDLE
+        @VisibleForTesting set
+
+    /** Flows for view control and action */
+    private val _effectProgress = MutableStateFlow<Float?>(null)
+    val effectProgress = _effectProgress.asStateFlow()
+
+    private val _actionType = MutableStateFlow<ActionType?>(null)
+    val actionType = _actionType.asStateFlow()
+
+    /** Haptic effects */
+    private val durations =
+        vibratorHelper?.getPrimitiveDurations(
+            VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+            VibrationEffect.Composition.PRIMITIVE_SPIN
+        )
+
+    private val longPressHint =
+        LongPressHapticBuilder.createLongPressHint(
+            durations?.get(0) ?: LongPressHapticBuilder.INVALID_DURATION,
+            durations?.get(1) ?: LongPressHapticBuilder.INVALID_DURATION,
+            effectDuration
+        )
+
+    private val snapEffect = LongPressHapticBuilder.createSnapEffect()
+
+    /* A coroutine scope and a timer job that waits for the pressedTimeout */
+    var scope: CoroutineScope? = null
+    private var waitJob: Job? = null
+
+    private val effectAnimator =
+        ValueAnimator.ofFloat(0f, 1f).apply {
+            duration = effectDuration.toLong()
+            interpolator = AccelerateDecelerateInterpolator()
+
+            doOnStart { handleAnimationStart() }
+            addUpdateListener { _effectProgress.value = animatedValue as Float }
+            doOnEnd { handleAnimationComplete() }
+            doOnCancel { handleAnimationCancel() }
+        }
+
+    private fun reverse() {
+        val pausedProgress = effectAnimator.animatedFraction
+        val effect =
+            LongPressHapticBuilder.createReversedEffect(
+                pausedProgress,
+                durations?.get(0) ?: 0,
+                effectDuration,
+            )
+        vibratorHelper?.cancel()
+        vibrate(effect)
+        effectAnimator.reverse()
+    }
+
+    private fun vibrate(effect: VibrationEffect?) {
+        if (vibratorHelper != null && effect != null) {
+            vibratorHelper.vibrate(effect)
+        }
+    }
+
+    /**
+     * Handle relevant touch events for the operation of a Tile.
+     *
+     * A click action is performed following the relevant logic that originates from the
+     * [MotionEvent.ACTION_UP] event depending on the current state.
+     */
+    @SuppressLint("ClickableViewAccessibility")
+    override fun onTouch(view: View?, event: MotionEvent?): Boolean {
+        when (event?.actionMasked) {
+            MotionEvent.ACTION_DOWN -> handleActionDown()
+            MotionEvent.ACTION_UP -> handleActionUp()
+            MotionEvent.ACTION_CANCEL -> handleActionCancel()
+        }
+        return true
+    }
+
+    private fun handleActionDown() {
+        when (state) {
+            State.IDLE -> {
+                startPressedTimeoutWait()
+                state = State.TIMEOUT_WAIT
+            }
+            State.RUNNING_BACKWARDS -> effectAnimator.cancel()
+            else -> {}
+        }
+    }
+
+    private fun startPressedTimeoutWait() {
+        waitJob =
+            scope?.launch {
+                try {
+                    delay(PRESSED_TIMEOUT)
+                    handleTimeoutComplete()
+                } catch (_: CancellationException) {
+                    state = State.IDLE
+                }
+            }
+    }
+
+    private fun handleActionUp() {
+        when (state) {
+            State.TIMEOUT_WAIT -> {
+                waitJob?.cancel()
+                _actionType.value = ActionType.CLICK
+                state = State.IDLE
+            }
+            State.RUNNING_FORWARD -> {
+                reverse()
+                state = State.RUNNING_BACKWARDS
+            }
+            else -> {}
+        }
+    }
+
+    private fun handleActionCancel() {
+        when (state) {
+            State.TIMEOUT_WAIT -> {
+                waitJob?.cancel()
+                state = State.IDLE
+            }
+            State.RUNNING_FORWARD -> {
+                reverse()
+                state = State.RUNNING_BACKWARDS
+            }
+            else -> {}
+        }
+    }
+
+    private fun handleAnimationStart() {
+        vibrate(longPressHint)
+        state = State.RUNNING_FORWARD
+    }
+
+    /** This function is called both when an animator completes or gets cancelled */
+    private fun handleAnimationComplete() {
+        if (state == State.RUNNING_FORWARD) {
+            vibrate(snapEffect)
+            _actionType.value = ActionType.LONG_PRESS
+            _effectProgress.value = null
+        }
+        if (state != State.TIMEOUT_WAIT) {
+            // This will happen if the animator did not finish by being cancelled
+            state = State.IDLE
+        }
+    }
+
+    private fun handleAnimationCancel() {
+        _effectProgress.value = 0f
+        startPressedTimeoutWait()
+        state = State.TIMEOUT_WAIT
+    }
+
+    private fun handleTimeoutComplete() {
+        if (state == State.TIMEOUT_WAIT && !effectAnimator.isRunning) {
+            effectAnimator.start()
+        }
+    }
+
+    fun clearActionType() {
+        _actionType.value = null
+    }
+
+    enum class State {
+        IDLE, /* The effect is idle waiting for touch input */
+        TIMEOUT_WAIT, /* The effect is waiting for a [PRESSED_TIMEOUT] period */
+        RUNNING_FORWARD, /* The effect is running normally */
+        RUNNING_BACKWARDS, /* The effect was interrupted and is now running backwards */
+    }
+
+    /* A type of action to perform on the view depending on the effect's state and logic */
+    enum class ActionType {
+        CLICK,
+        LONG_PRESS,
+    }
+
+    companion object {
+        /**
+         * A timeout to let the tile resolve if it is being swiped/scrolled. Since QS tiles are
+         * inside a scrollable container, they will be considered pressed only after a tap timeout.
+         */
+        val PRESSED_TIMEOUT = ViewConfiguration.getTapTimeout().toLong() + 20L
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
new file mode 100644
index 0000000..e298154
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.qs
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.qs.tileimpl.QSTileViewImpl
+import kotlinx.coroutines.launch
+
+object QSLongPressEffectViewBinder {
+
+    fun bind(
+        tile: QSTileViewImpl,
+        effect: QSLongPressEffect?,
+    ) {
+        if (effect == null) return
+
+        tile.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                effect.scope = this
+
+                launch {
+                    effect.effectProgress.collect { progress ->
+                        progress?.let {
+                            if (it == 0f) {
+                                tile.bringToFront()
+                            }
+                            tile.updateLongPressEffectProperties(it)
+                        }
+                    }
+                }
+
+                launch {
+                    effect.actionType.collect { action ->
+                        action?.let {
+                            when (it) {
+                                QSLongPressEffect.ActionType.CLICK -> tile.performClick()
+                                QSLongPressEffect.ActionType.LONG_PRESS -> tile.performLongClick()
+                            }
+                            effect.clearActionType()
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
index 931a869..ed82278 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
@@ -163,6 +163,6 @@
     }
 
     companion object {
-        const val KEY_UP_TIMEOUT = 100L
+        const val KEY_UP_TIMEOUT = 60L
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt
index 3ed58a7..89433d3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt
@@ -20,15 +20,16 @@
 import android.content.Context
 import android.view.Gravity
 import android.view.Window
+import android.view.WindowInsets
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND
 import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
 import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
 import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL
 import androidx.activity.ComponentDialog
-import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyboard.stickykeys.ui.view.createStickyKeyIndicatorView
 import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
 import com.android.systemui.res.R
 import javax.inject.Inject
@@ -45,10 +46,10 @@
     }
 
     private fun createStickyKeyIndicator(viewModel: StickyKeysIndicatorViewModel): Dialog {
-        return ComponentDialog(context, R.style.Theme_SystemUI_Dialog).apply {
+        return ComponentDialog(context, R.style.Theme_SystemUI_Dialog_StickyKeys).apply {
             // because we're requesting window feature it must be called before setting content
             window?.setStickyKeyWindowAttributes()
-            setContentView(ComposeFacade.createStickyKeysIndicatorContent(context, viewModel))
+            setContentView(createStickyKeyIndicatorView(context, viewModel))
         }
     }
 
@@ -61,6 +62,9 @@
         attributes =
             WindowManager.LayoutParams().apply {
                 copyFrom(attributes)
+                // needed because we're above system bars windows, see [TYPE_STATUS_BAR_SUB_PANEL]
+                receiveInsetsIgnoringZOrder = true
+                fitInsetsTypes = WindowInsets.Type.systemBars()
                 title = "StickyKeysIndicator"
             }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt
index 842fd04..78c4e77 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt
@@ -17,15 +17,13 @@
 package com.android.systemui.keyboard.stickykeys.ui
 
 import android.app.Dialog
-import android.util.Log
-import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
 import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
+import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
-import javax.inject.Inject
 
 @SysUISingleton
 class StickyKeysIndicatorCoordinator
@@ -40,11 +38,6 @@
     private var dialog: Dialog? = null
 
     fun startListening() {
-        // this check needs to be moved to PhysicalKeyboardCoreStartable
-        if (!ComposeFacade.isComposeAvailable()) {
-            Log.e("StickyKeysIndicatorCoordinator", "Compose is required for this UI")
-            return
-        }
         applicationScope.launch {
             viewModel.indicatorContent.collect { stickyKeys ->
                 stickyKeysLogger.logNewUiState(stickyKeys)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 2e233d8..3134e35 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -289,6 +289,8 @@
         };
     }
 
+    private final WindowManagerOcclusionManager mWmOcclusionManager;
+
     @Inject
     public KeyguardService(
             KeyguardViewMediator keyguardViewMediator,
@@ -302,7 +304,8 @@
             KeyguardSurfaceBehindParamsApplier keyguardSurfaceBehindAnimator,
             @Application CoroutineScope scope,
             FeatureFlags featureFlags,
-            PowerInteractor powerInteractor) {
+            PowerInteractor powerInteractor,
+            WindowManagerOcclusionManager windowManagerOcclusionManager) {
         super();
         mKeyguardViewMediator = keyguardViewMediator;
         mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
@@ -323,6 +326,8 @@
                     keyguardSurfaceBehindAnimator,
                     scope);
         }
+
+        mWmOcclusionManager = windowManagerOcclusionManager;
     }
 
     @Override
@@ -414,7 +419,11 @@
 
             Trace.beginSection("KeyguardService.mBinder#setOccluded");
             checkPermission();
-            mKeyguardViewMediator.setOccluded(isOccluded, animate);
+            if (!KeyguardWmStateRefactor.isEnabled()) {
+                mKeyguardViewMediator.setOccluded(isOccluded, animate);
+            } else {
+                mWmOcclusionManager.onKeyguardServiceSetOccluded(isOccluded);
+            }
             Trace.endSection();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 301942f..5565ee2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -20,6 +20,9 @@
 import android.content.Context
 import android.view.LayoutInflater
 import android.view.View
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.ComposeView
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
 import androidx.constraintlayout.widget.ConstraintSet.END
@@ -35,16 +38,18 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.common.ui.ConfigurationState
-import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.shared.ComposeLockscreen
 import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
 import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder
 import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
 import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
+import com.android.systemui.keyguard.ui.composable.LockscreenContent
+import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
 import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
 import com.android.systemui.keyguard.ui.view.layout.KeyguardBlueprintCommandListener
@@ -103,6 +108,7 @@
     private val lockscreenContentViewModel: LockscreenContentViewModel,
     private val lockscreenSceneBlueprintsLazy: Lazy<Set<LockscreenSceneBlueprint>>,
     private val keyguardBlueprintViewBinder: KeyguardBlueprintViewBinder,
+    private val clockInteractor: KeyguardClockInteractor,
 ) : CoreStartable {
 
     private var rootViewHandle: DisposableHandle? = null
@@ -132,7 +138,7 @@
         if (!SceneContainerFlag.isEnabled) {
             if (ComposeLockscreen.isEnabled) {
                 val composeView =
-                    ComposeFacade.createLockscreen(
+                    createLockscreen(
                         context = context,
                         viewModel = lockscreenContentViewModel,
                         blueprints = lockscreenSceneBlueprintsLazy.get(),
@@ -207,6 +213,25 @@
             )
     }
 
+    private fun createLockscreen(
+        context: Context,
+        viewModel: LockscreenContentViewModel,
+        blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>,
+    ): View {
+        val sceneBlueprints =
+            blueprints.mapNotNull { it as? ComposableLockscreenSceneBlueprint }.toSet()
+        return ComposeView(context).apply {
+            setContent {
+                LockscreenContent(
+                        viewModel = viewModel,
+                        blueprints = sceneBlueprints,
+                        clockInteractor = clockInteractor
+                    )
+                    .Content(modifier = Modifier.fillMaxSize())
+            }
+        }
+    }
+
     /**
      * Temporary, to allow NotificationPanelViewController to use the same instance while code is
      * migrated: b/288242803
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 6d917bb..43a8b40 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -140,13 +140,13 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.ui.viewmodel.DreamViewModel;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.SystemPropertiesHelper;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -1229,9 +1229,7 @@
 
                         if (isDream) {
                             initAlphaForAnimationTargets(wallpapers);
-                            getRemoteSurfaceAlphaApplier().accept(0.0f);
-                            mDreamingToLockscreenTransitionViewModel.get()
-                                    .startTransition();
+                            mDreamViewModel.get().startTransitionFromDream();
                             mUnoccludeFromDreamFinishedCallback = finishedCallback;
                             return;
                         }
@@ -1359,13 +1357,14 @@
     private final UiEventLogger mUiEventLogger;
     private final SessionTracker mSessionTracker;
     private final CoroutineDispatcher mMainDispatcher;
-    private final Lazy<DreamingToLockscreenTransitionViewModel>
-            mDreamingToLockscreenTransitionViewModel;
+    private final Lazy<DreamViewModel> mDreamViewModel;
     private RemoteAnimationTarget mRemoteAnimationTarget;
 
     private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
 
+    private WindowManagerOcclusionManager mWmOcclusionManager;
     /**
+
      * Injected constructor. See {@link KeyguardModule}.
      */
     public KeyguardViewMediator(
@@ -1407,11 +1406,12 @@
             SystemSettings systemSettings,
             SystemClock systemClock,
             @Main CoroutineDispatcher mainDispatcher,
-            Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
+            Lazy<DreamViewModel> dreamViewModel,
             SystemPropertiesHelper systemPropertiesHelper,
             Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
             SelectedUserInteractor selectedUserInteractor,
-            KeyguardInteractor keyguardInteractor) {
+            KeyguardInteractor keyguardInteractor,
+            WindowManagerOcclusionManager wmOcclusionManager) {
         mContext = context;
         mUserTracker = userTracker;
         mFalsingCollector = falsingCollector;
@@ -1477,7 +1477,7 @@
         mUiEventLogger = uiEventLogger;
         mSessionTracker = sessionTracker;
 
-        mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel;
+        mDreamViewModel = dreamViewModel;
         mWmLockscreenVisibilityManager = wmLockscreenVisibilityManager;
         mMainDispatcher = mainDispatcher;
 
@@ -1486,6 +1486,8 @@
 
         mShowKeyguardWakeLock = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "show keyguard");
         mShowKeyguardWakeLock.setReferenceCounted(false);
+
+        mWmOcclusionManager = wmOcclusionManager;
     }
 
     public void userActivity() {
@@ -1606,9 +1608,8 @@
 
             ViewRootImpl viewRootImpl = mKeyguardViewControllerLazy.get().getViewRootImpl();
             if (viewRootImpl != null) {
-                DreamingToLockscreenTransitionViewModel viewModel =
-                        mDreamingToLockscreenTransitionViewModel.get();
-                collectFlow(viewRootImpl.getView(), viewModel.getDreamOverlayAlpha(),
+                final DreamViewModel viewModel = mDreamViewModel.get();
+                collectFlow(viewRootImpl.getView(), viewModel.getDreamAlpha(),
                         getRemoteSurfaceAlphaApplier(), mMainDispatcher);
                 collectFlow(viewRootImpl.getView(), viewModel.getTransitionEnded(),
                         getFinishedCallbackConsumer(), mMainDispatcher);
@@ -2103,15 +2104,27 @@
     }
 
     public IRemoteAnimationRunner getOccludeAnimationRunner() {
-        return validatingRemoteAnimationRunner(mOccludeAnimationRunner);
+        if (KeyguardWmStateRefactor.isEnabled()) {
+            return validatingRemoteAnimationRunner(mWmOcclusionManager.getOccludeAnimationRunner());
+        } else {
+            return validatingRemoteAnimationRunner(mOccludeAnimationRunner);
+        }
     }
 
+    /**
+     * TODO(b/326464548): Move this to WindowManagerOcclusionManager
+     */
     public IRemoteAnimationRunner getOccludeByDreamAnimationRunner() {
         return validatingRemoteAnimationRunner(mOccludeByDreamAnimationRunner);
     }
 
     public IRemoteAnimationRunner getUnoccludeAnimationRunner() {
-        return validatingRemoteAnimationRunner(mUnoccludeAnimationRunner);
+        if (KeyguardWmStateRefactor.isEnabled()) {
+            return validatingRemoteAnimationRunner(
+                    mWmOcclusionManager.getUnoccludeAnimationRunner());
+        } else {
+            return validatingRemoteAnimationRunner(mUnoccludeAnimationRunner);
+        }
     }
 
     public boolean isHiding() {
@@ -2145,8 +2158,10 @@
 
             if (mOccluded != isOccluded) {
                 mOccluded = isOccluded;
-                mKeyguardViewControllerLazy.get().setOccluded(isOccluded, animate
-                        && mDeviceInteractive);
+                if (!KeyguardWmStateRefactor.isEnabled()) {
+                    mKeyguardViewControllerLazy.get().setOccluded(isOccluded, animate
+                            && mDeviceInteractive);
+                }
                 adjustStatusBarLocked();
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
index 8ebcece..00f5002 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -140,8 +140,14 @@
         nonApps: Array<RemoteAnimationTarget>,
         finishedCallback: IRemoteAnimationFinishedCallback
     ) {
-        goingAwayRemoteAnimationFinishedCallback = finishedCallback
-        keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0])
+        if (apps.isNotEmpty()) {
+            goingAwayRemoteAnimationFinishedCallback = finishedCallback
+            keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0])
+        } else {
+            // Nothing to do here if we have no apps, end the animation, which will cancel it and WM
+            // will make *something* visible.
+            finishedCallback.onAnimationFinished()
+        }
     }
 
     fun onKeyguardGoingAwayRemoteAnimationCancelled() {
@@ -174,13 +180,19 @@
      * if so, true should be the right choice.
      */
     private fun setWmLockscreenState(
-            lockscreenShowing: Boolean = this.isLockscreenShowing ?: true.also {
-                Log.d(TAG, "Using isLockscreenShowing=true default in setWmLockscreenState, " +
-                        "because setAodVisible was called before the first setLockscreenShown " +
-                        "call during boot. This is not typical, but is theoretically possible. " +
-                        "If you're investigating the lockscreen showing unexpectedly, start here.")
-            },
-            aodVisible: Boolean = this.isAodVisible
+        lockscreenShowing: Boolean =
+            this.isLockscreenShowing
+                ?: true.also {
+                    Log.d(
+                        TAG,
+                        "Using isLockscreenShowing=true default in setWmLockscreenState, " +
+                            "because setAodVisible was called before the first " +
+                            "setLockscreenShown call during boot. This is not typical, but is " +
+                            "theoretically possible. If you're investigating the lockscreen " +
+                            "showing unexpectedly, start here."
+                    )
+                },
+        aodVisible: Boolean = this.isAodVisible
     ) {
         Log.d(
             TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt
new file mode 100644
index 0000000..aab90c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Matrix
+import android.os.RemoteException
+import android.util.Log
+import android.view.IRemoteAnimationFinishedCallback
+import android.view.IRemoteAnimationRunner
+import android.view.RemoteAnimationTarget
+import android.view.SyncRtSurfaceTransactionApplier
+import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+import androidx.annotation.VisibleForTesting
+import com.android.app.animation.Interpolators
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.policy.ScreenDecorationsUtils
+import com.android.keyguard.KeyguardViewController
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.TransitionAnimator
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.res.R
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+private val UNOCCLUDE_ANIMATION_DURATION = 250
+private val UNOCCLUDE_TRANSLATE_DISTANCE_PERCENT = 0.1f
+
+/**
+ * Keeps track of Window Manager's occlusion state and RemoteAnimations related to changes in
+ * occlusion state. Occlusion is when a [FLAG_SHOW_WHEN_LOCKED] activity is displaying over the
+ * lockscreen - we're still locked, but the user can interact with the activity.
+ *
+ * Typical "occlusion" use cases include launching the camera over the lockscreen, tapping a quick
+ * affordance to bring up Google Pay/Wallet/whatever it's called by the time you're reading this,
+ * and Maps Navigation.
+ *
+ * Window Manager considers the keyguard to be 'occluded' whenever a [FLAG_SHOW_WHEN_LOCKED]
+ * activity is on top of the task stack, even if the device is unlocked and the keyguard is not
+ * visible. System UI considers the keyguard to be [KeyguardState.OCCLUDED] only when we're on the
+ * keyguard and an activity is displaying over it.
+ *
+ * For all System UI use cases, you should use [KeyguardTransitionInteractor] to determine if we're
+ * in the [KeyguardState.OCCLUDED] state and react accordingly. If you are sure that you need to
+ * check whether Window Manager considers OCCLUDED=true even though the lockscreen is not showing,
+ * use [KeyguardShowWhenLockedActivityInteractor.isShowWhenLockedActivityOnTop] in combination with
+ * [KeyguardTransitionInteractor] state.
+ *
+ * This is a very sensitive piece of state that has caused many headaches in the past. Please be
+ * careful.
+ */
+@SysUISingleton
+class WindowManagerOcclusionManager
+@Inject
+constructor(
+    val keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+    val activityTransitionAnimator: ActivityTransitionAnimator,
+    val keyguardViewController: dagger.Lazy<KeyguardViewController>,
+    val powerInteractor: PowerInteractor,
+    val context: Context,
+    val interactionJankMonitor: InteractionJankMonitor,
+    @Main executor: Executor,
+    val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+    val occlusionInteractor: KeyguardOcclusionInteractor,
+) {
+    val powerButtonY =
+        context.resources.getDimensionPixelSize(
+            R.dimen.physical_power_button_center_screen_location_y
+        )
+    val windowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
+
+    var occludeAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null
+
+    /**
+     * Animation runner provided to WindowManager, which will be used if an occluding activity is
+     * launched and Window Manager wants us to animate it in. This is used as a signal that we are
+     * now occluded, and should update our state accordingly.
+     */
+    val occludeAnimationRunner: IRemoteAnimationRunner =
+        object : IRemoteAnimationRunner.Stub() {
+            override fun onAnimationStart(
+                transit: Int,
+                apps: Array<RemoteAnimationTarget>,
+                wallpapers: Array<RemoteAnimationTarget>,
+                nonApps: Array<RemoteAnimationTarget>,
+                finishedCallback: IRemoteAnimationFinishedCallback?
+            ) {
+                Log.d(TAG, "occludeAnimationRunner#onAnimationStart")
+                // Wrap the callback so that it's guaranteed to be nulled out once called.
+                occludeAnimationFinishedCallback =
+                    object : IRemoteAnimationFinishedCallback.Stub() {
+                        override fun onAnimationFinished() {
+                            finishedCallback?.onAnimationFinished()
+                            occludeAnimationFinishedCallback = null
+                        }
+                    }
+                keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
+                    showWhenLockedActivityOnTop = true,
+                    taskInfo = apps.firstOrNull()?.taskInfo,
+                )
+                activityTransitionAnimator
+                    .createRunner(occludeAnimationController)
+                    .onAnimationStart(
+                        transit,
+                        apps,
+                        wallpapers,
+                        nonApps,
+                        occludeAnimationFinishedCallback,
+                    )
+            }
+
+            override fun onAnimationCancelled() {
+                Log.d(TAG, "occludeAnimationRunner#onAnimationCancelled")
+            }
+        }
+
+    var unoccludeAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null
+
+    /**
+     * Animation runner provided to WindowManager, which will be used if an occluding activity is
+     * finished and Window Manager wants us to animate it out. This is used as a signal that we are
+     * no longer occluded, and should update our state accordingly.
+     *
+     * TODO(b/326464548): Restore dream specific animation.
+     */
+    val unoccludeAnimationRunner: IRemoteAnimationRunner =
+        object : IRemoteAnimationRunner.Stub() {
+            var unoccludeAnimator: ValueAnimator? = null
+            val unoccludeMatrix = Matrix()
+
+            /** TODO(b/326470033): Extract this logic into ViewModels. */
+            override fun onAnimationStart(
+                transit: Int,
+                apps: Array<RemoteAnimationTarget>,
+                wallpapers: Array<RemoteAnimationTarget>,
+                nonApps: Array<RemoteAnimationTarget>,
+                finishedCallback: IRemoteAnimationFinishedCallback?
+            ) {
+                Log.d(TAG, "unoccludeAnimationRunner#onAnimationStart")
+                // Wrap the callback so that it's guaranteed to be nulled out once called.
+                unoccludeAnimationFinishedCallback =
+                    object : IRemoteAnimationFinishedCallback.Stub() {
+                        override fun onAnimationFinished() {
+                            finishedCallback?.onAnimationFinished()
+                            unoccludeAnimationFinishedCallback = null
+                        }
+                    }
+                keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
+                    showWhenLockedActivityOnTop = false,
+                    taskInfo = apps.firstOrNull()?.taskInfo,
+                )
+                interactionJankMonitor.begin(
+                    createInteractionJankMonitorConf(
+                        InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION,
+                        "UNOCCLUDE"
+                    )
+                )
+                if (apps.isEmpty()) {
+                    Log.d(
+                        TAG,
+                        "No apps provided to unocclude runner; " +
+                            "skipping animation and unoccluding."
+                    )
+                    unoccludeAnimationFinishedCallback?.onAnimationFinished()
+                    return
+                }
+                val target = apps[0]
+                val localView: View = keyguardViewController.get().getViewRootImpl().getView()
+                val applier = SyncRtSurfaceTransactionApplier(localView)
+                // TODO(
+                executor.execute {
+                    unoccludeAnimator?.cancel()
+                    unoccludeAnimator =
+                        ValueAnimator.ofFloat(1f, 0f).apply {
+                            duration = UNOCCLUDE_ANIMATION_DURATION.toLong()
+                            interpolator = Interpolators.TOUCH_RESPONSE
+                            addUpdateListener { animation: ValueAnimator ->
+                                val animatedValue = animation.animatedValue as Float
+                                val surfaceHeight: Float =
+                                    target.screenSpaceBounds.height().toFloat()
+
+                                unoccludeMatrix.setTranslate(
+                                    0f,
+                                    (1f - animatedValue) *
+                                        surfaceHeight *
+                                        UNOCCLUDE_TRANSLATE_DISTANCE_PERCENT
+                                )
+
+                                SurfaceParams.Builder(target.leash)
+                                    .withAlpha(animatedValue)
+                                    .withMatrix(unoccludeMatrix)
+                                    .withCornerRadius(windowCornerRadius)
+                                    .build()
+                                    .also { applier.scheduleApply(it) }
+                            }
+                            addListener(
+                                object : AnimatorListenerAdapter() {
+                                    override fun onAnimationEnd(animation: Animator) {
+                                        try {
+                                            unoccludeAnimationFinishedCallback
+                                                ?.onAnimationFinished()
+                                            unoccludeAnimator = null
+                                            interactionJankMonitor.end(
+                                                InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION
+                                            )
+                                        } catch (e: RemoteException) {
+                                            e.printStackTrace()
+                                        }
+                                    }
+                                }
+                            )
+                            start()
+                        }
+                }
+            }
+
+            override fun onAnimationCancelled() {
+                Log.d(TAG, "unoccludeAnimationRunner#onAnimationCancelled")
+                context.mainExecutor.execute { unoccludeAnimator?.cancel() }
+                Log.d(TAG, "Unocclude animation cancelled.")
+                interactionJankMonitor.cancel(InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION)
+            }
+        }
+
+    /**
+     * Called when Window Manager tells the KeyguardService directly that we're occluded or not
+     * occluded, without starting an occlude/unocclude remote animation. This happens if occlusion
+     * state changes without an animation (such as if a SHOW_WHEN_LOCKED activity is launched while
+     * we're unlocked), or if an animation has been cancelled/interrupted and Window Manager wants
+     * to make sure that we're in the correct state.
+     */
+    fun onKeyguardServiceSetOccluded(occluded: Boolean) {
+        Log.d(TAG, "#onKeyguardServiceSetOccluded($occluded)")
+        keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(occluded)
+    }
+
+    @VisibleForTesting
+    val occludeAnimationController: ActivityTransitionAnimator.Controller =
+        object : ActivityTransitionAnimator.Controller {
+
+            override var transitionContainer: ViewGroup
+                get() = keyguardViewController.get().getViewRootImpl().view as ViewGroup
+                set(_) {
+                    // Should never be set.
+                }
+
+            /** TODO(b/326470033): Extract this logic into ViewModels. */
+            override fun createAnimatorState(): TransitionAnimator.State {
+                val fullWidth = transitionContainer.width
+                val fullHeight = transitionContainer.height
+
+                if (
+                    keyguardOcclusionInteractor.showWhenLockedActivityLaunchedFromPowerGesture.value
+                ) {
+                    val initialHeight = fullHeight / 3f
+                    val initialWidth = fullWidth / 3f
+
+                    // Start the animation near the power button, at one-third size, since the
+                    // camera was launched from the power button.
+                    return TransitionAnimator.State(
+                        top = (powerButtonY - initialHeight / 2f).toInt(),
+                        bottom = (powerButtonY + initialHeight / 2f).toInt(),
+                        left = (fullWidth - initialWidth).toInt(),
+                        right = fullWidth,
+                        topCornerRadius = windowCornerRadius,
+                        bottomCornerRadius = windowCornerRadius,
+                    )
+                } else {
+                    val initialHeight = fullHeight / 2f
+                    val initialWidth = fullWidth / 2f
+
+                    // Start the animation in the center of the screen, scaled down to half
+                    // size.
+                    return TransitionAnimator.State(
+                        top = (fullHeight - initialHeight).toInt() / 2,
+                        bottom = (initialHeight + (fullHeight - initialHeight) / 2).toInt(),
+                        left = (fullWidth - initialWidth).toInt() / 2,
+                        right = (initialWidth + (fullWidth - initialWidth) / 2).toInt(),
+                        topCornerRadius = windowCornerRadius,
+                        bottomCornerRadius = windowCornerRadius,
+                    )
+                }
+            }
+        }
+
+    private fun createInteractionJankMonitorConf(
+        cuj: Int,
+        tag: String?
+    ): InteractionJankMonitor.Configuration.Builder {
+        val builder =
+            InteractionJankMonitor.Configuration.Builder.withView(
+                cuj,
+                keyguardViewController.get().getViewRootImpl().view
+            )
+        return if (tag != null) builder.setTag(tag) else builder
+    }
+
+    companion object {
+        val TAG = "WindowManagerOcclusion"
+    }
+}
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 968c3e3..a243b8e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -43,6 +43,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.ui.viewmodel.DreamViewModel;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.SystemPropertiesHelper;
@@ -50,6 +51,7 @@
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager;
+import com.android.systemui.keyguard.WindowManagerOcclusionManager;
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
 import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthModule;
 import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
@@ -58,7 +60,6 @@
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger;
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl;
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransitionModule;
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.settings.UserTracker;
@@ -159,11 +160,12 @@
             SystemSettings systemSettings,
             SystemClock systemClock,
             @Main CoroutineDispatcher mainDispatcher,
-            Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
+            Lazy<DreamViewModel> dreamViewModel,
             SystemPropertiesHelper systemPropertiesHelper,
             Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
             SelectedUserInteractor selectedUserInteractor,
-            KeyguardInteractor keyguardInteractor) {
+            KeyguardInteractor keyguardInteractor,
+            WindowManagerOcclusionManager windowManagerOcclusionManager) {
         return new KeyguardViewMediator(
                 context,
                 uiEventLogger,
@@ -205,11 +207,12 @@
                 systemSettings,
                 systemClock,
                 mainDispatcher,
-                dreamingToLockscreenTransitionViewModel,
+                dreamViewModel,
                 systemPropertiesHelper,
                 wmLockscreenVisibilityManager,
                 selectedUserInteractor,
-                keyguardInteractor);
+                keyguardInteractor,
+                windowManagerOcclusionManager);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
index 0659c7c..a49b3ae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
@@ -19,7 +19,6 @@
 
 import android.os.Handler
 import android.util.Log
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
@@ -30,7 +29,6 @@
 import java.io.PrintWriter
 import java.util.TreeMap
 import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 
@@ -49,7 +47,6 @@
 class KeyguardBlueprintRepository
 @Inject
 constructor(
-    configurationRepository: ConfigurationRepository,
     blueprints: Set<@JvmSuppressWildcards KeyguardBlueprint>,
     @Main val handler: Handler,
     val assert: ThreadAssert,
@@ -60,7 +57,6 @@
         TreeMap<String, KeyguardBlueprint>().apply { putAll(blueprints.associateBy { it.id }) }
     val blueprint: MutableStateFlow<KeyguardBlueprint> = MutableStateFlow(blueprintIdMap[DEFAULT]!!)
     val refreshTransition = MutableSharedFlow<Config>(extraBufferCapacity = 1)
-    val configurationChange: Flow<Unit> = configurationRepository.onAnyConfigurationChange
     private var targetTransitionConfig: Config? = null
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
index 59288a1..7ad5aac 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
@@ -41,7 +41,6 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.flow.onStart
@@ -60,7 +59,7 @@
 
     val currentClock: StateFlow<ClockController?>
 
-    val previewClock: StateFlow<ClockController>
+    val previewClockPair: StateFlow<Pair<ClockController, ClockController>>
 
     val clockEventController: ClockEventController
     fun setClockSize(@ClockSize size: Int)
@@ -111,7 +110,6 @@
                 awaitClose { clockRegistry.unregisterClockChangeListener(listener) }
             }
             .mapNotNull { it }
-            .distinctUntilChanged()
 
     override val currentClock: StateFlow<ClockController?> =
         currentClockId
@@ -122,13 +120,14 @@
                 initialValue = clockRegistry.createCurrentClock()
             )
 
-    override val previewClock: StateFlow<ClockController> =
+    override val previewClockPair: StateFlow<Pair<ClockController, ClockController>> =
         currentClockId
-            .map { clockRegistry.createCurrentClock() }
+            .map { Pair(clockRegistry.createCurrentClock(), clockRegistry.createCurrentClock()) }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = clockRegistry.createCurrentClock()
+                initialValue =
+                    Pair(clockRegistry.createCurrentClock(), clockRegistry.createCurrentClock())
             )
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepository.kt
new file mode 100644
index 0000000..e3654b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepository.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/**
+ * Information about the SHOW_WHEN_LOCKED activity that is either newly on top of the task stack, or
+ * newly not on top of the task stack.
+ */
+data class ShowWhenLockedActivityInfo(
+    /** Whether the activity is on top. If not, we're unoccluding and will be animating it out. */
+    val isOnTop: Boolean,
+
+    /**
+     * Information about the activity, which we use for transition internals and also to customize
+     * animations.
+     */
+    val taskInfo: RunningTaskInfo? = null
+) {
+    fun isDream(): Boolean {
+        return taskInfo?.topActivityType == WindowConfiguration.ACTIVITY_TYPE_DREAM
+    }
+}
+
+/**
+ * Maintains state about "occluding" activities - activities with FLAG_SHOW_WHEN_LOCKED, which are
+ * capable of displaying over the lockscreen while the device is still locked (such as Google Maps
+ * navigation).
+ *
+ * Window Manager considers the device to be in the "occluded" state whenever such an activity is on
+ * top of the task stack, including while we're unlocked, while keyguard code considers us to be
+ * occluded only when we're locked, with an occluding activity currently displaying over the
+ * lockscreen.
+ *
+ * This dual definition is confusing, so this repository collects all of the signals WM gives us,
+ * and consolidates them into [showWhenLockedActivityInfo.isOnTop], which is the actual question WM
+ * is answering when they say whether we're 'occluded'. Keyguard then uses this signal to
+ * conditionally transition to [KeyguardState.OCCLUDED] where appropriate.
+ */
+@SysUISingleton
+class KeyguardOcclusionRepository @Inject constructor() {
+    val showWhenLockedActivityInfo = MutableStateFlow(ShowWhenLockedActivityInfo(isOnTop = false))
+
+    /**
+     * Sets whether there's a SHOW_WHEN_LOCKED activity on top of the task stack, and optionally,
+     * information about the activity itself.
+     *
+     * If no value is provided for [taskInfo], we'll default to the current [taskInfo].
+     *
+     * The [taskInfo] is always present when this method is called from the occlude/unocclude
+     * animation runners. We use the default when calling from [KeyguardService.isOccluded], since
+     * we only receive a true/false value there. isOccluded is mostly redundant - it's almost always
+     * called with true after an occlusion animation has started, and with false after an unocclude
+     * animation has started. In those cases, we don't want to clear out the taskInfo just because
+     * it wasn't available at that call site.
+     */
+    fun setShowWhenLockedActivityInfo(
+        onTop: Boolean,
+        taskInfo: RunningTaskInfo? = showWhenLockedActivityInfo.value.taskInfo
+    ) {
+        showWhenLockedActivityInfo.value =
+            ShowWhenLockedActivityInfo(
+                isOnTop = onTop,
+                taskInfo = taskInfo,
+            )
+    }
+}
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 64e2870..1298fa5 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
@@ -24,7 +24,6 @@
 import com.android.systemui.biometrics.data.repository.FacePropertyRepository
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.common.shared.model.Position
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
@@ -80,12 +79,6 @@
     val keyguardAlpha: StateFlow<Float>
 
     /**
-     * Observable of the relative offset of the lock-screen clock from its natural position on the
-     * screen.
-     */
-    val clockPosition: StateFlow<Position>
-
-    /**
      * Observable for whether the keyguard is showing.
      *
      * Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in
@@ -95,6 +88,7 @@
     val isKeyguardShowing: Flow<Boolean>
 
     /** Is an activity showing over the keyguard? */
+    @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.OCCLUDED")
     val isKeyguardOccluded: Flow<Boolean>
 
     /**
@@ -240,11 +234,6 @@
     fun setKeyguardAlpha(alpha: Float)
 
     /**
-     * Sets the relative offset of the lock-screen clock from its natural position on the screen.
-     */
-    fun setClockPosition(x: Int, y: Int)
-
-    /**
      * Returns whether the keyguard bottom area should be constrained to the top of the lock icon
      */
     fun isUdfpsSupported(): Boolean
@@ -323,9 +312,6 @@
     private val _keyguardAlpha = MutableStateFlow(1f)
     override val keyguardAlpha = _keyguardAlpha.asStateFlow()
 
-    private val _clockPosition = MutableStateFlow(Position(0, 0))
-    override val clockPosition = _clockPosition.asStateFlow()
-
     private val _clockShouldBeCentered = MutableStateFlow(true)
     override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered.asStateFlow()
 
@@ -677,10 +663,6 @@
         _keyguardAlpha.value = alpha
     }
 
-    override fun setClockPosition(x: Int, y: Int) {
-        _clockPosition.value = Position(x, y)
-    }
-
     override fun isUdfpsSupported(): Boolean = keyguardUpdateMonitor.isUdfpsSupported
 
     override fun setQuickSettingsVisible(isVisible: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
index 9b3f13d..eac476f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
-import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.data.repository.PowerRepository
 import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.power.shared.model.WakeSleepReason.TAP
 import com.android.systemui.res.R
@@ -47,6 +47,7 @@
 import kotlinx.coroutines.flow.map
 
 val DEFAULT_REVEAL_EFFECT = LiftReveal
+const val DEFAULT_REVEAL_DURATION = 500L
 
 /**
  * Encapsulates state relevant to the light reveal scrim, the view used to reveal/hide screen
@@ -63,7 +64,9 @@
 
     val revealAmount: Flow<Float>
 
-    fun startRevealAmountAnimator(reveal: Boolean)
+    val isAnimating: Boolean
+
+    fun startRevealAmountAnimator(reveal: Boolean, duration: Long = DEFAULT_REVEAL_DURATION)
 }
 
 @SysUISingleton
@@ -72,10 +75,9 @@
 constructor(
     keyguardRepository: KeyguardRepository,
     val context: Context,
-    powerInteractor: PowerInteractor,
+    powerRepository: PowerRepository,
     private val scrimLogger: ScrimLogger,
 ) : LightRevealScrimRepository {
-
     companion object {
         val TAG = LightRevealScrimRepository::class.simpleName!!
     }
@@ -126,7 +128,7 @@
 
     /** The reveal effect we'll use for the next non-biometric unlock (tap, power button, etc). */
     private val nonBiometricRevealEffect: Flow<LightRevealEffect?> =
-        powerInteractor.detailedWakefulness.flatMapLatest { wakefulnessModel ->
+        powerRepository.wakefulness.flatMapLatest { wakefulnessModel ->
             when {
                 wakefulnessModel.isAwakeOrAsleepFrom(WakeSleepReason.POWER_BUTTON) ->
                     powerButtonRevealEffect
@@ -135,7 +137,7 @@
             }
         }
 
-    private val revealAmountAnimator = ValueAnimator.ofFloat(0f, 1f).apply { duration = 500 }
+    private val revealAmountAnimator = ValueAnimator.ofFloat(0f, 1f)
 
     override val revealAmount: Flow<Float> = callbackFlow {
         val updateListener =
@@ -150,14 +152,21 @@
         revealAmountAnimator.addUpdateListener(updateListener)
         awaitClose { revealAmountAnimator.removeUpdateListener(updateListener) }
     }
+    override val isAnimating: Boolean
+        get() = revealAmountAnimator.isRunning
 
     private var willBeOrIsRevealed: Boolean? = null
 
-    override fun startRevealAmountAnimator(reveal: Boolean) {
+    override fun startRevealAmountAnimator(reveal: Boolean, duration: Long) {
         if (reveal == willBeOrIsRevealed) return
         willBeOrIsRevealed = reveal
-        if (reveal) revealAmountAnimator.start() else revealAmountAnimator.reverse()
-        scrimLogger.d(TAG, "startRevealAmountAnimator, reveal: ", reveal)
+        revealAmountAnimator.duration = duration
+        if (reveal && !revealAmountAnimator.isRunning) {
+            revealAmountAnimator.start()
+        } else {
+            revealAmountAnimator.reverse()
+        }
+        scrimLogger.d(TAG, "startRevealAmountAnimator, reveal", reveal)
     }
 
     override val revealEffect =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
index 7ae70a9..ca86289 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
@@ -19,7 +19,7 @@
 
 import android.content.Context
 import androidx.annotation.DimenRes
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.doze.util.BurnInHelperWrapper
@@ -47,13 +47,15 @@
     private val context: Context,
     private val burnInHelperWrapper: BurnInHelperWrapper,
     @Application private val scope: CoroutineScope,
-    private val configurationRepository: ConfigurationRepository,
+    private val configurationInteractor: ConfigurationInteractor,
     private val keyguardInteractor: KeyguardInteractor,
 ) {
     val deviceEntryIconXOffset: StateFlow<Int> =
         burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_x, isXAxis = true)
+            .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
     val deviceEntryIconYOffset: StateFlow<Int> =
         burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_y, isXAxis = false)
+            .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
     val udfpsProgress: StateFlow<Float> =
         keyguardInteractor.dozeTimeTick
             .mapLatest { burnInHelperWrapper.burnInProgressOffset() }
@@ -63,18 +65,18 @@
                 burnInHelperWrapper.burnInProgressOffset()
             )
 
-    val keyguardBurnIn: Flow<BurnInModel> =
-        combine(
-                burnInOffset(R.dimen.burn_in_prevention_offset_x, isXAxis = true),
-                burnInOffset(R.dimen.burn_in_prevention_offset_y, isXAxis = false).map {
-                    it * 2 -
-                        context.resources.getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y)
+    /** Given the max x,y dimens, determine the current translation shifts. */
+    fun burnIn(xDimenResourceId: Int, yDimenResourceId: Int): Flow<BurnInModel> {
+        return combine(
+                burnInOffset(xDimenResourceId, isXAxis = true),
+                burnInOffset(yDimenResourceId, isXAxis = false).map {
+                    it * 2 - context.resources.getDimensionPixelSize(yDimenResourceId)
                 }
             ) { translationX, translationY ->
                 BurnInModel(translationX, translationY, burnInHelperWrapper.burnInScale())
             }
             .distinctUntilChanged()
-            .stateIn(scope, SharingStarted.Lazily, BurnInModel())
+    }
 
     /**
      * Use for max burn-in offsets that are NOT specified in pixels. This flow will recalculate the
@@ -84,23 +86,14 @@
     private fun burnInOffset(
         @DimenRes maxBurnInOffsetResourceId: Int,
         isXAxis: Boolean,
-    ): StateFlow<Int> {
-        return configurationRepository.onAnyConfigurationChange
-            .flatMapLatest {
-                val maxBurnInOffsetPixels =
-                    context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
-                keyguardInteractor.dozeTimeTick.mapLatest {
-                    calculateOffset(maxBurnInOffsetPixels, isXAxis)
-                }
+    ): Flow<Int> {
+        return configurationInteractor.onAnyConfigurationChange.flatMapLatest {
+            val maxBurnInOffsetPixels =
+                context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
+            keyguardInteractor.dozeTimeTick.mapLatest {
+                calculateOffset(maxBurnInOffsetPixels, isXAxis)
             }
-            .stateIn(
-                scope,
-                SharingStarted.Lazily,
-                calculateOffset(
-                    context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId),
-                    isXAxis,
-                )
-            )
+        }
     }
 
     /**
@@ -111,24 +104,14 @@
     private fun burnInOffsetDefinedInPixels(
         @DimenRes maxBurnInOffsetResourceId: Int,
         isXAxis: Boolean,
-    ): StateFlow<Int> {
-        return configurationRepository.scaleForResolution
-            .flatMapLatest { scale ->
-                val maxBurnInOffsetPixels =
-                    context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
-                keyguardInteractor.dozeTimeTick.mapLatest {
-                    calculateOffset(maxBurnInOffsetPixels, isXAxis, scale)
-                }
+    ): Flow<Int> {
+        return configurationInteractor.scaleForResolution.flatMapLatest { scale ->
+            val maxBurnInOffsetPixels =
+                context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
+            keyguardInteractor.dozeTimeTick.mapLatest {
+                calculateOffset(maxBurnInOffsetPixels, isXAxis, scale)
             }
-            .stateIn(
-                scope,
-                SharingStarted.WhileSubscribed(),
-                calculateOffset(
-                    context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId),
-                    isXAxis,
-                    configurationRepository.getResolutionScale(),
-                )
-            )
+        }
     }
 
     private fun calculateOffset(
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 9fc3692..88ddfd4 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
@@ -21,6 +21,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -31,8 +32,8 @@
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.FlowPreview
-import kotlinx.coroutines.flow.sample
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -46,13 +47,16 @@
     @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
     private val communalInteractor: CommunalInteractor,
-    private val powerInteractor: PowerInteractor,
+    powerInteractor: PowerInteractor,
+    keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.ALTERNATE_BOUNCER,
         transitionInteractor = transitionInteractor,
         mainDispatcher = mainDispatcher,
         bgDispatcher = bgDispatcher,
+        powerInteractor = powerInteractor,
+        keyguardOcclusionInteractor = keyguardOcclusionInteractor,
     ) {
 
     override fun start() {
@@ -62,7 +66,6 @@
         listenForTransitionToCamera(scope, keyguardInteractor)
     }
 
-    @FlowPreview
     private fun listenForAlternateBouncerToLockscreenHubAodOrDozing() {
         scope.launch {
             keyguardInteractor.alternateBouncerShowing
@@ -71,7 +74,7 @@
                 // happening prematurely.
                 // This should eventually be removed in favor of
                 // [KeyguardTransitionInteractor#startDismissKeyguardTransition]
-                .sample(150L)
+                .onEach { delay(150L) }
                 .sampleCombine(
                     keyguardInteractor.primaryBouncerShowing,
                     startedKeyguardTransitionStep,
@@ -113,6 +116,11 @@
     }
 
     private fun listenForAlternateBouncerToGone() {
+        if (KeyguardWmStateRefactor.isEnabled) {
+            // Handled via #dismissAlternateBouncer.
+            return
+        }
+
         scope.launch {
             keyguardInteractor.isKeyguardGoingAway
                 .sampleUtil(finishedKeyguardState, ::Pair)
@@ -150,6 +158,10 @@
         }
     }
 
+    fun dismissAlternateBouncer() {
+        scope.launch { startTransitionTo(KeyguardState.GONE) }
+    }
+
     companion object {
         const val TAG = "FromAlternateBouncerTransitionInteractor"
         val TRANSITION_DURATION_MS = 300.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 e0b5c0e..9040e03 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
@@ -27,12 +27,17 @@
 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.keyguard.shared.model.TransitionStep
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.sample
 import com.android.systemui.util.kotlin.sample
+import java.util.UUID
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -45,67 +50,126 @@
     @Background bgDispatcher: CoroutineDispatcher,
     @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
+    powerInteractor: PowerInteractor,
+    keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.AOD,
         transitionInteractor = transitionInteractor,
         mainDispatcher = mainDispatcher,
         bgDispatcher = bgDispatcher,
+        powerInteractor = powerInteractor,
+        keyguardOcclusionInteractor = keyguardOcclusionInteractor,
     ) {
 
     override fun start() {
-        listenForAodToLockscreen()
+        listenForAodToAwake()
+        listenForAodToOccluded()
         listenForAodToPrimaryBouncer()
         listenForAodToGone()
-        listenForAodToOccluded()
         listenForTransitionToCamera(scope, keyguardInteractor)
     }
 
     /**
+     * Listen for the signal that we're waking up and figure what state we need to transition to.
+     */
+    private fun listenForAodToAwake() {
+        val transitionToLockscreen: suspend (TransitionStep) -> UUID? =
+            { startedStep: TransitionStep ->
+                val modeOnCanceled =
+                    if (startedStep.from == KeyguardState.LOCKSCREEN) {
+                        TransitionModeOnCanceled.REVERSE
+                    } else if (startedStep.from == KeyguardState.GONE) {
+                        TransitionModeOnCanceled.RESET
+                    } else {
+                        TransitionModeOnCanceled.LAST_VALUE
+                    }
+                startTransitionTo(
+                    toState = KeyguardState.LOCKSCREEN,
+                    modeOnCanceled = modeOnCanceled,
+                )
+            }
+
+        if (KeyguardWmStateRefactor.isEnabled) {
+            // The refactor uses PowerInteractor's wakefulness, which is the earliest wake signal
+            // available. We have all of the information we need at this time to make a decision
+            // about where to transition.
+            scope.launch {
+                powerInteractor.detailedWakefulness
+                    // React only to wake events.
+                    .filter { it.isAwake() }
+                    .sample(
+                        startedKeyguardTransitionStep,
+                        keyguardInteractor.biometricUnlockState,
+                        keyguardInteractor.primaryBouncerShowing,
+                    )
+                    // Make sure we've at least STARTED a transition to AOD.
+                    .filter { (_, startedStep, _, _) -> startedStep.to == KeyguardState.AOD }
+                    .collect { (_, startedStep, biometricUnlockState, primaryBouncerShowing) ->
+                        // Check with the superclass to see if an occlusion transition is needed.
+                        // Also, don't react to wake and unlock events, as we'll be receiving a call
+                        // to #dismissAod() shortly when the authentication completes.
+                        if (
+                            !maybeStartTransitionToOccludedOrInsecureCamera() &&
+                                !isWakeAndUnlock(biometricUnlockState) &&
+                                !primaryBouncerShowing
+                        ) {
+                            transitionToLockscreen(startedStep)
+                        }
+                    }
+            }
+        } else {
+            scope.launch {
+                keyguardInteractor
+                    .dozeTransitionTo(DozeStateModel.FINISH)
+                    .sample(
+                        keyguardInteractor.isKeyguardShowing,
+                        startedKeyguardTransitionStep,
+                        keyguardInteractor.isKeyguardOccluded,
+                        keyguardInteractor.biometricUnlockState,
+                        keyguardInteractor.primaryBouncerShowing,
+                    )
+                    .collect {
+                        (
+                            _,
+                            isKeyguardShowing,
+                            lastStartedStep,
+                            occluded,
+                            biometricUnlockState,
+                            primaryBouncerShowing) ->
+                        if (
+                            lastStartedStep.to == KeyguardState.AOD &&
+                                !occluded &&
+                                !isWakeAndUnlock(biometricUnlockState) &&
+                                isKeyguardShowing &&
+                                !primaryBouncerShowing
+                        ) {
+                            transitionToLockscreen(lastStartedStep)
+                        }
+                    }
+            }
+        }
+    }
+
+    /**
      * There are cases where the transition to AOD begins but never completes, such as tapping power
      * during an incoming phone call when unlocked. In this case, GONE->AOD should be interrupted to
      * run AOD->OCCLUDED.
      */
     private fun listenForAodToOccluded() {
-        scope.launch {
-            keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect {
-                (isOccluded, startedKeyguardState) ->
-                if (isOccluded && startedKeyguardState == KeyguardState.AOD) {
-                    startTransitionTo(
-                        toState = KeyguardState.OCCLUDED,
-                        modeOnCanceled = TransitionModeOnCanceled.RESET
-                    )
-                }
-            }
+        if (KeyguardWmStateRefactor.isEnabled) {
+            // Handled by calls to maybeStartTransitionToOccludedOrInsecureCamera on waking.
+            return
         }
-    }
 
-    private fun listenForAodToLockscreen() {
         scope.launch {
-            keyguardInteractor
-                .dozeTransitionTo(DozeStateModel.FINISH)
-                .sample(
-                    startedKeyguardTransitionStep,
-                    keyguardInteractor.isKeyguardOccluded,
-                    keyguardInteractor.biometricUnlockState,
-                )
-                .collect { (_, lastStartedStep, occluded, biometricUnlockState) ->
-                    if (
-                        lastStartedStep.to == KeyguardState.AOD &&
-                            !occluded &&
-                            !isWakeAndUnlock(biometricUnlockState)
-                    ) {
-                        val modeOnCanceled =
-                            if (lastStartedStep.from == KeyguardState.LOCKSCREEN) {
-                                TransitionModeOnCanceled.REVERSE
-                            } else if (lastStartedStep.from == KeyguardState.GONE) {
-                                TransitionModeOnCanceled.RESET
-                            } else {
-                                TransitionModeOnCanceled.LAST_VALUE
-                            }
+            keyguardInteractor.isKeyguardOccluded
+                .sample(startedKeyguardTransitionStep, ::Pair)
+                .collect { (isOccluded, lastStartedStep) ->
+                    if (isOccluded && lastStartedStep.to == KeyguardState.AOD) {
                         startTransitionTo(
-                            toState = KeyguardState.LOCKSCREEN,
-                            modeOnCanceled = modeOnCanceled,
+                            toState = KeyguardState.OCCLUDED,
+                            modeOnCanceled = TransitionModeOnCanceled.RESET
                         )
                     }
                 }
@@ -130,17 +194,36 @@
 
     private fun listenForAodToGone() {
         if (KeyguardWmStateRefactor.isEnabled) {
+            // Handled via #dismissAod.
             return
         }
 
         scope.launch {
-            keyguardInteractor.biometricUnlockState.sample(finishedKeyguardState, ::Pair).collect {
-                (biometricUnlockState, keyguardState) ->
-                KeyguardWmStateRefactor.assertInLegacyMode()
-                if (keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)) {
-                    startTransitionTo(KeyguardState.GONE)
+            powerInteractor.isAwake
+                .debounce(50L)
+                .sample(
+                    keyguardInteractor.biometricUnlockState,
+                    startedKeyguardTransitionStep,
+                    keyguardInteractor.isKeyguardShowing,
+                    keyguardInteractor.isKeyguardDismissible,
+                )
+                .collect {
+                    (
+                        isAwake,
+                        biometricUnlockState,
+                        lastStartedTransitionStep,
+                        isKeyguardShowing,
+                        isKeyguardDismissible) ->
+                    KeyguardWmStateRefactor.assertInLegacyMode()
+                    if (
+                        isAwake &&
+                            lastStartedTransitionStep.to == KeyguardState.AOD &&
+                            (isWakeAndUnlock(biometricUnlockState) ||
+                                (!isKeyguardShowing && isKeyguardDismissible))
+                    ) {
+                        startTransitionTo(KeyguardState.GONE)
+                    }
                 }
-            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 54d5908..57b2a63 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -22,17 +22,20 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.util.kotlin.Utils.Companion.toQuad
-import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.kotlin.Utils.Companion.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -45,39 +48,116 @@
     @Background bgDispatcher: CoroutineDispatcher,
     @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
-    private val powerInteractor: PowerInteractor,
+    powerInteractor: PowerInteractor,
     private val communalInteractor: CommunalInteractor,
+    keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.DOZING,
         transitionInteractor = transitionInteractor,
         mainDispatcher = mainDispatcher,
         bgDispatcher = bgDispatcher,
+        powerInteractor = powerInteractor,
+        keyguardOcclusionInteractor = keyguardOcclusionInteractor,
     ) {
 
     override fun start() {
-        listenForDozingToLockscreenHubOrOccluded()
-        listenForDozingToGone()
+        listenForDozingToAny()
+        listenForWakeFromDozing()
         listenForTransitionToCamera(scope, keyguardInteractor)
     }
 
-    private fun listenForDozingToLockscreenHubOrOccluded() {
+    private val canDismissLockScreen: Flow<Boolean> =
+        combine(
+            keyguardInteractor.isKeyguardShowing,
+            keyguardInteractor.isKeyguardDismissible,
+        ) { isKeyguardShowing, isKeyguardDismissible ->
+            isKeyguardDismissible && !isKeyguardShowing
+        }
+
+    private fun listenForDozingToAny() {
+        if (KeyguardWmStateRefactor.isEnabled) {
+            return
+        }
+
         scope.launch {
             powerInteractor.isAwake
+                .debounce(50L)
                 .sample(
-                    combine(
-                        startedKeyguardTransitionStep,
-                        keyguardInteractor.isKeyguardOccluded,
-                        communalInteractor.isIdleOnCommunal,
-                        ::Triple
-                    ),
-                    ::toQuad
+                    keyguardInteractor.biometricUnlockState,
+                    startedKeyguardTransitionStep,
+                    keyguardInteractor.isKeyguardOccluded,
+                    communalInteractor.isIdleOnCommunal,
+                    canDismissLockScreen,
+                    keyguardInteractor.primaryBouncerShowing,
                 )
-                .collect { (isAwake, lastStartedTransition, occluded, isIdleOnCommunal) ->
-                    if (isAwake && lastStartedTransition.to == KeyguardState.DOZING) {
+                .collect {
+                    (
+                        isAwake,
+                        biometricUnlockState,
+                        lastStartedTransition,
+                        occluded,
+                        isIdleOnCommunal,
+                        canDismissLockScreen,
+                        primaryBouncerShowing) ->
+                    if (!(isAwake && lastStartedTransition.to == KeyguardState.DOZING)) {
+                        return@collect
+                    }
+                    startTransitionTo(
+                        if (isWakeAndUnlock(biometricUnlockState)) {
+                            KeyguardState.GONE
+                        } else if (canDismissLockScreen) {
+                            KeyguardState.GONE
+                        } else if (primaryBouncerShowing) {
+                            KeyguardState.PRIMARY_BOUNCER
+                        } else if (occluded) {
+                            KeyguardState.OCCLUDED
+                        } else if (isIdleOnCommunal) {
+                            KeyguardState.GLANCEABLE_HUB
+                        } else {
+                            KeyguardState.LOCKSCREEN
+                        }
+                    )
+                }
+        }
+    }
+
+    /** Figure out what state to transition to when we awake from DOZING. */
+    private fun listenForWakeFromDozing() {
+        if (!KeyguardWmStateRefactor.isEnabled) {
+            return
+        }
+
+        scope.launch {
+            powerInteractor.detailedWakefulness
+                .filter { it.isAwake() }
+                .sample(
+                    startedKeyguardTransitionStep,
+                    communalInteractor.isIdleOnCommunal,
+                    keyguardInteractor.biometricUnlockState,
+                    canDismissLockScreen,
+                    keyguardInteractor.primaryBouncerShowing,
+                )
+                // If we haven't at least STARTED a transition to DOZING, ignore.
+                .filter { (_, startedStep, _, _) -> startedStep.to == KeyguardState.DOZING }
+                .collect {
+                    (
+                        _,
+                        _,
+                        isIdleOnCommunal,
+                        biometricUnlockState,
+                        canDismissLockscreen,
+                        primaryBouncerShowing) ->
+                    if (
+                        !maybeStartTransitionToOccludedOrInsecureCamera() &&
+                            // Handled by dismissFromDozing().
+                            !isWakeAndUnlock(biometricUnlockState)
+                    ) {
                         startTransitionTo(
-                            if (occluded) {
-                                KeyguardState.OCCLUDED
+                            if (canDismissLockscreen) {
+                                KeyguardState.GONE
+                            } else if (primaryBouncerShowing) {
+                                KeyguardState.PRIMARY_BOUNCER
                             } else if (isIdleOnCommunal) {
                                 KeyguardState.GLANCEABLE_HUB
                             } else {
@@ -89,19 +169,9 @@
         }
     }
 
-    private fun listenForDozingToGone() {
-        scope.launch {
-            keyguardInteractor.biometricUnlockState
-                .sample(startedKeyguardTransitionStep, ::Pair)
-                .collect { (biometricUnlockState, lastStartedTransition) ->
-                    if (
-                        lastStartedTransition.to == KeyguardState.DOZING &&
-                            isWakeAndUnlock(biometricUnlockState)
-                    ) {
-                        startTransitionTo(KeyguardState.GONE)
-                    }
-                }
-        }
+    /** Dismisses keyguard from the DOZING state. */
+    fun dismissFromDozing() {
+        scope.launch { startTransitionTo(KeyguardState.GONE) }
     }
 
     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
index a6cdaa8..6433d0e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
@@ -46,12 +47,16 @@
     @Background bgDispatcher: CoroutineDispatcher,
     @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
+    powerInteractor: PowerInteractor,
+    keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
         transitionInteractor = transitionInteractor,
         mainDispatcher = mainDispatcher,
         bgDispatcher = bgDispatcher,
+        powerInteractor = powerInteractor,
+        keyguardOcclusionInteractor = keyguardOcclusionInteractor,
     ) {
 
     override fun start() {
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 acfa107..9a6088d 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
@@ -23,17 +23,23 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.Utils
+import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
 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 kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -47,17 +53,23 @@
     @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
     private val glanceableHubTransitions: GlanceableHubTransitions,
+    powerInteractor: PowerInteractor,
+    keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.DREAMING,
         transitionInteractor = transitionInteractor,
         mainDispatcher = mainDispatcher,
         bgDispatcher = bgDispatcher,
+        powerInteractor = powerInteractor,
+        keyguardOcclusionInteractor = keyguardOcclusionInteractor,
     ) {
 
     override fun start() {
         listenForDreamingToOccluded()
-        listenForDreamingToGone()
+        listenForDreamingToGoneWhenDismissable()
+        listenForDreamingToGoneFromBiometricUnlock()
+        listenForDreamingToLockscreen()
         listenForDreamingToAodOrDozing()
         listenForTransitionToCamera(scope, keyguardInteractor)
         listenForDreamingToGlanceableHub()
@@ -76,6 +88,7 @@
 
     fun startToLockscreenTransition() {
         scope.launch {
+            KeyguardWmStateRefactor.isUnexpectedlyInLegacyMode()
             if (
                 transitionInteractor.startedKeyguardState.replayCache.last() ==
                     KeyguardState.DREAMING
@@ -85,23 +98,93 @@
         }
     }
 
-    private fun listenForDreamingToOccluded() {
+    fun startToGlanceableHubTransition() {
         scope.launch {
-            combine(keyguardInteractor.isKeyguardOccluded, keyguardInteractor.isDreaming, ::Pair)
-                .sample(startedKeyguardTransitionStep, ::toTriple)
-                .collect { (isOccluded, isDreaming, lastStartedTransition) ->
-                    if (
-                        isOccluded &&
-                            !isDreaming &&
-                            lastStartedTransition.to == KeyguardState.DREAMING
-                    ) {
-                        startTransitionTo(KeyguardState.OCCLUDED)
+            KeyguardWmStateRefactor.isUnexpectedlyInLegacyMode()
+            if (
+                transitionInteractor.startedKeyguardState.replayCache.last() ==
+                    KeyguardState.DREAMING
+            ) {
+                startTransitionTo(KeyguardState.GLANCEABLE_HUB)
+            }
+        }
+    }
+
+    private fun listenForDreamingToOccluded() {
+        if (KeyguardWmStateRefactor.isEnabled) {
+            scope.launch {
+                combine(
+                        keyguardInteractor.isDreaming,
+                        keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop,
+                        ::Pair
+                    )
+                    .sample(startedKeyguardTransitionStep, ::toTriple)
+                    .filter { (isDreaming, _, startedStep) ->
+                        !isDreaming && startedStep.to == KeyguardState.DREAMING
+                    }
+                    .collect { maybeStartTransitionToOccludedOrInsecureCamera() }
+            }
+        } else {
+            scope.launch {
+                combine(
+                        keyguardInteractor.isKeyguardOccluded,
+                        keyguardInteractor.isDreaming,
+                        ::Pair
+                    )
+                    .sample(startedKeyguardTransitionStep, Utils.Companion::toTriple)
+                    .collect { (isOccluded, isDreaming, lastStartedTransition) ->
+                        if (
+                            isOccluded &&
+                                !isDreaming &&
+                                lastStartedTransition.to == KeyguardState.DREAMING
+                        ) {
+                            startTransitionTo(KeyguardState.OCCLUDED)
+                        }
+                    }
+            }
+        }
+    }
+
+    private fun listenForDreamingToLockscreen() {
+        if (!KeyguardWmStateRefactor.isEnabled) {
+            return
+        }
+
+        scope.launch {
+            keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop
+                .filter { onTop -> !onTop }
+                .sample(startedKeyguardState)
+                .collect { startedState ->
+                    if (startedState == KeyguardState.DREAMING) {
+                        startTransitionTo(KeyguardState.LOCKSCREEN)
                     }
                 }
         }
     }
 
-    private fun listenForDreamingToGone() {
+    private fun listenForDreamingToGoneWhenDismissable() {
+        scope.launch {
+            keyguardInteractor.isAbleToDream
+                .sampleCombine(
+                    keyguardInteractor.isKeyguardShowing,
+                    keyguardInteractor.isKeyguardDismissible,
+                    startedKeyguardTransitionStep,
+                )
+                .collect {
+                    (isDreaming, isKeyguardShowing, isKeyguardDismissible, lastStartedTransition) ->
+                    if (
+                        !isDreaming &&
+                            lastStartedTransition.to == KeyguardState.DREAMING &&
+                            isKeyguardDismissible &&
+                            !isKeyguardShowing
+                    ) {
+                        startTransitionTo(KeyguardState.GONE)
+                    }
+                }
+        }
+    }
+
+    private fun listenForDreamingToGoneFromBiometricUnlock() {
         scope.launch {
             keyguardInteractor.biometricUnlockState
                 .sample(startedKeyguardTransitionStep, ::Pair)
@@ -135,14 +218,18 @@
         return ValueAnimator().apply {
             interpolator = Interpolators.LINEAR
             duration =
-                if (toState == KeyguardState.LOCKSCREEN) TO_LOCKSCREEN_DURATION.inWholeMilliseconds
-                else DEFAULT_DURATION.inWholeMilliseconds
+                when (toState) {
+                    KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
+                    KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION
+                    else -> DEFAULT_DURATION
+                }.inWholeMilliseconds
         }
     }
 
     companion object {
         const val TAG = "FromDreamingTransitionInteractor"
         private val DEFAULT_DURATION = 500.milliseconds
+        val TO_GLANCEABLE_HUB_DURATION = 1.seconds
         val TO_LOCKSCREEN_DURATION = 1167.milliseconds
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 786c3c6..51bc3ae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
@@ -35,6 +36,7 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
@@ -49,14 +51,18 @@
     private val keyguardInteractor: KeyguardInteractor,
     override val transitionRepository: KeyguardTransitionRepository,
     transitionInteractor: KeyguardTransitionInteractor,
-    private val powerInteractor: PowerInteractor,
+    powerInteractor: PowerInteractor,
+    keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.GLANCEABLE_HUB,
         transitionInteractor = transitionInteractor,
         mainDispatcher = mainDispatcher,
         bgDispatcher = bgDispatcher,
+        powerInteractor = powerInteractor,
+        keyguardOcclusionInteractor = keyguardOcclusionInteractor,
     ) {
+
     override fun start() {
         if (!Flags.communalHub()) {
             return
@@ -151,14 +157,27 @@
     }
 
     private fun listenForHubToOccluded() {
-        scope.launch {
-            and(keyguardInteractor.isKeyguardOccluded, not(keyguardInteractor.isDreaming))
-                .sample(startedKeyguardState, ::Pair)
-                .collect { (isOccludedAndNotDreaming, keyguardState) ->
-                    if (isOccludedAndNotDreaming && keyguardState == fromState) {
-                        startTransitionTo(KeyguardState.OCCLUDED)
+        if (KeyguardWmStateRefactor.isEnabled) {
+            scope.launch {
+                keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop
+                    .filter { onTop -> onTop }
+                    .sample(startedKeyguardState)
+                    .collect {
+                        if (it == KeyguardState.GLANCEABLE_HUB) {
+                            maybeStartTransitionToOccludedOrInsecureCamera()
+                        }
                     }
-                }
+            }
+        } else {
+            scope.launch {
+                and(keyguardInteractor.isKeyguardOccluded, not(keyguardInteractor.isDreaming))
+                    .sample(startedKeyguardState, ::Pair)
+                    .collect { (isOccludedAndNotDreaming, keyguardState) ->
+                        if (isOccludedAndNotDreaming && keyguardState == fromState) {
+                            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 7593ac2..d5a9bd1 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,8 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
@@ -34,6 +36,8 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -46,14 +50,18 @@
     @Background bgDispatcher: CoroutineDispatcher,
     @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
-    private val powerInteractor: PowerInteractor,
+    powerInteractor: PowerInteractor,
     private val communalInteractor: CommunalInteractor,
+    keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+    private val biometricSettingsRepository: BiometricSettingsRepository,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.GONE,
         transitionInteractor = transitionInteractor,
         mainDispatcher = mainDispatcher,
         bgDispatcher = bgDispatcher,
+        powerInteractor = powerInteractor,
+        keyguardOcclusionInteractor = keyguardOcclusionInteractor,
     ) {
 
     override fun start() {
@@ -65,23 +73,46 @@
 
     // Primarily for when the user chooses to lock down the device
     private fun listenForGoneToLockscreenOrHub() {
-        scope.launch {
-            keyguardInteractor.isKeyguardShowing
-                .sample(
-                    startedKeyguardState,
-                    communalInteractor.isIdleOnCommunal,
-                )
-                .collect { (isKeyguardShowing, startedState, isIdleOnCommunal) ->
-                    if (isKeyguardShowing && startedState == KeyguardState.GONE) {
-                        val to =
-                            if (isIdleOnCommunal) {
-                                KeyguardState.GLANCEABLE_HUB
-                            } else {
-                                KeyguardState.LOCKSCREEN
-                            }
-                        startTransitionTo(to)
+        if (KeyguardWmStateRefactor.isEnabled) {
+            scope.launch {
+                biometricSettingsRepository.isCurrentUserInLockdown
+                    .distinctUntilChanged()
+                    .filter { inLockdown -> inLockdown }
+                    .sample(
+                        startedKeyguardState,
+                        communalInteractor.isIdleOnCommunal,
+                    )
+                    .collect { (_, startedState, isIdleOnCommunal) ->
+                        if (startedState == KeyguardState.GONE) {
+                            val to =
+                                if (isIdleOnCommunal) {
+                                    KeyguardState.GLANCEABLE_HUB
+                                } else {
+                                    KeyguardState.LOCKSCREEN
+                                }
+                            startTransitionTo(to, ownerReason = "User initiated lockdown")
+                        }
                     }
-                }
+            }
+        } else {
+            scope.launch {
+                keyguardInteractor.isKeyguardShowing
+                    .sample(
+                        startedKeyguardState,
+                        communalInteractor.isIdleOnCommunal,
+                    )
+                    .collect { (isKeyguardShowing, startedState, isIdleOnCommunal) ->
+                        if (isKeyguardShowing && startedState == KeyguardState.GONE) {
+                            val to =
+                                if (isIdleOnCommunal) {
+                                    KeyguardState.GLANCEABLE_HUB
+                                } else {
+                                    KeyguardState.LOCKSCREEN
+                                }
+                            startTransitionTo(to)
+                        }
+                    }
+            }
         }
     }
 
@@ -122,24 +153,10 @@
 
     private fun listenForGoneToAodOrDozing() {
         scope.launch {
-            powerInteractor.isAsleep
-                .sample(
-                    combine(
-                        startedKeyguardTransitionStep,
-                        keyguardInteractor.isAodAvailable,
-                        ::Pair
-                    ),
-                    ::toTriple
-                )
-                .collect { (isAsleep, lastStartedStep, isAodAvailable) ->
-                    if (lastStartedStep.to == KeyguardState.GONE && isAsleep) {
-                        startTransitionTo(
-                            toState =
-                                if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING,
-                            modeOnCanceled = TransitionModeOnCanceled.RESET,
-                        )
-                    }
-                }
+            listenForSleepTransition(
+                from = KeyguardState.GONE,
+                modeOnCanceledFromStartedStep = { TransitionModeOnCanceled.RESET },
+            )
         }
     }
 
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 7263ae9..12b27eb 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
@@ -33,7 +33,6 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.data.repository.ShadeRepository
 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
@@ -44,6 +43,8 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
@@ -61,21 +62,24 @@
     private val keyguardInteractor: KeyguardInteractor,
     private val flags: FeatureFlags,
     private val shadeRepository: ShadeRepository,
-    private val powerInteractor: PowerInteractor,
+    powerInteractor: PowerInteractor,
     private val glanceableHubTransitions: GlanceableHubTransitions,
     private val swipeToDismissInteractor: SwipeToDismissInteractor,
+    keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.LOCKSCREEN,
         transitionInteractor = transitionInteractor,
         mainDispatcher = mainDispatcher,
         bgDispatcher = bgDispatcher,
+        powerInteractor = powerInteractor,
+        keyguardOcclusionInteractor = keyguardOcclusionInteractor,
     ) {
 
     override fun start() {
         listenForLockscreenToGone()
         listenForLockscreenToGoneDragging()
-        listenForLockscreenToOccluded()
+        listenForLockscreenToOccludedOrDreaming()
         listenForLockscreenToAodOrDozing()
         listenForLockscreenToPrimaryBouncer()
         listenForLockscreenToDreaming()
@@ -114,6 +118,10 @@
     }
 
     private fun listenForLockscreenToDreaming() {
+        if (KeyguardWmStateRefactor.isEnabled) {
+            return
+        }
+
         val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
         scope.launch {
             keyguardInteractor.isAbleToDream
@@ -156,7 +164,10 @@
                     if (
                         isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
                     ) {
-                        startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
+                        startTransitionTo(
+                            KeyguardState.PRIMARY_BOUNCER,
+                            ownerReason = "#listenForLockscreenToPrimaryBouncer"
+                        )
                     }
                 }
         }
@@ -253,7 +264,8 @@
                                 transitionId =
                                     startTransitionTo(
                                         toState = KeyguardState.PRIMARY_BOUNCER,
-                                        animator = null, // transition will be manually controlled
+                                        animator = null, // transition will be manually controlled,
+                                        ownerReason = "#listenForLockscreenToPrimaryBouncerDragging"
                                     )
                             }
                         }
@@ -287,7 +299,7 @@
         if (KeyguardWmStateRefactor.isEnabled) {
             // When the refactor is enabled, we no longer use isKeyguardGoingAway.
             scope.launch {
-                swipeToDismissInteractor.dismissFling.collect { _ ->
+                swipeToDismissInteractor.dismissFling.filterNotNull().collect { _ ->
                     startTransitionTo(KeyguardState.GONE)
                 }
             }
@@ -308,47 +320,52 @@
         }
     }
 
-    private fun listenForLockscreenToOccluded() {
-        scope.launch {
-            keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect {
-                (isOccluded, keyguardState) ->
-                if (isOccluded && keyguardState == KeyguardState.LOCKSCREEN) {
-                    startTransitionTo(KeyguardState.OCCLUDED)
-                }
+    private fun listenForLockscreenToOccludedOrDreaming() {
+        if (KeyguardWmStateRefactor.isEnabled) {
+            scope.launch {
+                keyguardOcclusionInteractor.showWhenLockedActivityInfo
+                    .filter { it.isOnTop }
+                    .sample(startedKeyguardState, ::Pair)
+                    .collect { (taskInfo, startedState) ->
+                        if (startedState == KeyguardState.LOCKSCREEN) {
+                            startTransitionTo(
+                                if (taskInfo.isDream()) {
+                                    KeyguardState.DREAMING
+                                } else {
+                                    KeyguardState.OCCLUDED
+                                }
+                            )
+                        }
+                    }
+            }
+        } else {
+            scope.launch {
+                keyguardInteractor.isKeyguardOccluded
+                    .sample(startedKeyguardState, ::Pair)
+                    .collect { (isOccluded, keyguardState) ->
+                        if (isOccluded && keyguardState == KeyguardState.LOCKSCREEN) {
+                            startTransitionTo(KeyguardState.OCCLUDED)
+                        }
+                    }
             }
         }
     }
 
     private fun listenForLockscreenToAodOrDozing() {
         scope.launch {
-            powerInteractor.isAsleep
-                .sample(
-                    combine(
-                        startedKeyguardTransitionStep,
-                        keyguardInteractor.isAodAvailable,
-                        ::Pair
-                    ),
-                    ::toTriple
-                )
-                .collect { (isAsleep, lastStartedStep, isAodAvailable) ->
-                    if (lastStartedStep.to == KeyguardState.LOCKSCREEN && isAsleep) {
-                        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,
-                        )
+            listenForSleepTransition(
+                from = KeyguardState.LOCKSCREEN,
+                modeOnCanceledFromStartedStep = { startedStep ->
+                    if (
+                        transitionInteractor.asleepKeyguardState.value == KeyguardState.AOD &&
+                            startedStep.from == KeyguardState.AOD
+                    ) {
+                        TransitionModeOnCanceled.REVERSE
+                    } else {
+                        TransitionModeOnCanceled.LAST_VALUE
                     }
                 }
+            )
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index efb604d..f10327e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -22,17 +22,17 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.sample
-import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -45,20 +45,23 @@
     @Background bgDispatcher: CoroutineDispatcher,
     @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
-    private val powerInteractor: PowerInteractor,
+    powerInteractor: PowerInteractor,
     private val communalInteractor: CommunalInteractor,
+    keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.OCCLUDED,
         transitionInteractor = transitionInteractor,
         mainDispatcher = mainDispatcher,
         bgDispatcher = bgDispatcher,
+        powerInteractor = powerInteractor,
+        keyguardOcclusionInteractor = keyguardOcclusionInteractor,
     ) {
 
     override fun start() {
         listenForOccludedToLockscreenOrHub()
         listenForOccludedToDreaming()
-        listenForOccludedToAodOrDozing()
+        listenForOccludedToAsleep()
         listenForOccludedToGone()
         listenForOccludedToAlternateBouncer()
         listenForOccludedToPrimaryBouncer()
@@ -90,43 +93,72 @@
     }
 
     private fun listenForOccludedToLockscreenOrHub() {
+        if (KeyguardWmStateRefactor.isEnabled) {
+            scope.launch {
+                keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop
+                    .filter { onTop -> !onTop }
+                    .sample(
+                        startedKeyguardState,
+                        communalInteractor.isIdleOnCommunal,
+                    )
+                    .collect { (_, startedState, isIdleOnCommunal) ->
+                        // Occlusion signals come from the framework, and should interrupt any
+                        // existing transition
+                        if (startedState == KeyguardState.OCCLUDED) {
+                            val to =
+                                if (isIdleOnCommunal) {
+                                    KeyguardState.GLANCEABLE_HUB
+                                } else {
+                                    KeyguardState.LOCKSCREEN
+                                }
+                            startTransitionTo(to)
+                        }
+                    }
+            }
+        } else {
+            scope.launch {
+                keyguardInteractor.isKeyguardOccluded
+                    .sample(
+                        keyguardInteractor.isKeyguardShowing,
+                        startedKeyguardTransitionStep,
+                        communalInteractor.isIdleOnCommunal,
+                    )
+                    .collect { (isOccluded, isShowing, lastStartedKeyguardState, isIdleOnCommunal)
+                        ->
+                        // Occlusion signals come from the framework, and should interrupt any
+                        // existing transition
+                        if (
+                            !isOccluded &&
+                                isShowing &&
+                                lastStartedKeyguardState.to == KeyguardState.OCCLUDED
+                        ) {
+                            val to =
+                                if (isIdleOnCommunal) {
+                                    KeyguardState.GLANCEABLE_HUB
+                                } else {
+                                    KeyguardState.LOCKSCREEN
+                                }
+                            startTransitionTo(to)
+                        }
+                    }
+            }
+        }
+    }
+
+    private fun listenForOccludedToGone() {
+        if (KeyguardWmStateRefactor.isEnabled) {
+            // We don't think OCCLUDED to GONE is possible. You should always have to go via a
+            // *_BOUNCER state to end up GONE. Launching an activity over a dismissable keyguard
+            // dismisses it, and even "extend unlock" doesn't unlock the device in the background.
+            // If we're wrong - sorry, add it back here.
+            return
+        }
+
         scope.launch {
             keyguardInteractor.isKeyguardOccluded
                 .sample(
                     keyguardInteractor.isKeyguardShowing,
                     startedKeyguardTransitionStep,
-                    communalInteractor.isIdleOnCommunal,
-                )
-                .collect { (isOccluded, isShowing, lastStartedKeyguardState, isIdleOnCommunal) ->
-                    // Occlusion signals come from the framework, and should interrupt any
-                    // existing transition
-                    if (
-                        !isOccluded &&
-                            isShowing &&
-                            lastStartedKeyguardState.to == KeyguardState.OCCLUDED
-                    ) {
-                        val to =
-                            if (isIdleOnCommunal) {
-                                KeyguardState.GLANCEABLE_HUB
-                            } else {
-                                KeyguardState.LOCKSCREEN
-                            }
-                        startTransitionTo(to)
-                    }
-                }
-        }
-    }
-
-    private fun listenForOccludedToGone() {
-        scope.launch {
-            keyguardInteractor.isKeyguardOccluded
-                .sample(
-                    combine(
-                        keyguardInteractor.isKeyguardShowing,
-                        startedKeyguardTransitionStep,
-                        ::Pair
-                    ),
-                    ::toTriple
                 )
                 .collect { (isOccluded, isShowing, lastStartedKeyguardState) ->
                     // Occlusion signals come from the framework, and should interrupt any
@@ -142,25 +174,12 @@
         }
     }
 
-    private fun listenForOccludedToAodOrDozing() {
-        scope.launch {
-            powerInteractor.isAsleep
-                .sample(
-                    combine(
-                        startedKeyguardTransitionStep,
-                        keyguardInteractor.isAodAvailable,
-                        ::Pair
-                    ),
-                    ::toTriple
-                )
-                .collect { (isAsleep, lastStartedStep, isAodAvailable) ->
-                    if (lastStartedStep.to == KeyguardState.OCCLUDED && isAsleep) {
-                        startTransitionTo(
-                            if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
-                        )
-                    }
-                }
-        }
+    fun dismissToGone() {
+        scope.launch { startTransitionTo(KeyguardState.GONE) }
+    }
+
+    private fun listenForOccludedToAsleep() {
+        scope.launch { listenForSleepTransition(from = KeyguardState.OCCLUDED) }
     }
 
     private fun listenForOccludedToAlternateBouncer() {
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 c5a2846..391dccc 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
@@ -31,7 +31,6 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.sample
-import com.android.systemui.util.kotlin.Utils.Companion.toQuad
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import com.android.wm.shell.animation.Interpolators
@@ -42,6 +41,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
 
@@ -59,18 +59,21 @@
     private val flags: FeatureFlags,
     private val keyguardSecurityModel: KeyguardSecurityModel,
     private val selectedUserInteractor: SelectedUserInteractor,
-    private val powerInteractor: PowerInteractor,
+    powerInteractor: PowerInteractor,
+    keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.PRIMARY_BOUNCER,
         transitionInteractor = transitionInteractor,
         mainDispatcher = mainDispatcher,
         bgDispatcher = bgDispatcher,
+        powerInteractor = powerInteractor,
+        keyguardOcclusionInteractor = keyguardOcclusionInteractor,
     ) {
 
     override fun start() {
         listenForPrimaryBouncerToGone()
-        listenForPrimaryBouncerToAodOrDozing()
+        listenForPrimaryBouncerToAsleep()
         listenForPrimaryBouncerToLockscreenHubOrOccluded()
         listenForPrimaryBouncerToDreamingLockscreenHosted()
         listenForTransitionToCamera(scope, keyguardInteractor)
@@ -128,72 +131,86 @@
     }
 
     private fun listenForPrimaryBouncerToLockscreenHubOrOccluded() {
-        scope.launch {
-            keyguardInteractor.primaryBouncerShowing
-                .sample(
-                    powerInteractor.isAwake,
-                    startedKeyguardTransitionStep,
-                    keyguardInteractor.isKeyguardOccluded,
-                    keyguardInteractor.isDreaming,
-                    keyguardInteractor.isActiveDreamLockscreenHosted,
-                    communalInteractor.isIdleOnCommunal,
-                )
-                .collect {
-                    (
-                        isBouncerShowing,
-                        isAwake,
-                        lastStartedTransitionStep,
-                        occluded,
-                        isDreaming,
-                        isActiveDreamLockscreenHosted,
-                        isIdleOnCommunal) ->
-                    if (
-                        !isBouncerShowing &&
-                            lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER &&
-                            isAwake &&
-                            !isActiveDreamLockscreenHosted
-                    ) {
-                        val toState =
-                            if (occluded && !isDreaming) {
-                                KeyguardState.OCCLUDED
-                            } else if (isIdleOnCommunal) {
-                                KeyguardState.GLANCEABLE_HUB
-                            } else if (isDreaming) {
-                                KeyguardState.DREAMING
-                            } else {
-                                KeyguardState.LOCKSCREEN
-                            }
-                        startTransitionTo(toState)
+        if (KeyguardWmStateRefactor.isEnabled) {
+            scope.launch {
+                keyguardInteractor.primaryBouncerShowing
+                    .sample(
+                        startedKeyguardTransitionStep,
+                        powerInteractor.isAwake,
+                        keyguardInteractor.isActiveDreamLockscreenHosted,
+                        communalInteractor.isIdleOnCommunal
+                    )
+                    .filter { (_, startedStep, _, _) ->
+                        startedStep.to == KeyguardState.PRIMARY_BOUNCER
                     }
-                }
+                    .collect {
+                        (
+                            isBouncerShowing,
+                            _,
+                            isAwake,
+                            isActiveDreamLockscreenHosted,
+                            isIdleOnCommunal) ->
+                        if (
+                            !maybeStartTransitionToOccludedOrInsecureCamera() &&
+                                !isBouncerShowing &&
+                                isAwake &&
+                                !isActiveDreamLockscreenHosted
+                        ) {
+                            val toState =
+                                if (isIdleOnCommunal) {
+                                    KeyguardState.GLANCEABLE_HUB
+                                } else {
+                                    KeyguardState.LOCKSCREEN
+                                }
+                            startTransitionTo(toState)
+                        }
+                    }
+            }
+        } else {
+            scope.launch {
+                keyguardInteractor.primaryBouncerShowing
+                    .sample(
+                        powerInteractor.isAwake,
+                        startedKeyguardTransitionStep,
+                        keyguardInteractor.isKeyguardOccluded,
+                        keyguardInteractor.isDreaming,
+                        keyguardInteractor.isActiveDreamLockscreenHosted,
+                        communalInteractor.isIdleOnCommunal,
+                    )
+                    .collect {
+                        (
+                            isBouncerShowing,
+                            isAwake,
+                            lastStartedTransitionStep,
+                            occluded,
+                            isDreaming,
+                            isActiveDreamLockscreenHosted,
+                            isIdleOnCommunal) ->
+                        if (
+                            !isBouncerShowing &&
+                                lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER &&
+                                isAwake &&
+                                !isActiveDreamLockscreenHosted
+                        ) {
+                            val toState =
+                                if (occluded && !isDreaming) {
+                                    KeyguardState.OCCLUDED
+                                } else if (isIdleOnCommunal) {
+                                    KeyguardState.GLANCEABLE_HUB
+                                } else if (isDreaming) {
+                                    KeyguardState.DREAMING
+                                } else {
+                                    KeyguardState.LOCKSCREEN
+                                }
+                            startTransitionTo(toState)
+                        }
+                    }
+            }
         }
     }
 
-    private fun listenForPrimaryBouncerToAodOrDozing() {
-        scope.launch {
-            keyguardInteractor.primaryBouncerShowing
-                .sample(
-                    combine(
-                        powerInteractor.isAsleep,
-                        startedKeyguardTransitionStep,
-                        keyguardInteractor.isAodAvailable,
-                        ::Triple
-                    ),
-                    ::toQuad
-                )
-                .collect { (isBouncerShowing, isAsleep, lastStartedTransitionStep, isAodAvailable)
-                    ->
-                    if (
-                        !isBouncerShowing &&
-                            lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER &&
-                            isAsleep
-                    ) {
-                        startTransitionTo(
-                            if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
-                        )
-                    }
-                }
-        }
+    private fun listenForPrimaryBouncerToAsleep() {
+        scope.launch { listenForSleepTransition(from = KeyguardState.PRIMARY_BOUNCER) }
     }
 
     private fun listenForPrimaryBouncerToDreamingLockscreenHosted() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
index 6cb1eb4..197221a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
@@ -20,8 +20,7 @@
 import com.android.app.animation.Interpolators
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalTransitionProgress
-import com.android.systemui.communal.shared.model.CommunalSceneKey
-import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
@@ -29,13 +28,10 @@
 import com.android.systemui.util.kotlin.sample
 import java.util.UUID
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.flow.flowOn
 
 class GlanceableHubTransitions
 @Inject
 constructor(
-    @Background private val bgDispatcher: CoroutineDispatcher,
     private val transitionInteractor: KeyguardTransitionInteractor,
     private val transitionRepository: KeyguardTransitionRepository,
     private val communalInteractor: CommunalInteractor,
@@ -55,25 +51,25 @@
     ) {
         val toScene =
             if (fromState == KeyguardState.GLANCEABLE_HUB) {
-                CommunalSceneKey.Blank
+                CommunalScenes.Blank
             } else {
-                CommunalSceneKey.Communal
+                CommunalScenes.Communal
             }
         var transitionId: UUID? = null
 
         communalInteractor
             .transitionProgressToScene(toScene)
             .sample(
-                transitionInteractor.startedKeyguardTransitionStep.flowOn(bgDispatcher),
+                transitionInteractor.startedKeyguardState,
                 ::Pair,
             )
-            .collect { (transitionProgress, lastStartedStep) ->
+            .collect { (transitionProgress, lastStartedState) ->
                 val id = transitionId
                 if (id == null) {
                     // No transition started.
                     if (
                         transitionProgress is CommunalTransitionProgress.Transition &&
-                            lastStartedStep.to == fromState
+                            lastStartedState == fromState
                     ) {
                         transitionId =
                             transitionRepository.startTransition(
@@ -86,7 +82,7 @@
                             )
                     }
                 } else {
-                    if (lastStartedStep.to != toState) {
+                    if (lastStartedState != toState) {
                         return@collect
                     }
                     // An existing `id` means a transition is started, and calls to
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index bc3f0cc..c877192 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -20,6 +20,8 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.content.Context
+import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
@@ -34,7 +36,9 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
 
@@ -47,6 +51,8 @@
     private val context: Context,
     private val splitShadeStateController: SplitShadeStateController,
     private val clockInteractor: KeyguardClockInteractor,
+    configurationInteractor: ConfigurationInteractor,
+    fingerprintPropertyInteractor: FingerprintPropertyInteractor,
 ) {
 
     /** The current blueprint for the lockscreen. */
@@ -58,11 +64,14 @@
      */
     val refreshTransition = keyguardBlueprintRepository.refreshTransition
 
+    private val configOrPropertyChange =
+        merge(
+            configurationInteractor.onAnyConfigurationChange,
+            fingerprintPropertyInteractor.propertiesInitialized.filter { it }.map {}, // map to Unit
+        )
     init {
         applicationScope.launch {
-            keyguardBlueprintRepository.configurationChange
-                .onStart { emit(Unit) }
-                .collect { updateBlueprint() }
+            configOrPropertyChange.onStart { emit(Unit) }.collect { updateBlueprint() }
         }
         applicationScope.launch { clockInteractor.currentClock.collect { updateBlueprint() } }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
index d2a7486..b9ec58c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
@@ -22,6 +22,8 @@
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
 
 /** Encapsulates business-logic specifically related to the keyguard bottom area. */
 @SysUISingleton
@@ -35,10 +37,13 @@
     /** The amount of alpha for the UI components of the bottom area. */
     val alpha: Flow<Float> = repository.bottomAreaAlpha
     /** The position of the keyguard clock. */
-    val clockPosition: Flow<Position> = repository.clockPosition
+    private val _clockPosition = MutableStateFlow(Position(0, 0))
+    /** See [ClockSection] */
+    @Deprecated("with migrateClocksToBlueprint()")
+    val clockPosition: Flow<Position> = _clockPosition.asStateFlow()
 
     fun setClockPosition(x: Int, y: Int) {
-        repository.setClockPosition(x, y)
+        _clockPosition.value = Position(x, y)
     }
 
     fun setAlpha(alpha: Float) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index 196770a..2cf9156 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -44,7 +44,8 @@
 
     val currentClock: StateFlow<ClockController?> = keyguardClockRepository.currentClock
 
-    val previewClock: StateFlow<ClockController> = keyguardClockRepository.previewClock
+    val previewClockPair: StateFlow<Pair<ClockController, ClockController>> =
+        keyguardClockRepository.previewClockPair
 
     var clock: ClockController? by keyguardClockRepository.clockEventController::clock
 
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 78749ea..143edf9 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
@@ -27,7 +27,6 @@
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.common.shared.model.NotificationContainerBounds
-import com.android.systemui.common.shared.model.Position
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -37,13 +36,14 @@
 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.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.util.kotlin.sample
@@ -163,15 +163,18 @@
             .distinctUntilChanged()
 
     /** Whether the keyguard is showing or not. */
+    @Deprecated("Use KeyguardTransitionInteractor + KeyguardState")
     val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
 
     /** Whether the keyguard is dismissible or not. */
     val isKeyguardDismissible: Flow<Boolean> = repository.isKeyguardDismissible
 
     /** Whether the keyguard is occluded (covered by an activity). */
+    @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.OCCLUDED")
     val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded
 
     /** Whether the keyguard is going away. */
+    @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.GONE")
     val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
 
     /** Keyguard can be clipped at the top as the shade is dragged */
@@ -232,9 +235,6 @@
     /** The approximate location on the screen of the face unlock sensor, if one is available. */
     val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation
 
-    /** The position of the keyguard clock. */
-    val clockPosition: Flow<Position> = repository.clockPosition
-
     @Deprecated("Use the relevant TransitionViewModel")
     val keyguardAlpha: Flow<Float> = repository.keyguardAlpha
 
@@ -269,8 +269,11 @@
         configurationInteractor
             .dimensionPixelSize(R.dimen.keyguard_translate_distance_on_swipe_up)
             .flatMapLatest { translationDistance ->
-                shadeRepository.legacyShadeExpansion.map {
-                    if (it == 0f) {
+                combine(
+                    shadeRepository.legacyShadeExpansion.onStart { emit(0f) },
+                    keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) },
+                ) { legacyShadeExpansion, goneValue ->
+                    if (goneValue == 1f || legacyShadeExpansion == 0f) {
                         // Reset the translation value
                         0f
                     } else {
@@ -278,11 +281,12 @@
                         MathUtils.lerp(
                             translationDistance,
                             0,
-                            Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it)
+                            Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(legacyShadeExpansion)
                         )
                     }
                 }
             }
+            .distinctUntilChanged()
 
     val clockShouldBeCentered: Flow<Boolean> = repository.clockShouldBeCentered
 
@@ -292,7 +296,7 @@
             sceneInteractorProvider
                 .get()
                 .transitioningTo
-                .map { it == SceneKey.Lockscreen }
+                .map { it == Scenes.Lockscreen }
                 .distinctUntilChanged()
                 .flatMapLatest { isTransitioningToLockscreenScene ->
                     if (isTransitioningToLockscreenScene) {
@@ -342,10 +346,6 @@
         repository.setQuickSettingsVisible(isVisible)
     }
 
-    fun setClockPosition(x: Int, y: Int) {
-        repository.setClockPosition(x, y)
-    }
-
     fun setAlpha(alpha: Float) {
         repository.setKeyguardAlpha(alpha)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
new file mode 100644
index 0000000..9aa2202
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.app.ActivityManager.RunningTaskInfo
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardOcclusionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Logic related to keyguard occlusion. The keyguard is occluded when an activity with
+ * FLAG_SHOW_WHEN_LOCKED is on top of the activity task stack, with that activity displaying on top
+ * of ("occluding") the lockscreen UI. Common examples of this are Google Maps Navigation and the
+ * secure camera.
+ *
+ * This should usually be used only by keyguard internal classes. Most System UI use cases should
+ * use [KeyguardTransitionInteractor] to see if we're in [KeyguardState.OCCLUDED] instead.
+ */
+@SysUISingleton
+class KeyguardOcclusionInteractor
+@Inject
+constructor(
+    @Application scope: CoroutineScope,
+    val repository: KeyguardOcclusionRepository,
+    val powerInteractor: PowerInteractor,
+    val transitionInteractor: KeyguardTransitionInteractor,
+    val keyguardInteractor: KeyguardInteractor,
+) {
+    val showWhenLockedActivityInfo = repository.showWhenLockedActivityInfo.asStateFlow()
+
+    /**
+     * Whether a SHOW_WHEN_LOCKED activity is on top of the task stack. This does not necessarily
+     * mean we're OCCLUDED, as we could be GONE (unlocked), with an activity that can (but is not
+     * currently) displaying over the lockscreen.
+     *
+     * Transition interactors use this to determine when we should transition to the OCCLUDED state.
+     *
+     * Outside of the transition/occlusion interactors, you almost certainly don't want to use this.
+     * Instead, use KeyguardTransitionInteractor to figure out if we're in KeyguardState.OCCLUDED.
+     */
+    val isShowWhenLockedActivityOnTop = showWhenLockedActivityInfo.map { it.isOnTop }
+
+    /** Whether we should start a transition due to the power button launch gesture. */
+    fun shouldTransitionFromPowerButtonGesture(): Boolean {
+        // powerButtonLaunchGestureTriggered remains true while we're awake from a power button
+        // gesture. Check that we were asleep or transitioning to asleep before starting a
+        // transition, to ensure we don't transition while moving between, for example,
+        // *_BOUNCER -> LOCKSCREEN.
+        return powerInteractor.detailedWakefulness.value.powerButtonLaunchGestureTriggered &&
+            KeyguardState.deviceIsAsleepInState(transitionInteractor.getStartedState())
+    }
+
+    /**
+     * Whether the SHOW_WHEN_LOCKED activity was launched from the double tap power button gesture.
+     * This remains true while the activity is running and emits false once it is killed.
+     */
+    val showWhenLockedActivityLaunchedFromPowerGesture =
+        merge(
+                // Emit true when the power launch gesture is triggered, since this means a
+                // SHOW_WHEN_LOCKED activity will be launched from the gesture (unless we're
+                // currently
+                // GONE, in which case we're going back to GONE and launching the insecure camera).
+                powerInteractor.detailedWakefulness
+                    .sample(transitionInteractor.currentKeyguardState, ::Pair)
+                    .map { (wakefulness, currentKeyguardState) ->
+                        wakefulness.powerButtonLaunchGestureTriggered &&
+                            currentKeyguardState != KeyguardState.GONE
+                    },
+                // Emit false once that activity goes away.
+                isShowWhenLockedActivityOnTop.filter { !it }.map { false }
+            )
+            .stateIn(scope, SharingStarted.Eagerly, false)
+
+    /**
+     * Whether launching an occluding activity will automatically dismiss keyguard. This happens if
+     * the keyguard is dismissable.
+     */
+    val occludingActivityWillDismissKeyguard =
+        keyguardInteractor.isKeyguardDismissible.stateIn(scope, SharingStarted.Eagerly, false)
+
+    /**
+     * Called to let System UI know that WM says a SHOW_WHEN_LOCKED activity is on top (or no longer
+     * on top).
+     *
+     * This signal arrives from WM when a SHOW_WHEN_LOCKED activity is started or killed - it is
+     * never set directly by System UI. While we might be the reason the activity was started
+     * (launching the camera from the power button gesture), we ultimately only receive this signal
+     * once that activity starts. It's up to us to start the appropriate keyguard transitions,
+     * because that activity is going to be visible (or not) regardless.
+     */
+    fun setWmNotifiedShowWhenLockedActivityOnTop(
+        showWhenLockedActivityOnTop: Boolean,
+        taskInfo: RunningTaskInfo? = null
+    ) {
+        repository.setShowWhenLockedActivityInfo(showWhenLockedActivityOnTop, taskInfo)
+    }
+}
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 b0a3881..8eb1a50 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
@@ -46,6 +46,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 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
@@ -55,6 +56,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
@@ -66,6 +68,7 @@
 @Inject
 constructor(
     private val keyguardInteractor: KeyguardInteractor,
+    private val shadeInteractor: ShadeInteractor,
     private val lockPatternUtils: LockPatternUtils,
     private val keyguardStateController: KeyguardStateController,
     private val userTracker: UserTracker,
@@ -100,9 +103,10 @@
             quickAffordanceAlwaysVisible(position),
             keyguardInteractor.isDozing,
             keyguardInteractor.isKeyguardShowing,
+            shadeInteractor.anyExpansion.map { it < 1.0f }.distinctUntilChanged(),
             biometricSettingsRepository.isCurrentUserInLockdown,
-        ) { affordance, isDozing, isKeyguardShowing, isUserInLockdown ->
-            if (!isDozing && isKeyguardShowing && !isUserInLockdown) {
+        ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible, isUserInLockdown ->
+            if (!isDozing && isKeyguardShowing && isQuickSettingsVisible && !isUserInLockdown) {
                 affordance
             } else {
                 KeyguardQuickAffordanceModel.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index a03fa38..c28e49d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -21,6 +21,8 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.core.LogLevel.VERBOSE
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
@@ -37,6 +39,9 @@
     private val keyguardInteractor: KeyguardInteractor,
     private val logger: KeyguardLogger,
     private val powerInteractor: PowerInteractor,
+    private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
+    private val shadeInteractor: ShadeInteractor,
+    private val keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
 ) {
 
     fun start() {
@@ -47,6 +52,30 @@
         }
 
         scope.launch {
+            sharedNotificationContainerViewModel
+                .getMaxNotifications { height, useExtraShelfSpace -> height.toInt() }
+                .collect { logger.log(TAG, VERBOSE, "Notif: max height in px", it) }
+        }
+
+        scope.launch {
+            sharedNotificationContainerViewModel.isOnLockscreen.collect {
+                logger.log(TAG, VERBOSE, "Notif: isOnLockscreen", it)
+            }
+        }
+
+        scope.launch {
+            shadeInteractor.isUserInteracting.collect {
+                logger.log(TAG, VERBOSE, "Shade: isUserInteracting", it)
+            }
+        }
+
+        scope.launch {
+            sharedNotificationContainerViewModel.isOnLockscreenWithoutShade.collect {
+                logger.log(TAG, VERBOSE, "Notif: isOnLockscreenWithoutShade", it)
+            }
+        }
+
+        scope.launch {
             keyguardInteractor.primaryBouncerShowing.collect {
                 logger.log(TAG, VERBOSE, "Primary bouncer showing", it)
             }
@@ -63,6 +92,12 @@
         }
 
         scope.launch {
+            keyguardInteractor.isKeyguardDismissible.collect {
+                logger.log(TAG, VERBOSE, "isKeyguardDismissable", it)
+            }
+        }
+
+        scope.launch {
             keyguardInteractor.isAbleToDream.collect {
                 logger.log(TAG, VERBOSE, "isAbleToDream", it)
             }
@@ -75,6 +110,18 @@
         }
 
         scope.launch {
+            keyguardInteractor.isKeyguardDismissible.collect {
+                logger.log(TAG, VERBOSE, "isDismissible", it)
+            }
+        }
+
+        scope.launch {
+            keyguardInteractor.isKeyguardShowing.collect {
+                logger.log(TAG, VERBOSE, "isShowing", it)
+            }
+        }
+
+        scope.launch {
             keyguardInteractor.dozeTransitionModel.collect {
                 logger.log(TAG, VERBOSE, "Doze transition", it)
             }
@@ -85,5 +132,11 @@
                 logger.log(TAG, VERBOSE, "onCameraLaunchDetected", it)
             }
         }
+
+        scope.launch {
+            keyguardOcclusionInteractor.showWhenLockedActivityInfo.collect {
+                logger.log(TAG, VERBOSE, "showWhenLockedActivityInfo", it)
+            }
+        }
     }
 }
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 d1fd719..00902b4 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
@@ -20,6 +20,7 @@
 import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
@@ -35,18 +36,20 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.kotlin.pairwise
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
 
 /** Encapsulates business-logic related to the keyguard transitions. */
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -55,11 +58,15 @@
 @Inject
 constructor(
     @Application val scope: CoroutineScope,
+    private val keyguardRepository: KeyguardRepository,
     private val repository: KeyguardTransitionRepository,
     private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
     private val fromPrimaryBouncerTransitionInteractor:
         dagger.Lazy<FromPrimaryBouncerTransitionInteractor>,
     private val fromAodTransitionInteractor: dagger.Lazy<FromAodTransitionInteractor>,
+    private val fromAlternateBouncerTransitionInteractor:
+        dagger.Lazy<FromAlternateBouncerTransitionInteractor>,
+    private val fromDozingTransitionInteractor: dagger.Lazy<FromDozingTransitionInteractor>,
 ) {
     private val TAG = this::class.simpleName
 
@@ -177,10 +184,16 @@
      * Lockscreen (0f).
      */
     val dozeAmountTransition: Flow<TransitionStep> =
-        merge(
-            aodToLockscreenTransition.map { step -> step.copy(value = 1f - step.value) },
-            lockscreenToAodTransition,
-        )
+        repository.transitions
+            .filter { step -> step.from == AOD || step.to == AOD }
+            .map { step ->
+                if (step.from == AOD) {
+                    step.copy(value = 1 - step.value)
+                } else {
+                    step
+                }
+            }
+            .shareIn(scope, SharingStarted.Eagerly, replay = 1)
 
     /** The last [TransitionStep] with a [TransitionState] of STARTED */
     val startedKeyguardTransitionStep: Flow<TransitionStep> =
@@ -200,6 +213,27 @@
             .map { step -> step.to }
             .shareIn(scope, SharingStarted.Eagerly, replay = 1)
 
+    /** Which keyguard state to use when the device goes to sleep. */
+    val asleepKeyguardState: StateFlow<KeyguardState> =
+        keyguardRepository.isAodAvailable
+            .map { aodAvailable -> if (aodAvailable) AOD else DOZING }
+            .stateIn(scope, SharingStarted.Eagerly, DOZING)
+
+    /**
+     * A pair of the most recent STARTED step, and the transition step immediately preceding it. The
+     * transition framework enforces that the previous step is either a CANCELED or FINISHED step,
+     * and that the previous step was *to* the state the STARTED step is *from*.
+     *
+     * This flow can be used to access the previous step to determine whether it was CANCELED or
+     * FINISHED. In the case of a CANCELED step, we can also figure out which state we were coming
+     * from when we were canceled.
+     */
+    val startedStepWithPrecedingStep =
+        transitions
+            .pairwise()
+            .filter { it.newValue.transitionState == TransitionState.STARTED }
+            .stateIn(scope, SharingStarted.Eagerly, null)
+
     /**
      * The last [KeyguardState] to which we [TransitionState.FINISHED] a transition.
      *
@@ -346,7 +380,10 @@
         when (val startedState = startedKeyguardState.replayCache.last()) {
             LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
             PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
+            ALTERNATE_BOUNCER ->
+                fromAlternateBouncerTransitionInteractor.get().dismissAlternateBouncer()
             AOD -> fromAodTransitionInteractor.get().dismissAod()
+            DOZING -> fromDozingTransitionInteractor.get().dismissFromDozing()
             else ->
                 Log.e(
                     "KeyguardTransitionInteractor",
@@ -399,12 +436,17 @@
         fromStatePredicate: (KeyguardState) -> Boolean,
         toStatePredicate: (KeyguardState) -> Boolean,
     ): Flow<Boolean> {
+        return isInTransitionWhere { from, to -> fromStatePredicate(from) && toStatePredicate(to) }
+    }
+
+    fun isInTransitionWhere(
+        fromToStatePredicate: (KeyguardState, KeyguardState) -> Boolean
+    ): Flow<Boolean> {
         return repository.transitions
             .filter { it.transitionState != TransitionState.CANCELED }
             .mapLatest {
                 it.transitionState != TransitionState.FINISHED &&
-                    fromStatePredicate(it.from) &&
-                    toStatePredicate(it.to)
+                    fromToStatePredicate(it.from, it.to)
             }
             .distinctUntilChanged()
     }
@@ -425,4 +467,16 @@
      */
     fun isFinishedInStateWhereValue(stateMatcher: (KeyguardState) -> Boolean) =
         stateMatcher(finishedKeyguardState.replayCache.last())
+
+    fun getCurrentState(): KeyguardState {
+        return currentKeyguardState.replayCache.last()
+    }
+
+    fun getStartedState(): KeyguardState {
+        return startedKeyguardState.replayCache.last()
+    }
+
+    fun getFinishedState(): KeyguardState {
+        return finishedKeyguardState.replayCache.last()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 4d731ec..2d944c6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -25,14 +25,13 @@
 import com.android.systemui.power.shared.model.ScreenPowerState
 import com.android.systemui.statusbar.LightRevealEffect
 import com.android.systemui.util.kotlin.sample
+import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
 
-@ExperimentalCoroutinesApi
 @SysUISingleton
 class LightRevealScrimInteractor
 @Inject
@@ -41,9 +40,8 @@
     private val lightRevealScrimRepository: LightRevealScrimRepository,
     @Application private val scope: CoroutineScope,
     private val scrimLogger: ScrimLogger,
-    private val powerInteractor: PowerInteractor,
+    private val powerInteractor: Lazy<PowerInteractor>,
 ) {
-
     init {
         listenForStartedKeyguardTransitionStep()
     }
@@ -52,9 +50,7 @@
         scope.launch {
             transitionInteractor.startedKeyguardTransitionStep.collect {
                 scrimLogger.d(TAG, "listenForStartedKeyguardTransitionStep", it)
-                lightRevealScrimRepository.startRevealAmountAnimator(
-                    willBeRevealedInState(it.to),
-                )
+                lightRevealScrimRepository.startRevealAmountAnimator(willBeRevealedInState(it.to))
             }
         }
     }
@@ -84,10 +80,11 @@
         }
 
     private fun screenIsShowingContent() =
-        powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_OFF &&
-            powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_TURNING_ON
+        powerInteractor.get().screenPowerState.value != ScreenPowerState.SCREEN_OFF &&
+            powerInteractor.get().screenPowerState.value != ScreenPowerState.SCREEN_TURNING_ON
 
-    companion object {
+    val isAnimating: Boolean
+        get() = lightRevealScrimRepository.isAnimating
 
     /**
      * Whether the light reveal scrim will be fully revealed (revealAmount = 1.0f) in the given
@@ -109,6 +106,7 @@
         }
     }
 
+    companion object {
         val TAG = LightRevealScrimInteractor::class.simpleName!!
     }
 }
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 3ccbdba..375df3e 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
@@ -18,15 +18,20 @@
 
 import android.animation.ValueAnimator
 import android.util.Log
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.util.kotlin.sample
 import java.util.UUID
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
@@ -46,6 +51,8 @@
     val transitionInteractor: KeyguardTransitionInteractor,
     val mainDispatcher: CoroutineDispatcher,
     val bgDispatcher: CoroutineDispatcher,
+    val powerInteractor: PowerInteractor,
+    val keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
 ) {
     val name = this::class.simpleName ?: "UnknownTransitionInteractor"
     abstract val transitionRepository: KeyguardTransitionRepository
@@ -65,7 +72,11 @@
     suspend fun startTransitionTo(
         toState: KeyguardState,
         animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState),
-        modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE
+        modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE,
+        // Even more information about why the owner started this transition, if this is a dangerous
+        // transition (*cough* occlusion) where you'd be sad to not have all the info you can get in
+        // a bugreport.
+        ownerReason: String = "",
     ): UUID? {
         if (
             fromState != transitionInteractor.startedKeyguardState.replayCache.last() &&
@@ -85,7 +96,7 @@
         return withContext(mainDispatcher) {
             transitionRepository.startTransition(
                 TransitionInfo(
-                    name,
+                    name + if (ownerReason.isNotBlank()) "($ownerReason)" else "",
                     fromState,
                     toState,
                     animator,
@@ -95,24 +106,107 @@
         }
     }
 
+    /**
+     * Check whether we need to transition to [KeyguardState.OCCLUDED], based on the presence of a
+     * SHOW_WHEN_LOCKED activity, or back to [KeyguardState.GONE], for some power button launch
+     * gesture cases. If so, start the transition.
+     *
+     * Returns true if a transition was started, false otherwise.
+     */
+    suspend fun maybeStartTransitionToOccludedOrInsecureCamera(): Boolean {
+        if (keyguardOcclusionInteractor.shouldTransitionFromPowerButtonGesture()) {
+            if (transitionInteractor.getCurrentState() == KeyguardState.GONE) {
+                // If the current state is GONE when the launch gesture is triggered, it means we
+                // were in transition from GONE -> DOZING/AOD due to the first power button tap. The
+                // second tap indicates that the user's intent was actually to launch the unlocked
+                // (insecure) camera, so we should transition back to GONE.
+                startTransitionTo(
+                    KeyguardState.GONE,
+                    ownerReason = "Power button gesture while GONE"
+                )
+            } else if (keyguardOcclusionInteractor.occludingActivityWillDismissKeyguard.value) {
+                // The double tap gesture occurred while not GONE (AOD/LOCKSCREEN/etc.), but the
+                // keyguard is dismissable. The activity launch will dismiss the keyguard, so we
+                // should transition to GONE.
+                startTransitionTo(
+                    KeyguardState.GONE,
+                    ownerReason = "Power button gesture on dismissable keyguard"
+                )
+            } else {
+                // Otherwise, the double tap gesture occurred while not GONE and not dismissable,
+                // which means we will launch the secure camera, which OCCLUDES the keyguard.
+                startTransitionTo(
+                    KeyguardState.OCCLUDED,
+                    ownerReason = "Power button gesture on lockscreen"
+                )
+            }
+
+            return true
+        } else if (keyguardOcclusionInteractor.showWhenLockedActivityInfo.value.isOnTop) {
+            // A SHOW_WHEN_LOCKED activity is on top of the task stack. Transition to OCCLUDED so
+            // it's visible.
+            // TODO(b/307976454) - Centralize transition to DREAMING here.
+            startTransitionTo(
+                KeyguardState.OCCLUDED,
+                ownerReason = "SHOW_WHEN_LOCKED activity on top"
+            )
+
+            return true
+        } else {
+            // No transition needed, let the interactor figure out where to go.
+            return false
+        }
+    }
+
+    /**
+     * Transition to the appropriate state when the device goes to sleep while in [from].
+     *
+     * We could also just use [fromState], but it's more readable in the From*TransitionInteractor
+     * if you're explicitly declaring which state you're listening from. If you passed in the wrong
+     * state, [startTransitionTo] would complain anyway.
+     */
+    suspend fun listenForSleepTransition(
+        from: KeyguardState,
+        modeOnCanceledFromStartedStep: (TransitionStep) -> TransitionModeOnCanceled = {
+            TransitionModeOnCanceled.LAST_VALUE
+        }
+    ) {
+        powerInteractor.isAsleep
+            .filter { isAsleep -> isAsleep }
+            .sample(startedKeyguardTransitionStep)
+            .filter { startedStep -> startedStep.to == from }
+            .map(modeOnCanceledFromStartedStep)
+            .collect { modeOnCanceled ->
+                startTransitionTo(
+                    toState = transitionInteractor.asleepKeyguardState.value,
+                    modeOnCanceled = modeOnCanceled,
+                    ownerReason = "Sleep transition triggered"
+                )
+            }
+    }
+
     /** This signal may come in before the occlusion signal, and can provide a custom transition */
     fun listenForTransitionToCamera(
         scope: CoroutineScope,
         keyguardInteractor: KeyguardInteractor,
     ) {
-        scope.launch {
-            keyguardInteractor.onCameraLaunchDetected
-                .sample(transitionInteractor.finishedKeyguardState)
-                .collect { finishedKeyguardState ->
-                    // Other keyguard state transitions may trigger on the first power button push,
-                    // so use the last finishedKeyguardState to determine the overriding FROM state
-                    if (finishedKeyguardState == fromState) {
-                        startTransitionTo(
-                            toState = KeyguardState.OCCLUDED,
-                            modeOnCanceled = TransitionModeOnCanceled.RESET,
-                        )
+        if (!KeyguardWmStateRefactor.isEnabled) {
+            scope.launch {
+                keyguardInteractor.onCameraLaunchDetected
+                    .sample(transitionInteractor.finishedKeyguardState)
+                    .collect { finishedKeyguardState ->
+                        // Other keyguard state transitions may trigger on the first power button
+                        // push,
+                        // so use the last finishedKeyguardState to determine the overriding FROM
+                        // state
+                        if (finishedKeyguardState == fromState) {
+                            startTransitionTo(
+                                toState = KeyguardState.OCCLUDED,
+                                modeOnCanceled = TransitionModeOnCanceled.RESET,
+                            )
+                        }
                     }
-                }
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index b81793e..cff74b3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -19,7 +19,9 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
+import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
@@ -121,19 +123,27 @@
      * want to know if the AOD/clock/notifs/etc. are visible.
      */
     val lockscreenVisibility: Flow<Boolean> =
-        combine(
-                transitionInteractor.startedKeyguardTransitionStep,
-                transitionInteractor.finishedKeyguardState,
-            ) { startedStep, finishedState ->
-                // If we finished the transition, use the finished state. If we're running a
-                // transition, use the state we're transitioning FROM. This can be different from
-                // the last finished state if a transition is interrupted. For example, if we were
-                // transitioning from GONE to AOD and then started AOD -> LOCKSCREEN mid-transition,
-                // we want to immediately use the visibility for AOD (lockscreenVisibility=true)
-                // even though the lastFinishedState is still GONE (lockscreenVisibility=false).
-                if (finishedState == startedStep.to) finishedState else startedStep.from
+        transitionInteractor.currentKeyguardState
+            .sample(transitionInteractor.startedStepWithPrecedingStep, ::Pair)
+            .map { (currentState, startedWithPrev) ->
+                val startedFromStep = startedWithPrev?.previousValue
+                val startedStep = startedWithPrev?.newValue
+                val returningToGoneAfterCancellation =
+                    startedStep?.to == KeyguardState.GONE &&
+                        startedFromStep?.transitionState == TransitionState.CANCELED &&
+                        startedFromStep.from == KeyguardState.GONE
+
+                if (!returningToGoneAfterCancellation) {
+                    // By default, apply the lockscreen visibility of the current state.
+                    KeyguardState.lockscreenVisibleInState(currentState)
+                } else {
+                    // If we're transitioning to GONE after a prior canceled transition from GONE,
+                    // then this is the camera launch transition from an asleep state back to GONE.
+                    // We don't want to show the lockscreen since we're aborting the lock and going
+                    // back to GONE.
+                    KeyguardState.lockscreenVisibleInState(KeyguardState.GONE)
+                }
             }
-            .map(KeyguardState::lockscreenVisibleInState)
             .distinctUntilChanged()
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt
index 7f0b483..601fbfa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.keyguard.shared
 
 import com.android.systemui.Flags
-import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.flags.FlagToken
 import com.android.systemui.flags.RefactorFlagUtils
 
@@ -34,7 +33,7 @@
     /** Is the refactor enabled */
     @JvmStatic
     inline val isEnabled
-        get() = Flags.composeLockscreen() && ComposeFacade.isComposeAvailable()
+        get() = Flags.composeLockscreen()
 
     /**
      * Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index 873cc84..f46a207 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -22,6 +22,7 @@
 import android.util.StateSet
 import android.view.HapticFeedbackConstants
 import android.view.View
+import androidx.core.view.isInvisible
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.common.ui.view.LongPressHandlingView
@@ -82,6 +83,11 @@
             // of the transition.
             repeatOnLifecycle(Lifecycle.State.CREATED) {
                 launch {
+                    viewModel.isVisible.collect { isVisible ->
+                        longPressHandlingView.isInvisible = !isVisible
+                    }
+                }
+                launch {
                     viewModel.isLongPressEnabled.collect { isEnabled ->
                         longPressHandlingView.setLongPressHandlingEnabled(isEnabled)
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index 66fc995..4812e03 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -36,12 +36,13 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
 import javax.inject.Inject
 import kotlin.math.max
 import kotlinx.coroutines.launch
 
 private const val TAG = "KeyguardBlueprintViewBinder"
-private const val DEBUG = true
+private const val DEBUG = false
 
 @SysUISingleton
 class KeyguardBlueprintViewBinder
@@ -127,6 +128,7 @@
                         runTransition(constraintLayout, transition, Config.DEFAULT) {
                             // Add and remove views of sections that are not contained by the other.
                             blueprint.replaceViews(prevBluePrint, constraintLayout)
+                            logAlphaVisibilityOfAppliedConstraintSet(cs, clockViewModel)
                             cs.applyTo(constraintLayout)
                         }
 
@@ -153,6 +155,7 @@
                             ),
                             transition,
                         ) {
+                            logAlphaVisibilityOfAppliedConstraintSet(cs, clockViewModel)
                             cs.applyTo(constraintLayout)
                         }
                         Trace.endSection()
@@ -205,4 +208,23 @@
             apply()
         }
     }
+
+    private fun logAlphaVisibilityOfAppliedConstraintSet(
+        cs: ConstraintSet,
+        viewModel: KeyguardClockViewModel
+    ) {
+        if (!DEBUG || viewModel.clock == null) return
+        val smallClockViewId = R.id.lockscreen_clock_view
+        val largeClockViewId = viewModel.clock!!.largeClock.layout.views[0].id
+        Log.i(
+            TAG,
+            "applyCsToSmallClock: vis=${cs.getVisibility(smallClockViewId)} " +
+                "alpha=${cs.getConstraint(smallClockViewId).propertySet}"
+        )
+        Log.i(
+            TAG,
+            "applyCsToLargeClock: vis=${cs.getVisibility(largeClockViewId)} " +
+                "alpha=${cs.getConstraint(largeClockViewId).propertySet}"
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index b56c998..46c354a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -72,38 +72,47 @@
     @JvmStatic
     fun bind(
         context: Context,
+        displayId: Int,
         rootView: ConstraintLayout,
         viewModel: KeyguardPreviewClockViewModel,
         clockEventController: ClockEventController,
-        updateClockAppearance: KSuspendFunction1<ClockController, Unit>
+        updateClockAppearance: KSuspendFunction1<ClockController, Unit>,
     ) {
+        // TODO(b/327668072): When this function is called multiple times, the clock view can be
+        //                    gone due to a race condition on removeView and addView.
         rootView.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
                 launch {
-                    combine(viewModel.selectedClockSize, viewModel.previewClock) { _, clock ->
+                    combine(viewModel.selectedClockSize, viewModel.previewClockPair) { _, clock ->
                             clock
                         }
-                        .collect { previewClock ->
-                            viewModel.lastClock?.let { lastClock ->
-                                (lastClock.largeClock.layout.views +
-                                        lastClock.smallClock.layout.views)
+                        .collect { previewClockPair ->
+                            viewModel.lastClockPair?.let { clockPair ->
+                                (clockPair.first.largeClock.layout.views +
+                                        clockPair.first.smallClock.layout.views)
+                                    .forEach { rootView.removeView(it) }
+                                (clockPair.second.largeClock.layout.views +
+                                        clockPair.second.smallClock.layout.views)
                                     .forEach { rootView.removeView(it) }
                             }
-                            viewModel.lastClock = previewClock
-                            clockEventController.clock = previewClock
-                            updateClockAppearance(previewClock)
+                            viewModel.lastClockPair = previewClockPair
+                            val clockPreview =
+                                if (displayId == 0) previewClockPair.first
+                                else previewClockPair.second
+                            clockEventController.clock = clockPreview
+                            updateClockAppearance(clockPreview)
 
                             if (viewModel.shouldHighlightSelectedAffordance) {
-                                (previewClock.largeClock.layout.views +
-                                        previewClock.smallClock.layout.views)
+                                (clockPreview.largeClock.layout.views +
+                                        clockPreview.smallClock.layout.views)
                                     .forEach { it.alpha = KeyguardPreviewRenderer.DIM_ALPHA }
                             }
-                            previewClock.largeClock.layout.views.forEach {
+                            clockPreview.largeClock.layout.views.forEach {
                                 (it.parent as? ViewGroup)?.removeView(it)
                                 rootView.addView(it)
                             }
 
-                            previewClock.smallClock.layout.views.forEach {
+                            clockPreview.smallClock.layout.views.forEach {
                                 (it.parent as? ViewGroup)?.removeView(it)
                                 rootView.addView(it)
                             }
@@ -164,10 +173,12 @@
         viewModel: KeyguardPreviewClockViewModel
     ) {
         val cs = ConstraintSet().apply { clone(rootView) }
-        val clock = viewModel.previewClock.value
+        val clockPair = viewModel.previewClockPair.value
         applyClockDefaultConstraints(context, cs)
-        clock.largeClock.layout.applyPreviewConstraints(cs)
-        clock.smallClock.layout.applyPreviewConstraints(cs)
+        clockPair.first.largeClock.layout.applyPreviewConstraints(cs)
+        clockPair.first.smallClock.layout.applyPreviewConstraints(cs)
+        clockPair.second.largeClock.layout.applyPreviewConstraints(cs)
+        clockPair.second.smallClock.layout.applyPreviewConstraints(cs)
 
         // When selectedClockSize is the initial value, make both clocks invisible to avoid
         // flickering
@@ -185,8 +196,10 @@
             }
 
         cs.apply {
-            setVisibility(clock.largeClock.layout.views, largeClockVisibility)
-            setVisibility(clock.smallClock.layout.views, smallClockVisibility)
+            setVisibility(clockPair.first.largeClock.layout.views, largeClockVisibility)
+            setVisibility(clockPair.first.smallClock.layout.views, smallClockVisibility)
+            setVisibility(clockPair.second.largeClock.layout.views, largeClockVisibility)
+            setVisibility(clockPair.second.smallClock.layout.views, smallClockVisibility)
         }
         cs.applyTo(rootView)
     }
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 dc1f33d..fc95ec9 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
@@ -73,7 +73,6 @@
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.launch
@@ -148,6 +147,7 @@
                             viewModel.alpha(viewState).collect { alpha ->
                                 view.alpha = alpha
                                 childViews[statusViewId]?.alpha = alpha
+                                childViews[burnInLayerId]?.alpha = alpha
                             }
                         }
                     }
@@ -195,66 +195,68 @@
                             // large clock isn't added to burnInLayer due to its scale transition
                             // so we also need to add translation to it here
                             // same as translationX
-                            burnInParams
-                                .flatMapLatest { params -> viewModel.translationY(params) }
-                                .collect { y ->
-                                    childViews[burnInLayerId]?.translationY = y
-                                    childViews[largeClockId]?.translationY = y
-                                    childViews[aodNotificationIconContainerId]?.translationY = y
-                                }
+                            viewModel.translationY.collect { y ->
+                                childViews[burnInLayerId]?.translationY = y
+                                childViews[largeClockId]?.translationY = y
+                                childViews[aodNotificationIconContainerId]?.translationY = y
+                            }
                         }
 
                         launch {
-                            burnInParams
-                                .flatMapLatest { params -> viewModel.translationX(params) }
-                                .collect { state ->
-                                    val px = state.value ?: return@collect
-                                    when {
-                                        state.isToOrFrom(KeyguardState.AOD) -> {
-                                            childViews[largeClockId]?.translationX = px
-                                            childViews[burnInLayerId]?.translationX = px
-                                            childViews[aodNotificationIconContainerId]
-                                                ?.translationX = px
-                                        }
-                                        state.isToOrFrom(KeyguardState.GLANCEABLE_HUB) -> {
-                                            for ((key, childView) in childViews.entries) {
-                                                when (key) {
-                                                    indicationArea,
-                                                    startButton,
-                                                    endButton,
-                                                    lockIcon -> {
-                                                        // Do not move these views
-                                                    }
-                                                    else -> childView.translationX = px
+                            viewModel.translationX.collect { state ->
+                                val px = state.value ?: return@collect
+                                when {
+                                    state.isToOrFrom(KeyguardState.AOD) -> {
+                                        childViews[largeClockId]?.translationX = px
+                                        childViews[burnInLayerId]?.translationX = px
+                                        childViews[aodNotificationIconContainerId]?.translationX =
+                                            px
+                                    }
+                                    state.isToOrFrom(KeyguardState.GLANCEABLE_HUB) -> {
+                                        for ((key, childView) in childViews.entries) {
+                                            when (key) {
+                                                indicationArea,
+                                                startButton,
+                                                endButton,
+                                                lockIcon -> {
+                                                    // Do not move these views
                                                 }
+                                                else -> childView.translationX = px
                                             }
                                         }
                                     }
                                 }
+                            }
                         }
 
                         launch {
-                            burnInParams
-                                .flatMapLatest { params -> viewModel.scale(params) }
-                                .collect { scaleViewModel ->
-                                    if (scaleViewModel.scaleClockOnly) {
-                                        // For clocks except weather clock, we have scale transition
-                                        // besides translate
-                                        childViews[largeClockId]?.let {
-                                            it.scaleX = scaleViewModel.scale
-                                            it.scaleY = scaleViewModel.scale
-                                        }
-                                    } else {
-                                        // For weather clock, large clock should have only scale
-                                        // transition with other parts in burnInLayer
-                                        childViews[burnInLayerId]?.scaleX = scaleViewModel.scale
-                                        childViews[burnInLayerId]?.scaleY = scaleViewModel.scale
-                                        childViews[aodNotificationIconContainerId]?.scaleX =
-                                            scaleViewModel.scale
-                                        childViews[aodNotificationIconContainerId]?.scaleY =
-                                            scaleViewModel.scale
+                            viewModel.scale.collect { scaleViewModel ->
+                                if (scaleViewModel.scaleClockOnly) {
+                                    // For clocks except weather clock, we have scale transition
+                                    // besides translate
+                                    childViews[largeClockId]?.let {
+                                        it.scaleX = scaleViewModel.scale
+                                        it.scaleY = scaleViewModel.scale
                                     }
+                                    // Make sure to reset these views, or they will be invisible
+                                    if (childViews[burnInLayerId]?.scaleX != 1f) {
+                                        childViews[burnInLayerId]?.scaleX = 1f
+                                        childViews[burnInLayerId]?.scaleY = 1f
+                                        childViews[aodNotificationIconContainerId]?.scaleX = 1f
+                                        childViews[aodNotificationIconContainerId]?.scaleY = 1f
+                                        view.requestLayout()
+                                    }
+                                } else {
+                                    // For weather clock, large clock should have only scale
+                                    // transition with other parts in burnInLayer
+                                    childViews[burnInLayerId]?.scaleX = scaleViewModel.scale
+                                    childViews[burnInLayerId]?.scaleY = scaleViewModel.scale
+                                    childViews[aodNotificationIconContainerId]?.scaleX =
+                                        scaleViewModel.scale
+                                    childViews[aodNotificationIconContainerId]?.scaleY =
+                                        scaleViewModel.scale
                                 }
+                            }
                         }
 
                         if (NotificationIconContainerRefactor.isEnabled) {
@@ -311,6 +313,8 @@
                         }
                     }
 
+                    launch { burnInParams.collect { viewModel.updateBurnInParams(it) } }
+
                     if (deviceEntryHapticsInteractor != null && vibratorHelper != null) {
                         launch {
                             deviceEntryHapticsInteractor.playSuccessHaptic.collect {
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 f95efaa..7c76e6a 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
@@ -408,6 +408,7 @@
             if (migrateClocksToBlueprint()) {
                 KeyguardPreviewClockViewBinder.bind(
                     context,
+                    displayId,
                     keyguardRootView,
                     clockViewModel,
                     clockController,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index 98bebd0..88ce9dc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -21,6 +21,8 @@
 import android.view.View
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
@@ -37,24 +39,24 @@
     private val clockViewModel: KeyguardClockViewModel,
 ) : KeyguardSection() {
     private lateinit var burnInLayer: AodBurnInLayer
+    // The burn-in layer requires at least 1 view at all times
+    private val emptyView: View by lazy {
+        View(context, null).apply {
+            id = R.id.burn_in_layer_empty_view
+            visibility = View.GONE
+        }
+    }
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (!migrateClocksToBlueprint()) {
             return
         }
 
-        // The burn-in layer requires at least 1 view at all times
-        val emptyView = View(context, null).apply { id = View.generateViewId() }
         constraintLayout.addView(emptyView)
         burnInLayer =
             AodBurnInLayer(context).apply {
                 id = R.id.burn_in_layer
                 registerListener(rootView)
                 addView(emptyView)
-                if (!migrateClocksToBlueprint()) {
-                    val statusView =
-                        constraintLayout.requireViewById<View>(R.id.keyguard_status_view)
-                    addView(statusView)
-                }
             }
         constraintLayout.addView(burnInLayer)
     }
@@ -70,6 +72,13 @@
         if (!migrateClocksToBlueprint()) {
             return
         }
+
+        constraintSet.apply {
+            // The empty view should not occupy any space
+            constrainHeight(R.id.burn_in_layer_empty_view, 1)
+            constrainWidth(R.id.burn_in_layer_empty_view, 0)
+            connect(R.id.burn_in_layer_empty_view, BOTTOM, PARENT_ID, BOTTOM)
+        }
     }
 
     override fun removeViews(constraintLayout: ConstraintLayout) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index a22671d..a183b72 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -52,6 +52,11 @@
     visibility: Int,
 ) = views.forEach { view -> this.setVisibility(view.id, visibility) }
 
+internal fun ConstraintSet.setAlpha(
+    views: Iterable<View>,
+    alpha: Float,
+) = views.forEach { view -> this.setAlpha(view.id, alpha) }
+
 open class ClockSection
 @Inject
 constructor(
@@ -101,6 +106,8 @@
         return constraintSet.apply {
             setVisibility(getTargetClockFace(clock).views, VISIBLE)
             setVisibility(getNonTargetClockFace(clock).views, GONE)
+            setAlpha(getTargetClockFace(clock).views, 1F)
+            setAlpha(getNonTargetClockFace(clock).views, 0F)
             if (!keyguardClockViewModel.useLargeClock) {
                 connect(sharedR.id.bc_smartspace_view, TOP, sharedR.id.date_smartspace_view, BOTTOM)
             }
@@ -179,7 +186,7 @@
                 context.resources.getDimensionPixelSize(customizationR.dimen.clock_padding_start) +
                     context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
             )
-            var smallClockTopMargin =
+            val smallClockTopMargin =
                 if (splitShadeStateController.shouldUseSplitNotificationShade(context.resources)) {
                     context.resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)
                 } else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index 3fc9b42..1085f94 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -26,7 +26,6 @@
 import androidx.annotation.VisibleForTesting
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
-import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.LockIconView
 import com.android.keyguard.LockIconViewController
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
@@ -56,7 +55,6 @@
 @Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
-    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val authController: AuthController,
     private val windowManager: WindowManager,
     private val context: Context,
@@ -111,7 +109,12 @@
     }
 
     override fun applyConstraints(constraintSet: ConstraintSet) {
-        val isUdfpsSupported = keyguardUpdateMonitor.isUdfpsSupported
+        val isUdfpsSupported =
+            if (DeviceEntryUdfpsRefactor.isEnabled) {
+                deviceEntryIconViewModel.get().isUdfpsSupported.value
+            } else {
+                authController.isUdfpsSupported
+            }
         val scaleFactor: Float = authController.scaleFactor
         val mBottomPaddingPx =
             context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
index f65f376..6184c82 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
@@ -199,8 +199,8 @@
             private val TRANSITION_PROPERTIES =
                 arrayOf(PROP_VISIBILITY, PROP_ALPHA, PROP_BOUNDS, SMARTSPACE_BOUNDS)
 
-            private val DEBUG = true
-            private val TAG = ClockFaceInTransition::class.simpleName!!
+            private val DEBUG = false
+            private val TAG = VisibilityBoundsTransition::class.simpleName!!
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt
index 9edb4d1..bbe5fed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt
@@ -23,13 +23,19 @@
 import com.android.systemui.deviceentry.shared.model.FaceTimeoutMessage
 import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage
 import com.android.systemui.deviceentry.shared.model.FingerprintMessage
+import com.android.systemui.statusbar.KeyguardIndicationController.DEFAULT_MESSAGE_TIME
+import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterIsInstance
 import kotlinx.coroutines.flow.filterNot
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
 
 /** View model for the alternate bouncer message area. */
 @ExperimentalCoroutinesApi
@@ -38,19 +44,32 @@
 constructor(
     biometricMessageInteractor: BiometricMessageInteractor,
     alternateBouncerInteractor: AlternateBouncerInteractor,
+    systemClock: com.android.systemui.util.time.SystemClock,
 ) {
-
-    private val faceMessage: Flow<FaceMessage> =
-        biometricMessageInteractor.faceMessage.filterNot { it is FaceTimeoutMessage }
+    private val fingerprintMessageWithTimestamp: Flow<Pair<FingerprintMessage?, Long>> =
+        biometricMessageInteractor.fingerprintMessage
+            .filterNot { it is FingerprintLockoutMessage }
+            .map { Pair(it, systemClock.uptimeMillis()) }
+            .filterIsInstance<Pair<FingerprintMessage?, Long>>()
+            .onStart { emit(Pair(null, -3500L)) }
     private val fingerprintMessage: Flow<FingerprintMessage> =
-        biometricMessageInteractor.fingerprintMessage.filterNot { it is FingerprintLockoutMessage }
+        fingerprintMessageWithTimestamp.filter { it.first != null }.map { it.first!! }
+    private val faceMessage: Flow<FaceMessage> =
+        biometricMessageInteractor.faceMessage
+            .filterNot { it is FaceTimeoutMessage }
+            // Don't show face messages if within the default message time for fp messages to show
+            .sample(fingerprintMessageWithTimestamp, ::Pair)
+            .filter { (_, fpMessage) ->
+                (systemClock.uptimeMillis() - fpMessage.second) >= DEFAULT_MESSAGE_TIME
+            }
+            .map { (faceMsg, _) -> faceMsg }
 
     val message: Flow<BiometricMessage?> =
         alternateBouncerInteractor.isVisible.flatMapLatest { isVisible ->
             if (isVisible) {
                 merge(
-                    faceMessage,
                     fingerprintMessage,
+                    faceMessage,
                 )
             } else {
                 flowOf(null)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
index 8a3b57b..5741b94 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
@@ -29,7 +29,6 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combineTransform
-import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.onStart
 
 /** Models UI state for the alpha of the AOD (always-on display). */
@@ -46,24 +45,23 @@
     /** The alpha level for the entire lockscreen while in AOD. */
     val alpha: Flow<Float> =
         combineTransform(
-                keyguardTransitionInteractor.transitions,
-                goneToAodTransitionViewModel.enterFromTopAnimationAlpha.onStart { emit(0f) },
-                goneToDozingTransitionViewModel.lockscreenAlpha.onStart { emit(0f) },
-                keyguardInteractor.keyguardAlpha.onStart { emit(1f) },
-            ) { step, goneToAodAlpha, goneToDozingAlpha, keyguardAlpha ->
-                if (step.to == GONE) {
-                    // When transitioning to GONE, only emit a value when complete as other
-                    // transitions may be controlling the alpha fade
-                    if (step.value == 1f) {
-                        emit(0f)
-                    }
-                } else if (step.from == GONE && step.to == AOD) {
-                    emit(goneToAodAlpha)
-                } else if (step.from == GONE && step.to == DOZING) {
-                    emit(goneToDozingAlpha)
-                } else if (!migrateClocksToBlueprint()) {
-                    emit(keyguardAlpha)
+            keyguardTransitionInteractor.transitions,
+            goneToAodTransitionViewModel.enterFromTopAnimationAlpha.onStart { emit(0f) },
+            goneToDozingTransitionViewModel.lockscreenAlpha.onStart { emit(0f) },
+            keyguardInteractor.keyguardAlpha.onStart { emit(1f) },
+        ) { step, goneToAodAlpha, goneToDozingAlpha, keyguardAlpha ->
+            if (step.to == GONE) {
+                // When transitioning to GONE, only emit a value when complete as other
+                // transitions may be controlling the alpha fade
+                if (step.value == 1f) {
+                    emit(0f)
                 }
+            } else if (step.from == GONE && step.to == AOD) {
+                emit(goneToAodAlpha)
+            } else if (step.from == GONE && step.to == DOZING) {
+                emit(goneToDozingAlpha)
+            } else if (!migrateClocksToBlueprint()) {
+                emit(keyguardAlpha)
             }
-            .distinctUntilChanged()
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
index 8665aab..f961e08 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
@@ -18,6 +18,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.util.Log
 import android.util.MathUtils
 import com.android.app.animation.Interpolators
 import com.android.keyguard.KeyguardClockSwitch
@@ -28,10 +29,6 @@
 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.KeyguardState.ALTERNATE_BOUNCER
-import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
-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.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
@@ -47,7 +44,6 @@
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
 
 /**
@@ -67,23 +63,26 @@
     private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
     private val keyguardClockViewModel: KeyguardClockViewModel,
 ) {
-    /** Horizontal translation for elements that need to apply anti-burn-in tactics. */
-    fun translationX(
-        params: BurnInParameters,
-    ): Flow<Float> {
-        return burnIn(params).map { it.translationX.toFloat() }
-    }
+    private val TAG = "AodBurnInViewModel"
 
-    /** Vertical translation for elements that need to apply anti-burn-in tactics. */
-    fun translationY(
-        params: BurnInParameters,
-    ): Flow<Float> {
+    /** All burn-in movement: x,y,scale, to shift items and prevent burn-in */
+    fun movement(
+        burnInParams: BurnInParameters,
+    ): Flow<BurnInModel> {
+        val params =
+            if (burnInParams.minViewY < burnInParams.topInset) {
+                // minViewY should never be below the inset. Correct it if needed
+                Log.w(TAG, "minViewY is below topInset: $burnInParams")
+                burnInParams.copy(minViewY = burnInParams.topInset)
+            } else {
+                burnInParams
+            }
         return configurationInteractor
             .dimensionPixelSize(R.dimen.keyguard_enter_from_top_translation_y)
             .flatMapLatest { enterFromTopAmount ->
                 combine(
                     keyguardInteractor.keyguardTranslationY.onStart { emit(0f) },
-                    burnIn(params).map { it.translationY.toFloat() }.onStart { emit(0f) },
+                    burnIn(params).onStart { emit(BurnInModel()) },
                     goneToAodTransitionViewModel
                         .enterFromTopTranslationY(enterFromTopAmount)
                         .onStart { emit(StateToValue()) },
@@ -93,32 +92,26 @@
                     aodToLockscreenTransitionViewModel.translationY(params.translationY).onStart {
                         emit(StateToValue())
                     },
-                ) { keyguardTranslationY, burnInY, goneToAod, occludedToLockscreen, aodToLockscreen
-                    ->
-                    if (isInTransition(aodToLockscreen.transitionState)) {
-                        aodToLockscreen.value ?: 0f
-                    } else if (isInTransition(goneToAod.transitionState)) {
-                        (goneToAod.value ?: 0f) + burnInY
-                    } else {
-                        burnInY + occludedToLockscreen + keyguardTranslationY
-                    }
+                ) {
+                    keyguardTranslationY,
+                    burnInModel,
+                    goneToAod,
+                    occludedToLockscreen,
+                    aodToLockscreen ->
+                    val translationY =
+                        if (isInTransition(aodToLockscreen.transitionState)) {
+                            aodToLockscreen.value ?: 0f
+                        } else if (isInTransition(goneToAod.transitionState)) {
+                            (goneToAod.value ?: 0f) + burnInModel.translationY
+                        } else {
+                            burnInModel.translationY + occludedToLockscreen + keyguardTranslationY
+                        }
+                    burnInModel.copy(translationY = translationY.toInt())
                 }
             }
             .distinctUntilChanged()
     }
 
-    /** Scale for elements that need to apply anti-burn-in tactics. */
-    fun scale(
-        params: BurnInParameters,
-    ): Flow<BurnInScaleViewModel> {
-        return burnIn(params).map {
-            BurnInScaleViewModel(
-                scale = it.scale,
-                scaleClockOnly = it.scaleClockOnly,
-            )
-        }
-    }
-
     private fun isInTransition(state: TransitionState): Boolean {
         return state == STARTED || state == RUNNING
     }
@@ -127,18 +120,13 @@
         params: BurnInParameters,
     ): Flow<BurnInModel> {
         return combine(
-            merge(
-                    keyguardTransitionInteractor.transition(GONE, AOD).map { it.value },
-                    keyguardTransitionInteractor.transition(AOD, PRIMARY_BOUNCER).map {
-                        1f - it.value
-                    },
-                    keyguardTransitionInteractor.transition(ALTERNATE_BOUNCER, AOD).map {
-                        it.value
-                    },
-                    keyguardTransitionInteractor.dozeAmountTransition.map { it.value },
-                )
-                .map { dozeAmount -> Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount) },
-            burnInInteractor.keyguardBurnIn,
+            keyguardTransitionInteractor.dozeAmountTransition.map {
+                Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it.value)
+            },
+            burnInInteractor.burnIn(
+                xDimenResourceId = R.dimen.burn_in_prevention_offset_x,
+                yDimenResourceId = R.dimen.burn_in_prevention_offset_y
+            ),
         ) { interpolated, burnIn ->
             val useScaleOnly =
                 (clockController(params.clockControllerProvider)
@@ -162,7 +150,6 @@
                     } else {
                         max(params.topInset, params.minViewY + burnInY) - params.minViewY
                     }
-
                 BurnInModel(
                     translationX = MathUtils.lerp(0, burnIn.translationX, interpolated).toInt(),
                     translationY = translationY,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
index b92a9a0..7e39a88 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
@@ -16,13 +16,16 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.util.MathUtils
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
 
 /** Breaks down AOD->GONE transition into discrete steps for corresponding views to consume. */
 @ExperimentalCoroutinesApi
@@ -40,5 +43,19 @@
             to = KeyguardState.GONE,
         )
 
+    /**
+     * AOD -> GONE should fade out the lockscreen contents. This transition plays both during wake
+     * and unlock, and also during insecure camera launch (which is GONE -> AOD (canceled) -> GONE).
+     */
+    fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
+        var startAlpha = 1f
+        return transitionAnimation.sharedFlow(
+            duration = 200.milliseconds,
+            onStart = { startAlpha = viewState.alpha() },
+            onStep = { MathUtils.lerp(startAlpha, 0f, it) },
+            onFinish = { 0f },
+        )
+    }
+
     override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
index 105a7ed..445575f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
@@ -16,12 +16,15 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.util.MathUtils
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
 
 /** Breaks down AOD->OCCLUDED transition into discrete steps for corresponding views to consume. */
 @SysUISingleton
@@ -37,5 +40,23 @@
             to = KeyguardState.OCCLUDED,
         )
 
+    /**
+     * Fade out the lockscreen during a transition to OCCLUDED.
+     *
+     * This happens when pressing the power button while a SHOW_WHEN_LOCKED activity is on the top
+     * of the task stack, as well as when the power button is double tapped on the LOCKSCREEN (the
+     * first tap transitions to AOD, the second cancels that transition and starts AOD -> OCCLUDED.
+     */
+    fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
+        var currentAlpha = 0f
+        return transitionAnimation.sharedFlow(
+            duration = 250.milliseconds,
+            startTime = 100.milliseconds, // Wait for the light reveal to "hit" the LS elements.
+            onStart = { currentAlpha = viewState.alpha() },
+            onStep = { MathUtils.lerp(currentAlpha, 0f, it) },
+            onCancel = { 0f },
+        )
+    }
+
     override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
index 662a77e..4c0a949 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
@@ -20,6 +20,8 @@
 import android.content.Context
 import com.android.settingslib.Utils
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
@@ -30,12 +32,14 @@
 import kotlinx.coroutines.flow.onStart
 
 /** Models the UI state for the device entry icon background view. */
+@Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA")
 @ExperimentalCoroutinesApi
 class DeviceEntryBackgroundViewModel
 @Inject
 constructor(
     val context: Context,
     val deviceEntryIconViewModel: DeviceEntryIconViewModel,
+    keyguardTransitionInteractor: KeyguardTransitionInteractor,
     configurationInteractor: ConfigurationInteractor,
     lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel,
     aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
@@ -94,6 +98,23 @@
                         alternateBouncerToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
                     )
                     .merge()
+                    .onStart {
+                        when (
+                            keyguardTransitionInteractor.currentKeyguardState.replayCache.last()
+                        ) {
+                            KeyguardState.GLANCEABLE_HUB,
+                            KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+                            KeyguardState.GONE,
+                            KeyguardState.OCCLUDED,
+                            KeyguardState.OFF,
+                            KeyguardState.DOZING,
+                            KeyguardState.DREAMING,
+                            KeyguardState.PRIMARY_BOUNCER,
+                            KeyguardState.AOD -> emit(0f)
+                            KeyguardState.ALTERNATE_BOUNCER,
+                            KeyguardState.LOCKSCREEN -> emit(1f)
+                        }
+                    }
             } else {
                 flowOf(0f)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index d657c24d..6281097 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -33,6 +33,7 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 
@@ -70,12 +71,13 @@
 
     // While dozing, the display can show the AOD UI; show the AOD udfps when dozing
     private val useAodIconVariant: Flow<Boolean> =
-        combine(isShowingAodOrDozing, deviceEntryUdfpsInteractor.isUdfpsSupported) {
-                isTransitionToAodOrDozing,
-                isUdfps ->
-                isTransitionToAodOrDozing && isUdfps
+        deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfspEnrolled ->
+            if (udfspEnrolled) {
+                isShowingAodOrDozing.distinctUntilChanged()
+            } else {
+                flowOf(false)
             }
-            .distinctUntilChanged()
+        }
 
     private val padding: Flow<Int> =
         deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { udfpsSupported ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index c9cf0c3..1a01897 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -36,7 +36,9 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.flowOf
@@ -61,6 +63,7 @@
     private val deviceEntryInteractor: DeviceEntryInteractor,
     private val deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
 ) {
+    val isUdfpsSupported: StateFlow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported
     private val intEvaluator = IntEvaluator()
     private val floatEvaluator = FloatEvaluator()
     private val showingAlternateBouncer: Flow<Boolean> =
@@ -136,7 +139,7 @@
         ) { alpha, alphaMultiplier ->
             alpha * alphaMultiplier
         }
-    val useBackgroundProtection: Flow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported
+    val useBackgroundProtection: StateFlow<Boolean> = isUdfpsSupported
     val burnInOffsets: Flow<BurnInOffsets> =
         deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolled ->
             if (udfpsEnrolled) {
@@ -179,7 +182,7 @@
         }
 
     private val isUnlocked: Flow<Boolean> =
-        deviceEntryInteractor.isUnlocked.flatMapLatest { isUnlocked ->
+        keyguardInteractor.isKeyguardDismissible.flatMapLatest { isUnlocked ->
             if (!isUnlocked) {
                 flowOf(false)
             } else {
@@ -196,7 +199,7 @@
     val iconType: Flow<DeviceEntryIconView.IconType> =
         combine(
             deviceEntryUdfpsInteractor.isListeningForUdfps,
-            keyguardInteractor.isKeyguardDismissible,
+            isUnlocked,
         ) { isListeningForUdfps, isUnlocked ->
             if (isListeningForUdfps) {
                 DeviceEntryIconView.IconType.FINGERPRINT
@@ -206,10 +209,11 @@
                 DeviceEntryIconView.IconType.LOCK
             }
         }
+    val isVisible: Flow<Boolean> = deviceEntryViewAlpha.map { it > 0f }.distinctUntilChanged()
     val isLongPressEnabled: Flow<Boolean> =
         combine(
             iconType,
-            deviceEntryUdfpsInteractor.isUdfpsSupported,
+            isUdfpsSupported,
         ) { deviceEntryStatus, isUdfps ->
             when (deviceEntryStatus) {
                 DeviceEntryIconView.IconType.LOCK -> isUdfps
@@ -217,6 +221,7 @@
                 DeviceEntryIconView.IconType.FINGERPRINT -> false
             }
         }
+
     val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> =
         combine(iconType, isLongPressEnabled) { deviceEntryStatus, longPressEnabled ->
             if (longPressEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt
index fca1604..8851a51 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt
@@ -16,12 +16,14 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.util.MathUtils
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor.Companion.TO_GONE_DURATION
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 
@@ -41,6 +43,16 @@
             to = KeyguardState.GONE,
         )
 
+    fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
+        var startAlpha = 1f
+        return transitionAnimation.sharedFlow(
+            duration = 200.milliseconds,
+            onStart = { startAlpha = viewState.alpha() },
+            onStep = { MathUtils.lerp(startAlpha, 0f, it) },
+            onFinish = { 0f },
+        )
+    }
+
     override val deviceEntryParentViewAlpha: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(0f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
new file mode 100644
index 0000000..c0b1195
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.util.MathUtils
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down DOZING->OCCLUDED transition into discrete steps for corresponding views to consume.
+ */
+@SysUISingleton
+class DozingToOccludedTransitionViewModel
+@Inject
+constructor(
+    animationFlow: KeyguardTransitionAnimationFlow,
+) : DeviceEntryIconTransition {
+    private val transitionAnimation =
+        animationFlow.setup(
+            duration = FromAodTransitionInteractor.TO_OCCLUDED_DURATION,
+            from = KeyguardState.DOZING,
+            to = KeyguardState.OCCLUDED,
+        )
+
+    /**
+     * Fade out the lockscreen during a transition to OCCLUDED.
+     *
+     * This happens when pressing the power button while a SHOW_WHEN_LOCKED activity is on the top
+     * of the task stack, as well as when the power button is double tapped on the LOCKSCREEN (the
+     * first tap transitions to DOZING, the second cancels that transition and starts DOZING ->
+     * OCCLUDED.
+     */
+    fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
+        var currentAlpha = 0f
+        return transitionAnimation.sharedFlow(
+            duration = 250.milliseconds,
+            startTime = 100.milliseconds, // Wait for the light reveal to "hit" the LS elements.
+            onStart = { currentAlpha = viewState.alpha() },
+            onStep = { MathUtils.lerp(currentAlpha, 0f, it) },
+            onCancel = { 0f },
+        )
+    }
+
+    override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
index c64f277..789e4fb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
@@ -19,6 +19,7 @@
 import com.android.app.animation.Interpolators.EMPHASIZED
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.res.R
@@ -36,7 +37,9 @@
 constructor(
     animationFlow: KeyguardTransitionAnimationFlow,
     configurationInteractor: ConfigurationInteractor,
+    private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor,
 ) {
+    fun startTransition() = fromDreamingTransitionInteractor.startToGlanceableHubTransition()
 
     private val transitionAnimation =
         animationFlow.setup(
@@ -58,6 +61,9 @@
                 )
             }
 
+    // Keep the dream visible while the hub swipes in over the dream.
+    val dreamAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(1f)
+
     val dreamOverlayAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
             duration = 167.milliseconds,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index d3277cd..f191aa7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -20,16 +20,13 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
 
 /**
  * Breaks down DREAMING->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -40,7 +37,6 @@
 class DreamingToLockscreenTransitionViewModel
 @Inject
 constructor(
-    keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
@@ -53,12 +49,6 @@
             to = KeyguardState.LOCKSCREEN,
         )
 
-    val transitionEnded =
-        keyguardTransitionInteractor.fromDreamingTransition.filter { step ->
-            step.transitionState == TransitionState.FINISHED ||
-                step.transitionState == TransitionState.CANCELED
-        }
-
     /** Dream overlay y-translation on exit */
     fun dreamOverlayTranslationY(translatePx: Int): Flow<Float> {
         return transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
index e5b5964..abf2372 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
@@ -79,6 +79,8 @@
 
     val notificationAlpha: Flow<Float> = keyguardAlpha
 
+    val shortcutsAlpha: Flow<Float> = keyguardAlpha
+
     val notificationTranslationX: Flow<Float> =
         keyguardTranslationX.map { it.value }.filterNotNull()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index f981fd5..58c45c7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -36,6 +36,7 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
 @SysUISingleton
@@ -67,7 +68,16 @@
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = LARGE
+                initialValue = LARGE,
+            )
+
+    val isLargeClockVisible =
+        clockSize
+            .map { it == LARGE }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
             )
 
     val currentClock = keyguardClockInteractor.currentClock
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
index 6458eda..e35e065 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
@@ -17,10 +17,14 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
+import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.res.R
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -32,9 +36,10 @@
 @Inject
 constructor(
     private val keyguardInteractor: KeyguardInteractor,
-    bottomAreaInteractor: KeyguardBottomAreaInteractor,
+    private val bottomAreaInteractor: KeyguardBottomAreaInteractor,
     keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel,
     private val burnInHelperWrapper: BurnInHelperWrapper,
+    private val burnInInteractor: BurnInInteractor,
     private val shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
     configurationInteractor: ConfigurationInteractor,
 ) {
@@ -63,24 +68,37 @@
                 }
                 .distinctUntilChanged()
         }
+
+    private val burnIn: Flow<BurnInModel> =
+        burnInInteractor
+            .burnIn(
+                xDimenResourceId = R.dimen.burn_in_prevention_offset_x,
+                yDimenResourceId = R.dimen.default_burn_in_prevention_offset,
+            )
+            .distinctUntilChanged()
+
     /** An observable for the x-offset by which the indication area should be translated. */
     val indicationAreaTranslationX: Flow<Float> =
-        if (keyguardBottomAreaRefactor()) {
-            keyguardInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
+        if (migrateClocksToBlueprint() || keyguardBottomAreaRefactor()) {
+            burnIn.map { it.translationX.toFloat() }
         } else {
             bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
         }
 
     /** Returns an observable for the y-offset by which the indication area should be translated. */
     fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
-        return keyguardInteractor.dozeAmount
-            .map { dozeAmount ->
-                dozeAmount *
-                    (burnInHelperWrapper.burnInOffset(
-                        /* amplitude = */ defaultBurnInOffset * 2,
-                        /* xAxis= */ false,
-                    ) - defaultBurnInOffset)
-            }
-            .distinctUntilChanged()
+        return if (migrateClocksToBlueprint()) {
+            burnIn.map { it.translationY.toFloat() }
+        } else {
+            keyguardInteractor.dozeAmount
+                .map { dozeAmount ->
+                    dozeAmount *
+                        (burnInHelperWrapper.burnInOffset(
+                            /* amplitude = */ defaultBurnInOffset * 2,
+                            /* xAxis= */ false,
+                        ) - defaultBurnInOffset)
+                }
+                .distinctUntilChanged()
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt
index f95a8a7..b9ff259 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt
@@ -45,9 +45,10 @@
     val isSmallClockVisible: Flow<Boolean> =
         interactor.selectedClockSize.map { it == SettingsClockSize.SMALL }
 
-    var lastClock: ClockController? = null
+    var lastClockPair: Pair<ClockController, ClockController>? = null
 
-    val previewClock: StateFlow<ClockController> = interactor.previewClock
+    val previewClockPair: StateFlow<Pair<ClockController, ClockController>> =
+        interactor.previewClockPair
 
     val selectedClockSize: StateFlow<SettingsClockSize?> =
         interactor.selectedClockSize.stateIn(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index 4db942cc..c4383fc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -52,6 +52,7 @@
     occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
     offToLockscreenTransitionViewModel: OffToLockscreenTransitionViewModel,
     primaryBouncerToLockscreenTransitionViewModel: PrimaryBouncerToLockscreenTransitionViewModel,
+    glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
     lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel,
     lockscreenToDozingTransitionViewModel: LockscreenToDozingTransitionViewModel,
     lockscreenToDreamingHostedTransitionViewModel: LockscreenToDreamingHostedTransitionViewModel,
@@ -59,6 +60,7 @@
     lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel,
     lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel,
     lockscreenToPrimaryBouncerTransitionViewModel: LockscreenToPrimaryBouncerTransitionViewModel,
+    lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
     transitionInteractor: KeyguardTransitionInteractor,
 ) {
 
@@ -110,6 +112,7 @@
             occludedToLockscreenTransitionViewModel.shortcutsAlpha,
             offToLockscreenTransitionViewModel.shortcutsAlpha,
             primaryBouncerToLockscreenTransitionViewModel.shortcutsAlpha,
+            glanceableHubToLockscreenTransitionViewModel.shortcutsAlpha,
         )
 
     /** alpha while fading the quick affordances in */
@@ -122,6 +125,7 @@
             lockscreenToGoneTransitionViewModel.shortcutsAlpha,
             lockscreenToOccludedTransitionViewModel.shortcutsAlpha,
             lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha,
+            lockscreenToGlanceableHubTransitionViewModel.shortcutsAlpha,
             shadeExpansionAlpha,
         )
 
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 921eb66..5ca9215 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
@@ -24,13 +24,16 @@
 import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 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.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.ui.StateToValue
@@ -46,8 +49,11 @@
 import com.android.systemui.util.ui.zip
 import javax.inject.Inject
 import kotlin.math.max
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -56,12 +62,14 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class KeyguardRootViewModel
 @Inject
 constructor(
+    @Application private val scope: CoroutineScope,
     private val deviceEntryInteractor: DeviceEntryInteractor,
     private val dozeParameters: DozeParameters,
     private val keyguardInteractor: KeyguardInteractor,
@@ -70,10 +78,19 @@
     private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
     private val alternateBouncerToGoneTransitionViewModel:
         AlternateBouncerToGoneTransitionViewModel,
+    private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel,
     private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+    private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
+    private val dozingToGoneTransitionViewModel: DozingToGoneTransitionViewModel,
     private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
+    private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
+    private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
     private val glanceableHubToLockscreenTransitionViewModel:
         GlanceableHubToLockscreenTransitionViewModel,
+    private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
+    private val goneToDozingTransitionViewModel: GoneToDozingTransitionViewModel,
+    private val lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel,
+    private val lockscreenToDozingTransitionViewModel: LockscreenToDozingTransitionViewModel,
     private val lockscreenToDreamingTransitionViewModel: LockscreenToDreamingTransitionViewModel,
     private val lockscreenToGlanceableHubTransitionViewModel:
         LockscreenToGlanceableHubTransitionViewModel,
@@ -92,6 +109,8 @@
     private val aodAlphaViewModel: AodAlphaViewModel,
     private val shadeInteractor: ShadeInteractor,
 ) {
+    private var burnInJob: Job? = null
+    private val burnInModel = MutableStateFlow(BurnInModel())
 
     val burnInLayerVisibility: Flow<Int> =
         keyguardTransitionInteractor.startedKeyguardState
@@ -116,6 +135,27 @@
             }
             .distinctUntilChanged()
 
+    /**
+     * Keyguard should not show while the communal hub is fully visible. This check is added since
+     * at the moment, closing the notification shade will cause the keyguard alpha to be set back to
+     * 1. Also ensure keyguard is never visible when GONE.
+     */
+    private val hideKeyguard: Flow<Boolean> =
+        combine(
+                communalInteractor.isIdleOnCommunal,
+                keyguardTransitionInteractor
+                    .transitionValue(GONE)
+                    .map { it == 1f }
+                    .onStart { emit(false) },
+                keyguardTransitionInteractor
+                    .transitionValue(OCCLUDED)
+                    .map { it == 1f }
+                    .onStart { emit(false) },
+            ) { isIdleOnCommunal, isGone, isOccluded ->
+                isIdleOnCommunal || isGone || isOccluded
+            }
+            .distinctUntilChanged()
+
     /** Last point that the root view was tapped */
     val lastRootViewTapPosition: Flow<Point?> = keyguardInteractor.lastRootViewTapPosition
 
@@ -132,18 +172,25 @@
     /** An observable for the alpha level for the entire keyguard root view. */
     fun alpha(viewState: ViewStateAccessor): Flow<Float> {
         return combine(
-                communalInteractor.isIdleOnCommunal,
-                keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) },
+                hideKeyguard,
                 // The transitions are mutually exclusive, so they are safe to merge to get the last
                 // value emitted by any of them. Do not add flows that cannot make this guarantee.
                 merge(
-                        aodAlphaViewModel.alpha,
                         alphaOnShadeExpansion,
                         keyguardInteractor.dismissAlpha.filterNotNull(),
                         alternateBouncerToGoneTransitionViewModel.lockscreenAlpha,
+                        aodToGoneTransitionViewModel.lockscreenAlpha(viewState),
                         aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
+                        aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
+                        dozingToGoneTransitionViewModel.lockscreenAlpha(viewState),
                         dozingToLockscreenTransitionViewModel.lockscreenAlpha,
+                        dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState),
+                        dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
                         glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
+                        goneToAodTransitionViewModel.enterFromTopAnimationAlpha,
+                        goneToDozingTransitionViewModel.lockscreenAlpha,
+                        lockscreenToAodTransitionViewModel.lockscreenAlpha(viewState),
+                        lockscreenToDozingTransitionViewModel.lockscreenAlpha,
                         lockscreenToDreamingTransitionViewModel.lockscreenAlpha,
                         lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha,
                         lockscreenToGoneTransitionViewModel.lockscreenAlpha(viewState),
@@ -156,12 +203,8 @@
                         primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha,
                     )
                     .onStart { emit(1f) }
-            ) { isIdleOnCommunal, goneValue, alpha ->
-                if (isIdleOnCommunal || goneValue == 1f) {
-                    // Keyguard should not show while the communal hub is fully visible. This check
-                    // is added since at the moment, closing the notification shade will cause the
-                    // keyguard alpha to be set back to 1. Also ensure keyguard is never visible
-                    // when GONE.
+            ) { hideKeyguard, alpha ->
+                if (hideKeyguard) {
                     0f
                 } else {
                     alpha
@@ -179,21 +222,33 @@
     /** For elements that appear and move during the animation -> AOD */
     val burnInLayerAlpha: Flow<Float> = aodAlphaViewModel.alpha
 
-    fun translationY(params: BurnInParameters): Flow<Float> {
-        return aodBurnInViewModel.translationY(params)
-    }
+    val translationY: Flow<Float> = burnInModel.map { it.translationY.toFloat() }
 
-    fun translationX(params: BurnInParameters): Flow<StateToValue> {
-        return merge(
-            aodBurnInViewModel.translationX(params).map { StateToValue(to = AOD, value = it) },
+    val translationX: Flow<StateToValue> =
+        merge(
+            burnInModel.map { StateToValue(to = AOD, value = it.translationX.toFloat()) },
             lockscreenToGlanceableHubTransitionViewModel.keyguardTranslationX,
             glanceableHubToLockscreenTransitionViewModel.keyguardTranslationX,
         )
+
+    fun updateBurnInParams(params: BurnInParameters) {
+        burnInJob?.cancel()
+
+        burnInJob =
+            scope.launch {
+                aodBurnInViewModel.movement(params).collect {
+                    burnInModel.value = it
+                }
+            }
     }
 
-    fun scale(params: BurnInParameters): Flow<BurnInScaleViewModel> {
-        return aodBurnInViewModel.scale(params)
-    }
+    val scale: Flow<BurnInScaleViewModel> =
+        burnInModel.map {
+            BurnInScaleViewModel(
+                scale = it.scale,
+                scaleClockOnly = it.scaleClockOnly,
+            )
+        }
 
     /** Is the notification icon container visible? */
     val isNotifIconContainerVisible: Flow<AnimatedValue<Boolean>> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index d792889..23320be 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.SplitShadeStateController
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
@@ -39,6 +40,7 @@
     private val interactor: KeyguardBlueprintInteractor,
     private val authController: AuthController,
     val longPress: KeyguardLongPressViewModel,
+    val splitShadeStateController: SplitShadeStateController,
 ) {
     private val clockSize = clockInteractor.clockSize
 
@@ -46,8 +48,10 @@
         get() = authController.isUdfpsSupported
     val isLargeClockVisible: Boolean
         get() = clockSize.value == KeyguardClockSwitch.LARGE
-    val areNotificationsVisible: Boolean
-        get() = !isLargeClockVisible
+    fun areNotificationsVisible(resources: Resources): Boolean {
+        return !isLargeClockVisible ||
+            splitShadeStateController.shouldUseSplitNotificationShade(resources)
+    }
 
     fun getSmartSpacePaddingTop(resources: Resources): Int {
         return if (isLargeClockVisible) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 9afe8fc..993e81b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -14,19 +14,29 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.keyguard.ui.viewmodel
 
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.stateIn
 
 /** Models UI state and handles user input for the lockscreen scene. */
@@ -37,30 +47,73 @@
     @Application applicationScope: CoroutineScope,
     deviceEntryInteractor: DeviceEntryInteractor,
     communalInteractor: CommunalInteractor,
+    shadeInteractor: ShadeInteractor,
     val longPress: KeyguardLongPressViewModel,
     val notifications: NotificationsPlaceholderViewModel,
 ) {
-    /** The key of the scene we should switch to when swiping up. */
-    val upDestinationSceneKey: StateFlow<SceneKey> =
-        deviceEntryInteractor.isUnlocked
-            .map { isUnlocked -> upDestinationSceneKey(isUnlocked) }
+    val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+        combine(
+                deviceEntryInteractor.isUnlocked,
+                communalInteractor.isCommunalAvailable,
+                shadeInteractor.shadeMode,
+            ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
+                destinationScenes(
+                    isDeviceUnlocked = isDeviceUnlocked,
+                    isCommunalAvailable = isCommunalAvailable,
+                    shadeMode = shadeMode,
+                )
+            }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = upDestinationSceneKey(deviceEntryInteractor.isUnlocked.value),
+                initialValue =
+                    destinationScenes(
+                        isDeviceUnlocked = deviceEntryInteractor.isUnlocked.value,
+                        isCommunalAvailable = false,
+                        shadeMode = shadeInteractor.shadeMode.value,
+                    ),
             )
 
-    private fun upDestinationSceneKey(isUnlocked: Boolean): SceneKey {
-        return if (isUnlocked) SceneKey.Gone else SceneKey.Bouncer
+    private fun destinationScenes(
+        isDeviceUnlocked: Boolean,
+        isCommunalAvailable: Boolean,
+        shadeMode: ShadeMode,
+    ): Map<UserAction, UserActionResult> {
+        val quickSettingsIfSingleShade =
+            if (shadeMode is ShadeMode.Single) {
+                Scenes.QuickSettings
+            } else {
+                Scenes.Shade
+            }
+
+        return mapOf(
+                Swipe.Left to UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable },
+                Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer,
+
+                // Swiping down from the top edge goes to QS (or shade if in split shade mode).
+                swipeDownFromTop(pointerCount = 1) to quickSettingsIfSingleShade,
+                swipeDownFromTop(pointerCount = 2) to quickSettingsIfSingleShade,
+
+                // Swiping down, not from the edge, always navigates to the shade scene.
+                swipeDown(pointerCount = 1) to Scenes.Shade,
+                swipeDown(pointerCount = 2) to Scenes.Shade,
+            )
+            .filterValues { it != null }
+            .mapValues { checkNotNull(it.value) }
     }
 
-    /** The key of the scene we should switch to when swiping left. */
-    val leftDestinationSceneKey: StateFlow<SceneKey?> =
-        communalInteractor.isCommunalAvailable
-            .map { available -> if (available) SceneKey.Communal else null }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = null,
-            )
+    private fun swipeDownFromTop(pointerCount: Int): Swipe {
+        return Swipe(
+            SwipeDirection.Down,
+            fromSource = Edge.Top,
+            pointerCount = pointerCount,
+        )
+    }
+
+    private fun swipeDown(pointerCount: Int): Swipe {
+        return Swipe(
+            SwipeDirection.Down,
+            pointerCount = pointerCount,
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt
index 7bf51a7..1f9f304 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.util.MathUtils
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
@@ -67,6 +68,15 @@
             onCancel = { 1f },
         )
 
+    fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
+        var startAlpha = 1f
+        return transitionAnimation.sharedFlow(
+            duration = 500.milliseconds,
+            onStart = { startAlpha = viewState.alpha() },
+            onStep = { MathUtils.lerp(startAlpha, 1f, it) },
+        )
+    }
+
     override val deviceEntryParentViewAlpha: Flow<Float> =
         deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest {
             isUdfpsEnrolledAndEnabled ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
index b60c52b..c836f01 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
@@ -44,6 +44,14 @@
             to = KeyguardState.DOZING,
         )
 
+    val lockscreenAlpha: Flow<Float> =
+        transitionAnimation.sharedFlow(
+            duration = 250.milliseconds,
+            onStep = { 1 - it },
+            onFinish = { 1f },
+            onCancel = { 1f },
+        )
+
     val shortcutsAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
             duration = 250.milliseconds,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
index 978e71e..b7f7b06 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
@@ -81,6 +81,8 @@
 
     val notificationAlpha: Flow<Float> = keyguardAlpha
 
+    val shortcutsAlpha: Flow<Float> = keyguardAlpha
+
     val notificationTranslationX: Flow<Float> =
         keyguardTranslationX.map { it.value }.filterNotNull()
 }
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
similarity index 65%
copy from packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
copy to packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
index 8ad0a08..027a739 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
@@ -14,8 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel.component.mediaoutput
+package com.android.systemui.keyguard.ui.viewmodel
 
-import dagger.Module
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import javax.inject.Inject
 
-@Module interface MediaOutputModule
+class MediaCarouselViewModel @Inject constructor(mediaDataManager: MediaDataManager) {
+    val isMediaVisible: Boolean = mediaDataManager.hasActiveMediaOrRecommendation()
+}
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 378ce52..53f4488 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
@@ -60,6 +60,22 @@
     private var leaveShadeOpen: Boolean = false
     private var willRunDismissFromKeyguard: Boolean = false
 
+    val notificationAlpha: Flow<Float> =
+        transitionAnimation.sharedFlow(
+            duration = 200.milliseconds,
+            onStart = {
+                leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
+                willRunDismissFromKeyguard = primaryBouncerInteractor.willRunDismissFromKeyguard()
+            },
+            onStep = {
+                if (willRunDismissFromKeyguard || leaveShadeOpen) {
+                    1f
+                } else {
+                    1f - it
+                }
+            },
+        )
+
     /** Bouncer container alpha */
     val bouncerAlpha: Flow<Float> =
         if (featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
@@ -94,6 +110,7 @@
         } else {
             createLockscreenAlpha(primaryBouncerInteractor::willRunDismissFromKeyguard)
         }
+
     private fun createLockscreenAlpha(willRunAnimationOnKeyguard: () -> Boolean): Flow<Float> {
         return transitionAnimation.sharedFlow(
             duration = 50.milliseconds,
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 8b7c85b..f2013be 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -546,7 +546,7 @@
     @SysUISingleton
     @KeyguardLog
     public static LogBuffer provideKeyguardLogBuffer(LogBufferFactory factory) {
-        return factory.create("KeyguardLog", 250);
+        return factory.create("KeyguardLog", 500);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
index 6fc22ea..865c49e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.media.controls.domain.pipeline
 
 import android.annotation.SuppressLint
+import android.app.ActivityOptions
 import android.app.BroadcastOptions
 import android.app.Notification
 import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
@@ -1272,7 +1273,7 @@
             val options = BroadcastOptions.makeBasic()
             options.setInteractive(true)
             options.setPendingIntentBackgroundActivityStartMode(
-                BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
             )
             intent.send(options.toBundle())
             true
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 840b309..26c63f3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -109,7 +109,7 @@
 import com.android.systemui.media.controls.util.MediaFlags;
 import com.android.systemui.media.controls.util.MediaUiEventLogger;
 import com.android.systemui.media.controls.util.SmallHash;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
 import com.android.systemui.monet.ColorScheme;
 import com.android.systemui.monet.Style;
 import com.android.systemui.plugins.ActivityStarter;
@@ -223,7 +223,7 @@
     protected int mUid = Process.INVALID_UID;
     private int mSmartspaceMediaItemsCount;
     private MediaCarouselController mMediaCarouselController;
-    private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+    private final MediaOutputDialogManager mMediaOutputDialogManager;
     private final FalsingManager mFalsingManager;
     private MetadataAnimationHandler mMetadataAnimationHandler;
     private ColorSchemeTransition mColorSchemeTransition;
@@ -304,7 +304,7 @@
             MediaViewController mediaViewController,
             SeekBarViewModel seekBarViewModel,
             Lazy<MediaDataManager> lazyMediaDataManager,
-            MediaOutputDialogFactory mediaOutputDialogFactory,
+            MediaOutputDialogManager mediaOutputDialogManager,
             MediaCarouselController mediaCarouselController,
             FalsingManager falsingManager,
             SystemClock systemClock,
@@ -324,7 +324,7 @@
         mSeekBarViewModel = seekBarViewModel;
         mMediaViewController = mediaViewController;
         mMediaDataManagerLazy = lazyMediaDataManager;
-        mMediaOutputDialogFactory = mediaOutputDialogFactory;
+        mMediaOutputDialogManager = mediaOutputDialogManager;
         mMediaCarouselController = mediaCarouselController;
         mFalsingManager = falsingManager;
         mSystemClock = systemClock;
@@ -737,7 +737,7 @@
                                     mPackageName, mMediaViewHolder.getSeamlessButton());
                         } else {
                             mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
-                            mMediaOutputDialogFactory.create(mPackageName, true,
+                            mMediaOutputDialogManager.createAndShow(mPackageName, true,
                                     mMediaViewHolder.getSeamlessButton());
                         }
                     } else {
@@ -761,7 +761,7 @@
                                 }
                             }
                         } else {
-                            mMediaOutputDialogFactory.create(mPackageName, true,
+                            mMediaOutputDialogManager.createAndShow(mPackageName, true,
                                     mMediaViewHolder.getSeamlessButton());
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
index 5d113a9..452cb7e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
@@ -31,7 +31,8 @@
 ) {
     /** Creates a [LocalMediaManager] for the given package. */
     fun create(packageName: String?): LocalMediaManager {
-        return InfoMediaManager.createInstance(context, packageName, null, localBluetoothManager)
-            .run { LocalMediaManager(context, localBluetoothManager, this, packageName) }
+        return InfoMediaManager.createInstance(context, packageName, localBluetoothManager).run {
+            LocalMediaManager(context, localBluetoothManager, this, packageName)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControlsRefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControlsRefactorFlag.kt
new file mode 100644
index 0000000..2850b4b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControlsRefactorFlag.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.util
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the media_controls_refactor flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object MediaControlsRefactorFlag {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_MEDIA_CONTROLS_REFACTOR
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the flag enabled? */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.mediaControlsRefactor()
+
+    /**
+     * 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/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 15747b9..d4bd6da 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -58,4 +58,7 @@
     /** Check whether to use scene framework */
     fun isSceneContainerEnabled() =
         sceneContainerFlags.isEnabled() && MediaInSceneContainerFlag.isEnabled
+
+    /** Check whether to use media refactor code */
+    fun isMediaControlsRefactorEnabled() = MediaControlsRefactorFlag.isEnabled
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
deleted file mode 100644
index b6e3937..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media.dialog
-
-import android.app.KeyguardManager
-import android.content.Context
-import android.media.AudioManager
-import android.media.session.MediaSessionManager
-import android.os.PowerExemptionManager
-import android.view.View
-import com.android.internal.logging.UiEventLogger
-import com.android.settingslib.bluetooth.LocalBluetoothManager
-import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.media.nearby.NearbyMediaDevicesManager
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
-import javax.inject.Inject
-
-/**
- * Factory to create [MediaOutputBroadcastDialog] objects.
- */
-class MediaOutputBroadcastDialogFactory @Inject constructor(
-    private val context: Context,
-    private val mediaSessionManager: MediaSessionManager,
-    private val lbm: LocalBluetoothManager?,
-    private val starter: ActivityStarter,
-    private val broadcastSender: BroadcastSender,
-    private val notifCollection: CommonNotifCollection,
-    private val uiEventLogger: UiEventLogger,
-    private val dialogTransitionAnimator: DialogTransitionAnimator,
-    private val nearbyMediaDevicesManager: NearbyMediaDevicesManager,
-    private val audioManager: AudioManager,
-    private val powerExemptionManager: PowerExemptionManager,
-    private val keyGuardManager: KeyguardManager,
-    private val featureFlags: FeatureFlags,
-    private val userTracker: UserTracker
-) {
-    var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null
-
-    /** Creates a [MediaOutputBroadcastDialog] for the given package. */
-    fun create(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
-        // Dismiss the previous dialog, if any.
-        mediaOutputBroadcastDialog?.dismiss()
-
-        val controller = MediaOutputController(context, packageName,
-                mediaSessionManager, lbm, starter, notifCollection,
-                dialogTransitionAnimator, nearbyMediaDevicesManager, audioManager,
-                powerExemptionManager, keyGuardManager, featureFlags, userTracker)
-        val dialog =
-                MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
-        mediaOutputBroadcastDialog = dialog
-
-        // Show the dialog.
-        if (view != null) {
-            dialogTransitionAnimator.showFromView(dialog, view)
-        } else {
-            dialog.show()
-        }
-    }
-
-    /** dismiss [MediaOutputBroadcastDialog] if exist. */
-    fun dismiss() {
-        mediaOutputBroadcastDialog?.dismiss()
-        mediaOutputBroadcastDialog = null
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
new file mode 100644
index 0000000..54d175c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dialog
+
+import android.content.Context
+import android.view.View
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.broadcast.BroadcastSender
+import javax.inject.Inject
+
+/** Manager to create and show a [MediaOutputBroadcastDialog]. */
+class MediaOutputBroadcastDialogManager
+@Inject
+constructor(
+    private val context: Context,
+    private val broadcastSender: BroadcastSender,
+    private val dialogTransitionAnimator: DialogTransitionAnimator,
+    private val mediaOutputControllerFactory: MediaOutputController.Factory
+) {
+    var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null
+
+    /** Creates a [MediaOutputBroadcastDialog] for the given package. */
+    fun createAndShow(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
+        // Dismiss the previous dialog, if any.
+        mediaOutputBroadcastDialog?.dismiss()
+
+        val controller = mediaOutputControllerFactory.create(packageName)
+        val dialog =
+            MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
+        mediaOutputBroadcastDialog = dialog
+
+        // Show the dialog.
+        if (view != null) {
+            dialogTransitionAnimator.showFromView(dialog, view)
+        } else {
+            dialog.show()
+        }
+    }
+
+    /** dismiss [MediaOutputBroadcastDialog] if exist. */
+    fun dismiss() {
+        mediaOutputBroadcastDialog?.dismiss()
+        mediaOutputBroadcastDialog = null
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index b3b7bce..adee7f2c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -64,6 +64,7 @@
 import android.view.WindowManager;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.core.graphics.drawable.IconCompat;
 
@@ -91,6 +92,10 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -105,8 +110,6 @@
 import java.util.concurrent.Executor;
 import java.util.stream.Collectors;
 
-import javax.inject.Inject;
-
 /**
  * Controller for media output dialog
  */
@@ -170,10 +173,13 @@
         ACTION_BROADCAST_INFO_ICON
     }
 
-    @Inject
-    public MediaOutputController(@NonNull Context context, String packageName,
-            MediaSessionManager mediaSessionManager, LocalBluetoothManager
-            lbm, ActivityStarter starter,
+    @AssistedInject
+    public MediaOutputController(
+            Context context,
+            @Assisted String packageName,
+            MediaSessionManager mediaSessionManager,
+            @Nullable LocalBluetoothManager lbm,
+            ActivityStarter starter,
             CommonNotifCollection notifCollection,
             DialogTransitionAnimator dialogTransitionAnimator,
             NearbyMediaDevicesManager nearbyMediaDevicesManager,
@@ -193,7 +199,7 @@
         mKeyGuardManager = keyGuardManager;
         mFeatureFlags = featureFlags;
         mUserTracker = userTracker;
-        InfoMediaManager imm = InfoMediaManager.createInstance(mContext, packageName, null, lbm);
+        InfoMediaManager imm = InfoMediaManager.createInstance(mContext, packageName, lbm);
         mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
         mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
         mDialogTransitionAnimator = dialogTransitionAnimator;
@@ -222,6 +228,12 @@
                 R.dimen.media_output_dialog_selectable_margin_end);
     }
 
+    @AssistedFactory
+    public interface Factory {
+        /** Construct a MediaOutputController */
+        MediaOutputController create(String packageName);
+    }
+
     protected void start(@NonNull Callback cb) {
         synchronized (mMediaDevicesLock) {
             mCachedMediaDevices.clear();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
similarity index 62%
rename from packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
rename to packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
index 02be0c1..e7816a4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
@@ -16,43 +16,24 @@
 
 package com.android.systemui.media.dialog
 
-import android.app.KeyguardManager
 import android.content.Context
-import android.media.AudioManager
-import android.media.session.MediaSessionManager
-import android.os.PowerExemptionManager
 import android.view.View
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.UiEventLogger
-import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.media.nearby.NearbyMediaDevicesManager
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import javax.inject.Inject
 
-/** Factory to create [MediaOutputDialog] objects. */
-open class MediaOutputDialogFactory
+/** Manager to create and show a [MediaOutputDialog]. */
+open class MediaOutputDialogManager
 @Inject
 constructor(
     private val context: Context,
-    private val mediaSessionManager: MediaSessionManager,
-    private val lbm: LocalBluetoothManager?,
-    private val starter: ActivityStarter,
     private val broadcastSender: BroadcastSender,
-    private val notifCollection: CommonNotifCollection,
     private val uiEventLogger: UiEventLogger,
     private val dialogTransitionAnimator: DialogTransitionAnimator,
-    private val nearbyMediaDevicesManager: NearbyMediaDevicesManager,
-    private val audioManager: AudioManager,
-    private val powerExemptionManager: PowerExemptionManager,
-    private val keyGuardManager: KeyguardManager,
-    private val featureFlags: FeatureFlags,
-    private val userTracker: UserTracker
+    private val mediaOutputControllerFactory: MediaOutputController.Factory,
 ) {
     companion object {
         const val INTERACTION_JANK_TAG = "media_output"
@@ -60,8 +41,8 @@
     }
 
     /** Creates a [MediaOutputDialog] for the given package. */
-    open fun create(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
-        createWithController(
+    open fun createAndShow(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
+        createAndShowWithController(
             packageName,
             aboveStatusBar,
             controller =
@@ -78,12 +59,12 @@
     }
 
     /** Creates a [MediaOutputDialog] for the given package. */
-    open fun createWithController(
+    open fun createAndShowWithController(
         packageName: String,
         aboveStatusBar: Boolean,
         controller: DialogTransitionAnimator.Controller?,
     ) {
-        create(
+        createAndShow(
             packageName,
             aboveStatusBar,
             dialogTransitionAnimatorController = controller,
@@ -91,8 +72,10 @@
         )
     }
 
-    open fun createDialogForSystemRouting(controller: DialogTransitionAnimator.Controller? = null) {
-        create(
+    open fun createAndShowForSystemRouting(
+        controller: DialogTransitionAnimator.Controller? = null
+    ) {
+        createAndShow(
             packageName = null,
             aboveStatusBar = false,
             dialogTransitionAnimatorController = null,
@@ -100,7 +83,7 @@
         )
     }
 
-    private fun create(
+    private fun createAndShow(
         packageName: String?,
         aboveStatusBar: Boolean,
         dialogTransitionAnimatorController: DialogTransitionAnimator.Controller?,
@@ -109,23 +92,9 @@
         // Dismiss the previous dialog, if any.
         mediaOutputDialog?.dismiss()
 
-        val controller =
-            MediaOutputController(
-                context,
-                packageName,
-                mediaSessionManager,
-                lbm,
-                starter,
-                notifCollection,
-                dialogTransitionAnimator,
-                nearbyMediaDevicesManager,
-                audioManager,
-                powerExemptionManager,
-                keyGuardManager,
-                featureFlags,
-                userTracker
-            )
-        val dialog =
+        val controller = mediaOutputControllerFactory.create(packageName)
+
+        val mediaOutputDialog =
             MediaOutputDialog(
                 context,
                 aboveStatusBar,
@@ -135,16 +104,15 @@
                 uiEventLogger,
                 includePlaybackAndAppMetadata
             )
-        mediaOutputDialog = dialog
 
         // Show the dialog.
         if (dialogTransitionAnimatorController != null) {
             dialogTransitionAnimator.show(
-                dialog,
+                mediaOutputDialog,
                 dialogTransitionAnimatorController,
             )
         } else {
-            dialog.show()
+            mediaOutputDialog.show()
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
index 38d31ed..774792f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
@@ -31,8 +31,8 @@
  * BroadcastReceiver for handling media output intent
  */
 class MediaOutputDialogReceiver @Inject constructor(
-    private val mediaOutputDialogFactory: MediaOutputDialogFactory,
-    private val mediaOutputBroadcastDialogFactory: MediaOutputBroadcastDialogFactory
+        private val mediaOutputDialogManager: MediaOutputDialogManager,
+        private val mediaOutputBroadcastDialogManager: MediaOutputBroadcastDialogManager
 ) : BroadcastReceiver() {
     override fun onReceive(context: Context, intent: Intent) {
         when (intent.action) {
@@ -42,7 +42,7 @@
                 launchMediaOutputDialogIfPossible(packageName)
             }
             MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG -> {
-                mediaOutputDialogFactory.createDialogForSystemRouting()
+                mediaOutputDialogManager.createAndShowForSystemRouting()
             }
             MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG -> {
                 if (!legacyLeAudioSharing()) return
@@ -55,7 +55,7 @@
 
     private fun launchMediaOutputDialogIfPossible(packageName: String?) {
         if (!packageName.isNullOrEmpty()) {
-            mediaOutputDialogFactory.create(packageName, false)
+            mediaOutputDialogManager.createAndShow(packageName, false)
         } else if (DEBUG) {
             Log.e(TAG, "Unable to launch media output dialog. Package name is empty.")
         }
@@ -63,7 +63,7 @@
 
     private fun launchMediaOutputBroadcastDialogIfPossible(packageName: String?) {
         if (!packageName.isNullOrEmpty()) {
-            mediaOutputBroadcastDialogFactory.create(
+            mediaOutputBroadcastDialogManager.createAndShow(
                     packageName, aboveStatusBar = true, view = null)
         } else if (DEBUG) {
             Log.e(TAG, "Unable to launch media output broadcast dialog. Package name is empty.")
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
index b5b1f0f..6e7e0f2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
@@ -34,15 +34,15 @@
     private static final String TAG = "MediaOutputSwitcherDialogUI";
 
     private final CommandQueue mCommandQueue;
-    private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+    private final MediaOutputDialogManager mMediaOutputDialogManager;
 
     @Inject
     public MediaOutputSwitcherDialogUI(
             Context context,
             CommandQueue commandQueue,
-            MediaOutputDialogFactory mediaOutputDialogFactory) {
+            MediaOutputDialogManager mediaOutputDialogManager) {
         mCommandQueue = commandQueue;
-        mMediaOutputDialogFactory = mediaOutputDialogFactory;
+        mMediaOutputDialogManager = mediaOutputDialogManager;
     }
 
     @Override
@@ -54,7 +54,7 @@
     @MainThread
     public void showMediaOutputSwitcher(String packageName) {
         if (!TextUtils.isEmpty(packageName)) {
-            mMediaOutputDialogFactory.create(packageName, false, null);
+            mMediaOutputDialogManager.createAndShow(packageName, false, null);
         } else {
             Log.e(TAG, "Unable to launch media output dialog. Package name is empty.");
         }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
index f1cade7..0b19bab 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
@@ -24,12 +24,14 @@
 import android.os.RemoteException
 import android.os.ServiceManager
 import android.util.Log
+import android.window.WindowContainerToken
+import javax.inject.Inject
 
 /**
  * Helper class that handles the media projection service related actions. It simplifies invoking
  * the MediaProjectionManagerService and updating the permission consent.
  */
-class MediaProjectionServiceHelper {
+class MediaProjectionServiceHelper @Inject constructor() {
     companion object {
         private const val TAG = "MediaProjectionServiceHelper"
         private val service =
@@ -90,4 +92,16 @@
             }
         }
     }
+
+    /** Updates the projected task to the task that has a matching [WindowContainerToken]. */
+    fun updateTaskRecordingSession(token: WindowContainerToken): Boolean {
+        return try {
+            true
+            // TODO: actually call the service once it is implemented
+            // service.updateTaskRecordingSession(token)
+        } catch (e: RemoteException) {
+            Log.e(TAG, "Unable to updateTaskRecordingSession", e)
+            false
+        }
+    }
 }
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 a811065..7c7efd0 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
@@ -18,7 +18,7 @@
 
 import android.app.ActivityOptions
 import android.app.ActivityOptions.LaunchCookie
-import android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+import android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
 import android.app.IActivityTaskManager
 import android.graphics.Rect
 import android.view.LayoutInflater
@@ -26,12 +26,13 @@
 import android.view.ViewGroup
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
-import com.android.systemui.res.R
+import com.android.systemui.Flags.pssAppSelectorAbruptExitFix
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
 import com.android.systemui.mediaprojection.appselector.view.RecentTasksAdapter.RecentTaskClickListener
 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
+import com.android.systemui.res.R
 import com.android.systemui.util.recycler.HorizontalSpacerItemDecoration
 import javax.inject.Inject
 
@@ -122,14 +123,7 @@
 
     override fun onRecentAppClicked(task: RecentTask, view: View) {
         val launchCookie = LaunchCookie()
-        val activityOptions =
-            ActivityOptions.makeScaleUpAnimation(
-                view,
-                /* startX= */ 0,
-                /* startY= */ 0,
-                view.width,
-                view.height
-            )
+        val activityOptions = createAnimation(task, view)
         activityOptions.pendingIntentBackgroundActivityStartMode =
             MODE_BACKGROUND_ACTIVITY_START_ALLOWED
         activityOptions.setLaunchCookie(launchCookie)
@@ -139,6 +133,28 @@
         resultHandler.returnSelectedApp(launchCookie)
     }
 
+    private fun createAnimation(task: RecentTask, view: View): ActivityOptions =
+        if (pssAppSelectorAbruptExitFix() && task.isForegroundTask) {
+            // When the selected task is in the foreground, the scale up animation doesn't work.
+            // We fallback to the default close animation.
+            ActivityOptions.makeCustomTaskAnimation(
+                view.context,
+                /* enterResId= */ 0,
+                /* exitResId= */ com.android.internal.R.anim.resolver_close_anim,
+                /* handler = */ null,
+                /* startedListener = */ null,
+                /* finishedListener = */ null
+            )
+        } else {
+            ActivityOptions.makeScaleUpAnimation(
+                view,
+                /* startX= */ 0,
+                /* startY= */ 0,
+                view.width,
+                view.height
+            )
+        }
+
     override fun onTaskSizeChanged(size: Rect) {
         views?.recentsContainer?.setTaskHeightSize()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt
deleted file mode 100644
index fc45228..0000000
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt
+++ /dev/null
@@ -1,33 +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.mediaprojection.devicepolicy
-
-import android.content.Context
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.phone.SystemUIDialog
-
-/** Dialog that shows that screen capture is disabled on this device. */
-class ScreenCaptureDisabledDialog(context: Context) : SystemUIDialog(context) {
-
-    init {
-        setTitle(context.getString(R.string.screen_capturing_disabled_by_policy_dialog_title))
-        setMessage(
-            context.getString(R.string.screen_capturing_disabled_by_policy_dialog_description)
-        )
-        setIcon(R.drawable.ic_cast)
-        setButton(BUTTON_POSITIVE, context.getString(android.R.string.ok)) { _, _ -> cancel() }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialogDelegate.kt
new file mode 100644
index 0000000..8aed535
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialogDelegate.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.mediaprojection.devicepolicy
+
+import android.content.DialogInterface.BUTTON_POSITIVE
+import android.content.res.Resources
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import javax.inject.Inject
+
+/** Dialog that shows that screen capture is disabled on this device. */
+class ScreenCaptureDisabledDialogDelegate @Inject constructor(
+        @Main private val resources: Resources,
+        private val systemUIDialogFactory: SystemUIDialog.Factory
+) : SystemUIDialog.Delegate {
+
+    override fun createDialog(): SystemUIDialog {
+        val dialog = systemUIDialogFactory.create(this)
+        dialog.setTitle(resources.getString(R.string.screen_capturing_disabled_by_policy_dialog_title))
+        dialog.setMessage(
+            resources.getString(R.string.screen_capturing_disabled_by_policy_dialog_description)
+        )
+        dialog.setIcon(R.drawable.ic_cast)
+        dialog.setButton(BUTTON_POSITIVE, resources.getString(android.R.string.ok)) {
+            _, _ -> dialog.cancel()
+        }
+
+        return dialog
+    }
+}
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 8b034b2..17f9caf 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -59,7 +59,7 @@
 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;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.AlertDialogWithDelegate;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -79,6 +79,7 @@
     private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver;
     private final StatusBarManager mStatusBarManager;
     private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+    private final ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate;
 
     private String mPackageName;
     private int mUid;
@@ -93,14 +94,17 @@
     private boolean mUserSelectingTask = false;
 
     @Inject
-    public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
+    public MediaProjectionPermissionActivity(
+            FeatureFlags featureFlags,
             Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver,
             StatusBarManager statusBarManager,
-            MediaProjectionMetricsLogger mediaProjectionMetricsLogger) {
+            MediaProjectionMetricsLogger mediaProjectionMetricsLogger,
+            ScreenCaptureDisabledDialogDelegate screenCaptureDisabledDialogDelegate) {
         mFeatureFlags = featureFlags;
         mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver;
         mStatusBarManager = statusBarManager;
         mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
+        mScreenCaptureDisabledDialogDelegate = screenCaptureDisabledDialogDelegate;
     }
 
     @Override
@@ -315,10 +319,7 @@
         final UserHandle hostUserHandle = getHostUserHandle();
         if (mScreenCaptureDevicePolicyResolver.get()
                 .isScreenCaptureCompletelyDisabled(hostUserHandle)) {
-            // Using application context for the dialog, instead of the activity context, so we get
-            // the correct screen width when in split screen.
-            Context dialogContext = getApplicationContext();
-            AlertDialog dialog = new ScreenCaptureDisabledDialog(dialogContext);
+            AlertDialog dialog = mScreenCaptureDisabledDialogDelegate.createDialog();
             setUpDialog(dialog);
             dialog.show();
             return true;
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt
index 9938f11..cfbcaf9 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt
@@ -16,11 +16,11 @@
 
 package com.android.systemui.mediaprojection.taskswitcher.data.model
 
-import android.app.TaskInfo
+import android.app.ActivityManager.RunningTaskInfo
 
 /** Represents the state of media projection. */
 sealed interface MediaProjectionState {
     object NotProjecting : MediaProjectionState
     object EntireScreen : MediaProjectionState
-    data class SingleTask(val task: TaskInfo) : MediaProjectionState
+    data class SingleTask(val task: RunningTaskInfo) : MediaProjectionState
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt
index 492d482..4ff54d4e 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt
@@ -17,10 +17,14 @@
 package com.android.systemui.mediaprojection.taskswitcher.data.repository
 
 import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityOptions
+import android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
 import android.app.ActivityTaskManager
+import android.app.IActivityTaskManager
 import android.app.TaskStackListener
 import android.os.IBinder
 import android.util.Log
+import android.view.Display
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
@@ -40,11 +44,24 @@
 class ActivityTaskManagerTasksRepository
 @Inject
 constructor(
-    private val activityTaskManager: ActivityTaskManager,
+    private val activityTaskManager: IActivityTaskManager,
     @Application private val applicationScope: CoroutineScope,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) : TasksRepository {
 
+    override suspend fun launchRecentTask(taskInfo: RunningTaskInfo) {
+        withContext(backgroundDispatcher) {
+            val activityOptions = ActivityOptions.makeBasic()
+            activityOptions.pendingIntentBackgroundActivityStartMode =
+                MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+            activityOptions.launchDisplayId = taskInfo.displayId
+            activityTaskManager.startActivityFromRecents(
+                taskInfo.taskId,
+                activityOptions.toBundle()
+            )
+        }
+    }
+
     override suspend fun findRunningTaskFromWindowContainerToken(
         windowContainerToken: IBinder
     ): RunningTaskInfo? =
@@ -53,7 +70,14 @@
         }
 
     private suspend fun getRunningTasks(): List<RunningTaskInfo> =
-        withContext(backgroundDispatcher) { activityTaskManager.getTasks(Integer.MAX_VALUE) }
+        withContext(backgroundDispatcher) {
+            activityTaskManager.getTasks(
+                /* maxNum = */ Integer.MAX_VALUE,
+                /* filterForVisibleRecents = */ false,
+                /* keepIntentExtra = */ false,
+                /* displayId = */ Display.INVALID_DISPLAY
+            )
+        }
 
     override val foregroundTask: Flow<RunningTaskInfo> =
         conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
index 6480a47..74d1992 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.mediaprojection.taskswitcher.data.repository
 
+import android.app.ActivityManager.RunningTaskInfo
 import android.media.projection.MediaProjectionInfo
 import android.media.projection.MediaProjectionManager
 import android.os.Handler
@@ -26,15 +27,19 @@
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.mediaprojection.MediaProjectionServiceHelper
 import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 @SysUISingleton
 class MediaProjectionManagerRepository
@@ -43,9 +48,21 @@
     private val mediaProjectionManager: MediaProjectionManager,
     @Main private val handler: Handler,
     @Application private val applicationScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val tasksRepository: TasksRepository,
+    private val mediaProjectionServiceHelper: MediaProjectionServiceHelper,
 ) : MediaProjectionRepository {
 
+    override suspend fun switchProjectedTask(task: RunningTaskInfo) {
+        withContext(backgroundDispatcher) {
+            if (mediaProjectionServiceHelper.updateTaskRecordingSession(task.token)) {
+                Log.d(TAG, "Successfully switched projected task")
+            } else {
+                Log.d(TAG, "Failed to switch projected task")
+            }
+        }
+    }
+
     override val mediaProjectionState: Flow<MediaProjectionState> =
         conflatedCallbackFlow {
                 val callback =
@@ -82,7 +99,9 @@
         }
         val matchingTask =
             tasksRepository.findRunningTaskFromWindowContainerToken(
-                checkNotNull(session.tokenToRecord)) ?: return MediaProjectionState.EntireScreen
+                checkNotNull(session.tokenToRecord)
+            )
+                ?: return MediaProjectionState.EntireScreen
         return MediaProjectionState.SingleTask(matchingTask)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt
index 5bec692..e495466 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt
@@ -16,12 +16,16 @@
 
 package com.android.systemui.mediaprojection.taskswitcher.data.repository
 
+import android.app.ActivityManager.RunningTaskInfo
 import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
 import kotlinx.coroutines.flow.Flow
 
 /** Represents a repository to retrieve and change data related to media projection. */
 interface MediaProjectionRepository {
 
+    /** Switches the task that should be projected. */
+    suspend fun switchProjectedTask(task: RunningTaskInfo)
+
     /** Represents the current [MediaProjectionState]. */
     val mediaProjectionState: Flow<MediaProjectionState>
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt
deleted file mode 100644
index 544eb6b..0000000
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt
+++ /dev/null
@@ -1,33 +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.mediaprojection.taskswitcher.data.repository
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.emptyFlow
-
-/**
- * No-op implementation of [MediaProjectionRepository] that does nothing. Currently used as a
- * placeholder, while the real implementation is not completed.
- */
-@SysUISingleton
-class NoOpMediaProjectionRepository @Inject constructor() : MediaProjectionRepository {
-
-    override val mediaProjectionState: Flow<MediaProjectionState> = emptyFlow()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt
index 6a535e4..9ef42b4 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt
@@ -23,6 +23,8 @@
 /** Repository responsible for retrieving data related to running tasks. */
 interface TasksRepository {
 
+    suspend fun launchRecentTask(taskInfo: RunningTaskInfo)
+
     /**
      * Tries to find a [RunningTaskInfo] with a matching window container token. Returns `null` when
      * no matching task was found.
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt
index fc5cf7d..eb9e6a5 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.mediaprojection.taskswitcher.domain.interactor
 
+import android.app.ActivityManager.RunningTaskInfo
 import android.app.TaskInfo
 import android.content.Intent
 import android.util.Log
@@ -37,10 +38,18 @@
 class TaskSwitchInteractor
 @Inject
 constructor(
-    mediaProjectionRepository: MediaProjectionRepository,
+    private val mediaProjectionRepository: MediaProjectionRepository,
     private val tasksRepository: TasksRepository,
 ) {
 
+    suspend fun switchProjectedTask(task: RunningTaskInfo) {
+        mediaProjectionRepository.switchProjectedTask(task)
+    }
+
+    suspend fun goBackToTask(task: RunningTaskInfo) {
+        tasksRepository.launchRecentTask(task)
+    }
+
     /**
      * Emits a stream of changes to the state of task switching, in the context of media projection.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt
index cd1258e..caabc64 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.mediaprojection.taskswitcher.domain.model
 
-import android.app.TaskInfo
+import android.app.ActivityManager.RunningTaskInfo
 
 /** Represents tha state of task switching in the context of single task media projection. */
 sealed interface TaskSwitchState {
@@ -25,6 +25,8 @@
     /** The foreground task is the same as the task that is currently being projected. */
     object TaskUnchanged : TaskSwitchState
     /** The foreground task is a different one to the task it currently being projected. */
-    data class TaskSwitched(val projectedTask: TaskInfo, val foregroundTask: TaskInfo) :
-        TaskSwitchState
+    data class TaskSwitched(
+        val projectedTask: RunningTaskInfo,
+        val foregroundTask: RunningTaskInfo
+    ) : TaskSwitchState
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt
index 7840da9..dab7439 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt
@@ -16,23 +16,25 @@
 
 package com.android.systemui.mediaprojection.taskswitcher.ui
 
+import android.app.ActivityManager.RunningTaskInfo
 import android.app.Notification
-import android.app.NotificationChannel
 import android.app.NotificationManager
+import android.app.PendingIntent
 import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.Parcelable
 import android.util.Log
-import com.android.systemui.res.R
+import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.NotShowing
 import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.Showing
 import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
+import com.android.systemui.res.R
 import com.android.systemui.util.NotificationChannels
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.launch
 
 /** Coordinator responsible for showing/hiding the task switcher notification. */
@@ -43,32 +45,54 @@
     private val context: Context,
     private val notificationManager: NotificationManager,
     @Application private val applicationScope: CoroutineScope,
-    @Main private val mainDispatcher: CoroutineDispatcher,
     private val viewModel: TaskSwitcherNotificationViewModel,
+    private val broadcastDispatcher: BroadcastDispatcher,
 ) {
+
     fun start() {
         applicationScope.launch {
-            viewModel.uiState.flowOn(mainDispatcher).collect { uiState ->
-                Log.d(TAG, "uiState -> $uiState")
-                when (uiState) {
-                    is Showing -> showNotification()
-                    is NotShowing -> hideNotification()
+            launch {
+                viewModel.uiState.collect { uiState ->
+                    Log.d(TAG, "uiState -> $uiState")
+                    when (uiState) {
+                        is Showing -> showNotification(uiState)
+                        is NotShowing -> hideNotification()
+                    }
                 }
             }
+            launch {
+                broadcastDispatcher
+                    .broadcastFlow(IntentFilter(SWITCH_ACTION)) { intent, _ ->
+                        intent.requireParcelableExtra<RunningTaskInfo>(EXTRA_ACTION_TASK)
+                    }
+                    .collect { task: RunningTaskInfo ->
+                        Log.d(TAG, "Switch action triggered: $task")
+                        viewModel.onSwitchTaskClicked(task)
+                    }
+            }
+            launch {
+                broadcastDispatcher
+                    .broadcastFlow(IntentFilter(GO_BACK_ACTION)) { intent, _ ->
+                        intent.requireParcelableExtra<RunningTaskInfo>(EXTRA_ACTION_TASK)
+                    }
+                    .collect { task ->
+                        Log.d(TAG, "Go back action triggered: $task")
+                        viewModel.onGoBackToTaskClicked(task)
+                    }
+            }
         }
     }
 
-    private fun showNotification() {
-        notificationManager.notify(TAG, NOTIFICATION_ID, createNotification())
+    private fun showNotification(uiState: Showing) {
+        notificationManager.notify(TAG, NOTIFICATION_ID, createNotification(uiState))
     }
 
-    private fun createNotification(): Notification {
-        // TODO(b/286201261): implement actions
+    private fun createNotification(uiState: Showing): Notification {
         val actionSwitch =
             Notification.Action.Builder(
                     /* icon = */ null,
                     context.getString(R.string.media_projection_task_switcher_action_switch),
-                    /* intent = */ null
+                    createActionPendingIntent(action = SWITCH_ACTION, task = uiState.foregroundTask)
                 )
                 .build()
 
@@ -76,34 +100,40 @@
             Notification.Action.Builder(
                     /* icon = */ null,
                     context.getString(R.string.media_projection_task_switcher_action_back),
-                    /* intent = */ null
+                    createActionPendingIntent(action = GO_BACK_ACTION, task = uiState.projectedTask)
                 )
                 .build()
-
-        val channel =
-            NotificationChannel(
-                NotificationChannels.HINTS,
-                context.getString(R.string.media_projection_task_switcher_notification_channel),
-                NotificationManager.IMPORTANCE_HIGH
-            )
-        notificationManager.createNotificationChannel(channel)
-        return Notification.Builder(context, channel.id)
+        return Notification.Builder(context, NotificationChannels.ALERTS)
             .setSmallIcon(R.drawable.qs_screen_record_icon_on)
             .setAutoCancel(true)
             .setContentText(context.getString(R.string.media_projection_task_switcher_text))
             .addAction(actionSwitch)
             .addAction(actionBack)
-            .setPriority(Notification.PRIORITY_HIGH)
-            .setDefaults(Notification.DEFAULT_VIBRATE)
             .build()
     }
 
     private fun hideNotification() {
-        notificationManager.cancel(NOTIFICATION_ID)
+        notificationManager.cancel(TAG, NOTIFICATION_ID)
     }
 
+    private fun createActionPendingIntent(action: String, task: RunningTaskInfo) =
+        PendingIntent.getBroadcast(
+            context,
+            /* requestCode= */ 0,
+            Intent(action).apply { putExtra(EXTRA_ACTION_TASK, task) },
+            /* flags= */ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
+        )
+
     companion object {
         private const val TAG = "TaskSwitchNotifCoord"
         private const val NOTIFICATION_ID = 5566
+
+        private const val EXTRA_ACTION_TASK = "extra_task"
+
+        private const val SWITCH_ACTION = "com.android.systemui.mediaprojection.SWITCH_TASK"
+        private const val GO_BACK_ACTION = "com.android.systemui.mediaprojection.GO_BACK"
     }
 }
+
+private fun <T : Parcelable> Intent.requireParcelableExtra(key: String) =
+    getParcelableExtra<T>(key)!!
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt
index 21aee72..f307761 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.mediaprojection.taskswitcher.ui.model
 
-import android.app.TaskInfo
+import android.app.ActivityManager.RunningTaskInfo
 
 /** Represents the UI state for the task switcher notification. */
 sealed interface TaskSwitcherNotificationUiState {
@@ -24,7 +24,7 @@
     object NotShowing : TaskSwitcherNotificationUiState
     /** The notification should be shown. */
     data class Showing(
-        val projectedTask: TaskInfo,
-        val foregroundTask: TaskInfo,
+        val projectedTask: RunningTaskInfo,
+        val foregroundTask: RunningTaskInfo,
     ) : TaskSwitcherNotificationUiState
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt
index d9754d4..d6629e0 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt
@@ -16,34 +16,64 @@
 
 package com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel
 
+import android.app.ActivityManager.RunningTaskInfo
 import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
 import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
 import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
 import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.transformLatest
+import kotlinx.coroutines.withContext
 
-class TaskSwitcherNotificationViewModel @Inject constructor(interactor: TaskSwitchInteractor) {
+class TaskSwitcherNotificationViewModel
+@Inject
+constructor(
+    private val interactor: TaskSwitchInteractor,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
 
     val uiState: Flow<TaskSwitcherNotificationUiState> =
-        interactor.taskSwitchChanges.map { taskSwitchChange ->
-            Log.d(TAG, "taskSwitchChange: $taskSwitchChange")
-            when (taskSwitchChange) {
-                is TaskSwitchState.TaskSwitched -> {
-                    TaskSwitcherNotificationUiState.Showing(
-                        projectedTask = taskSwitchChange.projectedTask,
-                        foregroundTask = taskSwitchChange.foregroundTask,
-                    )
-                }
-                is TaskSwitchState.NotProjectingTask,
-                is TaskSwitchState.TaskUnchanged -> {
-                    TaskSwitcherNotificationUiState.NotShowing
+        interactor.taskSwitchChanges
+            .map { taskSwitchChange ->
+                Log.d(TAG, "taskSwitchChange: $taskSwitchChange")
+                when (taskSwitchChange) {
+                    is TaskSwitchState.TaskSwitched -> {
+                        TaskSwitcherNotificationUiState.Showing(
+                            projectedTask = taskSwitchChange.projectedTask,
+                            foregroundTask = taskSwitchChange.foregroundTask,
+                        )
+                    }
+                    is TaskSwitchState.NotProjectingTask,
+                    is TaskSwitchState.TaskUnchanged -> {
+                        TaskSwitcherNotificationUiState.NotShowing
+                    }
                 }
             }
-        }
+            .transformLatest { uiState ->
+                emit(uiState)
+                if (uiState is TaskSwitcherNotificationUiState.Showing) {
+                    delay(NOTIFICATION_MAX_SHOW_DURATION)
+                    Log.d(TAG, "Auto hiding notification after $NOTIFICATION_MAX_SHOW_DURATION")
+                    emit(TaskSwitcherNotificationUiState.NotShowing)
+                }
+            }
+
+    suspend fun onSwitchTaskClicked(task: RunningTaskInfo) {
+        interactor.switchProjectedTask(task)
+    }
+
+    suspend fun onGoBackToTaskClicked(task: RunningTaskInfo) =
+        withContext(backgroundDispatcher) { interactor.goBackToTask(task) }
 
     companion object {
+        @VisibleForTesting val NOTIFICATION_MAX_SHOW_DURATION = 5.seconds
         private const val TAG = "TaskSwitchNotifVM"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
index 6eb6226..e7b6e63 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
@@ -16,11 +16,12 @@
 
 package com.android.systemui.model
 
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE
@@ -67,11 +68,11 @@
          */
         val EvaluatorByFlag =
             mapOf<Int, (SceneKey) -> Boolean>(
-                SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to { it != SceneKey.Gone },
-                SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to { it == SceneKey.Shade },
-                SYSUI_STATE_QUICK_SETTINGS_EXPANDED to { it == SceneKey.QuickSettings },
-                SYSUI_STATE_BOUNCER_SHOWING to { it == SceneKey.Bouncer },
-                SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to { it == SceneKey.Lockscreen },
+                SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to { it != Scenes.Gone },
+                SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to { it == Scenes.Shade },
+                SYSUI_STATE_QUICK_SETTINGS_EXPANDED to { it == Scenes.QuickSettings },
+                SYSUI_STATE_BOUNCER_SHOWING to { it == Scenes.Bouncer },
+                SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to { it == Scenes.Lockscreen },
             )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt b/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt
index c74a71c..5c49156 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt
@@ -25,11 +25,11 @@
  * ```
  * sysuiState.updateFlags(
  *     displayId,
- *     SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to (sceneKey != SceneKey.Gone),
- *     SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to (sceneKey == SceneKey.Shade),
- *     SYSUI_STATE_QUICK_SETTINGS_EXPANDED to (sceneKey == SceneKey.QuickSettings),
- *     SYSUI_STATE_BOUNCER_SHOWING to (sceneKey == SceneKey.Bouncer),
- *     SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to (sceneKey == SceneKey.Lockscreen),
+ *     SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to (sceneKey != Scenes.Gone),
+ *     SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to (sceneKey == Scenes.Shade),
+ *     SYSUI_STATE_QUICK_SETTINGS_EXPANDED to (sceneKey == Scenes.QuickSettings),
+ *     SYSUI_STATE_BOUNCER_SHOWING to (sceneKey == Scenes.Bouncer),
+ *     SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to (sceneKey == Scenes.Lockscreen),
  * )
  * ```
  *
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index 152f193..9f7d1b3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -19,6 +19,7 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE;
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
+
 import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG;
 import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
 import static com.android.wm.shell.Flags.enableTaskbarNavbarUnification;
@@ -28,7 +29,6 @@
 import android.content.res.Configuration;
 import android.hardware.display.DisplayManager;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.RemoteException;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -70,9 +70,11 @@
 
 import java.io.PrintWriter;
 import java.util.Optional;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
+
 @SysUISingleton
 public class NavigationBarControllerImpl implements
         ConfigurationController.ConfigurationListener,
@@ -82,7 +84,7 @@
     private static final String TAG = NavigationBarControllerImpl.class.getSimpleName();
 
     private final Context mContext;
-    private final Handler mHandler;
+    private final Executor mExecutor;
     private final NavigationBarComponent.Factory mNavigationBarComponentFactory;
     private final SecureSettings mSecureSettings;
     private final DisplayTracker mDisplayTracker;
@@ -119,7 +121,7 @@
             NavigationModeController navigationModeController,
             SysUiState sysUiFlagsContainer,
             CommandQueue commandQueue,
-            @Main Handler mainHandler,
+            @Main Executor mainExecutor,
             ConfigurationController configurationController,
             NavBarHelper navBarHelper,
             TaskbarDelegate taskbarDelegate,
@@ -133,7 +135,7 @@
             SecureSettings secureSettings,
             DisplayTracker displayTracker) {
         mContext = context;
-        mHandler = mainHandler;
+        mExecutor = mainExecutor;
         mNavigationBarComponentFactory = navigationBarComponentFactory;
         mSecureSettings = secureSettings;
         mDisplayTracker = displayTracker;
@@ -193,7 +195,7 @@
         mNavMode = mode;
         updateAccessibilityButtonModeIfNeeded();
 
-        mHandler.post(() -> {
+        mExecutor.execute(() -> {
             // create/destroy nav bar based on nav mode only in unfolded state
             if (oldMode != mNavMode) {
                 updateNavbarForTaskbar();
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 91c86df..9d0ea5e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -19,6 +19,7 @@
 import static android.view.InputDevice.SOURCE_TOUCHPAD;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
 
+import static com.android.systemui.Flags.edgebackGestureHandlerGetRunningTasksBackground;
 import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
 import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll;
 import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe;
@@ -54,7 +55,6 @@
 import android.view.IWindowManager;
 import android.view.InputDevice;
 import android.view.InputEvent;
-import android.view.InputMonitor;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -104,6 +104,7 @@
 import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -151,7 +152,12 @@
     private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
         @Override
         public void onTaskStackChanged() {
-            mGestureBlockingActivityRunning = isGestureBlockingActivityRunning();
+            if (edgebackGestureHandlerGetRunningTasksBackground()) {
+                mBackgroundExecutor.execute(() -> mGestureBlockingActivityRunning.set(
+                        isGestureBlockingActivityRunning()));
+            } else {
+                mGestureBlockingActivityRunning.set(isGestureBlockingActivityRunning());
+            }
         }
         @Override
         public void onTaskCreated(int taskId, ComponentName componentName) {
@@ -241,6 +247,8 @@
 
     private final PointF mDownPoint = new PointF();
     private final PointF mEndPoint = new PointF();
+    private AtomicBoolean mGestureBlockingActivityRunning = new AtomicBoolean();
+
     private boolean mThresholdCrossed = false;
     private boolean mAllowGesture = false;
     private boolean mLogGesture = false;
@@ -256,7 +264,6 @@
     private boolean mIsEnabled;
     private boolean mIsNavBarShownTransiently;
     private boolean mIsBackGestureAllowed;
-    private boolean mGestureBlockingActivityRunning;
     private boolean mIsNewBackAffordanceEnabled;
     private boolean mIsTrackpadGestureFeaturesEnabled;
     private boolean mIsTrackpadThreeFingerSwipe;
@@ -1017,7 +1024,7 @@
             mInRejectedExclusion = false;
             boolean isWithinInsets = isWithinInsets((int) ev.getX(), (int) ev.getY());
             boolean isBackAllowedCommon = !mDisabledForQuickstep && mIsBackGestureAllowed
-                    && !mGestureBlockingActivityRunning
+                    && !mGestureBlockingActivityRunning.get()
                     && !QuickStepContract.isBackGestureDisabled(mSysUiFlags,
                             mIsTrackpadThreeFingerSwipe)
                     && !isTrackpadScroll(mIsTrackpadGestureFeaturesEnabled, ev);
@@ -1053,8 +1060,8 @@
                     curTime, curTimeStr, mAllowGesture, mIsTrackpadThreeFingerSwipe,
                     mIsOnLeftEdge, mDeferSetIsOnLeftEdge, mIsBackGestureAllowed,
                     QuickStepContract.isBackGestureDisabled(mSysUiFlags,
-                            mIsTrackpadThreeFingerSwipe),
-                    mDisabledForQuickstep, mGestureBlockingActivityRunning, mIsInPip, mDisplaySize,
+                            mIsTrackpadThreeFingerSwipe), mDisabledForQuickstep,
+                    mGestureBlockingActivityRunning.get(), mIsInPip, mDisplaySize,
                     mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion));
         } else if (mAllowGesture || mLogGesture) {
             if (!mThresholdCrossed) {
@@ -1236,7 +1243,7 @@
         pw.println("  mIsBackGestureAllowed=" + mIsBackGestureAllowed);
         pw.println("  mIsGestureHandlingEnabled=" + mIsGestureHandlingEnabled);
         pw.println("  mIsNavBarShownTransiently=" + mIsNavBarShownTransiently);
-        pw.println("  mGestureBlockingActivityRunning=" + mGestureBlockingActivityRunning);
+        pw.println("  mGestureBlockingActivityRunning=" + mGestureBlockingActivityRunning.get());
         pw.println("  mAllowGesture=" + mAllowGesture);
         pw.println("  mUseMLModel=" + mUseMLModel);
         pw.println("  mDisabledForQuickstep=" + mDisabledForQuickstep);
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index c0e688f..c7aae3c 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -23,6 +23,7 @@
 import android.app.role.RoleManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.notetask.NoteTaskBubblesController.NoteTaskBubblesService
 import com.android.systemui.notetask.quickaffordance.NoteTaskQuickAffordanceModule
 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
 import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
@@ -37,20 +38,21 @@
 interface NoteTaskModule {
 
     @[Binds IntoMap ClassKey(NoteTaskControllerUpdateService::class)]
-    fun NoteTaskControllerUpdateService.bindNoteTaskControllerUpdateService(): Service
+    fun bindNoteTaskControllerUpdateService(service: NoteTaskControllerUpdateService): Service
 
-    @[Binds IntoMap ClassKey(NoteTaskBubblesController.NoteTaskBubblesService::class)]
-    fun NoteTaskBubblesController.NoteTaskBubblesService.bindNoteTaskBubblesService(): Service
+    @[Binds IntoMap ClassKey(NoteTaskBubblesService::class)]
+    fun bindNoteTaskBubblesService(service: NoteTaskBubblesService): Service
 
     @[Binds IntoMap ClassKey(LaunchNoteTaskActivity::class)]
-    fun LaunchNoteTaskActivity.bindNoteTaskLauncherActivity(): Activity
+    fun bindNoteTaskLauncherActivity(activity: LaunchNoteTaskActivity): Activity
 
     @[Binds IntoMap ClassKey(LaunchNotesRoleSettingsTrampolineActivity::class)]
-    fun LaunchNotesRoleSettingsTrampolineActivity.bindLaunchNotesRoleSettingsTrampolineActivity():
-        Activity
+    fun bindLaunchNotesRoleSettingsTrampolineActivity(
+        activity: LaunchNotesRoleSettingsTrampolineActivity
+    ): Activity
 
     @[Binds IntoMap ClassKey(CreateNoteTaskShortcutActivity::class)]
-    fun CreateNoteTaskShortcutActivity.bindNoteTaskShortcutActivity(): Activity
+    fun bindNoteTaskShortcutActivity(activity: CreateNoteTaskShortcutActivity): Activity
 
     companion object {
 
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
index 2d63dbc..7d3f2a5 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
@@ -25,5 +25,7 @@
 interface NoteTaskQuickAffordanceModule {
 
     @[Binds IntoSet]
-    fun NoteTaskQuickAffordanceConfig.bindNoteTaskQuickAffordance(): KeyguardQuickAffordanceConfig
+    fun bindNoteTaskQuickAffordance(
+        impl: NoteTaskQuickAffordanceConfig
+    ): KeyguardQuickAffordanceConfig
 }
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt
index 5b7eb45..deb0fed 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt
@@ -20,14 +20,15 @@
 import android.os.Bundle
 import android.util.Log
 import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.compose.ComposeFacade.isComposeAvailable
-import com.android.systemui.compose.ComposeFacade.setPeopleSpaceActivityContent
+import com.android.compose.theme.PlatformTheme
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.people.ui.compose.PeopleScreen
 import com.android.systemui.people.ui.view.PeopleViewBinder
 import com.android.systemui.people.ui.view.PeopleViewBinder.bind
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
@@ -65,13 +66,11 @@
         }
 
         // Set the content of the activity, using either the View or Compose implementation.
-        if (featureFlags.isEnabled(Flags.COMPOSE_PEOPLE_SPACE) && isComposeAvailable()) {
+        if (featureFlags.isEnabled(Flags.COMPOSE_PEOPLE_SPACE)) {
             Log.d(TAG, "Using the Compose implementation of the PeopleSpaceActivity")
-            setPeopleSpaceActivityContent(
-                activity = this,
-                viewModel,
-                onResult = { finishActivity(it) },
-            )
+            setContent {
+                PlatformTheme { PeopleScreen(viewModel, onResult = { finishActivity(it) }) }
+            }
         } else {
             Log.d(TAG, "Using the View implementation of the PeopleSpaceActivity")
             val view = PeopleViewBinder.create(this)
diff --git a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
index e1d1ec2..5432793d 100644
--- a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
@@ -20,20 +20,24 @@
      * the [KeyguardTransitionInteractor].
      */
     internal val internalWakefulnessState: WakefulnessState = WakefulnessState.AWAKE,
-
     val lastWakeReason: WakeSleepReason = WakeSleepReason.OTHER,
     val lastSleepReason: WakeSleepReason = WakeSleepReason.OTHER,
 
-        /**
+    /**
      * Whether the power button double tap gesture was triggered since the last time went to sleep.
      * If this value is true while [isAsleep]=true, it means we'll be waking back up shortly. If it
      * is true while [isAwake]=true, it means we're awake because of the button gesture.
      *
-     * This value remains true until the next time [isAsleep]=true.
+     * This value remains true until the next time [isAsleep]=true, since it would otherwise be
+     * totally arbitrary at what point we decide the gesture was no longer "triggered". Since a
+     * sleep event is guaranteed to arrive prior to the next power button gesture (as the first tap
+     * of the double tap always begins a sleep transition), this will always be reset to false prior
+     * to a subsequent power gesture.
      */
     val powerButtonLaunchGestureTriggered: Boolean = false,
 ) {
-    fun isAwake() = internalWakefulnessState == WakefulnessState.AWAKE ||
+    fun isAwake() =
+        internalWakefulnessState == WakefulnessState.AWAKE ||
             internalWakefulnessState == WakefulnessState.STARTING_TO_WAKE
 
     fun isAsleep() = !isAwake()
@@ -48,11 +52,10 @@
     fun isAsleepFrom(wakeSleepReason: WakeSleepReason) =
         isAsleep() && lastSleepReason == wakeSleepReason
 
-    fun isAwakeOrAsleepFrom(reason: WakeSleepReason) =
-        isAsleepFrom(reason) || isAwakeFrom(reason)
+    fun isAwakeOrAsleepFrom(reason: WakeSleepReason) = isAsleepFrom(reason) || isAwakeFrom(reason)
 
     fun isAwakeFromTapOrGesture(): Boolean {
-        return isAwake() && (lastWakeReason == WakeSleepReason.TAP ||
-                lastWakeReason == WakeSleepReason.GESTURE)
+        return isAwake() &&
+            (lastWakeReason == WakeSleepReason.TAP || lastWakeReason == WakeSleepReason.GESTURE)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index 7413362..a000d63 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -47,7 +47,6 @@
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.Dumpable;
 import com.android.systemui.animation.ShadeInterpolation;
-import com.android.systemui.compose.ComposeFacade;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
@@ -301,8 +300,7 @@
     private void bindFooterActionsView(View root) {
         LinearLayout footerActionsView = root.findViewById(R.id.qs_footer_actions);
 
-        if (!mFeatureFlags.isEnabled(Flags.COMPOSE_QS_FOOTER_ACTIONS)
-                || !ComposeFacade.INSTANCE.isComposeAvailable()) {
+        if (!mFeatureFlags.isEnabled(Flags.COMPOSE_QS_FOOTER_ACTIONS)) {
             Log.d(TAG, "Binding the View implementation of the QS footer actions");
             mFooterActionsView = footerActionsView;
             mFooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
@@ -312,7 +310,7 @@
 
         // Compose is available, so let's use the Compose implementation of the footer actions.
         Log.d(TAG, "Binding the Compose implementation of the QS footer actions");
-        View composeView = ComposeFacade.INSTANCE.createFooterActionsView(root.getContext(),
+        View composeView = QSUtils.createFooterActionsView(root.getContext(),
                 mQSFooterActionsViewModel, mListeningAndVisibilityLifecycleOwner);
         mFooterActionsView = composeView;
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 2440651..cd65119 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -38,6 +38,7 @@
 import com.android.systemui.settings.brightness.BrightnessController;
 import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
@@ -90,9 +91,11 @@
             FalsingManager falsingManager,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             SplitShadeStateController splitShadeStateController,
-            SceneContainerFlags sceneContainerFlags) {
+            SceneContainerFlags sceneContainerFlags,
+            VibratorHelper vibratorHelper) {
         super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost,
-                metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController);
+                metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController,
+                vibratorHelper);
         mTunerService = tunerService;
         mQsCustomizerController = qsCustomizerController;
         mQsTileRevealControllerFactory = qsTileRevealControllerFactory;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 975c871..5e12b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -39,6 +39,7 @@
 import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileViewImpl;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.animation.DisappearParameters;
@@ -87,6 +88,8 @@
 
     private SplitShadeStateController mSplitShadeStateController;
 
+    private final VibratorHelper mVibratorHelper;
+
     @VisibleForTesting
     protected final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
             new QSPanel.OnConfigurationChangedListener() {
@@ -144,7 +147,8 @@
             UiEventLogger uiEventLogger,
             QSLogger qsLogger,
             DumpManager dumpManager,
-            SplitShadeStateController splitShadeStateController
+            SplitShadeStateController splitShadeStateController,
+            VibratorHelper vibratorHelper
     ) {
         super(view);
         mHost = host;
@@ -158,6 +162,7 @@
         mSplitShadeStateController = splitShadeStateController;
         mShouldUseSplitNotificationShade =
                 mSplitShadeStateController.shouldUseSplitNotificationShade(getResources());
+        mVibratorHelper = vibratorHelper;
     }
 
     @Override
@@ -300,7 +305,8 @@
     }
 
     private void addTile(final QSTile tile, boolean collapsedView) {
-        final QSTileViewImpl tileView = new QSTileViewImpl(getContext(), collapsedView);
+        final QSTileViewImpl tileView = new QSTileViewImpl(
+                getContext(), collapsedView, mVibratorHelper);
         final TileRecord r = new TileRecord(tile, tileView);
         // TODO(b/250618218): Remove the QSLogger in QSTileViewImpl once we know the root cause of
         // b/250618218.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt b/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt
index e42264f24..15c3f27 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt
@@ -1,7 +1,13 @@
 package com.android.systemui.qs
 
 import android.content.Context
+import android.view.View
+import androidx.lifecycle.LifecycleOwner
+import com.android.compose.theme.PlatformTheme
+import com.android.compose.ui.platform.DensityAwareComposeView
 import com.android.internal.policy.SystemBarUtils
+import com.android.systemui.qs.footer.ui.compose.FooterActions
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.util.LargeScreenUtils.shouldUseLargeScreenShadeHeader
 
 object QSUtils {
@@ -21,4 +27,15 @@
             SystemBarUtils.getQuickQsOffsetHeight(context)
         }
     }
-}
\ No newline at end of file
+
+    @JvmStatic
+    fun createFooterActionsView(
+        context: Context,
+        viewModel: FooterActionsViewModel,
+        qsVisibilityLifecycleOwner: LifecycleOwner,
+    ): View {
+        return DensityAwareComposeView(context).apply {
+            setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index a8e88da..05bb088 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -32,6 +32,7 @@
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.res.R;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
 import com.android.systemui.util.leak.RotationUtils;
 
@@ -56,10 +57,11 @@
             @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA)
                     Provider<Boolean> usingCollapsedLandscapeMediaProvider,
             MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
-            DumpManager dumpManager, SplitShadeStateController splitShadeStateController
+            DumpManager dumpManager, SplitShadeStateController splitShadeStateController,
+            VibratorHelper vibratorHelper
     ) {
         super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
-                uiEventLogger, qsLogger, dumpManager, splitShadeStateController);
+                uiEventLogger, qsLogger, dumpManager, splitShadeStateController, vibratorHelper);
         mUsingCollapsedLandscapeMediaProvider = usingCollapsedLandscapeMediaProvider;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index e5af8e6..58858df 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -46,7 +46,6 @@
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.res.R;
 import com.android.systemui.qs.QSEditEvent;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.TileLayout;
@@ -57,6 +56,7 @@
 import com.android.systemui.qs.dagger.QSThemedContext;
 import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.qs.tileimpl.QSTileViewImpl;
+import com.android.systemui.res.R;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -319,6 +319,9 @@
         }
         FrameLayout frame = (FrameLayout) inflater.inflate(R.layout.qs_customize_tile_frame, parent,
                 false);
+        if (com.android.systemui.Flags.qsTileFocusState()) {
+            frame.setClipChildren(false);
+        }
         View view = new CustomizeTileView(context);
         frame.addView(view);
         return new Holder(frame);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt
new file mode 100644
index 0000000..a2ded6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tileimpl
+
+/**
+ * List of properties that define the state of a tile during a long-press gesture.
+ *
+ * These properties are used during animation if a tile supports a long-press action.
+ */
+data class QSLongPressProperties(
+    var xScale: Float,
+    var yScale: Float,
+    var cornerRadius: Float,
+    var backgroundColor: Int,
+    var labelColor: Int,
+    var secondaryLabelColor: Int,
+    var chevronColor: Int,
+    var overlayColor: Int,
+    var iconColor: Int,
+)
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 9fefcbd..63963de 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -26,6 +26,7 @@
 import android.graphics.Color
 import android.graphics.PorterDuff
 import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
 import android.graphics.drawable.LayerDrawable
 import android.graphics.drawable.RippleDrawable
 import android.os.Trace
@@ -36,6 +37,7 @@
 import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.View
+import android.view.ViewConfiguration
 import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import android.view.accessibility.AccessibilityNodeInfo
@@ -47,9 +49,13 @@
 import androidx.annotation.VisibleForTesting
 import com.android.app.tracing.traceSection
 import com.android.settingslib.Utils
+import com.android.systemui.Flags
+import com.android.systemui.Flags.quickSettingsVisualHapticsLongpress
 import com.android.systemui.FontSizeUtils
 import com.android.systemui.animation.LaunchableView
 import com.android.systemui.animation.LaunchableViewDelegate
+import com.android.systemui.haptics.qs.QSLongPressEffect
+import com.android.systemui.haptics.qs.QSLongPressEffectViewBinder
 import com.android.systemui.plugins.qs.QSIconView
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.qs.QSTile.AdapterState
@@ -57,12 +63,15 @@
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.children
 import java.util.Objects
 
 private const val TAG = "QSTileViewImpl"
 open class QSTileViewImpl @JvmOverloads constructor(
     context: Context,
-    private val collapsed: Boolean = false
+    private val collapsed: Boolean = false,
+    private val vibratorHelper: VibratorHelper? = null,
 ) : QSTileView(context), HeightOverrideable, LaunchableView {
 
     companion object {
@@ -134,7 +143,7 @@
      */
     protected var showRippleEffect = true
 
-    private lateinit var ripple: RippleDrawable
+    private lateinit var qsTileBackground: LayerDrawable
     private lateinit var backgroundDrawable: LayerDrawable
     private lateinit var backgroundBaseDrawable: Drawable
     private lateinit var backgroundOverlayDrawable: Drawable
@@ -162,6 +171,7 @@
     private var lastStateDescription: CharSequence? = null
     private var tileState = false
     private var lastState = INVALID
+    private var lastIconTint = 0
     private val launchableViewDelegate = LaunchableViewDelegate(
         this,
         superSetVisibility = { super.setVisibility(it) },
@@ -170,6 +180,12 @@
 
     private val locInScreen = IntArray(2)
 
+    /** Visuo-haptic long-press effects */
+    private var longPressEffect: QSLongPressEffect? = null
+    private var initialLongPressProperties: QSLongPressProperties? = null
+    private var finalLongPressProperties: QSLongPressProperties? = null
+    private val colorEvaluator = ArgbEvaluator.getInstance()
+
     init {
         val typedValue = TypedValue()
         if (!getContext().theme.resolveAttribute(R.attr.isQsTheme, typedValue, true)) {
@@ -283,15 +299,20 @@
         addView(sideView)
     }
 
-    fun createTileBackground(): Drawable {
-        ripple = mContext.getDrawable(R.drawable.qs_tile_background) as RippleDrawable
-        backgroundDrawable = ripple.findDrawableByLayerId(R.id.background) as LayerDrawable
+    private fun createTileBackground(): Drawable {
+        qsTileBackground = if (Flags.qsTileFocusState()) {
+            mContext.getDrawable(R.drawable.qs_tile_background_flagged) as LayerDrawable
+        } else {
+            mContext.getDrawable(R.drawable.qs_tile_background) as RippleDrawable
+        }
+        backgroundDrawable =
+            qsTileBackground.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
+        return qsTileBackground
     }
 
     override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
@@ -333,6 +354,9 @@
                     true
                 }
         )
+        if (quickSettingsVisualHapticsLongpress()) {
+            isHapticFeedbackEnabled = false // Haptics will be handled by the [QSLongPressEffect]
+        }
     }
 
     private fun init(
@@ -366,14 +390,16 @@
 
     override fun setClickable(clickable: Boolean) {
         super.setClickable(clickable)
-        background = if (clickable && showRippleEffect) {
-            ripple.also {
-                // In case that the colorBackgroundDrawable was used as the background, make sure
-                // it has the correct callback instead of null
-                backgroundDrawable.callback = it
+        if (!Flags.qsTileFocusState()){
+            background = if (clickable && showRippleEffect) {
+                qsTileBackground.also {
+                    // In case that the colorBackgroundDrawable was used as the background, make sure
+                    // it has the correct callback instead of null
+                    backgroundDrawable.callback = it
+                }
+            } else {
+                backgroundDrawable
             }
-        } else {
-            backgroundDrawable
         }
     }
 
@@ -581,6 +607,27 @@
 
         lastState = state.state
         lastDisabledByPolicy = state.disabledByPolicy
+        lastIconTint = icon.getColor(state)
+
+        // Long-press effects
+        if (quickSettingsVisualHapticsLongpress()){
+            if (state.handlesLongClick) {
+                // initialize the long-press effect and set it as the touch listener
+                showRippleEffect = false
+                initializeLongPressEffect()
+                setOnTouchListener(longPressEffect)
+                QSLongPressEffectViewBinder.bind(this, longPressEffect)
+            } else {
+                // Long-press effects might have been enabled before but the new state does not
+                // handle a long-press. In this case, we go back to the behaviour of a regular tile
+                // and clean-up the resources
+                showRippleEffect = isClickable
+                setOnTouchListener(null)
+                longPressEffect = null
+                initialLongPressProperties = null
+                finalLongPressProperties = null
+            }
+        }
     }
 
     private fun setAllColors(
@@ -701,6 +748,140 @@
         }
     }
 
+    override fun onActivityLaunchAnimationEnd() = resetLongPressEffectProperties()
+
+    fun updateLongPressEffectProperties(effectProgress: Float) {
+        if (!isLongClickable) return
+        setAllColors(
+            colorEvaluator.evaluate(
+                effectProgress,
+                initialLongPressProperties?.backgroundColor ?: 0,
+                finalLongPressProperties?.backgroundColor ?: 0,
+            ) as Int,
+            colorEvaluator.evaluate(
+                effectProgress,
+                initialLongPressProperties?.labelColor ?: 0,
+                finalLongPressProperties?.labelColor ?: 0,
+            ) as Int,
+            colorEvaluator.evaluate(
+                effectProgress,
+                initialLongPressProperties?.secondaryLabelColor ?: 0,
+                finalLongPressProperties?.secondaryLabelColor ?: 0,
+            ) as Int,
+            colorEvaluator.evaluate(
+                effectProgress,
+                initialLongPressProperties?.chevronColor ?: 0,
+                finalLongPressProperties?.chevronColor ?: 0,
+            ) as Int,
+            colorEvaluator.evaluate(
+                effectProgress,
+                initialLongPressProperties?.overlayColor ?: 0,
+                finalLongPressProperties?.overlayColor ?: 0,
+            ) as Int,
+        )
+        icon.setTint(
+            icon.mIcon as ImageView,
+            colorEvaluator.evaluate(
+                effectProgress,
+                initialLongPressProperties?.iconColor ?: 0,
+                finalLongPressProperties?.iconColor ?: 0,
+            ) as Int,
+        )
+
+        val newScaleX =
+            interpolateFloat(
+                effectProgress,
+                initialLongPressProperties?.xScale ?: 1f,
+                finalLongPressProperties?.xScale ?: 1f,
+            )
+        val newScaleY =
+            interpolateFloat(
+                effectProgress,
+                initialLongPressProperties?.xScale ?: 1f,
+                finalLongPressProperties?.xScale ?: 1f,
+            )
+        val newRadius =
+            interpolateFloat(
+                effectProgress,
+                initialLongPressProperties?.cornerRadius ?: 0f,
+                finalLongPressProperties?.cornerRadius ?: 0f,
+            )
+        scaleX = newScaleX
+        scaleY = newScaleY
+        for (child in children) {
+            child.scaleX = 1f / newScaleX
+            child.scaleY = 1f / newScaleY
+        }
+        changeCornerRadius(newRadius)
+    }
+
+    private fun interpolateFloat(fraction: Float, start: Float, end: Float): Float =
+        start + fraction * (end - start)
+
+    private fun resetLongPressEffectProperties() {
+        scaleY = 1f
+        scaleX = 1f
+        for (child in children) {
+            child.scaleY = 1f
+            child.scaleX = 1f
+        }
+        changeCornerRadius(resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat())
+        setAllColors(
+            getBackgroundColorForState(lastState, lastDisabledByPolicy),
+            getLabelColorForState(lastState, lastDisabledByPolicy),
+            getSecondaryLabelColorForState(lastState, lastDisabledByPolicy),
+            getChevronColorForState(lastState, lastDisabledByPolicy),
+            getOverlayColorForState(lastState),
+        )
+        icon.setTint(icon.mIcon as ImageView, lastIconTint)
+    }
+
+    private fun initializeLongPressEffect() {
+        initializeLongPressProperties()
+        longPressEffect =
+            QSLongPressEffect(
+                vibratorHelper,
+                ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout(),
+            )
+    }
+
+    private fun initializeLongPressProperties() {
+        initialLongPressProperties =
+            QSLongPressProperties(
+                /* xScale= */1f,
+                /* yScale= */1f,
+                resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat(),
+                getBackgroundColorForState(lastState),
+                getLabelColorForState(lastState),
+                getSecondaryLabelColorForState(lastState),
+                getChevronColorForState(lastState),
+                getOverlayColorForState(lastState),
+                lastIconTint,
+            )
+
+        finalLongPressProperties =
+            QSLongPressProperties(
+                /* xScale= */1.1f,
+                /* yScale= */1.2f,
+                resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat() - 20,
+                getBackgroundColorForState(Tile.STATE_ACTIVE),
+                getLabelColorForState(Tile.STATE_ACTIVE),
+                getSecondaryLabelColorForState(Tile.STATE_ACTIVE),
+                getChevronColorForState(Tile.STATE_ACTIVE),
+                getOverlayColorForState(Tile.STATE_ACTIVE),
+                Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive),
+            )
+    }
+
+    private fun changeCornerRadius(radius: Float) {
+        for (i in 0 until backgroundDrawable.numberOfLayers) {
+            val layer = backgroundDrawable.getDrawable(i)
+            if (layer is GradientDrawable) {
+                layer.cornerRadius = radius
+            }
+        }
+    }
+
     @VisibleForTesting
     internal fun getCurrentColors(): List<Int> = listOf(
             backgroundColor,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
index 168fa08..15b8cfb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
@@ -39,6 +39,7 @@
         subtitleIdsMap["cast"] = R.array.tile_states_cast
         subtitleIdsMap["night"] = R.array.tile_states_night
         subtitleIdsMap["screenrecord"] = R.array.tile_states_screenrecord
+        subtitleIdsMap["record_issue"] = R.array.tile_states_record_issue
         subtitleIdsMap["reverse"] = R.array.tile_states_reverse
         subtitleIdsMap["reduce_brightness"] = R.array.tile_states_reduce_brightness
         subtitleIdsMap["cameratoggle"] = R.array.tile_states_cameratoggle
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index b1e2467..bd66843 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -85,7 +85,13 @@
 
     override fun getTileLabel(): CharSequence = mContext.getString(R.string.qs_record_issue_label)
 
-    override fun isAvailable(): Boolean = recordIssueQsTile()
+    /**
+     * There are SELinux constraints that are stopping this tile from reaching production builds.
+     * Once those are resolved, this condition will be removed, but the solution (of properly
+     * creating a distince SELinux context for com.android.systemui) is complex and will take time
+     * to implement.
+     */
+    override fun isAvailable(): Boolean = android.os.Build.IS_DEBUGGABLE && recordIssueQsTile()
 
     override fun newTileState(): QSTile.BooleanState =
         QSTile.BooleanState().apply {
@@ -97,14 +103,27 @@
     public override fun handleClick(view: View?) {
         if (isRecording) {
             isRecording = false
-            stopScreenRecord()
+            stopIssueRecordingService()
         } else {
             mUiHandler.post { showPrompt(view) }
         }
         refreshState()
     }
 
-    private fun stopScreenRecord() =
+    private fun startIssueRecordingService(screenRecord: Boolean, winscopeTracing: Boolean) =
+        PendingIntent.getForegroundService(
+                userContextProvider.userContext,
+                RecordingService.REQUEST_CODE,
+                IssueRecordingService.getStartIntent(
+                    userContextProvider.userContext,
+                    screenRecord,
+                    winscopeTracing
+                ),
+                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+            )
+            .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle())
+
+    private fun stopIssueRecordingService() =
         PendingIntent.getService(
                 userContextProvider.userContext,
                 RecordingService.REQUEST_CODE,
@@ -118,6 +137,7 @@
             delegateFactory
                 .create {
                     isRecording = true
+                    startIssueRecordingService(it.screenRecord, it.winscopeTracing)
                     refreshState()
                 }
                 .createDialog()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index 0dd0a60..52cf4ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -481,7 +481,8 @@
                 mSecondaryMobileTitleText.setTextAppearance(
                         R.style.TextAppearance_InternetDialog_Active);
 
-                TextView mSecondaryMobileSummaryText = mDialogView.requireViewById(R.id.secondary_mobile_summary);
+                TextView mSecondaryMobileSummaryText =
+                        mDialogView.requireViewById(R.id.secondary_mobile_summary);
                 summary = getMobileNetworkSummary(autoSwitchNonDdsSubId);
                 if (!TextUtils.isEmpty(summary)) {
                     mSecondaryMobileSummaryText.setText(
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/BluetoothTileDialogDelegate.kt
similarity index 66%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
rename to packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt
index 6b53c7a..9d53703 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/BluetoothTileDialogDelegate.kt
@@ -32,16 +32,19 @@
 import android.widget.ProgressBar
 import android.widget.Switch
 import android.widget.TextView
+import androidx.annotation.StringRes
 import androidx.recyclerview.widget.AsyncListDiffer
 import androidx.recyclerview.widget.DiffUtil
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import com.android.internal.logging.UiEventLogger
-import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.util.time.SystemClock
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.MutableSharedFlow
@@ -52,19 +55,24 @@
 import kotlinx.coroutines.withContext
 
 /** Dialog for showing active, connected and saved bluetooth devices. */
-@SysUISingleton
-internal class BluetoothTileDialog
-constructor(
-    private val bluetoothToggleInitialValue: Boolean,
-    private val initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
-    private val cachedContentHeight: Int,
-    private val bluetoothTileDialogCallback: BluetoothTileDialogCallback,
+class BluetoothTileDialogDelegate
+@AssistedInject
+internal constructor(
+    @Assisted private val context: Context,
+    @Assisted private val initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
+    @Assisted private val cachedContentHeight: Int,
+    @Assisted private val bluetoothToggleInitialValue: Boolean,
+    @Assisted private val bluetoothTileDialogCallback: BluetoothTileDialogCallback,
+    @Assisted private val dismissListener: Runnable,
     @Main private val mainDispatcher: CoroutineDispatcher,
     private val systemClock: SystemClock,
     private val uiEventLogger: UiEventLogger,
     private val logger: BluetoothTileDialogLogger,
-    context: Context,
-) : SystemUIDialog(context, DEFAULT_THEME, DEFAULT_DISMISS_ON_DEVICE_LOCK) {
+    private val systemuiDialogFactory: SystemUIDialog.Factory,
+    mainLayoutInflater: LayoutInflater,
+) : SystemUIDialog.Delegate {
+
+    private val layoutInflater = mainLayoutInflater.cloneInContext(context)
 
     private val mutableBluetoothStateToggle: MutableStateFlow<Boolean> =
         MutableStateFlow(bluetoothToggleInitialValue)
@@ -91,78 +99,72 @@
 
     private var lastItemRow: Int = -1
 
-    private lateinit var toggleView: Switch
-    private lateinit var subtitleTextView: TextView
-    private lateinit var autoOnToggle: Switch
-    private lateinit var autoOnToggleView: View
-    private lateinit var doneButton: View
-    private lateinit var seeAllButton: View
-    private lateinit var pairNewDeviceButton: View
-    private lateinit var deviceListView: RecyclerView
-    private lateinit var scrollViewContent: View
-    private lateinit var progressBarAnimation: ProgressBar
-    private lateinit var progressBarBackground: View
+    @AssistedFactory
+    internal interface Factory {
+        fun create(
+            context: Context,
+            initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
+            cachedContentHeight: Int,
+            bluetoothEnabled: Boolean,
+            dialogCallback: BluetoothTileDialogCallback,
+            dimissListener: Runnable
+        ): BluetoothTileDialogDelegate
+    }
 
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
+    override fun createDialog(): SystemUIDialog {
+        val dialog = systemuiDialogFactory.create(this, context)
+
+        return dialog
+    }
+
+    override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+        SystemUIDialog.registerDismissListener(dialog, dismissListener)
         uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_TILE_DIALOG_SHOWN)
 
-        LayoutInflater.from(context).inflate(R.layout.bluetooth_tile_dialog, null).apply {
+        layoutInflater.inflate(R.layout.bluetooth_tile_dialog, null).apply {
             accessibilityPaneTitle = context.getText(R.string.accessibility_desc_quick_settings)
-            setContentView(this)
+            dialog.setContentView(this)
         }
 
-        toggleView = requireViewById(R.id.bluetooth_toggle)
-        subtitleTextView = requireViewById(R.id.bluetooth_tile_dialog_subtitle) as TextView
-        autoOnToggle = requireViewById(R.id.bluetooth_auto_on_toggle)
-        autoOnToggleView = requireViewById(R.id.bluetooth_auto_on_toggle_layout)
-        doneButton = requireViewById(R.id.done_button)
-        seeAllButton = requireViewById(R.id.see_all_button)
-        pairNewDeviceButton = requireViewById(R.id.pair_new_device_button)
-        deviceListView = requireViewById<RecyclerView>(R.id.device_list)
+        setupToggle(dialog)
+        setupRecyclerView(dialog)
 
-        setupToggle()
-        setupRecyclerView()
-
-        subtitleTextView.text = context.getString(initialUiProperties.subTitleResId)
-        doneButton.setOnClickListener { dismiss() }
-        seeAllButton.setOnClickListener { bluetoothTileDialogCallback.onSeeAllClicked(it) }
-        pairNewDeviceButton.setOnClickListener {
+        getSubtitleTextView(dialog).text = context.getString(initialUiProperties.subTitleResId)
+        dialog.requireViewById<View>(R.id.done_button).setOnClickListener { dialog.dismiss() }
+        getSeeAllButton(dialog).setOnClickListener {
+            bluetoothTileDialogCallback.onSeeAllClicked(it)
+        }
+        getPairNewDeviceButton(dialog).setOnClickListener {
             bluetoothTileDialogCallback.onPairNewDeviceClicked(it)
         }
-        requireViewById<View>(R.id.scroll_view).apply {
-            scrollViewContent = this
+        getScrollViewContent(dialog).apply {
             minimumHeight =
                 resources.getDimensionPixelSize(initialUiProperties.scrollViewMinHeightResId)
             layoutParams.height = maxOf(cachedContentHeight, minimumHeight)
         }
-        progressBarAnimation = requireViewById(R.id.bluetooth_tile_dialog_progress_animation)
-        progressBarBackground = requireViewById(R.id.bluetooth_tile_dialog_progress_background)
     }
 
-    override fun start() {
+    override fun onStart(dialog: SystemUIDialog) {
         lastUiUpdateMs = systemClock.elapsedRealtime()
     }
 
-    override fun dismiss() {
-        if (::scrollViewContent.isInitialized) {
-            mutableContentHeight.tryEmit(scrollViewContent.measuredHeight)
-        }
-        super.dismiss()
+    override fun onStop(dialog: SystemUIDialog) {
+        mutableContentHeight.tryEmit(getScrollViewContent(dialog).measuredHeight)
     }
 
-    internal suspend fun animateProgressBar(animate: Boolean) {
+    internal suspend fun animateProgressBar(dialog: SystemUIDialog, animate: Boolean) {
         withContext(mainDispatcher) {
             if (animate) {
-                showProgressBar()
+                showProgressBar(dialog)
             } else {
                 delay(PROGRESS_BAR_ANIMATION_DURATION_MS)
-                hideProgressBar()
+                hideProgressBar(dialog)
             }
         }
     }
 
     internal suspend fun onDeviceItemUpdated(
+        dialog: SystemUIDialog,
         deviceItem: List<DeviceItem>,
         showSeeAll: Boolean,
         showPairNewDevice: Boolean
@@ -176,10 +178,11 @@
             }
             if (isActive) {
                 deviceItemAdapter.refreshDeviceItemList(deviceItem) {
-                    seeAllButton.visibility = if (showSeeAll) VISIBLE else GONE
-                    pairNewDeviceButton.visibility = if (showPairNewDevice) VISIBLE else GONE
+                    getSeeAllButton(dialog).visibility = if (showSeeAll) VISIBLE else GONE
+                    getPairNewDeviceButton(dialog).visibility =
+                        if (showPairNewDevice) VISIBLE else GONE
                     // Update the height after data is updated
-                    scrollViewContent.layoutParams.height = WRAP_CONTENT
+                    getScrollViewContent(dialog).layoutParams.height = WRAP_CONTENT
                     lastUiUpdateMs = systemClock.elapsedRealtime()
                     lastItemRow = itemRow
                     logger.logDeviceUiUpdate(lastUiUpdateMs - start)
@@ -189,29 +192,34 @@
     }
 
     internal fun onBluetoothStateUpdated(
+        dialog: SystemUIDialog,
         isEnabled: Boolean,
         uiProperties: BluetoothTileDialogViewModel.UiProperties
     ) {
-        toggleView.apply {
+        getToggleView(dialog).apply {
             isChecked = isEnabled
             setEnabled(true)
             alpha = ENABLED_ALPHA
         }
-        subtitleTextView.text = context.getString(uiProperties.subTitleResId)
-        autoOnToggleView.visibility = uiProperties.autoOnToggleVisibility
+        getSubtitleTextView(dialog).text = context.getString(uiProperties.subTitleResId)
+        getAutoOnToggleView(dialog).visibility = uiProperties.autoOnToggleVisibility
     }
 
-    internal fun onBluetoothAutoOnUpdated(isEnabled: Boolean) {
-        if (::autoOnToggle.isInitialized) {
-            autoOnToggle.apply {
-                isChecked = isEnabled
-                setEnabled(true)
-                alpha = ENABLED_ALPHA
-            }
+    internal fun onBluetoothAutoOnUpdated(
+        dialog: SystemUIDialog,
+        isEnabled: Boolean,
+        @StringRes infoResId: Int
+    ) {
+        getAutoOnToggle(dialog).apply {
+            isChecked = isEnabled
+            setEnabled(true)
+            alpha = ENABLED_ALPHA
         }
+        getAutoOnToggleInfoTextView(dialog).text = context.getString(infoResId)
     }
 
-    private fun setupToggle() {
+    private fun setupToggle(dialog: SystemUIDialog) {
+        val toggleView = getToggleView(dialog)
         toggleView.isChecked = bluetoothToggleInitialValue
         toggleView.setOnCheckedChangeListener { view, isChecked ->
             mutableBluetoothStateToggle.value = isChecked
@@ -223,8 +231,8 @@
             uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_TOGGLE_CLICKED)
         }
 
-        autoOnToggleView.visibility = initialUiProperties.autoOnToggleVisibility
-        autoOnToggle.setOnCheckedChangeListener { view, isChecked ->
+        getAutoOnToggleView(dialog).visibility = initialUiProperties.autoOnToggleVisibility
+        getAutoOnToggle(dialog).setOnCheckedChangeListener { view, isChecked ->
             mutableBluetoothAutoOnToggle.value = isChecked
             view.apply {
                 isEnabled = false
@@ -234,30 +242,70 @@
         }
     }
 
-    private fun setupRecyclerView() {
-        deviceListView.apply {
+    private fun getToggleView(dialog: SystemUIDialog): Switch {
+        return dialog.requireViewById(R.id.bluetooth_toggle)
+    }
+
+    private fun getSubtitleTextView(dialog: SystemUIDialog): TextView {
+        return dialog.requireViewById(R.id.bluetooth_tile_dialog_subtitle)
+    }
+
+    private fun getSeeAllButton(dialog: SystemUIDialog): View {
+        return dialog.requireViewById(R.id.see_all_button)
+    }
+
+    private fun getPairNewDeviceButton(dialog: SystemUIDialog): View {
+        return dialog.requireViewById(R.id.pair_new_device_button)
+    }
+
+    private fun getDeviceListView(dialog: SystemUIDialog): RecyclerView {
+        return dialog.requireViewById(R.id.device_list)
+    }
+
+    private fun getAutoOnToggle(dialog: SystemUIDialog): Switch {
+        return dialog.requireViewById(R.id.bluetooth_auto_on_toggle)
+    }
+
+    private fun getAutoOnToggleView(dialog: SystemUIDialog): View {
+        return dialog.requireViewById(R.id.bluetooth_auto_on_toggle_layout)
+    }
+
+    private fun getAutoOnToggleInfoTextView(dialog: SystemUIDialog): TextView {
+        return dialog.requireViewById(R.id.bluetooth_auto_on_toggle_info_text)
+    }
+
+    private fun getProgressBarAnimation(dialog: SystemUIDialog): ProgressBar {
+        return dialog.requireViewById(R.id.bluetooth_tile_dialog_progress_animation)
+    }
+
+    private fun getProgressBarBackground(dialog: SystemUIDialog): View {
+        return dialog.requireViewById(R.id.bluetooth_tile_dialog_progress_animation)
+    }
+
+    private fun getScrollViewContent(dialog: SystemUIDialog): View {
+        return dialog.requireViewById(R.id.scroll_view)
+    }
+
+    private fun setupRecyclerView(dialog: SystemUIDialog) {
+        getDeviceListView(dialog).apply {
             layoutManager = LinearLayoutManager(context)
             adapter = deviceItemAdapter
         }
     }
 
-    private fun showProgressBar() {
-        if (
-            ::progressBarAnimation.isInitialized &&
-                ::progressBarBackground.isInitialized &&
-                progressBarAnimation.visibility != VISIBLE
-        ) {
+    private fun showProgressBar(dialog: SystemUIDialog) {
+        val progressBarAnimation = getProgressBarAnimation(dialog)
+        val progressBarBackground = getProgressBarBackground(dialog)
+        if (progressBarAnimation.visibility != VISIBLE) {
             progressBarAnimation.visibility = VISIBLE
             progressBarBackground.visibility = INVISIBLE
         }
     }
 
-    private fun hideProgressBar() {
-        if (
-            ::progressBarAnimation.isInitialized &&
-                ::progressBarBackground.isInitialized &&
-                progressBarAnimation.visibility != INVISIBLE
-        ) {
+    private fun hideProgressBar(dialog: SystemUIDialog) {
+        val progressBarAnimation = getProgressBarAnimation(dialog)
+        val progressBarBackground = getProgressBarBackground(dialog)
+        if (progressBarAnimation.visibility != INVISIBLE) {
             progressBarAnimation.visibility = INVISIBLE
             progressBarBackground.visibility = VISIBLE
         }
@@ -295,9 +343,7 @@
         private val asyncListDiffer = AsyncListDiffer(this, diffUtilCallback)
 
         override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DeviceItemViewHolder {
-            val view =
-                LayoutInflater.from(parent.context)
-                    .inflate(R.layout.bluetooth_device_item, parent, false)
+            val view = layoutInflater.inflate(R.layout.bluetooth_device_item, parent, false)
             return DeviceItemViewHolder(view)
         }
 
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 5a14e5f..e4f3c19 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
@@ -38,13 +38,11 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.ACTION_PAIR_NEW_DEVICE
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.MAX_DEVICE_ITEM_ENTRY
+import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS
+import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.Companion.ACTION_PAIR_NEW_DEVICE
+import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
+import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.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
@@ -67,13 +65,12 @@
     private val bluetoothAutoOnInteractor: BluetoothAutoOnInteractor,
     private val dialogTransitionAnimator: DialogTransitionAnimator,
     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,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     @Main private val sharedPreferences: SharedPreferences,
+    private val bluetoothDialogDelegateFactory: BluetoothTileDialogDelegate.Factory,
 ) : BluetoothTileDialogCallback {
 
     private var job: Job? = null
@@ -92,7 +89,8 @@
             coroutineScope.launch(mainDispatcher) {
                 var updateDeviceItemJob: Job?
                 var updateDialogUiJob: Job? = null
-                val dialog = createBluetoothTileDialog(context)
+                val dialogDelegate = createBluetoothTileDialog(context)
+                val dialog = dialogDelegate.createDialog()
 
                 view?.let {
                     dialogTransitionAnimator.showFromView(
@@ -118,13 +116,14 @@
                     .onEach {
                         updateDialogUiJob?.cancel()
                         updateDialogUiJob = launch {
-                            dialog.apply {
+                            dialogDelegate.apply {
                                 onDeviceItemUpdated(
+                                    dialog,
                                     it.take(MAX_DEVICE_ITEM_ENTRY),
                                     showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY,
                                     showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled
                                 )
-                                animateProgressBar(false)
+                                animateProgressBar(dialog, false)
                             }
                         }
                     }
@@ -134,7 +133,7 @@
                 // the device item list and animiate the progress bar.
                 deviceItemInteractor.deviceItemUpdateRequest
                     .onEach {
-                        dialog.animateProgressBar(true)
+                        dialogDelegate.animateProgressBar(dialog, true)
                         updateDeviceItemJob?.cancel()
                         updateDeviceItemJob = launch {
                             deviceItemInteractor.updateDeviceItems(
@@ -150,7 +149,8 @@
                 bluetoothStateInteractor.bluetoothStateUpdate
                     .filterNotNull()
                     .onEach {
-                        dialog.onBluetoothStateUpdated(
+                        dialogDelegate.onBluetoothStateUpdated(
+                            dialog,
                             it,
                             UiProperties.build(it, isAutoOnToggleFeatureAvailable())
                         )
@@ -166,20 +166,20 @@
 
                 // bluetoothStateToggle is emitted when user toggles the bluetooth state switch,
                 // send the new value to the bluetoothStateInteractor and animate the progress bar.
-                dialog.bluetoothStateToggle
+                dialogDelegate.bluetoothStateToggle
                     .onEach {
-                        dialog.animateProgressBar(true)
+                        dialogDelegate.animateProgressBar(dialog, true)
                         bluetoothStateInteractor.isBluetoothEnabled = it
                     }
                     .launchIn(this)
 
                 // deviceItemClick is emitted when user clicked on a device item.
-                dialog.deviceItemClick
+                dialogDelegate.deviceItemClick
                     .onEach { deviceItemInteractor.updateDeviceItemOnClick(it) }
                     .launchIn(this)
 
                 // contentHeight is emitted when the dialog is dismissed.
-                dialog.contentHeight
+                dialogDelegate.contentHeight
                     .onEach {
                         withContext(backgroundDispatcher) {
                             sharedPreferences.edit().putInt(CONTENT_HEIGHT_PREF_KEY, it).apply()
@@ -191,12 +191,19 @@
                     // bluetoothAutoOnUpdate is emitted when bluetooth auto on on/off state is
                     // changed.
                     bluetoothAutoOnInteractor.isEnabled
-                        .onEach { dialog.onBluetoothAutoOnUpdated(it) }
+                        .onEach {
+                            dialogDelegate.onBluetoothAutoOnUpdated(
+                                dialog,
+                                it,
+                                if (it) R.string.turn_on_bluetooth_auto_info_enabled
+                                else R.string.turn_on_bluetooth_auto_info_disabled
+                            )
+                        }
                         .launchIn(this)
 
                     // bluetoothAutoOnToggle is emitted when user toggles the bluetooth auto on
                     // switch, send the new value to the bluetoothAutoOnInteractor.
-                    dialog.bluetoothAutoOnToggle
+                    dialogDelegate.bluetoothAutoOnToggle
                         .filterNotNull()
                         .onEach { bluetoothAutoOnInteractor.setEnabled(it) }
                         .launchIn(this)
@@ -206,7 +213,7 @@
             }
     }
 
-    private suspend fun createBluetoothTileDialog(context: Context): BluetoothTileDialog {
+    private suspend fun createBluetoothTileDialog(context: Context): BluetoothTileDialogDelegate {
         val cachedContentHeight =
             withContext(backgroundDispatcher) {
                 sharedPreferences.getInt(
@@ -215,21 +222,17 @@
                 )
             }
 
-        return BluetoothTileDialog(
+        return bluetoothDialogDelegateFactory.create(
+            context,
+            UiProperties.build(
                 bluetoothStateInteractor.isBluetoothEnabled,
-                UiProperties.build(
-                    bluetoothStateInteractor.isBluetoothEnabled,
-                    isAutoOnToggleFeatureAvailable()
-                ),
-                cachedContentHeight,
-                this@BluetoothTileDialogViewModel,
-                mainDispatcher,
-                systemClock,
-                uiEventLogger,
-                logger,
-                context
-            )
-            .apply { SystemUIDialog.registerDismissListener(this) { cancelJob() } }
+                isAutoOnToggleFeatureAvailable()
+            ),
+            cachedContentHeight,
+            bluetoothStateInteractor.isBluetoothEnabled,
+            this@BluetoothTileDialogViewModel,
+            { cancelJob() }
+        )
     }
 
     override fun onDeviceItemGearClicked(deviceItem: DeviceItem, view: View) {
@@ -308,7 +311,7 @@
     }
 }
 
-internal interface BluetoothTileDialogCallback {
+interface BluetoothTileDialogCallback {
     fun onDeviceItemGearClicked(deviceItem: DeviceItem, view: View)
     fun onSeeAllClicked(view: View)
     fun onPairNewDeviceClicked(view: View)
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 1c9be0f..f13ecf3 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
@@ -21,6 +21,7 @@
 import android.media.AudioManager
 import com.android.settingslib.bluetooth.BluetoothUtils
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.flags.Flags
 import com.android.systemui.res.R
 
 private val backgroundOn = R.drawable.settingslib_switch_bar_bg_on
@@ -36,6 +37,7 @@
 /** Factories to create different types of Bluetooth device items from CachedBluetoothDevice. */
 internal abstract class DeviceItemFactory {
     abstract fun isFilterMatched(
+        context: Context,
         cachedDevice: CachedBluetoothDevice,
         audioManager: AudioManager?
     ): Boolean
@@ -45,6 +47,7 @@
 
 internal class ActiveMediaDeviceItemFactory : DeviceItemFactory() {
     override fun isFilterMatched(
+        context: Context,
         cachedDevice: CachedBluetoothDevice,
         audioManager: AudioManager?
     ): Boolean {
@@ -71,6 +74,7 @@
 
 internal class AvailableMediaDeviceItemFactory : DeviceItemFactory() {
     override fun isFilterMatched(
+        context: Context,
         cachedDevice: CachedBluetoothDevice,
         audioManager: AudioManager?
     ): Boolean {
@@ -99,10 +103,18 @@
 
 internal class ConnectedDeviceItemFactory : DeviceItemFactory() {
     override fun isFilterMatched(
+        context: Context,
         cachedDevice: CachedBluetoothDevice,
         audioManager: AudioManager?
     ): Boolean {
-        return BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
+        return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
+            !BluetoothUtils.isExclusivelyManagedBluetoothDevice(
+                context,
+                cachedDevice.getDevice()
+            ) && BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
+        } else {
+            BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
+        }
     }
 
     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
@@ -125,10 +137,18 @@
 
 internal class SavedDeviceItemFactory : DeviceItemFactory() {
     override fun isFilterMatched(
+        context: Context,
         cachedDevice: CachedBluetoothDevice,
         audioManager: AudioManager?
     ): Boolean {
-        return cachedDevice.bondState == BluetoothDevice.BOND_BONDED && !cachedDevice.isConnected
+        return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
+            !BluetoothUtils.isExclusivelyManagedBluetoothDevice(
+                context,
+                cachedDevice.getDevice()
+            ) && cachedDevice.bondState == BluetoothDevice.BOND_BONDED && !cachedDevice.isConnected
+        } else {
+            cachedDevice.bondState == BluetoothDevice.BOND_BONDED && !cachedDevice.isConnected
+        }
     }
 
     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
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 fcd45a6..1df496b 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
@@ -133,7 +133,7 @@
                 bluetoothTileDialogRepository.cachedDevices
                     .mapNotNull { cachedDevice ->
                         deviceItemFactoryList
-                            .firstOrNull { it.isFilterMatched(cachedDevice, audioManager) }
+                            .firstOrNull { it.isFilterMatched(context, cachedDevice, audioManager) }
                             ?.create(context, cachedDevice)
                     }
                     .sort(displayPriority, bluetoothAdapter?.mostRecentlyConnectedDevices)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileDataInteractor.kt
new file mode 100644
index 0000000..22bbbbb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileDataInteractor.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.dagger.qualifiers.Background
+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.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.util.kotlin.combine
+import com.android.systemui.util.kotlin.getBatteryLevel
+import com.android.systemui.util.kotlin.isBatteryPowerSaveEnabled
+import com.android.systemui.util.kotlin.isDevicePluggedIn
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+
+/** Observes BatterySaver mode state changes providing the [BatterySaverTileModel.Standard]. */
+open class BatterySaverTileDataInteractor
+@Inject
+constructor(
+    @Background private val bgCoroutineContext: CoroutineContext,
+    private val batteryController: BatteryController,
+) : QSTileDataInteractor<BatterySaverTileModel> {
+
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<BatterySaverTileModel> =
+        combine(
+            batteryController.isDevicePluggedIn().distinctUntilChanged().flowOn(bgCoroutineContext),
+            batteryController
+                .isBatteryPowerSaveEnabled()
+                .distinctUntilChanged()
+                .flowOn(bgCoroutineContext),
+            batteryController.getBatteryLevel().distinctUntilChanged().flowOn(bgCoroutineContext),
+        ) {
+            isPluggedIn: Boolean,
+            isPowerSaverEnabled: Boolean,
+            _, // we are only interested in battery level change, not the actual level
+            ->
+            BatterySaverTileModel.Standard(isPluggedIn, isPowerSaverEnabled)
+        }
+
+    override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt
new file mode 100644
index 0000000..1e4eb38
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.policy.BatteryController
+import javax.inject.Inject
+
+/** Handles airplane mode tile clicks and long clicks. */
+class BatterySaverTileUserActionInteractor
+@Inject
+constructor(
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+    private val batteryController: BatteryController
+) : QSTileUserActionInteractor<BatterySaverTileModel> {
+
+    override suspend fun handleInput(input: QSTileInput<BatterySaverTileModel>) =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    if (!data.isPluggedIn) {
+                        batteryController.setPowerSaveMode(!data.isPowerSaving, action.view)
+                    }
+                }
+                is QSTileUserAction.LongClick -> {
+                    qsTileIntentUserActionHandler.handle(
+                        action.view,
+                        Intent(Settings.ACTION_BATTERY_SAVER_SETTINGS)
+                    )
+                }
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/model/BatterySaverTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/model/BatterySaverTileModel.kt
new file mode 100644
index 0000000..dbec50d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/model/BatterySaverTileModel.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.domain.model
+
+/** BatterySaver mode tile model. */
+sealed interface BatterySaverTileModel {
+
+    val isPluggedIn: Boolean
+    val isPowerSaving: Boolean
+
+    /** For when the device does not support extreme battery saver mode. */
+    data class Standard(
+        override val isPluggedIn: Boolean,
+        override val isPowerSaving: Boolean,
+    ) : BatterySaverTileModel
+
+    /**
+     * For when device supports extreme battery saver mode. Whether or not that mode is enabled is
+     * determined through [isExtremeSaving].
+     */
+    data class Extreme(
+        override val isPluggedIn: Boolean,
+        override val isPowerSaving: Boolean,
+        val isExtremeSaving: Boolean,
+    ) : BatterySaverTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
new file mode 100644
index 0000000..0c08fba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.ui
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [BatterySaverTileModel] to [QSTileState]. */
+open class BatterySaverTileMapper
+@Inject
+constructor(
+    @Main protected val resources: Resources,
+    private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<BatterySaverTileModel> {
+
+    override fun map(config: QSTileConfig, data: BatterySaverTileModel): QSTileState =
+        QSTileState.build(resources, theme, config.uiConfig) {
+            label = resources.getString(R.string.battery_detail_switch_title)
+            contentDescription = label
+
+            icon = {
+                Icon.Loaded(
+                    resources.getDrawable(
+                        if (data.isPowerSaving) R.drawable.qs_battery_saver_icon_on
+                        else R.drawable.qs_battery_saver_icon_off,
+                        theme
+                    ),
+                    null
+                )
+            }
+
+            sideViewIcon = QSTileState.SideViewIcon.None
+
+            if (data.isPluggedIn) {
+                activationState = QSTileState.ActivationState.UNAVAILABLE
+                supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+                secondaryLabel = ""
+            } else if (data.isPowerSaving) {
+                activationState = QSTileState.ActivationState.ACTIVE
+                supportedActions =
+                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+
+                if (data is BatterySaverTileModel.Extreme) {
+                    secondaryLabel =
+                        resources.getString(
+                            if (data.isExtremeSaving) R.string.extreme_battery_saver_text
+                            else R.string.standard_battery_saver_text
+                        )
+                    stateDescription = secondaryLabel
+                } else {
+                    secondaryLabel = ""
+                }
+            } else {
+                activationState = QSTileState.ActivationState.INACTIVE
+                supportedActions =
+                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+                secondaryLabel = ""
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
new file mode 100644
index 0000000..caae4d2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet.domain
+
+import android.content.Context
+import android.content.res.Resources
+import android.widget.Switch
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text.Companion.loadText
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [InternetTileModel] to [QSTileState]. */
+class InternetTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Resources.Theme,
+    private val context: Context,
+) : QSTileDataToStateMapper<InternetTileModel> {
+
+    override fun map(config: QSTileConfig, data: InternetTileModel): QSTileState =
+        QSTileState.build(resources, theme, config.uiConfig) {
+            label = resources.getString(R.string.quick_settings_internet_label)
+            expandedAccessibilityClass = Switch::class
+
+            if (data.secondaryLabel != null) {
+                secondaryLabel = data.secondaryLabel.loadText(context)
+            } else {
+                secondaryLabel = data.secondaryTitle
+            }
+
+            stateDescription = data.stateDescription.loadContentDescription(context)
+            contentDescription = data.contentDescription.loadContentDescription(context)
+
+            if (data.icon != null) {
+                this.icon = { data.icon }
+            } else if (data.iconId != null) {
+                val loadedIcon =
+                    Icon.Loaded(
+                        resources.getDrawable(data.iconId!!, theme),
+                        contentDescription = null
+                    )
+                this.icon = { loadedIcon }
+            }
+
+            sideViewIcon = QSTileState.SideViewIcon.Chevron
+
+            activationState =
+                if (data is InternetTileModel.Active) QSTileState.ActivationState.ACTIVE
+                else QSTileState.ActivationState.INACTIVE
+
+            supportedActions =
+                setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
new file mode 100644
index 0000000..fdc596b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet.domain.interactor
+
+import android.annotation.StringRes
+import android.content.Context
+import android.os.UserHandle
+import android.text.Html
+import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.qualifiers.Application
+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.internet.domain.model.InternetTileModel
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.stateIn
+
+@OptIn(ExperimentalCoroutinesApi::class)
+/** Observes internet state changes providing the [InternetTileModel]. */
+class InternetTileDataInteractor
+@Inject
+constructor(
+    private val context: Context,
+    @Application private val scope: CoroutineScope,
+    airplaneModeRepository: AirplaneModeRepository,
+    private val connectivityRepository: ConnectivityRepository,
+    ethernetInteractor: EthernetInteractor,
+    mobileIconsInteractor: MobileIconsInteractor,
+    wifiInteractor: WifiInteractor,
+) : QSTileDataInteractor<InternetTileModel> {
+    private val internetLabel: String = context.getString(R.string.quick_settings_internet_label)
+
+    // Three symmetrical Flows that can be switched upon based on the value of
+    // [DefaultConnectionModel]
+    private val wifiIconFlow: Flow<InternetTileModel> =
+        wifiInteractor.wifiNetwork.flatMapLatest {
+            val wifiIcon = WifiIcon.fromModel(it, context, showHotspotInfo = true)
+            if (it is WifiNetworkModel.Active && wifiIcon is WifiIcon.Visible) {
+                val secondary = removeDoubleQuotes(it.ssid)
+                flowOf(
+                    InternetTileModel.Active(
+                        secondaryTitle = secondary,
+                        icon = Icon.Loaded(context.getDrawable(wifiIcon.icon.res)!!, null),
+                        stateDescription = wifiIcon.contentDescription,
+                        contentDescription = ContentDescription.Loaded("$internetLabel,$secondary"),
+                    )
+                )
+            } else {
+                notConnectedFlow
+            }
+        }
+
+    private val mobileDataContentName: Flow<CharSequence?> =
+        mobileIconsInteractor.activeDataIconInteractor.flatMapLatest {
+            if (it == null) {
+                flowOf(null)
+            } else {
+                combine(it.isRoaming, it.networkTypeIconGroup) { isRoaming, networkTypeIconGroup ->
+                    val cd = loadString(networkTypeIconGroup.contentDescription)
+                    if (isRoaming) {
+                        val roaming = context.getString(R.string.data_connection_roaming)
+                        if (cd != null) {
+                            context.getString(R.string.mobile_data_text_format, roaming, cd)
+                        } else {
+                            roaming
+                        }
+                    } else {
+                        cd
+                    }
+                }
+            }
+        }
+
+    private val mobileIconFlow: Flow<InternetTileModel> =
+        mobileIconsInteractor.activeDataIconInteractor.flatMapLatest {
+            if (it == null) {
+                notConnectedFlow
+            } else {
+                combine(
+                    it.networkName,
+                    it.signalLevelIcon,
+                    mobileDataContentName,
+                ) { networkNameModel, signalIcon, dataContentDescription ->
+                    when (signalIcon) {
+                        is SignalIconModel.Cellular -> {
+                            val secondary =
+                                mobileDataContentConcat(
+                                    networkNameModel.name,
+                                    dataContentDescription
+                                )
+
+                            val stateLevel = signalIcon.level
+                            val drawable = SignalDrawable(context)
+                            drawable.setLevel(stateLevel)
+                            val loadedIcon = Icon.Loaded(drawable, null)
+
+                            InternetTileModel.Active(
+                                secondaryTitle = secondary,
+                                icon = loadedIcon,
+                                stateDescription = ContentDescription.Loaded(secondary.toString()),
+                                contentDescription = ContentDescription.Loaded(internetLabel),
+                            )
+                        }
+                        is SignalIconModel.Satellite -> {
+                            val secondary =
+                                signalIcon.icon.contentDescription.loadContentDescription(context)
+                            InternetTileModel.Active(
+                                secondaryTitle = secondary,
+                                iconId = signalIcon.icon.res,
+                                stateDescription = ContentDescription.Loaded(secondary),
+                                contentDescription = ContentDescription.Loaded(internetLabel),
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+    private fun mobileDataContentConcat(
+        networkName: String?,
+        dataContentDescription: CharSequence?
+    ): CharSequence {
+        if (dataContentDescription == null) {
+            return networkName ?: ""
+        }
+        if (networkName == null) {
+            return Html.fromHtml(dataContentDescription.toString(), 0)
+        }
+
+        return Html.fromHtml(
+            context.getString(
+                R.string.mobile_carrier_text_format,
+                networkName,
+                dataContentDescription
+            ),
+            0
+        )
+    }
+
+    private fun loadString(@StringRes resId: Int): CharSequence? =
+        if (resId != 0) {
+            context.getString(resId)
+        } else {
+            null
+        }
+
+    private val ethernetIconFlow: Flow<InternetTileModel> =
+        ethernetInteractor.icon.flatMapLatest {
+            if (it == null) {
+                notConnectedFlow
+            } else {
+                val secondary = it.contentDescription
+                flowOf(
+                    InternetTileModel.Active(
+                        secondaryLabel = secondary?.toText(),
+                        iconId = it.res,
+                        stateDescription = null,
+                        contentDescription = secondary,
+                    )
+                )
+            }
+        }
+
+    private val notConnectedFlow: StateFlow<InternetTileModel> =
+        combine(
+                wifiInteractor.areNetworksAvailable,
+                airplaneModeRepository.isAirplaneMode,
+            ) { networksAvailable, isAirplaneMode ->
+                when {
+                    isAirplaneMode -> {
+                        val secondary = context.getString(R.string.status_bar_airplane)
+                        InternetTileModel.Inactive(
+                            secondaryTitle = secondary,
+                            iconId = R.drawable.ic_qs_no_internet_unavailable,
+                            stateDescription = null,
+                            contentDescription = ContentDescription.Loaded(secondary),
+                        )
+                    }
+                    networksAvailable -> {
+                        val secondary =
+                            context.getString(R.string.quick_settings_networks_available)
+                        InternetTileModel.Inactive(
+                            secondaryTitle = secondary,
+                            iconId = R.drawable.ic_qs_no_internet_available,
+                            stateDescription = null,
+                            contentDescription =
+                                ContentDescription.Loaded("$internetLabel,$secondary")
+                        )
+                    }
+                    else -> {
+                        NOT_CONNECTED_NETWORKS_UNAVAILABLE
+                    }
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), NOT_CONNECTED_NETWORKS_UNAVAILABLE)
+
+    /**
+     * Consumable flow describing the correct state for the InternetTile.
+     *
+     * Strict ordering of which repo is sending its data to the internet tile. Swaps between each of
+     * the interim providers (wifi, mobile, ethernet, or not-connected).
+     */
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<InternetTileModel> =
+        connectivityRepository.defaultConnections.flatMapLatest {
+            when {
+                it.ethernet.isDefault -> ethernetIconFlow
+                it.mobile.isDefault || it.carrierMerged.isDefault -> mobileIconFlow
+                it.wifi.isDefault -> wifiIconFlow
+                else -> notConnectedFlow
+            }
+        }
+
+    override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+
+    private companion object {
+        val NOT_CONNECTED_NETWORKS_UNAVAILABLE =
+            InternetTileModel.Inactive(
+                secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable),
+                iconId = R.drawable.ic_qs_no_internet_unavailable,
+                stateDescription = null,
+                contentDescription =
+                    ContentDescription.Resource(R.string.quick_settings_networks_unavailable),
+            )
+
+        fun removeDoubleQuotes(string: String?): String? {
+            if (string == null) return null
+            return if (string.firstOrNull() == '"' && string.lastOrNull() == '"') {
+                string.substring(1, string.length - 1)
+            } else string
+        }
+
+        fun ContentDescription.toText(): Text =
+            when (this) {
+                is ContentDescription.Loaded -> Text.Loaded(this.description)
+                is ContentDescription.Resource -> Text.Resource(this.res)
+            }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
new file mode 100644
index 0000000..2620cd5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.dialog.InternetDialogManager
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.connectivity.AccessPointController
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
+
+/** Handles internet tile clicks. */
+class InternetTileUserActionInteractor
+@Inject
+constructor(
+    @Main private val mainContext: CoroutineContext,
+    private val internetDialogManager: InternetDialogManager,
+    private val accessPointController: AccessPointController,
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+) : QSTileUserActionInteractor<InternetTileModel> {
+
+    override suspend fun handleInput(input: QSTileInput<InternetTileModel>): Unit =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    withContext(mainContext) {
+                        internetDialogManager.create(
+                            aboveStatusBar = true,
+                            accessPointController.canConfigMobileData(),
+                            accessPointController.canConfigWifi(),
+                            action.view,
+                        )
+                    }
+                }
+                is QSTileUserAction.LongClick -> {
+                    qsTileIntentUserActionHandler.handle(
+                        action.view,
+                        Intent(Settings.ACTION_WIFI_SETTINGS)
+                    )
+                }
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt
new file mode 100644
index 0000000..ece90461
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet.domain.model
+
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+
+/** Model describing the state that the QS Internet tile should be in. */
+sealed interface InternetTileModel {
+    val secondaryTitle: CharSequence?
+    val secondaryLabel: Text?
+    val iconId: Int?
+    val icon: Icon?
+    val stateDescription: ContentDescription?
+    val contentDescription: ContentDescription?
+
+    data class Active(
+        override val secondaryTitle: CharSequence? = null,
+        override val secondaryLabel: Text? = null,
+        override val iconId: Int? = null,
+        override val icon: Icon? = null,
+        override val stateDescription: ContentDescription? = null,
+        override val contentDescription: ContentDescription? = null,
+    ) : InternetTileModel
+
+    data class Inactive(
+        override val secondaryTitle: CharSequence? = null,
+        override val secondaryLabel: Text? = null,
+        override val iconId: Int? = null,
+        override val icon: Icon? = null,
+        override val stateDescription: ContentDescription? = null,
+        override val contentDescription: ContentDescription? = null,
+    ) : InternetTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt
new file mode 100644
index 0000000..736e1a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.rotation.domain.interactor
+
+import android.Manifest
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.os.UserHandle
+import com.android.systemui.camera.data.repository.CameraAutoRotateRepository
+import com.android.systemui.camera.data.repository.CameraSensorPrivacyRepository
+import com.android.systemui.dagger.qualifiers.Main
+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.rotation.domain.model.RotationLockTileModel
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.RotationLockController
+import com.android.systemui.util.kotlin.isBatteryPowerSaveEnabled
+import com.android.systemui.util.kotlin.isRotationLockEnabled
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
+
+/** Observes rotation lock state changes providing the [RotationLockTileModel]. */
+class RotationLockTileDataInteractor
+@Inject
+constructor(
+    private val rotationLockController: RotationLockController,
+    private val batteryController: BatteryController,
+    private val cameraAutoRotateRepository: CameraAutoRotateRepository,
+    private val cameraSensorPrivacyRepository: CameraSensorPrivacyRepository,
+    private val packageManager: PackageManager,
+    @Main private val resources: Resources,
+) : QSTileDataInteractor<RotationLockTileModel> {
+
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<RotationLockTileModel> =
+        combine(
+            rotationLockController.isRotationLockEnabled(),
+            cameraSensorPrivacyRepository.isEnabled(user),
+            batteryController.isBatteryPowerSaveEnabled(),
+            cameraAutoRotateRepository.isCameraAutoRotateSettingEnabled(user)
+        ) {
+            isRotationLockEnabled,
+            isCamPrivacySensorEnabled,
+            isBatteryPowerSaveEnabled,
+            isCameraAutoRotateEnabled,
+            ->
+            RotationLockTileModel(
+                isRotationLockEnabled,
+                isCameraRotationEnabled(
+                    isBatteryPowerSaveEnabled,
+                    isCamPrivacySensorEnabled,
+                    isCameraAutoRotateEnabled
+                ),
+            )
+        }
+
+    override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+
+    private fun hasSufficientPermission(): Boolean {
+        val rotationPackage: String = packageManager.rotationResolverPackageName
+        return rotationPackage != null &&
+            packageManager.checkPermission(Manifest.permission.CAMERA, rotationPackage) ==
+                PackageManager.PERMISSION_GRANTED
+    }
+
+    private fun isCameraRotationEnabled(
+        isBatteryPowerSaverModeOn: Boolean,
+        isCameraSensorPrivacyEnabled: Boolean,
+        isCameraAutoRotateEnabled: Boolean
+    ): Boolean =
+        resources.getBoolean(com.android.internal.R.bool.config_allowRotationResolver) &&
+            !isBatteryPowerSaverModeOn &&
+            !isCameraSensorPrivacyEnabled &&
+            hasSufficientPermission() &&
+            isCameraAutoRotateEnabled
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractor.kt
new file mode 100644
index 0000000..8530926
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractor.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.rotation.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.policy.RotationLockController
+import javax.inject.Inject
+
+/** Handles rotation lock tile clicks. */
+class RotationLockTileUserActionInteractor
+@Inject
+constructor(
+    private val controller: RotationLockController,
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+) : QSTileUserActionInteractor<RotationLockTileModel> {
+
+    override suspend fun handleInput(input: QSTileInput<RotationLockTileModel>) {
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    controller.setRotationLocked(!data.isRotationLocked, CALLER)
+                }
+                is QSTileUserAction.LongClick -> {
+                    qsTileIntentUserActionHandler.handle(
+                        action.view,
+                        Intent(Settings.ACTION_AUTO_ROTATE_SETTINGS)
+                    )
+                }
+            }
+        }
+    }
+
+    companion object {
+        private const val CALLER = "QSTileUserActionInteractor#handleInput"
+    }
+}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/model/RotationLockTileModel.kt
similarity index 74%
copy from packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
copy to packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/model/RotationLockTileModel.kt
index 8ad0a08..32e6cb8c 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/model/RotationLockTileModel.kt
@@ -14,8 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel.component.mediaoutput
+package com.android.systemui.qs.tiles.impl.rotation.domain.model
 
-import dagger.Module
-
-@Module interface MediaOutputModule
+/** Model for rotation lock tile */
+class RotationLockTileModel(
+    val isRotationLocked: Boolean,
+    val isCameraRotationEnabled: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
new file mode 100644
index 0000000..070cdef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.rotation.ui.mapper
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.DevicePostureController
+import javax.inject.Inject
+
+/** Maps [RotationLockTileModel] to [QSTileState]. */
+class RotationLockTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Resources.Theme,
+    private val devicePostureController: DevicePostureController
+) : QSTileDataToStateMapper<RotationLockTileModel> {
+    override fun map(config: QSTileConfig, data: RotationLockTileModel): QSTileState =
+        QSTileState.build(resources, theme, config.uiConfig) {
+            this.label = resources.getString(R.string.quick_settings_rotation_unlocked_label)
+            this.contentDescription =
+                resources.getString(R.string.accessibility_quick_settings_rotation)
+
+            if (data.isRotationLocked) {
+                activationState = QSTileState.ActivationState.INACTIVE
+                this.secondaryLabel = EMPTY_SECONDARY_STRING
+                this.icon = {
+                    Icon.Loaded(
+                        resources.getDrawable(R.drawable.qs_auto_rotate_icon_off, theme),
+                        contentDescription = null
+                    )
+                }
+            } else {
+                activationState = QSTileState.ActivationState.ACTIVE
+                this.secondaryLabel =
+                    if (data.isCameraRotationEnabled) {
+                        resources.getString(R.string.rotation_lock_camera_rotation_on)
+                    } else {
+                        EMPTY_SECONDARY_STRING
+                    }
+                this.icon = {
+                    Icon.Loaded(
+                        resources.getDrawable(R.drawable.qs_auto_rotate_icon_on, theme),
+                        contentDescription = null
+                    )
+                }
+            }
+            if (isDeviceFoldable()) {
+                this.secondaryLabel = getSecondaryLabelWithPosture(this.activationState)
+            }
+            this.stateDescription = this.secondaryLabel
+            this.sideViewIcon = QSTileState.SideViewIcon.None
+            supportedActions =
+                setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+        }
+
+    private fun isDeviceFoldable(): Boolean {
+        val intArray = resources.getIntArray(com.android.internal.R.array.config_foldedDeviceStates)
+        return intArray.isNotEmpty()
+    }
+
+    private fun getSecondaryLabelWithPosture(activationState: QSTileState.ActivationState): String {
+        val stateNames = resources.getStringArray(R.array.tile_states_rotation)
+        val stateName =
+            stateNames[
+                if (activationState == QSTileState.ActivationState.ACTIVE) ON_INDEX else OFF_INDEX]
+        val posture =
+            if (
+                devicePostureController.devicePosture ==
+                    DevicePostureController.DEVICE_POSTURE_CLOSED
+            )
+                resources.getString(R.string.quick_settings_rotation_posture_folded)
+            else resources.getString(R.string.quick_settings_rotation_posture_unfolded)
+
+        return resources.getString(
+            R.string.rotation_tile_with_posture_secondary_label_template,
+            stateName,
+            posture
+        )
+    }
+
+    private companion object {
+        const val EMPTY_SECONDARY_STRING = ""
+        const val OFF_INDEX = 1
+        const val ON_INDEX = 2
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index 17454a9..34f66b8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -17,14 +17,16 @@
 package com.android.systemui.qs.ui.viewmodel
 
 import androidx.lifecycle.LifecycleOwner
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.qs.FooterActionsController
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
-import com.android.systemui.scene.shared.model.Direction
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.shared.model.UserAction
-import com.android.systemui.scene.shared.model.UserActionResult
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import java.util.concurrent.atomic.AtomicBoolean
@@ -45,13 +47,11 @@
     val destinationScenes =
         qsSceneAdapter.isCustomizing.map { customizing ->
             if (customizing) {
-                mapOf<UserAction, UserActionResult>(
-                    UserAction.Back to UserActionResult(SceneKey.QuickSettings)
-                )
+                mapOf<UserAction, UserActionResult>(Back to UserActionResult(Scenes.QuickSettings))
             } else {
                 mapOf(
-                    UserAction.Back to UserActionResult(SceneKey.Shade),
-                    UserAction.Swipe(Direction.UP) to UserActionResult(SceneKey.Shade),
+                    Back to UserActionResult(Scenes.Shade),
+                    Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade),
                 )
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 000f3c09d..d0ff338 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -33,6 +33,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
@@ -111,6 +112,7 @@
 import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
 import com.android.systemui.statusbar.policy.CallbackController;
 import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.sysui.ShellInterface;
 
 import dagger.Lazy;
@@ -221,7 +223,7 @@
                         // If scene framework is enabled, set the scene container window to
                         // visible and let the touch "slip" into that window.
                         if (mSceneContainerFlags.isEnabled()) {
-                            mSceneInteractor.get().setVisible(true, "swipe down on launcher");
+                            mSceneInteractor.get().onRemoteUserInteractionStarted("launcher swipe");
                         } else {
                             mShadeViewControllerLazy.get().startInputFocusTransfer();
                         }
@@ -673,9 +675,13 @@
             }
 
             @Override
-            public void enterStageSplitFromRunningApp(boolean leftOrTop) {
+            public void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) {
                 if (mOverviewProxy != null) {
                     try {
+                        if (DesktopModeStatus.isEnabled() && (sysUiState.getFlags()
+                                & SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0) {
+                            return;
+                        }
                         mOverviewProxy.enterStageSplitFromRunningApp(leftOrTop);
                     } catch (RemoteException e) {
                         Log.w(TAG_OPS, "Unable to enter stage split from the current running app");
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingConfig.kt
similarity index 81%
copy from packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
copy to packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingConfig.kt
index 8ad0a08..bb3b654 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingConfig.kt
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel.component.mediaoutput
+package com.android.systemui.recordissue
 
-import dagger.Module
-
-@Module interface MediaOutputModule
+data class IssueRecordingConfig(val screenRecord: Boolean, val winscopeTracing: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index f487258..8a84496 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -19,8 +19,13 @@
 import android.app.NotificationManager
 import android.content.Context
 import android.content.Intent
+import android.content.pm.LauncherApps
 import android.content.res.Resources
+import android.net.Uri
 import android.os.Handler
+import android.os.UserHandle
+import android.util.Log
+import androidx.core.content.FileProvider
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.dagger.qualifiers.LongRunning
 import com.android.systemui.dagger.qualifiers.Main
@@ -30,7 +35,14 @@
 import com.android.systemui.screenrecord.RecordingServiceStrings
 import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil
+import com.android.traceur.FileSender
+import com.android.traceur.TraceUtils
+import java.io.File
+import java.io.FileOutputStream
+import java.nio.file.Files
 import java.util.concurrent.Executor
+import java.util.zip.ZipEntry
+import java.util.zip.ZipOutputStream
 import javax.inject.Inject
 
 class IssueRecordingService
@@ -60,9 +72,122 @@
 
     override fun provideRecordingServiceStrings(): RecordingServiceStrings = IrsStrings(resources)
 
+    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+        when (intent?.action) {
+            ACTION_START -> {
+                TraceUtils.traceStart(
+                    contentResolver,
+                    DEFAULT_TRACE_TAGS,
+                    DEFAULT_BUFFER_SIZE,
+                    DEFAULT_IS_INCLUDING_WINSCOPE,
+                    DEFAULT_IS_INCLUDING_APP_TRACE,
+                    DEFAULT_IS_LONG_TRACE,
+                    DEFAULT_ATTACH_TO_BUGREPORT,
+                    DEFAULT_MAX_TRACE_SIZE,
+                    DEFAULT_MAX_TRACE_DURATION_IN_MINUTES
+                )
+                if (!intent.getBooleanExtra(EXTRA_SCREEN_RECORD, false)) {
+                    // If we don't want to record the screen, the ACTION_SHOW_START_NOTIF action
+                    // will circumvent the RecordingService's screen recording start code.
+                    return super.onStartCommand(Intent(ACTION_SHOW_START_NOTIF), flags, startId)
+                }
+            }
+            ACTION_STOP,
+            ACTION_STOP_NOTIF -> {
+                // ViewCapture needs to save it's data before it is disabled, or else the data will
+                // be lost. This is expected to change in the near future, and when that happens
+                // this line should be removed.
+                getSystemService(LauncherApps::class.java)?.saveViewCaptureData()
+                TraceUtils.traceStop(contentResolver)
+            }
+            ACTION_SHARE -> {
+                shareRecording(intent)
+
+                // Unlike all other actions, action_share has different behavior for the screen
+                // recording qs tile than it does for the record issue qs tile. Return sticky to
+                // avoid running any of the base class' code for this action.
+                return START_STICKY
+            }
+            else -> {}
+        }
+        return super.onStartCommand(intent, flags, startId)
+    }
+
+    private fun shareRecording(intent: Intent) {
+        val sharableUri: Uri =
+            zipAndPackageRecordings(
+                TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).get(),
+                intent.getStringExtra(EXTRA_PATH)
+            )
+                ?: return
+        val sendIntent =
+            FileSender.buildSendIntent(this, listOf(sharableUri))
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+        if (mNotificationId != NOTIF_BASE_ID) {
+            mNotificationManager.cancelAsUser(
+                null,
+                mNotificationId,
+                UserHandle(mUserContextTracker.userContext.userId)
+            )
+        }
+
+        // TODO: Debug why the notification shade isn't closing upon starting the BetterBug activity
+        mKeyguardDismissUtil.executeWhenUnlocked(
+            {
+                startActivity(sendIntent)
+                false
+            },
+            false,
+            false
+        )
+    }
+
+    private fun zipAndPackageRecordings(traceFiles: List<File>, screenRecordingUri: String?): Uri? {
+        try {
+            externalCacheDir?.mkdirs()
+            val outZip: File = File.createTempFile(TEMP_FILE_PREFIX, ZIP_SUFFIX, externalCacheDir)
+            ZipOutputStream(FileOutputStream(outZip)).use { os ->
+                traceFiles.forEach { file ->
+                    os.putNextEntry(ZipEntry(file.name))
+                    Files.copy(file.toPath(), os)
+                    os.closeEntry()
+                }
+                if (screenRecordingUri != null) {
+                    contentResolver.openInputStream(Uri.parse(screenRecordingUri))?.use {
+                        os.putNextEntry(ZipEntry(SCREEN_RECORDING_ZIP_LABEL))
+                        it.transferTo(os)
+                        os.closeEntry()
+                    }
+                }
+            }
+            return FileProvider.getUriForFile(this, AUTHORITY, outZip)
+        } catch (e: Exception) {
+            Log.e(TAG, "Failed to zip and package Recordings. Cannot share with BetterBug.", e)
+            return null
+        }
+    }
+
     companion object {
         private const val TAG = "IssueRecordingService"
         private const val CHANNEL_ID = "issue_record"
+        private const val EXTRA_SCREEN_RECORD = "extra_screenRecord"
+        private const val EXTRA_WINSCOPE_TRACING = "extra_winscopeTracing"
+        private const val ZIP_SUFFIX = ".zip"
+        private const val TEMP_FILE_PREFIX = "issue_recording"
+        private const val SCREEN_RECORDING_ZIP_LABEL = "screen-recording.mp4"
+
+        private val DEFAULT_TRACE_TAGS = listOf<String>()
+        private const val DEFAULT_BUFFER_SIZE = 16384
+        private const val DEFAULT_IS_INCLUDING_WINSCOPE = true
+        private const val DEFAULT_IS_LONG_TRACE = false
+        private const val DEFAULT_IS_INCLUDING_APP_TRACE = true
+        private const val DEFAULT_ATTACH_TO_BUGREPORT = true
+        private const val DEFAULT_MAX_TRACE_SIZE = 10240
+        private const val DEFAULT_MAX_TRACE_DURATION_IN_MINUTES = 30
+
+        private val TRACE_FILE_NAME = TraceUtils.getOutputFilename(TraceUtils.RecordingType.TRACE)
+        private const val AUTHORITY = "com.android.systemui.fileprovider"
 
         /**
          * Get an intent to stop the issue recording service.
@@ -71,7 +196,7 @@
          * @return
          */
         fun getStopIntent(context: Context): Intent =
-            Intent(context, RecordingService::class.java)
+            Intent(context, IssueRecordingService::class.java)
                 .setAction(ACTION_STOP)
                 .putExtra(Intent.EXTRA_USER_HANDLE, context.userId)
 
@@ -80,8 +205,15 @@
          *
          * @param context Context from the requesting activity
          */
-        fun getStartIntent(context: Context): Intent =
-            Intent(context, RecordingService::class.java).setAction(ACTION_START)
+        fun getStartIntent(
+            context: Context,
+            screenRecord: Boolean,
+            winscopeTracing: Boolean
+        ): Intent =
+            Intent(context, IssueRecordingService::class.java)
+                .setAction(ACTION_START)
+                .putExtra(EXTRA_SCREEN_RECORD, screenRecord)
+                .putExtra(EXTRA_WINSCOPE_TRACING, winscopeTracing)
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index 80f11f1..ff18a11 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -17,8 +17,6 @@
 package com.android.systemui.recordissue
 
 import android.annotation.SuppressLint
-import android.app.BroadcastOptions
-import android.app.PendingIntent
 import android.content.Context
 import android.content.res.ColorStateList
 import android.graphics.Color
@@ -40,11 +38,9 @@
 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.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate
 import com.android.systemui.qs.tiles.RecordIssueTile
 import com.android.systemui.res.R
-import com.android.systemui.screenrecord.RecordingService
-import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -52,12 +48,12 @@
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import java.util.concurrent.Executor
+import java.util.function.Consumer
 
 class RecordIssueDialogDelegate
 @AssistedInject
 constructor(
     private val factory: SystemUIDialog.Factory,
-    private val userContextProvider: UserContextProvider,
     private val userTracker: UserTracker,
     private val flags: FeatureFlagsClassic,
     @Background private val bgExecutor: Executor,
@@ -65,14 +61,15 @@
     private val devicePolicyResolver: dagger.Lazy<ScreenCaptureDevicePolicyResolver>,
     private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
     private val userFileManager: UserFileManager,
-    @Assisted private val onStarted: Runnable,
+    private val screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate,
+    @Assisted private val onStarted: Consumer<IssueRecordingConfig>,
 ) : SystemUIDialog.Delegate {
 
     /** To inject dependencies and allow for easier testing */
     @AssistedFactory
     interface Factory {
         /** Create a dialog object */
-        fun create(onStarted: Runnable): RecordIssueDialogDelegate
+        fun create(onStarted: Consumer<IssueRecordingConfig>): RecordIssueDialogDelegate
     }
 
     @SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch
@@ -86,10 +83,12 @@
             setIcon(R.drawable.qs_record_issue_icon_off)
             setNegativeButton(R.string.cancel) { _, _ -> dismiss() }
             setPositiveButton(R.string.qs_record_issue_start) { _, _ ->
-                onStarted.run()
-                if (screenRecordSwitch.isChecked) {
-                    requestScreenCapture()
-                }
+                onStarted.accept(
+                    IssueRecordingConfig(
+                        screenRecordSwitch.isChecked,
+                        true /* TODO: Base this on issueType selected */
+                    )
+                )
                 dismiss()
             }
         }
@@ -124,7 +123,7 @@
                         .isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId))
             ) {
                 mainExecutor.execute {
-                    ScreenCaptureDisabledDialog(context).show()
+                    screenCaptureDisabledDialogDelegate.createDialog().show()
                     screenRecordSwitch.isChecked = false
                 }
                 return@execute
@@ -176,13 +175,4 @@
             show()
         }
     }
-
-    private fun requestScreenCapture() =
-        PendingIntent.getForegroundService(
-                userContextProvider.userContext,
-                RecordingService.REQUEST_CODE,
-                IssueRecordingService.getStartIntent(userContextProvider.userContext),
-                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
-            )
-            .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle())
 }
diff --git a/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockNewModule.kt b/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockNewModule.kt
new file mode 100644
index 0000000..eb64dd6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockNewModule.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.rotationlock
+
+import com.android.systemui.camera.CameraRotationModule
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.impl.rotation.domain.interactor.RotationLockTileDataInteractor
+import com.android.systemui.qs.tiles.impl.rotation.domain.interactor.RotationLockTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel
+import com.android.systemui.qs.tiles.impl.rotation.ui.mapper.RotationLockTileMapper
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.res.R
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+@Module(includes = [CameraRotationModule::class])
+interface RotationLockNewModule {
+    companion object {
+        private const val ROTATION_TILE_SPEC = "rotation"
+
+        /** Inject rotation tile config */
+        @Provides
+        @IntoMap
+        @StringKey(ROTATION_TILE_SPEC)
+        fun provideRotationTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+            QSTileConfig(
+                tileSpec = TileSpec.create(ROTATION_TILE_SPEC),
+                uiConfig =
+                    QSTileUIConfig.Resource(
+                        iconRes = R.drawable.qs_auto_rotate_icon_off,
+                        labelRes = R.string.quick_settings_rotation_unlocked_label,
+                    ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+            )
+
+        /** Inject Rotation tile into tileViewModelMap in QSModule */
+        @Provides
+        @IntoMap
+        @StringKey(ROTATION_TILE_SPEC)
+        fun provideRotationTileViewModel(
+            factory: QSTileViewModelFactory.Static<RotationLockTileModel>,
+            mapper: RotationLockTileMapper,
+            stateInteractor: RotationLockTileDataInteractor,
+            userActionInteractor: RotationLockTileUserActionInteractor
+        ): QSTileViewModel =
+            factory.create(
+                TileSpec.create(ROTATION_TILE_SPEC),
+                userActionInteractor,
+                stateInteractor,
+                mapper,
+            )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index 356eb85..afd0746 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.scene.shared.flag.SceneContainerFlagsModule
 import com.android.systemui.scene.shared.model.SceneContainerConfig
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import dagger.Module
 import dagger.Provides
 
@@ -44,11 +44,11 @@
             // last one is top-most.
             sceneKeys =
                 listOf(
-                    SceneKey.Gone,
-                    SceneKey.QuickSettings,
-                    SceneKey.Shade,
+                    Scenes.Gone,
+                    Scenes.QuickSettings,
+                    Scenes.Shade,
                 ),
-            initialSceneKey = SceneKey.Gone,
+            initialSceneKey = Scenes.Gone,
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index c7d3a4a..62b0914 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -17,11 +17,12 @@
 package com.android.systemui.scene
 
 import com.android.systemui.CoreStartable
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlagsModule
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
 import com.android.systemui.scene.domain.startable.SceneContainerStartable
 import com.android.systemui.scene.shared.flag.SceneContainerFlagsModule
 import com.android.systemui.scene.shared.model.SceneContainerConfig
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
@@ -34,6 +35,7 @@
         [
             BouncerSceneModule::class,
             CommunalSceneModule::class,
+            ComposeBouncerFlagsModule::class,
             EmptySceneModule::class,
             GoneSceneModule::class,
             LockscreenSceneModule::class,
@@ -65,14 +67,14 @@
                 // last one is top-most.
                 sceneKeys =
                     listOf(
-                        SceneKey.Gone,
-                        SceneKey.Communal,
-                        SceneKey.Lockscreen,
-                        SceneKey.Bouncer,
-                        SceneKey.QuickSettings,
-                        SceneKey.Shade,
+                        Scenes.Gone,
+                        Scenes.Communal,
+                        Scenes.Lockscreen,
+                        Scenes.Bouncer,
+                        Scenes.QuickSettings,
+                        Scenes.Shade,
                     ),
-                initialSceneKey = SceneKey.Lockscreen,
+                initialSceneKey = Scenes.Lockscreen,
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
index c10e51b..0665c9e 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.scene.shared.flag.SceneContainerFlagsModule
 import com.android.systemui.scene.shared.model.SceneContainerConfig
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import dagger.Module
 import dagger.Provides
 
@@ -44,11 +44,11 @@
             // last one is top-most.
             sceneKeys =
                 listOf(
-                    SceneKey.Gone,
-                    SceneKey.Lockscreen,
-                    SceneKey.Bouncer,
+                    Scenes.Gone,
+                    Scenes.Lockscreen,
+                    Scenes.Bouncer,
                 ),
-            initialSceneKey = SceneKey.Lockscreen,
+            initialSceneKey = Scenes.Lockscreen,
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
index a302194..994b012 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
@@ -18,12 +18,12 @@
 
 package com.android.systemui.scene.data.repository
 
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionKey
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.SceneDataSource
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.shared.model.TransitionKey
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -49,6 +49,13 @@
     private val _isVisible = MutableStateFlow(true)
     val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow()
 
+    /**
+     * Whether there's an ongoing remotely-initiated user interaction.
+     *
+     * For more information see the logic in `SceneInteractor` that mutates this.
+     */
+    val isRemoteUserInteractionOngoing = MutableStateFlow(false)
+
     private val defaultTransitionState = ObservableTransitionState.Idle(config.initialSceneKey)
     private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null)
     val transitionState: StateFlow<ObservableTransitionState> =
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 0add444..75bf131 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -16,14 +16,15 @@
 
 package com.android.systemui.scene.domain.interactor
 
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionKey
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
 import com.android.systemui.scene.data.repository.SceneContainerRepository
 import com.android.systemui.scene.shared.logger.SceneLogger
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.shared.model.TransitionKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.util.kotlin.pairwiseBy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -31,6 +32,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
@@ -121,7 +123,21 @@
             )
 
     /** Whether the scene container is visible. */
-    val isVisible: StateFlow<Boolean> = repository.isVisible
+    val isVisible: StateFlow<Boolean> =
+        combine(
+                repository.isVisible,
+                repository.isRemoteUserInteractionOngoing,
+            ) { isVisible, isRemoteUserInteractionOngoing ->
+                isVisibleInternal(
+                    raw = isVisible,
+                    isRemoteUserInteractionOngoing = isRemoteUserInteractionOngoing,
+                )
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = isVisibleInternal()
+            )
 
     /**
      * Returns the keys of all scenes in the container.
@@ -140,12 +156,13 @@
      * desired scene. Once enough of the transition has occurred, the [currentScene] will become
      * [toScene] (unless the transition is canceled by user action or another call to this method).
      */
+    @JvmOverloads
     fun changeScene(
         toScene: SceneKey,
         loggingReason: String,
         transitionKey: TransitionKey? = null,
     ) {
-        check(toScene != SceneKey.Gone || deviceUnlockedInteractor.isDeviceUnlocked.value) {
+        check(toScene != Scenes.Gone || deviceUnlockedInteractor.isDeviceUnlocked.value) {
             "Cannot change to the Gone scene while the device is locked. Logging reason for scene" +
                 " change was: $loggingReason"
         }
@@ -164,7 +181,14 @@
         repository.changeScene(toScene, transitionKey)
     }
 
-    /** Sets the visibility of the container. */
+    /**
+     * Sets the visibility of the container.
+     *
+     * Please do not call this from outside of the scene framework. If you are trying to force the
+     * visibility to visible or invisible, prefer making changes to the existing caller of this
+     * method or to upstream state used to calculate [isVisible]; for an example of the latter,
+     * please see [onRemoteUserInteractionStarted] and [onUserInteractionFinished].
+     */
     fun setVisible(isVisible: Boolean, loggingReason: String) {
         val wasVisible = repository.isVisible.value
         if (wasVisible == isVisible) {
@@ -180,6 +204,31 @@
     }
 
     /**
+     * Notifies that a remote user interaction has begun.
+     *
+     * This is a user interaction that originates outside of the UI of the scene container and
+     * possibly outside of the System UI process itself.
+     *
+     * As an example, consider the dragging that can happen in the launcher that expands the shade.
+     * This is a user interaction that begins remotely (as it starts in the launcher process) and is
+     * then rerouted by window manager to System UI. While the user interaction definitely continues
+     * within the System UI process and code, it also originates remotely.
+     */
+    fun onRemoteUserInteractionStarted(loggingReason: String) {
+        logger.logRemoteUserInteractionStarted(loggingReason)
+        repository.isRemoteUserInteractionOngoing.value = true
+    }
+
+    /**
+     * Notifies that the current user interaction (internally or remotely started, see
+     * [onRemoteUserInteractionStarted]) has finished.
+     */
+    fun onUserInteractionFinished() {
+        logger.logUserInteractionFinished()
+        repository.isRemoteUserInteractionOngoing.value = false
+    }
+
+    /**
      * Binds the given flow so the system remembers it.
      *
      * Note that you must call is with `null` when the UI is done or risk a memory leak.
@@ -187,4 +236,11 @@
     fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
         repository.setTransitionState(transitionState)
     }
+
+    private fun isVisibleInternal(
+        raw: Boolean = repository.isVisible.value,
+        isRemoteUserInteractionOngoing: Boolean = repository.isRemoteUserInteractionOngoing.value,
+    ): Boolean {
+        return raw || isRemoteUserInteractionOngoing
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
index 1c37908..c736707 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.scene.domain.interactor
 
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -24,8 +25,7 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.NotificationPresenter
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.init.NotificationsController
@@ -77,12 +77,12 @@
                 .map { state ->
                     when (state) {
                         is ObservableTransitionState.Idle ->
-                            state.scene == SceneKey.Shade || state.scene == SceneKey.Lockscreen
+                            state.scene == Scenes.Shade || state.scene == Scenes.Lockscreen
                         is ObservableTransitionState.Transition ->
-                            state.toScene == SceneKey.Shade ||
-                                state.toScene == SceneKey.Lockscreen ||
-                                state.fromScene == SceneKey.Shade ||
-                                state.fromScene == SceneKey.Lockscreen
+                            state.toScene == Scenes.Shade ||
+                                state.toScene == Scenes.Lockscreen ||
+                                state.fromScene == Scenes.Shade ||
+                                state.fromScene == Scenes.Lockscreen
                     }
                 }
                 .distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index b642d38..6df57ed 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -19,6 +19,8 @@
 package com.android.systemui.scene.domain.startable
 
 import android.app.StatusBarManager
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.CoreStartable
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -26,6 +28,7 @@
 import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.classifier.FalsingCollectorActual
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.DisplayId
@@ -34,12 +37,13 @@
 import com.android.systemui.model.SceneContainerPlugin
 import com.android.systemui.model.SysUiState
 import com.android.systemui.model.updateFlags
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.FalsingManager.FalsingBeliefListener
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.scene.shared.logger.SceneLogger
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
 import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
@@ -53,6 +57,7 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -82,6 +87,7 @@
     @DisplayId private val displayId: Int,
     private val sceneLogger: SceneLogger,
     @FalsingCollectorActual private val falsingCollector: FalsingCollector,
+    private val falsingManager: FalsingManager,
     private val powerInteractor: PowerInteractor,
     private val simBouncerInteractor: Lazy<SimBouncerInteractor>,
     private val authenticationInteractor: Lazy<AuthenticationInteractor>,
@@ -98,6 +104,7 @@
             automaticallySwitchScenes()
             hydrateSystemUiState()
             collectFalsingSignals()
+            respondToFalsingDetections()
             hydrateWindowFocus()
             hydrateInteractionState()
         } else {
@@ -134,14 +141,14 @@
                             .mapNotNull { state ->
                                 when (state) {
                                     is ObservableTransitionState.Idle -> {
-                                        if (state.scene != SceneKey.Gone) {
+                                        if (state.scene != Scenes.Gone) {
                                             true to "scene is not Gone"
                                         } else {
                                             false to "scene is Gone"
                                         }
                                     }
                                     is ObservableTransitionState.Transition -> {
-                                        if (state.fromScene == SceneKey.Gone) {
+                                        if (state.fromScene == Scenes.Gone) {
                                             true to "scene transitioning away from Gone"
                                         } else {
                                             null
@@ -174,9 +181,9 @@
         applicationScope.launch {
             // TODO (b/308001302): Move this to a bouncer specific interactor.
             bouncerInteractor.onImeHiddenByUser.collectLatest {
-                if (sceneInteractor.currentScene.value == SceneKey.Bouncer) {
+                if (sceneInteractor.currentScene.value == Scenes.Bouncer) {
                     sceneInteractor.changeScene(
-                        toScene = SceneKey.Lockscreen,
+                        toScene = Scenes.Lockscreen,
                         loggingReason = "IME hidden",
                     )
                 }
@@ -190,13 +197,13 @@
                 when {
                     isAnySimLocked -> {
                         switchToScene(
-                            targetSceneKey = SceneKey.Bouncer,
+                            targetSceneKey = Scenes.Bouncer,
                             loggingReason = "Need to authenticate locked SIM card."
                         )
                     }
                     isUnlocked && canSwipeToEnter == false -> {
                         switchToScene(
-                            targetSceneKey = SceneKey.Gone,
+                            targetSceneKey = Scenes.Gone,
                             loggingReason =
                                 "All SIM cards unlocked and device already" +
                                     " unlocked and lockscreen doesn't require a swipe to dismiss."
@@ -204,7 +211,7 @@
                     }
                     else -> {
                         switchToScene(
-                            targetSceneKey = SceneKey.Lockscreen,
+                            targetSceneKey = Scenes.Lockscreen,
                             loggingReason =
                                 "All SIM cards unlocked and device still locked" +
                                     " or lockscreen still requires a swipe to dismiss."
@@ -225,8 +232,8 @@
                                     transitionState.toScene,
                                 )
                         }
-                    val isOnLockscreen = renderedScenes.contains(SceneKey.Lockscreen)
-                    val isOnBouncer = renderedScenes.contains(SceneKey.Bouncer)
+                    val isOnLockscreen = renderedScenes.contains(Scenes.Lockscreen)
+                    val isOnBouncer = renderedScenes.contains(Scenes.Bouncer)
                     if (!isUnlocked) {
                         return@mapNotNull if (isOnLockscreen || isOnBouncer) {
                             // Already on lockscreen or bouncer, no need to change scenes.
@@ -234,7 +241,7 @@
                         } else {
                             // The device locked while on a scene that's not Lockscreen or Bouncer,
                             // go to Lockscreen.
-                            SceneKey.Lockscreen to
+                            Scenes.Lockscreen to
                                 "device locked in non-Lockscreen and non-Bouncer scene"
                         }
                     }
@@ -244,7 +251,7 @@
                     when {
                         isOnBouncer ->
                             // When the device becomes unlocked in Bouncer, go to Gone.
-                            SceneKey.Gone to "device was unlocked in Bouncer scene"
+                            Scenes.Gone to "device was unlocked in Bouncer scene"
                         isOnLockscreen ->
                             // The lockscreen should be dismissed automatically in 2 scenarios:
                             // 1. When face auth bypass is enabled and authentication happens while
@@ -256,11 +263,11 @@
                             //    authentication attempt.
                             when {
                                 isBypassEnabled ->
-                                    SceneKey.Gone to
+                                    Scenes.Gone to
                                         "device has been unlocked on lockscreen with bypass" +
                                             " enabled"
                                 canSwipeToEnter == false ->
-                                    SceneKey.Gone to
+                                    Scenes.Gone to
                                         "device has been unlocked on lockscreen using an active" +
                                             " authentication mechanism"
                                 else -> null
@@ -281,7 +288,7 @@
             powerInteractor.isAsleep.collect { isAsleep ->
                 if (isAsleep) {
                     switchToScene(
-                        targetSceneKey = SceneKey.Lockscreen,
+                        targetSceneKey = Scenes.Lockscreen,
                         loggingReason = "device is starting to sleep",
                     )
                 } else {
@@ -289,7 +296,7 @@
                     val isUnlocked = deviceEntryInteractor.isUnlocked.value
                     if (isUnlocked && canSwipeToEnter == false) {
                         switchToScene(
-                            targetSceneKey = SceneKey.Gone,
+                            targetSceneKey = Scenes.Gone,
                             loggingReason =
                                 "device is waking up while unlocked without the ability" +
                                     " to swipe up on lockscreen to enter.",
@@ -299,7 +306,7 @@
                             AuthenticationMethodModel.Sim
                     ) {
                         switchToScene(
-                            targetSceneKey = SceneKey.Bouncer,
+                            targetSceneKey = Scenes.Bouncer,
                             loggingReason = "device is starting to wake up with a locked sim"
                         )
                     }
@@ -364,7 +371,7 @@
 
         applicationScope.launch {
             sceneInteractor.currentScene
-                .map { it == SceneKey.Bouncer }
+                .map { it == Scenes.Bouncer }
                 .distinctUntilChanged()
                 .collect { switchedToBouncerScene ->
                     if (switchedToBouncerScene) {
@@ -376,6 +383,18 @@
         }
     }
 
+    /** Switches to the lockscreen when falsing is detected. */
+    private fun respondToFalsingDetections() {
+        applicationScope.launch {
+            conflatedCallbackFlow {
+                    val listener = FalsingBeliefListener { trySend(Unit) }
+                    falsingManager.addFalsingBeliefListener(listener)
+                    awaitClose { falsingManager.removeFalsingBeliefListener(listener) }
+                }
+                .collect { switchToScene(Scenes.Lockscreen, "Falsing detected.") }
+        }
+    }
+
     /** Keeps the focus state of the window view up-to-date. */
     private fun hydrateWindowFocus() {
         applicationScope.launch {
@@ -385,7 +404,7 @@
                 }
                 .distinctUntilChanged()
                 .collect { sceneKey ->
-                    windowController.setNotificationShadeFocusable(sceneKey != SceneKey.Gone)
+                    windowController.setNotificationShadeFocusable(sceneKey != Scenes.Gone)
                 }
         }
     }
@@ -409,9 +428,9 @@
                                     //
                                     // This is done here in order to match the legacy
                                     // implementation. The real reason why is lost to lore and myth.
-                                    SceneKey.Lockscreen -> true
-                                    SceneKey.Bouncer -> false
-                                    SceneKey.Shade -> false
+                                    Scenes.Lockscreen -> true
+                                    Scenes.Bouncer -> false
+                                    Scenes.Shade -> false
                                     else -> null
                                 }
                             }
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 8408c51..1808d98 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
@@ -24,7 +24,6 @@
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.Flags.sceneContainer
-import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FlagToken
 import com.android.systemui.flags.Flags.SCENE_CONTAINER_ENABLED
@@ -47,9 +46,8 @@
                 keyguardBottomAreaRefactor() &&
                 migrateClocksToBlueprint() &&
                 ComposeLockscreen.isEnabled &&
-                MediaInSceneContainerFlag.isEnabled &&
-                // NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer
-                ComposeFacade.isComposeAvailable()
+                MediaInSceneContainerFlag.isEnabled
+    // NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer
 
     /**
      * The main static flag, SCENE_CONTAINER_ENABLED. This is an explicit static flag check that
@@ -74,11 +72,7 @@
 
     /** The full set of requirements for SceneContainer */
     inline fun getAllRequirements(): Sequence<FlagToken> {
-        val composeRequirement =
-            FlagToken("ComposeFacade.isComposeAvailable()", ComposeFacade.isComposeAvailable())
-        return sequenceOf(getMainStaticFlag(), getMainAconfigFlag()) +
-            getSecondaryFlags() +
-            composeRequirement
+        return sequenceOf(getMainStaticFlag(), getMainAconfigFlag()) + getSecondaryFlags()
     }
 
     /** Return all dependencies of this flag in pairs where [Pair.first] depends on [Pair.second] */
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
index d59fcff..f44779a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
@@ -16,10 +16,10 @@
 
 package com.android.systemui.scene.shared.logger
 
+import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.SceneFrameworkLog
-import com.android.systemui.scene.shared.model.SceneKey
 import javax.inject.Inject
 
 class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: LogBuffer) {
@@ -95,6 +95,26 @@
         )
     }
 
+    fun logRemoteUserInteractionStarted(
+        reason: String,
+    ) {
+        logBuffer.log(
+            tag = TAG,
+            level = LogLevel.INFO,
+            messageInitializer = { str1 = reason },
+            messagePrinter = { "remote user interaction started, reason: $str3" },
+        )
+    }
+
+    fun logUserInteractionFinished() {
+        logBuffer.log(
+            tag = TAG,
+            level = LogLevel.INFO,
+            messageInitializer = {},
+            messagePrinter = { "user interaction finished" },
+        )
+    }
+
     companion object {
         private const val TAG = "SceneFramework"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt
deleted file mode 100644
index f704894..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt
+++ /dev/null
@@ -1,54 +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.systemui.scene.shared.model
-
-import kotlinx.coroutines.flow.Flow
-
-/**
- * This is a fork of a class by the same name in the `com.android.compose.animation.scene` package.
- *
- * TODO(b/293899074): remove this fork, once we can compile Compose into System UI.
- */
-sealed class ObservableTransitionState {
-    /** No transition/animation is currently running. */
-    data class Idle(val scene: SceneKey) : ObservableTransitionState()
-
-    /** There is a transition animating between two scenes. */
-    data class Transition(
-        val fromScene: SceneKey,
-        val toScene: SceneKey,
-        val progress: Flow<Float>,
-
-        /**
-         * Whether the transition was originally triggered by user input rather than being
-         * programmatic. If this value is initially true, it will remain true until the transition
-         * fully completes, even if the user input that triggered the transition has ended. Any
-         * sub-transitions launched by this one will inherit this value. For example, if the user
-         * drags a pointer but does not exceed the threshold required to transition to another
-         * 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>,
-    ) : ObservableTransitionState()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
index 05056c1..939d5bc 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
@@ -16,6 +16,9 @@
 
 package com.android.systemui.scene.shared.model
 
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
 import kotlinx.coroutines.flow.StateFlow
 
 /**
@@ -53,39 +56,3 @@
      */
     val destinationScenes: StateFlow<Map<UserAction, UserActionResult>>
 }
-
-/** Enumerates all scene framework supported user actions. */
-sealed interface UserAction {
-
-    /** The user is scrolling, dragging, swiping, or flinging. */
-    data class Swipe(
-        /** The direction of the swipe. */
-        val direction: Direction,
-        /**
-         * The edge from which the swipe originated or `null`, if the swipe didn't start close to an
-         * edge.
-         */
-        val fromEdge: Edge? = null,
-        /** The number of pointers that were used (for example, one or two fingers). */
-        val pointerCount: Int = 1,
-    ) : UserAction
-
-    /** The user has hit the back button or performed the back navigation gesture. */
-    data object Back : UserAction
-}
-
-/** Enumerates all known "cardinal" directions for user actions. */
-enum class Direction {
-    LEFT,
-    UP,
-    RIGHT,
-    DOWN,
-}
-
-/** Enumerates all known edges from which a swipe can start. */
-enum class Edge {
-    LEFT,
-    TOP,
-    RIGHT,
-    BOTTOM,
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
index 8204edc..53cdaaa 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.scene.shared.model
 
+import com.android.compose.animation.scene.SceneKey
+
 /** Models the configuration of the scene container. */
 data class SceneContainerConfig(
 
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt
index f7b45e5..0e078d5 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.scene.shared.model
 
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionKey
 import kotlinx.coroutines.flow.StateFlow
 
 /** Defines interface for classes that provide access to scene state. */
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt
index a50830c..69dce83 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt
@@ -18,6 +18,8 @@
 
 package com.android.systemui.scene.shared.model
 
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionKey
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt
similarity index 77%
rename from packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt
rename to packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt
index 609d2b9..73fcca8 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt
@@ -16,41 +16,37 @@
 
 package com.android.systemui.scene.shared.model
 
+import com.android.compose.animation.scene.SceneKey
+
 /**
  * Keys of all known scenes.
  *
  * PLEASE KEEP THE KEYS SORTED ALPHABETICALLY.
  */
-sealed class SceneKey(
-    private val loggingName: String,
-) {
+object Scenes {
     /**
      * The bouncer is the scene that displays authentication challenges like PIN, password, or
      * pattern.
      */
-    object Bouncer : SceneKey("bouncer")
+    @JvmField val Bouncer = SceneKey("bouncer")
 
     /** The communal scene shows the glanceable hub when device is locked and docked. */
-    object Communal : SceneKey("communal")
+    @JvmField val Communal = SceneKey("communal")
 
     /**
      * "Gone" is not a real scene but rather the absence of scenes when we want to skip showing any
      * content from the scene framework.
      */
-    object Gone : SceneKey("gone")
+    @JvmField val Gone = SceneKey("gone")
 
     /** The lockscreen is the scene that shows when the device is locked. */
-    object Lockscreen : SceneKey("lockscreen")
+    @JvmField val Lockscreen = SceneKey("lockscreen")
 
     /** The quick settings scene shows the quick setting tiles. */
-    object QuickSettings : SceneKey("quick_settings")
+    @JvmField val QuickSettings = SceneKey("quick_settings")
 
     /**
      * The shade is the scene whose primary purpose is to show a scrollable list of notifications.
      */
-    object Shade : SceneKey("shade")
-
-    override fun toString(): String {
-        return loggingName
-    }
+    @JvmField val Shade = SceneKey("shade")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt
index 926878c..b91dd04 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.scene.shared.model
 
+import com.android.compose.animation.scene.TransitionKey
+
 /**
  * Defines all known named transitions.
  *
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/UserActionResult.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/UserActionResult.kt
deleted file mode 100644
index c6ae215..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/UserActionResult.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.shared.model
-
-data class UserActionResult(
-
-    /** The scene we should be transitioning due to the [UserAction]. */
-    val toScene: SceneKey,
-
-    /**
-     * The key of the transition that should be used, if a specific one should be used.
-     *
-     * If `null`, the transition used will be the corresponding transition from the collection
-     * passed into the UI layer.
-     */
-    val transitionKey: TransitionKey? = null,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index 45b6f65..7c31ca2 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -16,31 +16,43 @@
 
 package com.android.systemui.scene.ui.view
 
-import android.view.Gravity
+import android.content.Context
+import android.graphics.Point
 import android.view.View
 import android.view.ViewGroup
 import android.view.WindowInsets
-import android.widget.FrameLayout
 import androidx.activity.OnBackPressedDispatcher
 import androidx.activity.OnBackPressedDispatcherOwner
 import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
 import androidx.core.view.isVisible
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.compose.ComposeFacade
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.theme.PlatformTheme
+import com.android.internal.policy.ScreenDecorationsUtils
+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.ScreenDecorProvider
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.scene.shared.model.Scene
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.scene.ui.composable.SceneContainer
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
 import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
-import java.time.Instant
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
 object SceneWindowRootViewBinder {
@@ -86,7 +98,7 @@
                     )
 
                     view.addView(
-                        ComposeFacade.createSceneContainerView(
+                        createSceneContainerView(
                                 scope = this,
                                 context = view.context,
                                 viewModel = viewModel,
@@ -98,7 +110,7 @@
                     )
 
                     val legacyView = view.requireViewById<View>(R.id.legacy_window_root)
-                    view.addView(createVisibilityToggleView(legacyView))
+                    legacyView.isVisible = false
 
                     // This moves the SharedNotificationContainer to the WindowRootView just after
                     //  the SceneContainerView. This SharedNotificationContainer should contain NSSL
@@ -124,28 +136,73 @@
         }
     }
 
-    private var clickCount = 0
-    private var lastClick = Instant.now()
-
-    /**
-     * A temporary UI to toggle on/off the visibility of the given [otherView]. It is toggled by
-     * tapping 5 times in quick succession on the device camera (top center).
-     */
-    // TODO(b/291321285): Remove this when the Flexiglass UI is mature enough to turn off legacy
-    //  SysUI altogether.
-    private fun createVisibilityToggleView(otherView: View): View {
-        val toggleView = View(otherView.context)
-        otherView.isVisible = false
-        toggleView.layoutParams = FrameLayout.LayoutParams(200, 200, Gravity.CENTER_HORIZONTAL)
-        toggleView.setOnClickListener {
-            val now = Instant.now()
-            clickCount = if (now.minusSeconds(2) > lastClick) 1 else clickCount + 1
-            if (clickCount == 5) {
-                otherView.isVisible = !otherView.isVisible
-                clickCount = 0
+    private fun createSceneContainerView(
+        scope: CoroutineScope,
+        context: Context,
+        viewModel: SceneContainerViewModel,
+        windowInsets: StateFlow<WindowInsets?>,
+        sceneByKey: Map<SceneKey, Scene>,
+        dataSourceDelegator: SceneDataSourceDelegator,
+    ): View {
+        return ComposeView(context).apply {
+            setContent {
+                PlatformTheme {
+                    ScreenDecorProvider(
+                        displayCutout = displayCutoutFromWindowInsets(scope, context, windowInsets),
+                        screenCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
+                    ) {
+                        SceneContainer(
+                            viewModel = viewModel,
+                            sceneByKey =
+                                sceneByKey.mapValues { (_, scene) -> scene as ComposableScene },
+                            dataSourceDelegator = dataSourceDelegator,
+                        )
+                    }
+                }
             }
-            lastClick = now
         }
-        return toggleView
+    }
+
+    // TODO(b/298525212): remove once Compose exposes window inset bounds.
+    private fun displayCutoutFromWindowInsets(
+        scope: CoroutineScope,
+        context: Context,
+        windowInsets: StateFlow<WindowInsets?>,
+    ): StateFlow<DisplayCutout> =
+        windowInsets
+            .map {
+                val boundingRect = it?.displayCutout?.boundingRectTop
+                val width = boundingRect?.let { boundingRect.right - boundingRect.left } ?: 0
+                val left = boundingRect?.left?.toDp(context) ?: 0.dp
+                val top = boundingRect?.top?.toDp(context) ?: 0.dp
+                val right = boundingRect?.right?.toDp(context) ?: 0.dp
+                val bottom = boundingRect?.bottom?.toDp(context) ?: 0.dp
+                val location =
+                    when {
+                        width <= 0f -> CutoutLocation.NONE
+                        left <= 0.dp -> CutoutLocation.LEFT
+                        right >= getDisplayWidth(context) -> CutoutLocation.RIGHT
+                        else -> CutoutLocation.CENTER
+                    }
+                DisplayCutout(
+                    left,
+                    top,
+                    right,
+                    bottom,
+                    location,
+                )
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), DisplayCutout())
+
+    // TODO(b/298525212): remove once Compose exposes window inset bounds.
+    private fun getDisplayWidth(context: Context): Dp {
+        val point = Point()
+        checkNotNull(context.display).getRealSize(point)
+        return point.x.dp
+    }
+
+    // TODO(b/298525212): remove once Compose exposes window inset bounds.
+    private fun Int.toDp(context: Context): Dp {
+        return (this.toFloat() / context.resources.displayMetrics.density).dp
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
index 4c2c979..ea19020 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
@@ -25,7 +25,7 @@
 import android.view.WindowInsets
 import android.widget.FrameLayout
 import androidx.core.view.updateMargins
-import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.compose.ComposeInitializer
 import com.android.systemui.res.R
 
 /** A view that can serve as the root of the main SysUI window. */
@@ -45,16 +45,16 @@
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
 
-        if (ComposeFacade.isComposeAvailable() && isRoot()) {
-            ComposeFacade.composeInitializer().onAttachedToWindow(this)
+        if (isRoot()) {
+            ComposeInitializer.onAttachedToWindow(this)
         }
     }
 
     override fun onDetachedFromWindow() {
         super.onDetachedFromWindow()
 
-        if (ComposeFacade.isComposeAvailable() && isRoot()) {
-            ComposeFacade.composeInitializer().onDetachedFromWindow(this)
+        if (isRoot()) {
+            ComposeInitializer.onDetachedFromWindow(this)
         }
     }
 
@@ -71,7 +71,6 @@
 
     override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets? {
         val insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars())
-        val displayCutout = rootWindowInsets.displayCutout
         if (fitsSystemWindows) {
             val paddingChanged = insets.top != paddingTop || insets.bottom != paddingBottom
 
@@ -79,23 +78,22 @@
             if (paddingChanged) {
                 setPadding(0, 0, 0, 0)
             }
-
-            val pairInsets: Pair<Int, Int> =
-                layoutInsetsController.getinsets(windowInsets, displayCutout)
-            leftInset = pairInsets.first
-            rightInset = pairInsets.second
-            applyMargins()
         } else {
             val changed =
                 paddingLeft != 0 || paddingRight != 0 || paddingTop != 0 || paddingBottom != 0
             if (changed) {
                 setPadding(0, 0, 0, 0)
             }
-
-            leftInset = 0
-            rightInset = 0
         }
+        leftInset = 0
+        rightInset = 0
 
+        val displayCutout = rootWindowInsets.displayCutout
+        val pairInsets: Pair<Int, Int> =
+            layoutInsetsController.getinsets(windowInsets, displayCutout)
+        leftInset = pairInsets.first
+        rightInset = pairInsets.second
+        applyMargins()
         return windowInsets
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
new file mode 100644
index 0000000..451fd67
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui.viewmodel
+
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+@SysUISingleton
+class GoneSceneViewModel
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    shadeInteractor: ShadeInteractor,
+) {
+    val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+        shadeInteractor.shadeMode
+            .map { shadeMode -> destinationScenes(shadeMode = shadeMode) }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = destinationScenes(shadeMode = shadeInteractor.shadeMode.value)
+            )
+
+    private fun destinationScenes(shadeMode: ShadeMode): Map<UserAction, UserActionResult> {
+        return buildMap {
+            if (shadeMode == ShadeMode.Single) {
+                this[
+                    Swipe(
+                        pointerCount = 2,
+                        fromSource = Edge.Top,
+                        direction = SwipeDirection.Down,
+                    )] = UserActionResult(Scenes.QuickSettings)
+            }
+
+            this[Swipe(direction = SwipeDirection.Down)] = UserActionResult(Scenes.Shade)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index 4cd3baa..231b284 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -17,13 +17,14 @@
 package com.android.systemui.scene.ui.viewmodel
 
 import android.view.MotionEvent
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.classifier.Classifier
 import com.android.systemui.classifier.domain.interactor.FalsingInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
@@ -68,6 +69,12 @@
     fun onMotionEvent(event: MotionEvent) {
         powerInteractor.onUserTouch()
         falsingInteractor.onTouchEvent(event)
+        if (
+            event.actionMasked == MotionEvent.ACTION_UP ||
+                event.actionMasked == MotionEvent.ACTION_CANCEL
+        ) {
+            sceneInteractor.onUserInteractionFinished()
+        }
     }
 
     /**
@@ -90,10 +97,10 @@
     fun canChangeScene(toScene: SceneKey): Boolean {
         val interactionTypeOrNull =
             when (toScene) {
-                SceneKey.Bouncer -> Classifier.BOUNCER_UNLOCK
-                SceneKey.Gone -> Classifier.UNLOCK
-                SceneKey.Shade -> Classifier.NOTIFICATION_DRAG_DOWN
-                SceneKey.QuickSettings -> Classifier.QUICK_SETTINGS
+                Scenes.Bouncer -> Classifier.BOUNCER_UNLOCK
+                Scenes.Gone -> Classifier.UNLOCK
+                Scenes.Shade -> Classifier.NOTIFICATION_DRAG_DOWN
+                Scenes.QuickSettings -> Classifier.QUICK_SETTINGS
                 else -> null
             }
 
@@ -103,7 +110,7 @@
             val isFalseTouch = falsingInteractor.isFalseTouch(interactionType)
 
             // Only enforce falsing if moving from the lockscreen scene to a new scene.
-            val fromLockscreenScene = currentScene.value == SceneKey.Lockscreen
+            val fromLockscreenScene = currentScene.value == Scenes.Lockscreen
 
             !fromLockscreenScene || !isFalseTouch
         }
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index a4ba2a2..8fe84c9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -42,11 +42,9 @@
 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.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate;
 import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.CallbackController;
 
 import dagger.Lazy;
@@ -71,18 +69,19 @@
     private CountDownTimer mCountDownTimer = null;
     private final Executor mMainExecutor;
     private final BroadcastDispatcher mBroadcastDispatcher;
-    private final Context mContext;
     private final FeatureFlags mFlags;
-    private final UserContextProvider mUserContextProvider;
     private final UserTracker mUserTracker;
     private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
-    private final SystemUIDialog.Factory mDialogFactory;
+    private final ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate;
+    private final ScreenRecordDialogDelegate.Factory mScreenRecordDialogFactory;
+    private final ScreenRecordPermissionDialogDelegate.Factory
+            mScreenRecordPermissionDialogDelegateFactory;
 
     protected static final String INTENT_UPDATE_STATE =
             "com.android.systemui.screenrecord.UPDATE_STATE";
     protected static final String EXTRA_STATE = "extra_state";
 
-    private CopyOnWriteArrayList<RecordingStateChangeCallback> mListeners =
+    private final CopyOnWriteArrayList<RecordingStateChangeCallback> mListeners =
             new CopyOnWriteArrayList<>();
 
     private final Lazy<ScreenCaptureDevicePolicyResolver> mDevicePolicyResolver;
@@ -115,24 +114,26 @@
      * Create a new RecordingController
      */
     @Inject
-    public RecordingController(@Main Executor mainExecutor,
+    public RecordingController(
+            @Main Executor mainExecutor,
             BroadcastDispatcher broadcastDispatcher,
-            Context context,
             FeatureFlags flags,
-            UserContextProvider userContextProvider,
             Lazy<ScreenCaptureDevicePolicyResolver> devicePolicyResolver,
             UserTracker userTracker,
             MediaProjectionMetricsLogger mediaProjectionMetricsLogger,
-            SystemUIDialog.Factory dialogFactory) {
+            ScreenCaptureDisabledDialogDelegate screenCaptureDisabledDialogDelegate,
+            ScreenRecordDialogDelegate.Factory screenRecordDialogFactory,
+            ScreenRecordPermissionDialogDelegate.Factory
+                    screenRecordPermissionDialogDelegateFactory) {
         mMainExecutor = mainExecutor;
-        mContext = context;
         mFlags = flags;
         mDevicePolicyResolver = devicePolicyResolver;
         mBroadcastDispatcher = broadcastDispatcher;
-        mUserContextProvider = userContextProvider;
         mUserTracker = userTracker;
         mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
-        mDialogFactory = dialogFactory;
+        mScreenCaptureDisabledDialogDelegate = screenCaptureDisabledDialogDelegate;
+        mScreenRecordDialogFactory = screenRecordDialogFactory;
+        mScreenRecordPermissionDialogDelegateFactory = screenRecordPermissionDialogDelegateFactory;
 
         BroadcastOptions options = BroadcastOptions.makeBasic();
         options.setInteractive(true);
@@ -163,27 +164,18 @@
         if (mFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)
                 && mDevicePolicyResolver.get()
                         .isScreenCaptureCompletelyDisabled(getHostUserHandle())) {
-            return new ScreenCaptureDisabledDialog(mContext);
+            return mScreenCaptureDisabledDialogDelegate.createDialog();
         }
 
         mMediaProjectionMetricsLogger.notifyProjectionInitiated(
                 getHostUid(), SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
 
-        return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
-                ? mDialogFactory.create(new ScreenRecordPermissionDialogDelegate(
-                getHostUserHandle(),
-                getHostUid(),
-                /* controller= */ this,
-                activityStarter,
-                mUserContextProvider,
-                onStartRecordingClicked,
-                mMediaProjectionMetricsLogger,
-                mDialogFactory))
-                : new ScreenRecordDialog(
-                        context,
-                        /* controller= */ this,
-                        mUserContextProvider,
-                        onStartRecordingClicked);
+        return (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
+                ? mScreenRecordPermissionDialogDelegateFactory
+                    .create(this, getHostUserHandle(), getHostUid(), onStartRecordingClicked)
+                : mScreenRecordDialogFactory
+                    .create(this, onStartRecordingClicked))
+                .createDialog();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index b355d2d..ac94f39 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -60,25 +60,27 @@
     public static final int REQUEST_CODE = 2;
 
     private static final int USER_ID_NOT_SPECIFIED = -1;
-    private static final int NOTIF_BASE_ID = 4273;
+    protected static final int NOTIF_BASE_ID = 4273;
     private static final String TAG = "RecordingService";
     private static final String CHANNEL_ID = "screen_record";
     private static final String GROUP_KEY = "screen_record_saved";
     private static final String EXTRA_RESULT_CODE = "extra_resultCode";
-    private static final String EXTRA_PATH = "extra_path";
+    protected static final String EXTRA_PATH = "extra_path";
     private static final String EXTRA_AUDIO_SOURCE = "extra_useAudio";
     private static final String EXTRA_SHOW_TAPS = "extra_showTaps";
     private static final String EXTRA_CAPTURE_TARGET = "extra_captureTarget";
 
     protected static final String ACTION_START = "com.android.systemui.screenrecord.START";
+    protected static final String ACTION_SHOW_START_NOTIF =
+            "com.android.systemui.screenrecord.START_NOTIF";
     protected static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP";
-    private static final String ACTION_STOP_NOTIF =
+    protected static final String ACTION_STOP_NOTIF =
             "com.android.systemui.screenrecord.STOP_FROM_NOTIF";
-    private static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE";
+    protected static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE";
     private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
 
     private final RecordingController mController;
-    private final KeyguardDismissUtil mKeyguardDismissUtil;
+    protected final KeyguardDismissUtil mKeyguardDismissUtil;
     private final Handler mMainHandler;
     private ScreenRecordingAudioSource mAudioSource;
     private boolean mShowTaps;
@@ -86,9 +88,9 @@
     private ScreenMediaRecorder mRecorder;
     private final Executor mLongExecutor;
     private final UiEventLogger mUiEventLogger;
-    private final NotificationManager mNotificationManager;
-    private final UserContextProvider mUserContextTracker;
-    private int mNotificationId = NOTIF_BASE_ID;
+    protected final NotificationManager mNotificationManager;
+    protected final UserContextProvider mUserContextTracker;
+    protected int mNotificationId = NOTIF_BASE_ID;
     private RecordingServiceStrings mStrings;
 
     @Inject
@@ -185,7 +187,10 @@
                     return Service.START_NOT_STICKY;
                 }
                 break;
-
+            case ACTION_SHOW_START_NOTIF:
+                createRecordingNotification();
+                mUiEventLogger.log(Events.ScreenRecordEvent.SCREEN_RECORD_START);
+                break;
             case ACTION_STOP_NOTIF:
             case ACTION_STOP:
                 // only difference for actions is the log event
@@ -232,6 +237,7 @@
         super.onCreate();
     }
 
+    @Nullable
     @VisibleForTesting
     protected ScreenMediaRecorder getRecorder() {
         return mRecorder;
@@ -337,8 +343,9 @@
     }
 
     @VisibleForTesting
-    protected Notification createSaveNotification(ScreenMediaRecorder.SavedRecording recording) {
-        Uri uri = recording.getUri();
+    protected Notification createSaveNotification(
+            @Nullable ScreenMediaRecorder.SavedRecording recording) {
+        Uri uri = recording != null ? recording.getUri() : null;
         Intent viewIntent = new Intent(Intent.ACTION_VIEW)
                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION)
                 .setDataAndType(uri, "video/mp4");
@@ -349,7 +356,7 @@
                 PendingIntent.getService(
                         this,
                         REQUEST_CODE,
-                        getShareIntent(this, uri.toString()),
+                        getShareIntent(this, uri != null ? uri.toString() : null),
                         PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
                 .build();
 
@@ -371,9 +378,10 @@
                 .addExtras(extras);
 
         // Add thumbnail if available
-        if (recording.getThumbnail() != null) {
+        Icon thumbnail = recording != null ? recording.getThumbnail() : null;
+        if (thumbnail != null) {
             Notification.BigPictureStyle pictureStyle = new Notification.BigPictureStyle()
-                    .bigPicture(recording.getThumbnail())
+                    .bigPicture(thumbnail)
                     .showBigPictureWhenCollapsed(true);
             builder.setStyle(pictureStyle);
         }
@@ -408,27 +416,29 @@
         }
         Log.d(getTag(), "notifying for user " + userId);
         setTapsVisible(mOriginalShowTaps);
-        if (getRecorder() != null) {
-            try {
+        try {
+            if (getRecorder() != null) {
                 getRecorder().end();
-                saveRecording(userId);
-            } catch (RuntimeException exception) {
+            }
+            saveRecording(userId);
+        } catch (RuntimeException exception) {
+            if (getRecorder() != null) {
                 // RuntimeException could happen if the recording stopped immediately after starting
                 // let's release the recorder and delete all temporary files in this case
                 getRecorder().release();
-                showErrorToast(R.string.screenrecord_start_error);
-                Log.e(getTag(), "stopRecording called, but there was an error when ending"
-                        + "recording");
-                exception.printStackTrace();
-                createErrorNotification();
-            } catch (Throwable throwable) {
+            }
+            showErrorToast(R.string.screenrecord_start_error);
+            Log.e(getTag(), "stopRecording called, but there was an error when ending"
+                    + "recording");
+            exception.printStackTrace();
+            createErrorNotification();
+        } catch (Throwable throwable) {
+            if (getRecorder() != null) {
                 // Something unexpected happen, SystemUI will crash but let's delete
                 // the temporary files anyway
                 getRecorder().release();
-                throw new RuntimeException(throwable);
             }
-        } else {
-            Log.e(getTag(), "stopRecording called, but recorder was null");
+            throw new RuntimeException(throwable);
         }
         updateState(false);
         stopForeground(STOP_FOREGROUND_DETACH);
@@ -443,7 +453,8 @@
         mLongExecutor.execute(() -> {
             try {
                 Log.d(getTag(), "saving recording");
-                Notification notification = createSaveNotification(getRecorder().save());
+                Notification notification = createSaveNotification(
+                        getRecorder() != null ? getRecorder().save() : null);
                 postGroupNotification(currentUser);
                 mNotificationManager.notifyAsUser(null, mNotificationId,  notification,
                         currentUser);
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialogDelegate.java
similarity index 77%
rename from packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
rename to packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialogDelegate.java
index b98093e..9f1447b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialogDelegate.java
@@ -49,52 +49,69 @@
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
 import java.util.Arrays;
 import java.util.List;
 
 /**
  * Dialog to select screen recording options
  */
-public class ScreenRecordDialog extends SystemUIDialog {
+public class ScreenRecordDialogDelegate implements SystemUIDialog.Delegate {
     private static final List<ScreenRecordingAudioSource> MODES = Arrays.asList(INTERNAL, MIC,
             MIC_AND_INTERNAL);
     private static final long DELAY_MS = 3000;
     private static final long INTERVAL_MS = 1000;
 
-    private final RecordingController mController;
+    private final SystemUIDialog.Factory mSystemUIDialogFactory;
     private final UserContextProvider mUserContextProvider;
-    @Nullable
+    private final RecordingController mController;
     private final Runnable mOnStartRecordingClicked;
     private Switch mTapsSwitch;
     private Switch mAudioSwitch;
     private Spinner mOptions;
 
-    public ScreenRecordDialog(Context context,
-                              RecordingController controller,
-                              UserContextProvider userContextProvider,
-                              @Nullable Runnable onStartRecordingClicked) {
-        super(context);
-        mController = controller;
+    @AssistedFactory
+    public interface Factory {
+        ScreenRecordDialogDelegate create(
+                RecordingController recordingController,
+                @Nullable Runnable onStartRecordingClicked
+        );
+    }
+
+    @AssistedInject
+    public ScreenRecordDialogDelegate(
+            SystemUIDialog.Factory systemUIDialogFactory,
+            UserContextProvider userContextProvider,
+            @Assisted RecordingController controller,
+            @Assisted @Nullable Runnable onStartRecordingClicked) {
+        mSystemUIDialogFactory = systemUIDialogFactory;
         mUserContextProvider = userContextProvider;
+        mController = controller;
         mOnStartRecordingClicked = onStartRecordingClicked;
     }
 
     @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
+    public SystemUIDialog createDialog() {
+        return mSystemUIDialogFactory.create(this);
+    }
 
-        Window window = getWindow();
+    @Override
+    public void onCreate(SystemUIDialog dialog, Bundle savedInstanceState) {
+        Window window = dialog.getWindow();
 
         window.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
 
         window.setGravity(Gravity.CENTER);
-        setTitle(R.string.screenrecord_title);
+        dialog.setTitle(R.string.screenrecord_title);
 
-        setContentView(R.layout.screen_record_dialog);
+        dialog.setContentView(R.layout.screen_record_dialog);
 
-        TextView cancelBtn = findViewById(R.id.button_cancel);
-        cancelBtn.setOnClickListener(v -> dismiss());
-        TextView startBtn = findViewById(R.id.button_start);
+        TextView cancelBtn = dialog.findViewById(R.id.button_cancel);
+        cancelBtn.setOnClickListener(v -> dialog.dismiss());
+        TextView startBtn = dialog.findViewById(R.id.button_start);
         startBtn.setOnClickListener(v -> {
             if (mOnStartRecordingClicked != null) {
                 // Note that it is important to run this callback before dismissing, so that the
@@ -104,13 +121,13 @@
 
             // Start full-screen recording
             requestScreenCapture(/* captureTarget= */ null);
-            dismiss();
+            dialog.dismiss();
         });
 
-        mAudioSwitch = findViewById(R.id.screenrecord_audio_switch);
-        mTapsSwitch = findViewById(R.id.screenrecord_taps_switch);
-        mOptions = findViewById(R.id.screen_recording_options);
-        ArrayAdapter a = new ScreenRecordingAdapter(getContext().getApplicationContext(),
+        mAudioSwitch = dialog.findViewById(R.id.screenrecord_audio_switch);
+        mTapsSwitch = dialog.findViewById(R.id.screenrecord_taps_switch);
+        mOptions = dialog.findViewById(R.id.screen_recording_options);
+        ArrayAdapter a = new ScreenRecordingAdapter(dialog.getContext().getApplicationContext(),
                 android.R.layout.simple_spinner_dropdown_item,
                 MODES);
         a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
index 3eb26f4..ba775cd3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
@@ -46,17 +46,20 @@
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.statusbar.phone.SystemUIDialog
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 
 /** Dialog to select screen recording options */
-class ScreenRecordPermissionDialogDelegate(
-    private val hostUserHandle: UserHandle,
-    private val hostUid: Int,
-    private val controller: RecordingController,
+class ScreenRecordPermissionDialogDelegate @AssistedInject constructor(
+    @Assisted private val hostUserHandle: UserHandle,
+    @Assisted private val hostUid: Int,
+    @Assisted private val controller: RecordingController,
     private val activityStarter: ActivityStarter,
     private val userContextProvider: UserContextProvider,
-    private val onStartRecordingClicked: Runnable?,
+    @Assisted private val onStartRecordingClicked: Runnable?,
     mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
-    private val systemUIDialogFactory: SystemUIDialog.Factory
+    private val systemUIDialogFactory: SystemUIDialog.Factory,
 ) :
     BaseMediaProjectionPermissionDialogDelegate<SystemUIDialog>(
         createOptionList(),
@@ -65,8 +68,19 @@
         mediaProjectionMetricsLogger,
         R.drawable.ic_screenrecord,
         R.color.screenrecord_icon_color
-    ),
-    SystemUIDialog.Delegate {
+    ), SystemUIDialog.Delegate {
+
+
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            recordingController: RecordingController,
+            hostUserHandle: UserHandle,
+            hostUid: Int,
+            onStartRecordingClicked: Runnable?
+        ): ScreenRecordPermissionDialogDelegate
+    }
+
     private lateinit var tapsSwitch: Switch
     private lateinit var tapsSwitchContainer: ViewGroup
     private lateinit var tapsView: View
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index fb5339d..1f9853b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.screenshot
 
+import android.app.ActivityOptions
+import android.app.ExitTransitionCoordinator
 import android.content.Context
 import android.content.Intent
 import android.os.Bundle
@@ -23,6 +25,7 @@
 import android.os.RemoteException
 import android.os.UserHandle
 import android.util.Log
+import android.util.Pair
 import android.view.IRemoteAnimationFinishedCallback
 import android.view.IRemoteAnimationRunner
 import android.view.RemoteAnimationAdapter
@@ -64,18 +67,18 @@
      */
     fun launchIntentAsync(
         intent: Intent,
-        options: Bundle?,
+        transition: Pair<ActivityOptions, ExitTransitionCoordinator>?,
         user: UserHandle,
         overrideTransition: Boolean,
     ) {
         applicationScope.launch("$TAG#launchIntentAsync") {
-            launchIntent(intent, options, user, overrideTransition)
+            launchIntent(intent, transition, user, overrideTransition)
         }
     }
 
     suspend fun launchIntent(
         intent: Intent,
-        options: Bundle?,
+        transition: Pair<ActivityOptions, ExitTransitionCoordinator>?,
         user: UserHandle,
         overrideTransition: Boolean,
     ) {
@@ -87,11 +90,14 @@
         } else {
             dismissKeyguard()
         }
+        transition?.second?.startExit()
 
         if (user == myUserHandle()) {
-            withContext(mainDispatcher) { context.startActivity(intent, options) }
+            withContext(mainDispatcher) {
+                context.startActivity(intent, transition?.first?.toBundle())
+            }
         } else {
-            launchCrossProfileIntent(user, intent, options)
+            launchCrossProfileIntent(user, intent, transition?.first?.toBundle())
         }
 
         if (overrideTransition) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
index b42cc98..c4287ca 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
@@ -21,7 +21,9 @@
 import android.annotation.IntRange;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
+import android.content.ContentUris;
 import android.content.ContentValues;
+import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.CompressFormat;
 import android.net.Uri;
@@ -50,6 +52,7 @@
 import java.io.OutputStream;
 import java.time.Duration;
 import java.time.Instant;
+import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.UUID;
@@ -145,32 +148,84 @@
     }
 
     /**
-     * Export the image using the given executor.
+     * Export the image using the given executor with an auto-generated file name based on display
+     * id.
      *
-     * @param executor the thread for execution
-     * @param bitmap the bitmap to export
+     * @param executor  the thread for execution
+     * @param bitmap    the bitmap to export
      * @param displayId the display id the bitmap comes from.
      * @return a listenable future result
      */
     public ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
             UserHandle owner, int displayId) {
-        return export(executor, requestId, bitmap, ZonedDateTime.now(), owner, displayId);
+        ZonedDateTime captureTime = ZonedDateTime.now(ZoneId.systemDefault());
+        return export(executor,
+                new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat,
+                        mQuality, /* publish */ true, owner, mFlags,
+                        createFilename(captureTime, mCompressFormat, displayId)));
+    }
+
+    /**
+     * Export the image using the given executor with a specified file name.
+     *
+     * @param executor the thread for execution
+     * @param bitmap   the bitmap to export
+     * @param format   the compress format of {@code bitmap} e.g. {@link CompressFormat.PNG}
+     * @param fileName a specified name for the exported file. No need to include file extension in
+     *                 file name. The extension will be internally appended based on
+     *                 {@code format}
+     * @return a listenable future result
+     */
+    public ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
+            CompressFormat format, UserHandle owner, String fileName) {
+        return export(executor,
+                new Task(mResolver,
+                        requestId,
+                        bitmap,
+                        ZonedDateTime.now(ZoneId.systemDefault()),
+                        format,
+                        mQuality, /* publish */ true, owner, mFlags,
+                        createSystemFileDisplayName(fileName, format),
+                        true /* allowOverwrite */));
     }
 
     /**
      * Export the image to MediaStore and publish.
      *
      * @param executor the thread for execution
-     * @param bitmap the bitmap to export
-     *
+     * @param bitmap   the bitmap to export
      * @return a listenable future result
      */
     ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
             ZonedDateTime captureTime, UserHandle owner, int displayId) {
+        return export(executor, new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat,
+                mQuality, /* publish */ true, owner, mFlags,
+                createFilename(captureTime, mCompressFormat, displayId)));
+    }
 
-        final Task task = new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat,
-                mQuality, /* publish */ true, owner, mFlags, displayId);
+    /**
+     * Export the image to MediaStore and publish.
+     *
+     * @param executor the thread for execution
+     * @param bitmap   the bitmap to export
+     * @return a listenable future result
+     */
+    ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
+            ZonedDateTime captureTime, UserHandle owner, String fileName) {
+        return export(executor, new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat,
+                mQuality, /* publish */ true, owner, mFlags,
+                createSystemFileDisplayName(fileName, mCompressFormat)));
+    }
 
+    /**
+     * Export the image to MediaStore and publish.
+     *
+     * @param executor the thread for execution
+     * @param task the exporting image {@link Task}.
+     *
+     * @return a listenable future result
+     */
+    private ListenableFuture<Result> export(Executor executor, Task task) {
         return CallbackToFutureAdapter.getFuture(
                 (completer) -> {
                     executor.execute(() -> {
@@ -220,9 +275,25 @@
         private final boolean mPublish;
         private final FeatureFlags mFlags;
 
+        /**
+         * This variable specifies the behavior when a file to be exported has a same name and
+         * format as one of the file on disk. If this is set to true, the new file overwrite the
+         * old file; otherwise, the system adds a number to the end of the newly exported file. For
+         * example, if the file is screenshot.png, the newly exported file's display name will be
+         * screenshot(1).png.
+         */
+        private final boolean mAllowOverwrite;
+
         Task(ContentResolver resolver, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime,
                 CompressFormat format, int quality, boolean publish, UserHandle owner,
-                FeatureFlags flags, int displayId) {
+                FeatureFlags flags, String fileName) {
+            this(resolver, requestId, bitmap, captureTime, format, quality, publish, owner, flags,
+                    fileName, false /* allowOverwrite */);
+        }
+
+        Task(ContentResolver resolver, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime,
+                CompressFormat format, int quality, boolean publish, UserHandle owner,
+                FeatureFlags flags, String fileName, boolean allowOverwrite) {
             mResolver = resolver;
             mRequestId = requestId;
             mBitmap = bitmap;
@@ -230,9 +301,10 @@
             mFormat = format;
             mQuality = quality;
             mOwner = owner;
-            mFileName = createFilename(mCaptureTime, mFormat, displayId);
+            mFileName = fileName;
             mPublish = publish;
             mFlags = flags;
+            mAllowOverwrite = allowOverwrite;
         }
 
         public Result execute() throws ImageExportException, InterruptedException {
@@ -246,7 +318,8 @@
                     start = Instant.now();
                 }
 
-                uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName, mOwner, mFlags);
+                uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName, mOwner, mFlags,
+                        mAllowOverwrite);
                 throwIfInterrupted();
 
                 writeImage(mResolver, mBitmap, mFormat, mQuality, uri);
@@ -290,21 +363,51 @@
     }
 
     private static Uri createEntry(ContentResolver resolver, CompressFormat format,
-            ZonedDateTime time, String fileName, UserHandle owner, FeatureFlags flags)
-            throws ImageExportException {
+            ZonedDateTime time, String fileName, UserHandle owner, FeatureFlags flags,
+            boolean allowOverwrite) throws ImageExportException {
         Trace.beginSection("ImageExporter_createEntry");
         try {
             final ContentValues values = createMetadata(time, format, fileName);
 
             Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
             Uri uriWithUserId = ContentProvider.maybeAddUserId(baseUri, owner.getIdentifier());
+            Uri resultUri = null;
 
-            Uri uri = resolver.insert(uriWithUserId, values);
-            if (uri == null) {
+            if (allowOverwrite) {
+                // Query to check if there is existing file with the same name and format.
+                Cursor cursor = resolver.query(
+                        baseUri,
+                        null,
+                        MediaStore.MediaColumns.DISPLAY_NAME + "=? AND "
+                                + MediaStore.MediaColumns.MIME_TYPE + "=?",
+                        new String[]{fileName, getMimeType(format)},
+                        null /* CancellationSignal */);
+                if (cursor != null) {
+                    if (cursor.moveToFirst()) {
+                        // If there is existing file, update the meta-data of its entry. The Entry's
+                        // corresponding uri is composed of volume base-uri(or with user-id) and
+                        // its row's unique ID.
+                        int idIndex = cursor.getColumnIndex(MediaStore.MediaColumns._ID);
+                        resultUri = ContentUris.withAppendedId(uriWithUserId,
+                                cursor.getLong(idIndex));
+                        resolver.update(resultUri, values, null);
+                        Log.d(TAG, "Updated existing URI: " + resultUri);
+                    }
+                    cursor.close();
+                }
+            }
+
+            if (resultUri == null) {
+                // If file overwriting is disabled or there is no existing file to overwrite, create
+                // and insert a new entry.
+                resultUri = resolver.insert(uriWithUserId, values);
+                Log.d(TAG, "Inserted new URI: " + resultUri);
+            }
+
+            if (resultUri == null) {
                 throw new ImageExportException(RESOLVER_INSERT_RETURNED_NULL);
             }
-            Log.d(TAG, "Inserted new URI: " + uri);
-            return uri;
+            return resultUri;
         } finally {
             Trace.endSection();
         }
@@ -383,6 +486,11 @@
             fileExtension(format));
     }
 
+    @VisibleForTesting
+    static String createSystemFileDisplayName(String originalDisplayName, CompressFormat format) {
+        return originalDisplayName + "." + fileExtension(format);
+    }
+
     static ContentValues createMetadata(ZonedDateTime captureTime, CompressFormat format,
             String fileName) {
         ContentValues values = new ContentValues();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
new file mode 100644
index 0000000..1d9b755
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.animation.Animator
+import android.app.Notification
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Rect
+import android.util.Log
+import android.view.Display
+import android.view.KeyEvent
+import android.view.LayoutInflater
+import android.view.ScrollCaptureResponse
+import android.view.View
+import android.view.ViewTreeObserver
+import android.view.WindowInsets
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
+import androidx.appcompat.content.res.AppCompatResources
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.res.R
+import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER
+
+/**
+ * Legacy implementation of screenshot view methods. Just proxies the calls down into the original
+ * ScreenshotView.
+ */
+class LegacyScreenshotViewProxy(private val context: Context, private val logger: UiEventLogger) :
+    ScreenshotViewProxy {
+    override val view: ScreenshotView =
+        LayoutInflater.from(context).inflate(R.layout.screenshot, null) as ScreenshotView
+    override val screenshotPreview: View
+    override var defaultDisplay: Int = Display.DEFAULT_DISPLAY
+        set(value) {
+            view.setDefaultDisplay(value)
+        }
+    override var flags: FeatureFlags? = null
+        set(value) {
+            view.setFlags(value)
+        }
+    override var packageName: String = ""
+        set(value) {
+            view.setPackageName(value)
+        }
+    override var callbacks: ScreenshotView.ScreenshotViewCallback? = null
+        set(value) {
+            view.setCallbacks(value)
+        }
+    override var screenshot: ScreenshotData? = null
+        set(value) {
+            field = value
+            value?.let {
+                val badgeBg =
+                    AppCompatResources.getDrawable(context, R.drawable.overlay_badge_background)
+                val user = it.userHandle
+                if (badgeBg != null && user != null) {
+                    view.badgeScreenshot(context.packageManager.getUserBadgedIcon(badgeBg, user))
+                }
+                view.setScreenshot(it)
+            }
+        }
+
+    override val isAttachedToWindow
+        get() = view.isAttachedToWindow
+    override val isDismissing
+        get() = view.isDismissing
+    override val isPendingSharedTransition
+        get() = view.isPendingSharedTransition
+
+    init {
+        view.setUiEventLogger(logger)
+        addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
+        setOnKeyListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
+        if (LogConfig.DEBUG_WINDOW) {
+            Log.d(TAG, "adding OnComputeInternalInsetsListener")
+        }
+        view.viewTreeObserver.addOnComputeInternalInsetsListener(view)
+        screenshotPreview = view.screenshotPreview
+    }
+
+    override fun reset() = view.reset()
+    override fun updateInsets(insets: WindowInsets) = view.updateInsets(insets)
+    override fun updateOrientation(insets: WindowInsets) = view.updateOrientation(insets)
+
+    override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator =
+        view.createScreenshotDropInAnimation(screenRect, showFlash)
+
+    override fun addQuickShareChip(quickShareAction: Notification.Action) =
+        view.addQuickShareChip(quickShareAction)
+
+    override fun setChipIntents(imageData: ScreenshotController.SavedImageData) =
+        view.setChipIntents(imageData)
+
+    override fun requestDismissal(event: ScreenshotEvent) {
+        if (DEBUG_DISMISS) {
+            Log.d(TAG, "screenshot dismissal requested")
+        }
+        // If we're already animating out, don't restart the animation
+        if (view.isDismissing) {
+            if (DEBUG_DISMISS) {
+                Log.v(TAG, "Already dismissing, ignoring duplicate command $event")
+            }
+            return
+        }
+        logger.log(event, 0, packageName)
+        view.animateDismissal()
+    }
+
+    override fun showScrollChip(packageName: String, onClick: Runnable) =
+        view.showScrollChip(packageName, onClick)
+
+    override fun hideScrollChip() = view.hideScrollChip()
+
+    override fun prepareScrollingTransition(
+        response: ScrollCaptureResponse,
+        screenBitmap: Bitmap,
+        newScreenshot: Bitmap,
+        screenshotTakenInPortrait: Boolean,
+        onTransitionPrepared: Runnable,
+    ) {
+        view.prepareScrollingTransition(
+            response,
+            screenBitmap,
+            newScreenshot,
+            screenshotTakenInPortrait
+        )
+        view.post { onTransitionPrepared.run() }
+    }
+
+    override fun startLongScreenshotTransition(
+        transitionDestination: Rect,
+        onTransitionEnd: Runnable,
+        longScreenshot: ScrollCaptureController.LongScreenshot
+    ) = view.startLongScreenshotTransition(transitionDestination, onTransitionEnd, longScreenshot)
+
+    override fun restoreNonScrollingUi() = view.restoreNonScrollingUi()
+
+    override fun stopInputListening() = view.stopInputListening()
+
+    override fun requestFocus() {
+        view.requestFocus()
+    }
+
+    override fun announceForAccessibility(string: String) = view.announceForAccessibility(string)
+
+    override fun prepareEntranceAnimation(runnable: Runnable) {
+        view.viewTreeObserver.addOnPreDrawListener(
+            object : ViewTreeObserver.OnPreDrawListener {
+                override fun onPreDraw(): Boolean {
+                    if (LogConfig.DEBUG_WINDOW) {
+                        Log.d(TAG, "onPreDraw: startAnimation")
+                    }
+                    view.viewTreeObserver.removeOnPreDrawListener(this)
+                    runnable.run()
+                    return true
+                }
+            }
+        )
+    }
+
+    private fun addPredictiveBackListener(onDismissRequested: (ScreenshotEvent) -> Unit) {
+        val onBackInvokedCallback = OnBackInvokedCallback {
+            if (LogConfig.DEBUG_INPUT) {
+                Log.d(TAG, "Predictive Back callback dispatched")
+            }
+            onDismissRequested.invoke(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
+        }
+        view.addOnAttachStateChangeListener(
+            object : View.OnAttachStateChangeListener {
+                override fun onViewAttachedToWindow(v: View) {
+                    if (LogConfig.DEBUG_INPUT) {
+                        Log.d(TAG, "Registering Predictive Back callback")
+                    }
+                    view
+                        .findOnBackInvokedDispatcher()
+                        ?.registerOnBackInvokedCallback(
+                            OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+                            onBackInvokedCallback
+                        )
+                }
+
+                override fun onViewDetachedFromWindow(view: View) {
+                    if (LogConfig.DEBUG_INPUT) {
+                        Log.d(TAG, "Unregistering Predictive Back callback")
+                    }
+                    view
+                        .findOnBackInvokedDispatcher()
+                        ?.unregisterOnBackInvokedCallback(onBackInvokedCallback)
+                }
+            }
+        )
+    }
+    private fun setOnKeyListener(onDismissRequested: (ScreenshotEvent) -> Unit) {
+        view.setOnKeyListener(
+            object : View.OnKeyListener {
+                override fun onKey(view: View, keyCode: Int, event: KeyEvent): Boolean {
+                    if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
+                        if (LogConfig.DEBUG_INPUT) {
+                            Log.d(TAG, "onKeyEvent: $keyCode")
+                        }
+                        onDismissRequested.invoke(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
+                        return true
+                    }
+                    return false
+                }
+            }
+        )
+    }
+
+    class Factory : ScreenshotViewProxy.Factory {
+        override fun getProxy(context: Context, logger: UiEventLogger): ScreenshotViewProxy {
+            return LegacyScreenshotViewProxy(context, logger)
+        }
+    }
+
+    companion object {
+        private const val TAG = "LegacyScreenshotViewProxy"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
index 0588fe2..30f5e8b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
@@ -18,6 +18,7 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.app.ActivityOptions;
 import android.app.BroadcastOptions;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -100,7 +101,7 @@
                 BroadcastOptions options = BroadcastOptions.makeBasic();
                 options.setInteractive(true);
                 options.setPendingIntentBackgroundActivityStartMode(
-                        BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
                 intent.send(options.toBundle());
                 finisher.run();
             } catch (PendingIntent.CanceledException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 31086d8..bbf7ed5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -16,40 +16,29 @@
 
 package com.android.systemui.screenshot;
 
-import static com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_STORAGE;
 import static com.android.systemui.screenshot.LogConfig.logTag;
 import static com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType;
 
-import android.app.ActivityTaskManager;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.ClipData;
 import android.content.ClipDescription;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Process;
-import android.os.RemoteException;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.provider.DeviceConfig;
-import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.systemui.res.R;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -60,7 +49,6 @@
 import java.util.Random;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
-import java.util.function.Supplier;
 
 /**
  * An AsyncTask that saves an image to the media store in the background.
@@ -81,7 +69,6 @@
     private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
     private String mScreenshotId;
     private final Random mRandom = new Random();
-    private final Supplier<ActionTransition> mSharedElementTransition;
     private final ImageExporter mImageExporter;
     private long mImageTime;
 
@@ -91,7 +78,6 @@
             ImageExporter exporter,
             ScreenshotSmartActions screenshotSmartActions,
             ScreenshotController.SaveImageInBackgroundData data,
-            Supplier<ActionTransition> sharedElementTransition,
             ScreenshotNotificationSmartActionsProvider
                     screenshotNotificationSmartActionsProvider
     ) {
@@ -100,7 +86,6 @@
         mScreenshotSmartActions = screenshotSmartActions;
         mImageData = new ScreenshotController.SavedImageData();
         mQuickShareData = new ScreenshotController.QuickShareData();
-        mSharedElementTransition = sharedElementTransition;
         mImageExporter = exporter;
 
         // Prepare all the output metadata
@@ -176,12 +161,6 @@
             mImageData.uri = uri;
             mImageData.owner = mParams.owner;
             mImageData.smartActions = smartActions;
-            mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri,
-                    smartActionsEnabled);
-            mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri,
-                    smartActionsEnabled);
-            mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri,
-                    smartActionsEnabled);
             mImageData.quickShareAction = createQuickShareAction(
                     mQuickShareData.quickShareAction, mScreenshotId, uri, mImageTime, image,
                     mParams.owner);
@@ -234,164 +213,6 @@
         mParams.clearImage();
     }
 
-    /**
-     * Assumes that the action intent is sent immediately after being supplied.
-     */
-    @VisibleForTesting
-    Supplier<ActionTransition> createShareAction(Context context, Resources r, Uri uri,
-            boolean smartActionsEnabled) {
-        return () -> {
-            ActionTransition transition = mSharedElementTransition.get();
-
-            // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
-            // order to do some common work like dismissing the keyguard and sending
-            // closeSystemWindows
-
-            // Create a share intent, this will always go through the chooser activity first
-            // which should not trigger auto-enter PiP
-            Intent sharingIntent = new Intent(Intent.ACTION_SEND);
-            sharingIntent.setDataAndType(uri, "image/png");
-            sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
-            // Include URI in ClipData also, so that grantPermission picks it up.
-            // We don't use setData here because some apps interpret this as "to:".
-            ClipData clipdata = new ClipData(new ClipDescription("content",
-                    new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
-                    new ClipData.Item(uri));
-            sharingIntent.setClipData(clipdata);
-            sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getSubjectString(mImageTime));
-            sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
-                    .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
-
-            // Make sure pending intents for the system user are still unique across users
-            // by setting the (otherwise unused) request code to the current user id.
-            int requestCode = context.getUserId();
-
-            Intent sharingChooserIntent = Intent.createChooser(sharingIntent, null)
-                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
-                    .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
-
-            // cancel current pending intent (if any) since clipData isn't used for matching
-            PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
-                    context, 0, sharingChooserIntent,
-                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
-                    transition.bundle, UserHandle.CURRENT);
-
-            // Create a share action for the notification
-            PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode,
-                    new Intent(context, ActionProxyReceiver.class)
-                            .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent)
-                            .putExtra(ScreenshotController.EXTRA_DISALLOW_ENTER_PIP, true)
-                            .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
-                            .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
-                                    smartActionsEnabled)
-                            .setAction(Intent.ACTION_SEND)
-                            .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
-                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
-                    UserHandle.SYSTEM);
-
-            Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
-                    Icon.createWithResource(r, R.drawable.ic_screenshot_share),
-                    r.getString(com.android.internal.R.string.share), shareAction);
-
-            transition.action = shareActionBuilder.build();
-            return transition;
-        };
-    }
-
-    @VisibleForTesting
-    Supplier<ActionTransition> createEditAction(Context context, Resources r, Uri uri,
-            boolean smartActionsEnabled) {
-        return () -> {
-            ActionTransition transition = mSharedElementTransition.get();
-            // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
-            // order to do some common work like dismissing the keyguard and sending
-            // closeSystemWindows
-
-            // Create an edit intent, if a specific package is provided as the editor, then
-            // launch that directly
-            String editorPackage = context.getString(R.string.config_screenshotEditor);
-            Intent editIntent = new Intent(Intent.ACTION_EDIT);
-            if (!TextUtils.isEmpty(editorPackage)) {
-                editIntent.setComponent(ComponentName.unflattenFromString(editorPackage));
-            }
-            editIntent.setDataAndType(uri, "image/png");
-            editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-            editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-            editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-
-            PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
-                    context, 0, editIntent, PendingIntent.FLAG_IMMUTABLE,
-                    transition.bundle, UserHandle.CURRENT);
-
-            // Make sure pending intents for the system user are still unique across users
-            // by setting the (otherwise unused) request code to the current user id.
-            int requestCode = mContext.getUserId();
-
-            // Create an edit action
-            PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode,
-                    new Intent(context, ActionProxyReceiver.class)
-                            .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent)
-                            .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
-                            .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
-                                    smartActionsEnabled)
-                            .putExtra(ScreenshotController.EXTRA_OVERRIDE_TRANSITION, true)
-                            .setAction(Intent.ACTION_EDIT)
-                            .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
-                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
-                    UserHandle.SYSTEM);
-            Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
-                    Icon.createWithResource(r, R.drawable.ic_screenshot_edit),
-                    r.getString(com.android.internal.R.string.screenshot_edit), editAction);
-
-            transition.action = editActionBuilder.build();
-            return transition;
-        };
-    }
-
-    @VisibleForTesting
-    Notification.Action createDeleteAction(Context context, Resources r, Uri uri,
-            boolean smartActionsEnabled) {
-        // Make sure pending intents for the system user are still unique across users
-        // by setting the (otherwise unused) request code to the current user id.
-        int requestCode = mContext.getUserId();
-
-        // Create a delete action for the notification
-        PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode,
-                new Intent(context, DeleteScreenshotReceiver.class)
-                        .putExtra(ScreenshotController.SCREENSHOT_URI_ID, uri.toString())
-                        .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
-                        .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
-                                smartActionsEnabled)
-                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
-                PendingIntent.FLAG_CANCEL_CURRENT
-                        | PendingIntent.FLAG_ONE_SHOT
-                        | PendingIntent.FLAG_IMMUTABLE);
-        Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
-                Icon.createWithResource(r, R.drawable.ic_screenshot_delete),
-                r.getString(com.android.internal.R.string.delete), deleteAction);
-
-        return deleteActionBuilder.build();
-    }
-
-    private UserHandle getUserHandleOfForegroundApplication(Context context) {
-        UserManager manager = UserManager.get(context);
-        int result;
-        // This logic matches
-        // com.android.systemui.statusbar.phone.PhoneStatusBarPolicy#updateManagedProfile
-        try {
-            result = ActivityTaskManager.getService().getLastResumedActivityUserId();
-        } catch (RemoteException e) {
-            if (DEBUG_ACTIONS) {
-                Log.d(TAG, "Failed to get UserHandle of foreground app: ", e);
-            }
-            result = context.getUserId();
-        }
-        UserInfo userInfo = manager.getUserInfo(result);
-        return userInfo.getUserHandle();
-    }
-
     private List<Notification.Action> buildSmartActions(
             List<Notification.Action> actions, Context context) {
         List<Notification.Action> broadcastActions = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 21a08a9..9aaed9e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -21,7 +21,6 @@
 
 import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_UI;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW;
@@ -39,7 +38,6 @@
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.ExitTransitionCoordinator;
-import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks;
 import android.app.ICompatCameraControlCallback;
 import android.app.Notification;
 import android.app.assist.AssistContent;
@@ -54,7 +52,6 @@
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.net.Uri;
-import android.os.Bundle;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -66,8 +63,6 @@
 import android.view.Display;
 import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IRemoteAnimationRunner;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationTarget;
 import android.view.ScrollCaptureResponse;
@@ -79,21 +74,18 @@
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Toast;
-import android.window.OnBackInvokedCallback;
-import android.window.OnBackInvokedDispatcher;
 import android.window.WindowContext;
 
 import com.android.internal.app.ChooserActivity;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.policy.PhoneWindow;
 import com.android.settingslib.applications.InterestingConfigChanges;
-import com.android.systemui.res.R;
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.clipboardoverlay.ClipboardOverlayController;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
+import com.android.systemui.res.R;
 import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
 import com.android.systemui.util.Assert;
 
@@ -111,7 +103,6 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.function.Consumer;
-import java.util.function.Supplier;
 
 import javax.inject.Provider;
 
@@ -169,33 +160,18 @@
     /**
      * Structure returned by the SaveImageInBackgroundTask
      */
-    static class SavedImageData {
+    public static class SavedImageData {
         public Uri uri;
-        public Supplier<ActionTransition> shareTransition;
-        public Supplier<ActionTransition> editTransition;
-        public Notification.Action deleteAction;
         public List<Notification.Action> smartActions;
         public Notification.Action quickShareAction;
         public UserHandle owner;
         public String subject;  // Title for sharing
 
         /**
-         * POD for shared element transition.
-         */
-        static class ActionTransition {
-            public Bundle bundle;
-            public Notification.Action action;
-            public Runnable onCancelRunnable;
-        }
-
-        /**
          * Used to reset the return data on error
          */
         public void reset() {
             uri = null;
-            shareTransition = null;
-            editTransition = null;
-            deleteAction = null;
             smartActions = null;
             quickShareAction = null;
             subject = null;
@@ -252,10 +228,11 @@
     // From WizardManagerHelper.java
     private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
 
-    private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
+    static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
 
     private final WindowContext mContext;
     private final FeatureFlags mFlags;
+    private final ScreenshotViewProxy mViewProxy;
     private final ScreenshotNotificationsController mNotificationsController;
     private final ScreenshotSmartActions mScreenshotSmartActions;
     private final UiEventLogger mUiEventLogger;
@@ -284,14 +261,6 @@
     private final UserManager mUserManager;
     private final AssistContentRequester mAssistContentRequester;
 
-    private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
-        if (DEBUG_INPUT) {
-            Log.d(TAG, "Predictive Back callback dispatched");
-        }
-        respondToKeyDismissal();
-    };
-
-    private ScreenshotView mScreenshotView;
     private final MessageContainerController mMessageContainerController;
     private Bitmap mScreenBitmap;
     private SaveImageInBackgroundTask mSaveInBgTask;
@@ -324,6 +293,7 @@
     ScreenshotController(
             Context context,
             FeatureFlags flags,
+            ScreenshotViewProxy.Factory viewProxyFactory,
             ScreenshotSmartActions screenshotSmartActions,
             ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,
             ScrollCaptureClient scrollCaptureClient,
@@ -361,12 +331,7 @@
 
         mScreenshotHandler = timeoutHandler;
         mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
-        mScreenshotHandler.setOnTimeoutRunnable(() -> {
-            if (DEBUG_UI) {
-                Log.d(TAG, "Corner timeout hit");
-            }
-            dismissScreenshot(SCREENSHOT_INTERACTION_TIMEOUT);
-        });
+
 
         mDisplayId = displayId;
         mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
@@ -379,6 +344,15 @@
         mMessageContainerController = messageContainerController;
         mAssistContentRequester = assistContentRequester;
 
+        mViewProxy = viewProxyFactory.getProxy(mContext, mUiEventLogger);
+
+        mScreenshotHandler.setOnTimeoutRunnable(() -> {
+            if (DEBUG_UI) {
+                Log.d(TAG, "Corner timeout hit");
+            }
+            mViewProxy.requestDismissal(SCREENSHOT_INTERACTION_TIMEOUT);
+        });
+
         mAccessibilityManager = AccessibilityManager.getInstance(mContext);
 
         // Setup the window that we are going to use
@@ -402,7 +376,7 @@
             @Override
             public void onReceive(Context context, Intent intent) {
                 if (ClipboardOverlayController.COPY_OVERLAY_ACTION.equals(intent.getAction())) {
-                    dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
+                    mViewProxy.requestDismissal(SCREENSHOT_DISMISSED_OTHER);
                 }
             }
         };
@@ -469,8 +443,9 @@
 
         if (!shouldShowUi()) {
             saveScreenshotInWorkerThread(
-                screenshot.getUserHandle(), finisher, this::logSuccessOnActionsReady,
-                (ignored) -> {});
+                    screenshot.getUserHandle(), finisher, this::logSuccessOnActionsReady,
+                    (ignored) -> {
+                    });
             return;
         }
 
@@ -479,17 +454,17 @@
 
         // The window is focusable by default
         setWindowFocusable(true);
-        mScreenshotView.requestFocus();
+        mViewProxy.requestFocus();
 
         enqueueScrollCaptureRequest(screenshot.getUserHandle());
 
         attachWindow();
 
-        boolean showFlash = true;
+        boolean showFlash;
         if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
             if (screenshot.getScreenBounds() != null
                     && aspectRatiosMatch(screenshot.getBitmap(), screenshot.getInsets(),
-                            screenshot.getScreenBounds())) {
+                    screenshot.getScreenBounds())) {
                 showFlash = false;
             } else {
                 showFlash = true;
@@ -497,16 +472,15 @@
                 screenshot.setScreenBounds(new Rect(0, 0, screenshot.getBitmap().getWidth(),
                         screenshot.getBitmap().getHeight()));
             }
+        } else {
+            showFlash = true;
         }
 
-        prepareAnimation(screenshot.getScreenBounds(), showFlash, () -> {
-            mMessageContainerController.onScreenshotTaken(screenshot);
-        });
+        mViewProxy.prepareEntranceAnimation(
+                () -> startAnimation(screenshot.getScreenBounds(), showFlash,
+                        () -> mMessageContainerController.onScreenshotTaken(screenshot)));
 
-        mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
-                mContext.getDrawable(R.drawable.overlay_badge_background),
-                screenshot.getUserHandle()));
-        mScreenshotView.setScreenshot(screenshot);
+        mViewProxy.setScreenshot(screenshot);
 
         // ignore system bar insets for the purpose of window layout
         mWindow.getDecorView().setOnApplyWindowInsetsListener(
@@ -521,55 +495,44 @@
     void prepareViewForNewScreenshot(ScreenshotData screenshot, String oldPackageName) {
         withWindowAttached(() -> {
             if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) {
-                mScreenshotView.announceForAccessibility(mContext.getResources().getString(
+                mViewProxy.announceForAccessibility(mContext.getResources().getString(
                         R.string.screenshot_saving_work_profile_title));
             } else {
-                mScreenshotView.announceForAccessibility(
+                mViewProxy.announceForAccessibility(
                         mContext.getResources().getString(R.string.screenshot_saving_title));
             }
         });
 
-        mScreenshotView.reset();
+        mViewProxy.reset();
 
-        if (mScreenshotView.isAttachedToWindow()) {
+        if (mViewProxy.isAttachedToWindow()) {
             // if we didn't already dismiss for another reason
-            if (!mScreenshotView.isDismissing()) {
+            if (!mViewProxy.isDismissing()) {
                 mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0,
                         oldPackageName);
             }
             if (DEBUG_WINDOW) {
                 Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. "
-                        + "(dismissing=" + mScreenshotView.isDismissing() + ")");
+                        + "(dismissing=" + mViewProxy.isDismissing() + ")");
             }
         }
 
-        mScreenshotView.setPackageName(mPackageName);
+        mViewProxy.setPackageName(mPackageName);
 
-        mScreenshotView.updateOrientation(
+        mViewProxy.updateOrientation(
                 mWindowManager.getCurrentWindowMetrics().getWindowInsets());
     }
 
     /**
-     * Clears current screenshot
+     * Requests the view to dismiss the current screenshot (may be ignored, if screenshot is already
+     * being dismissed)
      */
-    void dismissScreenshot(ScreenshotEvent event) {
-        if (DEBUG_DISMISS) {
-            Log.d(TAG, "dismissScreenshot");
-        }
-        // If we're already animating out, don't restart the animation
-        if (mScreenshotView.isDismissing()) {
-            if (DEBUG_DISMISS) {
-                Log.v(TAG, "Already dismissing, ignoring duplicate command");
-            }
-            return;
-        }
-        mUiEventLogger.log(event, 0, mPackageName);
-        mScreenshotHandler.cancelTimeout();
-        mScreenshotView.animateDismissal();
+    void requestDismissal(ScreenshotEvent event) {
+        mViewProxy.requestDismissal(event);
     }
 
     boolean isPendingSharedTransition() {
-        return mScreenshotView.isPendingSharedTransition();
+        return mViewProxy.isPendingSharedTransition();
     }
 
     // Any cleanup needed when the service is being destroyed.
@@ -594,11 +557,7 @@
 
     private void releaseMediaPlayer() {
         if (mScreenshotSoundController == null) return;
-        mScreenshotSoundController.releaseScreenshotSound();
-    }
-
-    private void respondToKeyDismissal() {
-        dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
+        mScreenshotSoundController.releaseScreenshotSoundAsync();
     }
 
     /**
@@ -609,31 +568,8 @@
             Log.d(TAG, "reloadAssets()");
         }
 
-        // Inflate the screenshot layout
-        mScreenshotView = (ScreenshotView)
-                LayoutInflater.from(mContext).inflate(R.layout.screenshot, null);
-        mMessageContainerController.setView(mScreenshotView);
-        mScreenshotView.addOnAttachStateChangeListener(
-                new View.OnAttachStateChangeListener() {
-                    @Override
-                    public void onViewAttachedToWindow(@NonNull View v) {
-                        if (DEBUG_INPUT) {
-                            Log.d(TAG, "Registering Predictive Back callback");
-                        }
-                        mScreenshotView.findOnBackInvokedDispatcher().registerOnBackInvokedCallback(
-                                OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback);
-                    }
-
-                    @Override
-                    public void onViewDetachedFromWindow(@NonNull View v) {
-                        if (DEBUG_INPUT) {
-                            Log.d(TAG, "Unregistering Predictive Back callback");
-                        }
-                        mScreenshotView.findOnBackInvokedDispatcher()
-                                .unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
-                    }
-                });
-        mScreenshotView.init(mUiEventLogger, new ScreenshotView.ScreenshotViewCallback() {
+        mMessageContainerController.setView(mViewProxy.getView());
+        mViewProxy.setCallbacks(new ScreenshotView.ScreenshotViewCallback() {
             @Override
             public void onUserInteraction() {
                 if (DEBUG_INPUT) {
@@ -643,6 +579,12 @@
             }
 
             @Override
+            public void onAction(Intent intent, UserHandle owner, boolean overrideTransition) {
+                mActionExecutor.launchIntentAsync(
+                        intent, createWindowTransition(), owner, overrideTransition);
+            }
+
+            @Override
             public void onDismiss() {
                 finishDismiss();
             }
@@ -652,45 +594,14 @@
                 // TODO(159460485): Remove this when focus is handled properly in the system
                 setWindowFocusable(false);
             }
-        }, mActionExecutor, mFlags);
-        mScreenshotView.setDefaultDisplay(mDisplayId);
-        mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
-
-        mScreenshotView.setOnKeyListener((v, keyCode, event) -> {
-            if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
-                if (DEBUG_INPUT) {
-                    Log.d(TAG, "onKeyEvent: " + keyCode);
-                }
-                respondToKeyDismissal();
-                return true;
-            }
-            return false;
         });
+        mViewProxy.setFlags(mFlags);
+        mViewProxy.setDefaultDisplay(mDisplayId);
 
         if (DEBUG_WINDOW) {
-            Log.d(TAG, "adding OnComputeInternalInsetsListener");
+            Log.d(TAG, "setContentView: " + mViewProxy.getView());
         }
-        mScreenshotView.getViewTreeObserver().addOnComputeInternalInsetsListener(mScreenshotView);
-        if (DEBUG_WINDOW) {
-            Log.d(TAG, "setContentView: " + mScreenshotView);
-        }
-        setContentView(mScreenshotView);
-    }
-
-    private void prepareAnimation(Rect screenRect, boolean showFlash,
-            Runnable onAnimationComplete) {
-        mScreenshotView.getViewTreeObserver().addOnPreDrawListener(
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        if (DEBUG_WINDOW) {
-                            Log.d(TAG, "onPreDraw: startAnimation");
-                        }
-                        mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this);
-                        startAnimation(screenRect, showFlash, onAnimationComplete);
-                        return true;
-                    }
-                });
+        setContentView(mViewProxy.getView());
     }
 
     private void enqueueScrollCaptureRequest(UserHandle owner) {
@@ -706,13 +617,13 @@
                             if (mConfigChanges.applyNewConfig(mContext.getResources())) {
                                 // Hide the scroll chip until we know it's available in this
                                 // orientation
-                                mScreenshotView.hideScrollChip();
+                                mViewProxy.hideScrollChip();
                                 // Delay scroll capture eval a bit to allow the underlying activity
                                 // to set up in the new orientation.
                                 mScreenshotHandler.postDelayed(() -> {
                                     requestScrollCapture(owner);
                                 }, 150);
-                                mScreenshotView.updateInsets(
+                                mViewProxy.updateInsets(
                                         mWindowManager.getCurrentWindowMetrics().getWindowInsets());
                                 // Screenshot animation calculations won't be valid anymore,
                                 // so just end
@@ -771,16 +682,20 @@
                     + mLastScrollCaptureResponse.getWindowTitle() + "]");
 
             final ScrollCaptureResponse response = mLastScrollCaptureResponse;
-            mScreenshotView.showScrollChip(response.getPackageName(), /* onClick */ () -> {
+            mViewProxy.showScrollChip(response.getPackageName(), /* onClick */ () -> {
                 DisplayMetrics displayMetrics = new DisplayMetrics();
                 getDisplay().getRealMetrics(displayMetrics);
                 Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId,
                         new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
 
-                mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
-                        mScreenshotTakenInPortrait);
-                // delay starting scroll capture to make sure the scrim is up before the app moves
-                mScreenshotView.post(() -> runBatchScrollCapture(response, owner));
+                if (newScreenshot != null) {
+                    // delay starting scroll capture to make sure scrim is up before the app moves
+                    mViewProxy.prepareScrollingTransition(
+                            response, mScreenBitmap, newScreenshot, mScreenshotTakenInPortrait,
+                            () -> runBatchScrollCapture(response, owner));
+                } else {
+                    Log.wtf(TAG, "failed to capture current screenshot for scroll transition");
+                }
             });
         } catch (InterruptedException | ExecutionException e) {
             Log.e(TAG, "requestScrollCapture failed", e);
@@ -806,19 +721,19 @@
                 return;
             } catch (InterruptedException | ExecutionException e) {
                 Log.e(TAG, "Exception", e);
-                mScreenshotView.restoreNonScrollingUi();
+                mViewProxy.restoreNonScrollingUi();
                 return;
             }
 
             if (longScreenshot.getHeight() == 0) {
-                mScreenshotView.restoreNonScrollingUi();
+                mViewProxy.restoreNonScrollingUi();
                 return;
             }
 
             mLongScreenshotHolder.setLongScreenshot(longScreenshot);
             mLongScreenshotHolder.setTransitionDestinationCallback(
                     (transitionDestination, onTransitionEnd) -> {
-                        mScreenshotView.startLongScreenshotTransition(
+                        mViewProxy.startLongScreenshotTransition(
                                 transitionDestination, onTransitionEnd,
                                 longScreenshot);
                         // TODO: Do this via ActionIntentExecutor instead.
@@ -894,16 +809,14 @@
             }
             mWindowManager.removeViewImmediate(decorView);
         }
-        // Ensure that we remove the input monitor
-        if (mScreenshotView != null) {
-            mScreenshotView.stopInputListening();
-        }
+
+        mViewProxy.stopInputListening();
     }
 
     private void playCameraSoundIfNeeded() {
         if (mScreenshotSoundController == null) return;
         // the controller is not-null only on the default display controller
-        mScreenshotSoundController.playCameraSound();
+        mScreenshotSoundController.playScreenshotSoundAsync();
     }
 
     /**
@@ -944,7 +857,7 @@
         }
 
         mScreenshotAnimation =
-                mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash);
+                mViewProxy.createScreenshotDropInAnimation(screenRect, showFlash);
         if (onAnimationComplete != null) {
             mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
                 @Override
@@ -964,6 +877,35 @@
         mScreenshotAnimation.start();
     }
 
+    /**
+     * Supplies the necessary bits for the shared element transition to share sheet.
+     * Note that once called, the action intent to share must be sent immediately after.
+     */
+    private Pair<ActivityOptions, ExitTransitionCoordinator> createWindowTransition() {
+        ExitTransitionCoordinator.ExitTransitionCallbacks callbacks =
+                new ExitTransitionCoordinator.ExitTransitionCallbacks() {
+                    @Override
+                    public boolean isReturnTransitionAllowed() {
+                        return false;
+                    }
+
+                    @Override
+                    public void hideSharedElements() {
+                        finishDismiss();
+                    }
+
+                    @Override
+                    public void onFinish() {
+                    }
+                };
+        Pair<ActivityOptions, ExitTransitionCoordinator> transition =
+                ActivityOptions.startSharedElementAnimation(mWindow, callbacks, null,
+                        Pair.create(mViewProxy.getScreenshotPreview(),
+                                ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
+
+        return transition;
+    }
+
     /** Reset screenshot view and then call onCompleteRunnable */
     private void finishDismiss() {
         Log.d(TAG, "finishDismiss");
@@ -982,7 +924,7 @@
             mCurrentRequestCallback.onFinish();
             mCurrentRequestCallback = null;
         }
-        mScreenshotView.reset();
+        mViewProxy.reset();
         removeWindow();
         mScreenshotHandler.cancelTimeout();
     }
@@ -1011,7 +953,7 @@
         }
 
         mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mFlags, mImageExporter,
-                mScreenshotSmartActions, data, getActionTransitionSupplier(),
+                mScreenshotSmartActions, data,
                 mScreenshotNotificationSmartActionsProvider);
         mSaveInBgTask.execute();
     }
@@ -1050,7 +992,7 @@
     }
 
     private void doPostAnimation(ScreenshotController.SavedImageData imageData) {
-        mScreenshotView.setChipIntents(imageData);
+        mViewProxy.setChipIntents(imageData);
     }
 
     /**
@@ -1067,37 +1009,17 @@
                         @Override
                         public void onAnimationEnd(Animator animation) {
                             super.onAnimationEnd(animation);
-                            mScreenshotView.addQuickShareChip(quickShareData.quickShareAction);
+                            mViewProxy.addQuickShareChip(quickShareData.quickShareAction);
                         }
                     });
                 } else {
-                    mScreenshotView.addQuickShareChip(quickShareData.quickShareAction);
+                    mViewProxy.addQuickShareChip(quickShareData.quickShareAction);
                 }
             });
         }
     }
 
     /**
-     * Supplies the necessary bits for the shared element transition to share sheet.
-     * Note that once supplied, the action intent to share must be sent immediately after.
-     */
-    private Supplier<ActionTransition> getActionTransitionSupplier() {
-        return () -> {
-            Pair<ActivityOptions, ExitTransitionCoordinator> transition =
-                    ActivityOptions.startSharedElementAnimation(
-                            mWindow, new ScreenshotExitTransitionCallbacksSupplier(true).get(),
-                            null, Pair.create(mScreenshotView.getScreenshotPreview(),
-                                    ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
-            transition.second.startExit();
-
-            ActionTransition supply = new ActionTransition();
-            supply.bundle = transition.first.toBundle();
-            supply.onCancelRunnable = () -> ActivityOptions.stopSharedElementAnimation(mWindow);
-            return supply;
-        };
-    }
-
-    /**
      * Logs success/failure of the screenshot saving task, and shows an error if it failed.
      */
     private void logSuccessOnActionsReady(ScreenshotController.SavedImageData imageData) {
@@ -1186,36 +1108,6 @@
         return matchWithinTolerance;
     }
 
-    private class ScreenshotExitTransitionCallbacksSupplier implements
-            Supplier<ExitTransitionCallbacks> {
-        final boolean mDismissOnHideSharedElements;
-
-        ScreenshotExitTransitionCallbacksSupplier(boolean dismissOnHideSharedElements) {
-            mDismissOnHideSharedElements = dismissOnHideSharedElements;
-        }
-
-        @Override
-        public ExitTransitionCallbacks get() {
-            return new ExitTransitionCallbacks() {
-                @Override
-                public boolean isReturnTransitionAllowed() {
-                    return false;
-                }
-
-                @Override
-                public void hideSharedElements() {
-                    if (mDismissOnHideSharedElements) {
-                        finishDismiss();
-                    }
-                }
-
-                @Override
-                public void onFinish() {
-                }
-            };
-        }
-    }
-
     /** Injectable factory to create screenshot controller instances for a specific display. */
     @AssistedFactory
     public interface Factory {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
index 2c0bdde..d3a7fc4a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
@@ -21,22 +21,34 @@
 import com.android.app.tracing.coroutines.async
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
-import com.google.errorprone.annotations.CanIgnoreReturnValue
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 import kotlinx.coroutines.withTimeout
 
 /** Controls sound reproduction after a screenshot is taken. */
 interface ScreenshotSoundController {
     /** Reproduces the camera sound. */
-    @CanIgnoreReturnValue fun playCameraSound(): Deferred<Unit>
+    suspend fun playScreenshotSound()
 
-    /** Releases the sound. [playCameraSound] behaviour is undefined after this has been called. */
-    @CanIgnoreReturnValue fun releaseScreenshotSound(): Deferred<Unit>
+    /**
+     * Releases the sound. [playScreenshotSound] behaviour is undefined after this has been called.
+     */
+    suspend fun releaseScreenshotSound()
+
+    /** Reproduces the camera sound. Used for compatibility with Java code. */
+    fun playScreenshotSoundAsync()
+
+    /**
+     * Releases the sound. [playScreenshotSound] behaviour is undefined after this has been called.
+     * Used for compatibility with Java code.
+     */
+    fun releaseScreenshotSoundAsync()
 }
 
 class ScreenshotSoundControllerImpl
@@ -47,8 +59,8 @@
     @Background private val bgDispatcher: CoroutineDispatcher
 ) : ScreenshotSoundController {
 
-    val player: Deferred<MediaPlayer?> =
-        coroutineScope.async("loadCameraSound", bgDispatcher) {
+    private val player: Deferred<MediaPlayer?> =
+        coroutineScope.async("loadScreenshotSound", bgDispatcher) {
             try {
                 soundProvider.getScreenshotSound()
             } catch (e: IllegalStateException) {
@@ -57,8 +69,8 @@
             }
         }
 
-    override fun playCameraSound(): Deferred<Unit> {
-        return coroutineScope.async("playCameraSound", bgDispatcher) {
+    override suspend fun playScreenshotSound() {
+        withContext(bgDispatcher) {
             try {
                 player.await()?.start()
             } catch (e: IllegalStateException) {
@@ -68,8 +80,8 @@
         }
     }
 
-    override fun releaseScreenshotSound(): Deferred<Unit> {
-        return coroutineScope.async("releaseScreenshotSound", bgDispatcher) {
+    override suspend fun releaseScreenshotSound() {
+        withContext(bgDispatcher) {
             try {
                 withTimeout(1.seconds) { player.await()?.release() }
             } catch (e: TimeoutCancellationException) {
@@ -79,6 +91,14 @@
         }
     }
 
+    override fun playScreenshotSoundAsync() {
+        coroutineScope.launch { playScreenshotSound() }
+    }
+
+    override fun releaseScreenshotSoundAsync() {
+        coroutineScope.launch { releaseScreenshotSound() }
+    }
+
     private companion object {
         const val TAG = "ScreenshotSoundControllerImpl"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 31c9284..1c5a8a1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -26,6 +26,7 @@
 import static com.android.systemui.screenshot.LogConfig.DEBUG_UI;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW;
 import static com.android.systemui.screenshot.LogConfig.logTag;
+import static com.android.systemui.screenshot.ScreenshotController.SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS;
 
 import static java.util.Objects.requireNonNull;
 
@@ -33,6 +34,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.BroadcastOptions;
 import android.app.Notification;
@@ -57,6 +59,7 @@
 import android.os.Bundle;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -86,9 +89,9 @@
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.res.R;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.shared.system.InputMonitorCompat;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -101,9 +104,11 @@
 public class ScreenshotView extends FrameLayout implements
         ViewTreeObserver.OnComputeInternalInsetsListener {
 
-    interface ScreenshotViewCallback {
+    public interface ScreenshotViewCallback {
         void onUserInteraction();
 
+        void onAction(Intent intent, UserHandle owner, boolean overrideTransition);
+
         void onDismiss();
 
         /** DOWN motion event was observed outside of the touchable areas of this view. */
@@ -165,8 +170,6 @@
     private ScreenshotData mScreenshotData;
 
     private final InteractionJankMonitor mInteractionJankMonitor;
-    private long mDefaultTimeoutOfTimeoutHandler;
-    private ActionIntentExecutor mActionExecutor;
     private FeatureFlags mFlags;
     private final Bundle mInteractiveBroadcastOption;
 
@@ -242,10 +245,6 @@
         return InteractionJankMonitor.getInstance();
     }
 
-    void setDefaultTimeoutMillis(long timeout) {
-        mDefaultTimeoutOfTimeoutHandler = timeout;
-    }
-
     public void hideScrollChip() {
         mScrollChip.setVisibility(View.GONE);
     }
@@ -424,17 +423,15 @@
         return mScreenshotPreview;
     }
 
-    /**
-     * Set up the logger and callback on dismissal.
-     *
-     * Note: must be called before any other (non-constructor) method or null pointer exceptions
-     * may occur.
-     */
-    void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks,
-            ActionIntentExecutor actionExecutor, FeatureFlags flags) {
+    void setUiEventLogger(UiEventLogger uiEventLogger) {
         mUiEventLogger = uiEventLogger;
+    }
+
+    void setCallbacks(ScreenshotViewCallback callbacks) {
         mCallbacks = callbacks;
-        mActionExecutor = actionExecutor;
+    }
+
+    void setFlags(FeatureFlags flags) {
         mFlags = flags;
     }
 
@@ -755,7 +752,7 @@
                         InteractionJankMonitor.Configuration.Builder.withView(
                                         CUJ_TAKE_SCREENSHOT, mScreenshotStatic)
                                 .setTag("Actions")
-                                .setTimeout(mDefaultTimeoutOfTimeoutHandler);
+                                .setTimeout(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
                 mInteractionJankMonitor.begin(builder);
             }
         });
@@ -781,7 +778,7 @@
         return animator;
     }
 
-    void badgeScreenshot(Drawable badge) {
+    void badgeScreenshot(@Nullable Drawable badge) {
         mScreenshotBadge.setImageDrawable(badge);
         mScreenshotBadge.setVisibility(badge != null ? View.VISIBLE : View.GONE);
     }
@@ -800,24 +797,21 @@
                 shareIntent = ActionIntentCreator.INSTANCE.createShareWithSubject(
                         imageData.uri, imageData.subject);
             }
-            mActionExecutor.launchIntentAsync(shareIntent,
-                    imageData.shareTransition.get().bundle,
-                    imageData.owner, false);
+            mCallbacks.onAction(shareIntent, imageData.owner, false);
+
         });
         mEditChip.setOnClickListener(v -> {
             mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED, 0, mPackageName);
             prepareSharedTransition();
-            mActionExecutor.launchIntentAsync(
+            mCallbacks.onAction(
                     ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext),
-                    imageData.editTransition.get().bundle,
                     imageData.owner, true);
         });
         mScreenshotPreview.setOnClickListener(v -> {
             mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED, 0, mPackageName);
             prepareSharedTransition();
-            mActionExecutor.launchIntentAsync(
+            mCallbacks.onAction(
                     ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext),
-                    imageData.editTransition.get().bundle,
                     imageData.owner, true);
         });
         if (mQuickShareChip != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
new file mode 100644
index 0000000..d019660
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.animation.Animator
+import android.app.Notification
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Rect
+import android.view.ScrollCaptureResponse
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.flags.FeatureFlags
+
+/** Abstraction of the surface between ScreenshotController and ScreenshotView */
+interface ScreenshotViewProxy {
+    val view: ViewGroup
+    val screenshotPreview: View
+
+    var defaultDisplay: Int
+    var flags: FeatureFlags?
+    var packageName: String
+    var callbacks: ScreenshotView.ScreenshotViewCallback?
+    var screenshot: ScreenshotData?
+
+    val isAttachedToWindow: Boolean
+    val isDismissing: Boolean
+    val isPendingSharedTransition: Boolean
+
+    fun reset()
+    fun updateInsets(insets: WindowInsets)
+    fun updateOrientation(insets: WindowInsets)
+    fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator
+    fun addQuickShareChip(quickShareAction: Notification.Action)
+    fun setChipIntents(imageData: ScreenshotController.SavedImageData)
+    fun requestDismissal(event: ScreenshotEvent)
+
+    fun showScrollChip(packageName: String, onClick: Runnable)
+    fun hideScrollChip()
+    fun prepareScrollingTransition(
+        response: ScrollCaptureResponse,
+        screenBitmap: Bitmap,
+        newScreenshot: Bitmap,
+        screenshotTakenInPortrait: Boolean,
+        onTransitionPrepared: Runnable,
+    )
+    fun startLongScreenshotTransition(
+        transitionDestination: Rect,
+        onTransitionEnd: Runnable,
+        longScreenshot: ScrollCaptureController.LongScreenshot
+    )
+    fun restoreNonScrollingUi()
+
+    fun stopInputListening()
+    fun requestFocus()
+    fun announceForAccessibility(string: String)
+    fun prepareEntranceAnimation(runnable: Runnable)
+
+    interface Factory {
+        fun getProxy(context: Context, logger: UiEventLogger): ScreenshotViewProxy
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index bb34ede..8a2678c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -75,7 +75,7 @@
     private String mWindowOwner;
     private volatile boolean mCancelled;
 
-    static class LongScreenshot {
+    public static class LongScreenshot {
         private final ImageTileSet mImageTileSet;
         private final Session mSession;
         // TODO: Add UserHandle so LongScreenshots can adhere to work profile screenshot policy
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index e464fd0..bc33755 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -136,7 +136,7 @@
     fun onCloseSystemDialogsReceived() {
         screenshotControllers.forEach { (_, screenshotController) ->
             if (!screenshotController.isPendingSharedTransition) {
-                screenshotController.dismissScreenshot(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
+                screenshotController.requestDismissal(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 0991c9a..9cf347b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -53,9 +53,9 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.util.ScreenshotRequest;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
 
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
@@ -89,7 +89,7 @@
                     // TODO(b/295143676): move receiver inside executor when the flag is enabled.
                     mTakeScreenshotExecutor.get().onCloseSystemDialogsReceived();
                 } else if (!mScreenshot.isPendingSharedTransition()) {
-                    mScreenshot.dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
+                    mScreenshot.requestDismissal(SCREENSHOT_DISMISSED_OTHER);
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
index 71c2cb4..5df6c45 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
@@ -40,7 +40,7 @@
     private final Context mContext;
 
     private Runnable mOnTimeout;
-    private int mDefaultTimeout = DEFAULT_TIMEOUT_MILLIS;
+    int mDefaultTimeout = DEFAULT_TIMEOUT_MILLIS;
 
     @Inject
     public TimeoutHandler(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 3797b8b..a00c81d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -20,6 +20,7 @@
 
 import com.android.systemui.screenshot.ImageCapture;
 import com.android.systemui.screenshot.ImageCaptureImpl;
+import com.android.systemui.screenshot.LegacyScreenshotViewProxy;
 import com.android.systemui.screenshot.RequestProcessor;
 import com.android.systemui.screenshot.ScreenshotPolicy;
 import com.android.systemui.screenshot.ScreenshotPolicyImpl;
@@ -29,12 +30,14 @@
 import com.android.systemui.screenshot.ScreenshotSoundControllerImpl;
 import com.android.systemui.screenshot.ScreenshotSoundProvider;
 import com.android.systemui.screenshot.ScreenshotSoundProviderImpl;
+import com.android.systemui.screenshot.ScreenshotViewProxy;
 import com.android.systemui.screenshot.TakeScreenshotService;
 import com.android.systemui.screenshot.appclips.AppClipsScreenshotHelperService;
 import com.android.systemui.screenshot.appclips.AppClipsService;
 
 import dagger.Binds;
 import dagger.Module;
+import dagger.Provides;
 import dagger.multibindings.ClassKey;
 import dagger.multibindings.IntoMap;
 
@@ -81,4 +84,9 @@
     @Binds
     abstract ScreenshotSoundController bindScreenshotSoundController(
             ScreenshotSoundControllerImpl screenshotSoundProviderImpl);
+
+    @Provides
+    static ScreenshotViewProxy.Factory providesScreenshotViewProxyFactory() {
+        return new LegacyScreenshotViewProxy.Factory();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
index d0585d3..20bd7c6 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
@@ -64,8 +64,6 @@
     private String mScrimName;
     private int mTintColor;
     private boolean mBlendWithMainColor = true;
-    private Runnable mChangeRunnable;
-    private Executor mChangeRunnableExecutor;
     private Executor mExecutor;
     private Looper mExecutorLooper;
     @Nullable
@@ -270,9 +268,6 @@
             mDrawable.invalidateSelf();
         }
 
-        if (mChangeRunnable != null) {
-            mChangeRunnableExecutor.execute(mChangeRunnable);
-        }
     }
 
     public int getTint() {
@@ -300,9 +295,6 @@
                 mViewAlpha = alpha;
 
                 mDrawable.setAlpha((int) (255 * alpha));
-                if (mChangeRunnable != null) {
-                    mChangeRunnableExecutor.execute(mChangeRunnable);
-                }
             }
         });
     }
@@ -311,14 +303,6 @@
         return mViewAlpha;
     }
 
-    /**
-     * Sets a callback that is invoked whenever the alpha, color, or tint change.
-     */
-    public void setChangeRunnable(Runnable changeRunnable, Executor changeRunnableExecutor) {
-        mChangeRunnable = changeRunnable;
-        mChangeRunnableExecutor = changeRunnableExecutor;
-    }
-
     @Override
     protected boolean canReceivePointerEvents() {
         return false;
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 6af9b73..92d6ec97 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -55,12 +55,12 @@
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.util.settings.SecureSettings;
 
-import java.util.concurrent.Executor;
-
 import dagger.assisted.Assisted;
 import dagger.assisted.AssistedFactory;
 import dagger.assisted.AssistedInject;
 
+import java.util.concurrent.Executor;
+
 public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController {
     private static final String TAG = "CentralSurfaces.BrightnessController";
     private static final int SLIDER_ANIMATION_DURATION = 3000;
@@ -96,6 +96,7 @@
     };
 
     private volatile boolean mAutomatic;  // Brightness adjusted automatically using ambient light.
+    private boolean mTrackingTouch = false; // Brightness adjusted via touch events.
     private volatile boolean mIsVrModeEnabled;
     private boolean mListening;
     private boolean mExternalChange;
@@ -330,6 +331,7 @@
 
     @Override
     public void onChanged(boolean tracking, int value, boolean stopTracking) {
+        mTrackingTouch = tracking;
         if (mExternalChange) return;
 
         if (mSliderAnimator != null) {
@@ -396,6 +398,12 @@
         }
     }
 
+    private boolean triggeredByBrightnessKey() {
+        // When the brightness mode is manual and the user isn't changing the brightness via the
+        // brightness slider, assume changes are coming from a brightness key.
+        return !mAutomatic && !mTrackingTouch;
+    }
+
     private void updateSlider(float brightnessValue, boolean inVrMode) {
         final float min = mBrightnessMin;
         final float max = mBrightnessMax;
@@ -417,12 +425,13 @@
     }
 
     private void animateSliderTo(int target) {
-        if (!mControlValueInitialized || !mControl.isVisible()) {
+        if (!mControlValueInitialized || !mControl.isVisible() || triggeredByBrightnessKey()) {
             // Don't animate the first value since its default state isn't meaningful to users.
             // We also don't want to animate slider if it's not visible - especially important when
             // two sliders are active at the same time in split shade (one in QS and one in QQS),
             // as this negatively affects transition between them and they share mirror slider -
-            // animating it from two different sources causes janky motion
+            // animating it from two different sources causes janky motion.
+            // Don't animate if the value is changed via the brightness keys of a keyboard.
             mControl.setValue(target);
             mControlValueInitialized = true;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index df845f5..3169e9c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -23,19 +23,20 @@
 import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
+import androidx.compose.ui.platform.ComposeView
+import com.android.compose.theme.PlatformTheme
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.ui.compose.CommunalContainer
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
-import com.android.systemui.compose.ComposeFacade.createCommunalContainer
-import com.android.systemui.compose.ComposeFacade.isComposeAvailable
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import com.android.systemui.util.kotlin.collectFlow
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOf
 
 /**
  * Controller that's responsible for the glanceable hub container view and its touch handling.
@@ -47,6 +48,7 @@
 constructor(
     private val communalInteractor: CommunalInteractor,
     private val communalViewModel: CommunalViewModel,
+    private val dialogFactory: SystemUIDialogFactory,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val shadeInteractor: ShadeInteractor,
     private val powerManager: PowerManager,
@@ -107,8 +109,7 @@
     private var shadeShowing = false
 
     /** Returns a flow that tracks whether communal hub is available. */
-    fun communalAvailable(): Flow<Boolean> =
-        if (isComposeAvailable()) communalInteractor.isCommunalAvailable else flowOf(false)
+    fun communalAvailable(): Flow<Boolean> = communalInteractor.isCommunalAvailable
 
     /**
      * Creates the container view containing the glanceable hub UI.
@@ -118,7 +119,18 @@
     fun initView(
         context: Context,
     ): View {
-        return initView(createCommunalContainer(context, communalViewModel))
+        return initView(
+            ComposeView(context).apply {
+                setContent {
+                    PlatformTheme {
+                        CommunalContainer(
+                            viewModel = communalViewModel,
+                            dialogFactory = dialogFactory,
+                        )
+                    }
+                }
+            }
+        )
     }
 
     /** Override for testing. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 7068f5f..8b791de 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -111,8 +111,6 @@
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.Gefingerpoken;
-import com.android.systemui.animation.ActivityTransitionAnimator;
-import com.android.systemui.animation.TransitionAnimator;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
@@ -219,7 +217,6 @@
 import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
 import com.android.systemui.statusbar.phone.TapAgainViewController;
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
@@ -249,6 +246,7 @@
 import javax.inject.Provider;
 
 import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.flow.Flow;
 
 @SysUISingleton
 public final class NotificationPanelViewController implements ShadeSurface, Dumpable {
@@ -259,10 +257,6 @@
     private static final boolean DEBUG_DRAWABLE = false;
     /** The parallax amount of the quick settings translation when dragging down the panel. */
     public static final float QS_PARALLAX_AMOUNT = 0.175f;
-    private static final long ANIMATION_DELAY_ICON_FADE_IN =
-            ActivityTransitionAnimator.TIMINGS.getTotalDuration()
-                    - CollapsedStatusBarFragment.FADE_IN_DURATION
-                    - CollapsedStatusBarFragment.FADE_IN_DELAY - 48;
     private static final int NO_FIXED_DURATION = -1;
     private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L;
     private static final long SHADE_OPEN_SPRING_BACK_DURATION = 400L;
@@ -358,7 +352,7 @@
     private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate();
     private final NotificationGutsManager mGutsManager;
     private final AlternateBouncerInteractor mAlternateBouncerInteractor;
-    private final QuickSettingsController mQsController;
+    private final QuickSettingsControllerImpl mQsController;
     private final NaturalScrollingSettingObserver mNaturalScrollingSettingObserver;
     private final TouchHandler mTouchHandler = new TouchHandler();
 
@@ -463,7 +457,6 @@
      */
     private float mLinearDarkAmount;
     private boolean mPulsing;
-    private boolean mHideIconsDuringLaunchAnimation = true;
     private int mStackScrollerMeasuringPass;
     /** Non-null if a heads-up notification's position is being tracked. */
     @Nullable
@@ -734,7 +727,7 @@
             TapAgainViewController tapAgainViewController,
             NavigationModeController navigationModeController,
             NavigationBarController navigationBarController,
-            QuickSettingsController quickSettingsController,
+            QuickSettingsControllerImpl quickSettingsController,
             FragmentService fragmentService,
             IStatusBarService statusBarService,
             ContentResolver contentResolver,
@@ -1201,7 +1194,13 @@
         // Primary bouncer->Gone (ensures lockscreen content is not visible on successful auth)
         if (!migrateClocksToBlueprint()) {
             collectFlow(mView, mPrimaryBouncerToGoneTransitionViewModel.getLockscreenAlpha(),
-                    setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
+                    setTransitionAlpha(mNotificationStackScrollLayoutController,
+                            /* excludeNotifications=*/ true), mMainDispatcher);
+            collectFlow(mView, mPrimaryBouncerToGoneTransitionViewModel.getNotificationAlpha(),
+                    (Float alpha) -> {
+                        mNotificationStackScrollLayoutController.setMaxAlphaForKeyguard(alpha,
+                                "mPrimaryBouncerToGoneTransitionViewModel.getNotificationAlpha()");
+                    }, mMainDispatcher);
         }
     }
 
@@ -1291,10 +1290,9 @@
                             mView.getContext().getDisplay());
             mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
             mKeyguardStatusViewController.init();
-        }
 
-        mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
-        mKeyguardStatusViewController.getView().addOnLayoutChangeListener(
+            mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
+            mKeyguardStatusViewController.getView().addOnLayoutChangeListener(
                 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
                     int oldHeight = oldBottom - oldTop;
                     if (v.getHeight() != oldHeight) {
@@ -1302,7 +1300,8 @@
                     }
                 });
 
-        updateClockAppearance();
+            updateClockAppearance();
+        }
     }
 
     @Override
@@ -1329,7 +1328,9 @@
 
     private void onSplitShadeEnabledChanged() {
         mShadeLog.logSplitShadeChanged(mSplitShadeEnabled);
-        mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
+        if (!migrateClocksToBlueprint()) {
+            mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
+        }
         // Reset any left over overscroll state. It is a rare corner case but can happen.
         mQsController.setOverScrollAmount(0);
         mScrimController.setNotificationsOverScrollAmount(0);
@@ -1444,11 +1445,13 @@
         mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
                 mStatusBarStateController.getInterpolatedDozeAmount());
 
-        mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
-                mBarState,
-                false,
-                false,
-                mBarState);
+        if (!migrateClocksToBlueprint()) {
+            mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
+                    mBarState,
+                    false,
+                    false,
+                    mBarState);
+        }
         if (mKeyguardQsUserSwitchController != null) {
             mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility(
                     mBarState,
@@ -1537,12 +1540,13 @@
         mKeyguardBottomArea = keyguardBottomArea;
     }
 
-    @Override
+    /** Sets a listener to be notified when the shade starts opening or finishes closing. */
     public void setOpenCloseListener(OpenCloseListener openCloseListener) {
+        SceneContainerFlag.assertInLegacyMode();
         mOpenCloseListener = openCloseListener;
     }
 
-    @Override
+    /** Sets a listener to be notified when touch tracking begins. */
     public void setTrackingStartedListener(TrackingStartedListener trackingStartedListener) {
         mTrackingStartedListener = trackingStartedListener;
     }
@@ -1667,13 +1671,11 @@
             mKeyguardStatusViewController.setLockscreenClockY(
                     mClockPositionAlgorithm.getExpandedPreferredClockY());
         }
-        if (keyguardBottomAreaRefactor()) {
-            mKeyguardInteractor.setClockPosition(
-                mClockPositionResult.clockX, mClockPositionResult.clockY);
-        } else {
+        if (!(migrateClocksToBlueprint() || keyguardBottomAreaRefactor())) {
             mKeyguardBottomAreaInteractor.setClockPosition(
                 mClockPositionResult.clockX, mClockPositionResult.clockY);
         }
+
         boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
         boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
 
@@ -1752,12 +1754,11 @@
 
     private void updateKeyguardStatusViewAlignment(boolean animate) {
         boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
-        ConstraintLayout layout;
         if (migrateClocksToBlueprint()) {
-            layout = mKeyguardViewConfigurator.getKeyguardRootView();
-        } else {
-            layout = mNotificationContainerParent;
+            mKeyguardInteractor.setClockShouldBeCentered(shouldBeCentered);
+            return;
         }
+        ConstraintLayout layout = mNotificationContainerParent;
         mKeyguardStatusViewController.updateAlignment(
                 layout, mSplitShadeEnabled, shouldBeCentered, animate);
         mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(shouldBeCentered));
@@ -1981,7 +1982,6 @@
         mNotificationStackScrollLayoutController.resetScrollPosition();
     }
 
-    @Override
     public void collapse(boolean animate, boolean delayed, float speedUpFactor) {
         boolean waiting = false;
         if (animate && !isFullyCollapsed()) {
@@ -1999,7 +1999,6 @@
         }
     }
 
-    @Override
     public void collapse(boolean delayed, float speedUpFactor) {
         if (!canBeCollapsed()) {
             return;
@@ -2047,7 +2046,6 @@
         mView.animate().cancel();
     }
 
-    @Override
     public void expandToQs() {
         if (mQsController.isExpansionEnabled()) {
             mQsController.setExpandImmediate(true);
@@ -2606,7 +2604,6 @@
         return maxHeight;
     }
 
-    @Override
     public boolean isExpandingOrCollapsing() {
         float lockscreenExpansionProgress = mQsController.getLockscreenShadeDragProgress();
         return mIsExpandingOrCollapsing
@@ -2722,7 +2719,8 @@
                 && !mQsController.getFullyExpanded()) {
                 alpha *= mClockPositionResult.clockAlpha;
             }
-            mNotificationStackScrollLayoutController.setMaxAlphaForExpansion(alpha);
+            mNotificationStackScrollLayoutController.setMaxAlphaForKeyguard(alpha,
+                    "NPVC.updateNotificationTranslucency()");
         }
     }
 
@@ -2773,7 +2771,9 @@
     }
 
     private void onExpandingFinished() {
-        mNotificationStackScrollLayoutController.onExpansionStopped();
+        if (!SceneContainerFlag.isEnabled()) {
+            mNotificationStackScrollLayoutController.onExpansionStopped();
+        }
         mHeadsUpManager.onExpandingFinished();
         mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed());
         mIsExpandingOrCollapsing = false;
@@ -2814,7 +2814,6 @@
         mQsController.setListening(listening);
     }
 
-    @Override
     public void expand(boolean animate) {
         if (isFullyCollapsed() || isCollapsing()) {
             mInstantExpanding = true;
@@ -3068,7 +3067,9 @@
     }
 
     private void onClosingFinished() {
-        mOpenCloseListener.onClosingFinished();
+        if (mOpenCloseListener != null) {
+            mOpenCloseListener.onClosingFinished();
+        }
         setClosingWithAlphaFadeout(false);
         mMediaHierarchyManager.closeGuts();
     }
@@ -3087,7 +3088,9 @@
             // The expandedHeight is always the full panel Height when bypassing
             expandedHeight = getMaxPanelHeight();
         }
-        mNotificationStackScrollLayoutController.setExpandedHeight(expandedHeight);
+        if (!SceneContainerFlag.isEnabled()) {
+            mNotificationStackScrollLayoutController.setExpandedHeight(expandedHeight);
+        }
         updateKeyguardBottomAreaAlpha();
         updateStatusBarIcons();
     }
@@ -3151,10 +3154,9 @@
         return mUnlockedScreenOffAnimationController.isAnimationPlaying();
     }
 
-    @Override
     public boolean shouldHideStatusBarIconsWhenExpanded() {
         if (isLaunchingActivity()) {
-            return mHideIconsDuringLaunchAnimation;
+            return false;
         }
         if (mHeadsUpAppearanceController != null
                 && mHeadsUpAppearanceController.shouldBeVisible()) {
@@ -3252,18 +3254,6 @@
     }
 
     @Override
-    public void applyLaunchAnimationProgress(float linearProgress) {
-        boolean hideIcons = TransitionAnimator.getProgress(ActivityTransitionAnimator.TIMINGS,
-                linearProgress, ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
-        if (hideIcons != mHideIconsDuringLaunchAnimation) {
-            mHideIconsDuringLaunchAnimation = hideIcons;
-            if (!hideIcons) {
-                mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */);
-            }
-        }
-    }
-
-    @Override
     public void performHapticFeedback(int constant) {
         mVibratorHelper.performHapticFeedback(mView, constant);
     }
@@ -3331,6 +3321,9 @@
         /** Updates the views to the initial state for the fold to AOD animation. */
         @Override
         public void prepareFoldToAodAnimation() {
+            if (migrateClocksToBlueprint()) {
+                return;
+            }
             // Force show AOD UI even if we are not locked
             showAodUi();
 
@@ -3352,6 +3345,9 @@
         @Override
         public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
                 Runnable cancelAction) {
+            if (migrateClocksToBlueprint()) {
+                return;
+            }
             final ViewPropertyAnimator viewAnimator = mView.animate();
             viewAnimator.cancel();
             viewAnimator
@@ -3387,6 +3383,9 @@
         /** Cancels fold to AOD transition and resets view state. */
         @Override
         public void cancelFoldToAodAnimation() {
+            if (migrateClocksToBlueprint()) {
+                return;
+            }
             cancelAnimation();
             resetAlpha();
             resetTranslation();
@@ -3465,7 +3464,6 @@
         ipw.print("mInterpolatedDarkAmount="); ipw.println(mInterpolatedDarkAmount);
         ipw.print("mLinearDarkAmount="); ipw.println(mLinearDarkAmount);
         ipw.print("mPulsing="); ipw.println(mPulsing);
-        ipw.print("mHideIconsDuringLaunchAnimation="); ipw.println(mHideIconsDuringLaunchAnimation);
         ipw.print("mStackScrollerMeasuringPass="); ipw.println(mStackScrollerMeasuringPass);
         ipw.print("mPanelAlpha="); ipw.println(mPanelAlpha);
         ipw.print("mBottomAreaShadeAlpha="); ipw.println(mBottomAreaShadeAlpha);
@@ -4007,11 +4005,15 @@
     }
 
     @Override
+    public Flow<Float> getLegacyPanelExpansion() {
+        return  mShadeRepository.getLegacyShadeExpansion();
+    }
+
+    @Override
     public boolean isFullyExpanded() {
         return mExpandedHeight >= getMaxPanelTransitionDistance();
     }
 
-    @Override
     public boolean isShadeFullyExpanded() {
         if (mBarState == SHADE) {
             return isFullyExpanded();
@@ -4042,7 +4044,6 @@
         return !isFullyCollapsed() && !isTracking() && !isClosing();
     }
 
-    @Override
     public void instantCollapse() {
         abortAnimations();
         setExpandedFraction(0f);
@@ -4157,7 +4158,6 @@
         return mShadeRepository.getLegacyIsClosing().getValue();
     }
 
-    @Override
     public void collapseWithDuration(int animationDuration) {
         mFixedDuration = animationDuration;
         collapse(false /* delayed */, 1.0f /* speedUpFactor */);
@@ -4463,11 +4463,13 @@
                 }
             }
 
-            mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
-                    statusBarState,
-                    keyguardFadingAway,
-                    goingToFullShade,
-                    mBarState);
+            if (!migrateClocksToBlueprint()) {
+                mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
+                        statusBarState,
+                        keyguardFadingAway,
+                        goingToFullShade,
+                        mBarState);
+            }
 
             if (!keyguardBottomAreaRefactor()) {
                 setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
@@ -4699,7 +4701,9 @@
             if (mSplitShadeEnabled && !isKeyguardShowing()) {
                 mQsController.setExpandImmediate(true);
             }
-            mOpenCloseListener.onOpenStarted();
+            if (mOpenCloseListener != null) {
+                mOpenCloseListener.onOpenStarted();
+            }
         }
         if (state == STATE_CLOSED) {
             mQsController.setExpandImmediate(false);
@@ -4724,9 +4728,17 @@
 
     private Consumer<Float> setTransitionAlpha(
             NotificationStackScrollLayoutController stackScroller) {
+        return setTransitionAlpha(stackScroller, /* excludeNotifications= */ false);
+    }
+
+    private Consumer<Float> setTransitionAlpha(
+            NotificationStackScrollLayoutController stackScroller,
+            boolean excludeNotifications) {
         return (Float alpha) -> {
             mKeyguardStatusViewController.setAlpha(alpha);
-            stackScroller.setMaxAlphaForExpansion(alpha);
+            if (!excludeNotifications) {
+                stackScroller.setMaxAlphaForKeyguard(alpha, "NPVC.setTransitionAlpha()");
+            }
 
             if (keyguardBottomAreaRefactor()) {
                 mKeyguardInteractor.setAlpha(alpha);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 8f9cef3..f7fed53 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -60,7 +60,6 @@
 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.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.scene.ui.view.WindowRootViewComponent;
 import com.android.systemui.settings.UserTracker;
@@ -507,7 +506,7 @@
 
     private void applyFitsSystemWindows(NotificationShadeWindowState state) {
         boolean fitsSystemWindows = !state.isKeyguardShowingAndNotOccluded();
-        if (!SceneContainerFlag.isEnabled() && mWindowRootView != null
+        if (mWindowRootView != null
                 && mWindowRootView.getFitsSystemWindows() != fitsSystemWindows) {
             mWindowRootView.setFitsSystemWindows(fitsSystemWindows);
             mWindowRootView.requestApplyInsets();
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt b/packages/SystemUI/src/com/android/systemui/shade/OpenCloseListener.kt
similarity index 68%
copy from packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
copy to packages/SystemUI/src/com/android/systemui/shade/OpenCloseListener.kt
index 8ad0a08..108dd478 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/OpenCloseListener.kt
@@ -14,8 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel.component.mediaoutput
+package com.android.systemui.shade
 
-import dagger.Module
+/** Listens for when shade begins opening or finishes closing. */
+interface OpenCloseListener {
+    /** Called when the shade finishes closing. */
+    fun onClosingFinished()
 
-@Module interface MediaOutputModule
+    /** Called when the shade starts opening. */
+    fun onOpenStarted()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.kt b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.kt
new file mode 100644
index 0000000..c96a339
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+interface QuickSettingsController {
+    /** Returns whether or not QuickSettings is expanded. */
+    val expanded: Boolean
+
+    /** Returns whether or not QuickSettings is being customized. */
+    val isCustomizing: Boolean
+
+    /** Returns Whether we should intercept a gesture to open Quick Settings. */
+    @Deprecated("specific to legacy touch handling")
+    fun shouldQuickSettingsIntercept(x: Float, y: Float, yDiff: Float): Boolean
+
+    /** Closes the Qs customizer. */
+    fun closeQsCustomizer()
+
+    /**
+     * This method closes QS but in split shade it should be used only in special cases: to make
+     * sure QS closes when shade is closed as well. Otherwise it will result in QS disappearing from
+     * split shade
+     */
+    @Deprecated("specific to legacy split shade") fun closeQs()
+
+    /** Calculate top padding for notifications */
+    @Deprecated("specific to legacy DebugDrawable")
+    fun calculateNotificationsTopPadding(
+        isShadeExpanding: Boolean,
+        keyguardNotificationStaticPadding: Int,
+        expandedFraction: Float,
+    ): Float
+
+    /** Calculate height of QS panel */
+    @Deprecated("specific to legacy DebugDrawable")
+    fun calculatePanelHeightExpanded(stackScrollerPadding: Int): Int
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
rename to packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index e7f9700..fd68c56 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -76,6 +76,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.shade.data.repository.ShadeRepository;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
@@ -118,7 +119,7 @@
  * TODO (b/264460656) make this dumpable
  */
 @SysUISingleton
-public class QuickSettingsController implements Dumpable {
+public class QuickSettingsControllerImpl implements QuickSettingsController, Dumpable {
     public static final String TAG = "QuickSettingsController";
 
     public static final int SHADE_BACK_ANIM_SCALE_MULTIPLIER = 100;
@@ -252,7 +253,7 @@
 
     /**
      * The window width currently in effect -- used together with
-     * {@link QuickSettingsController#mCachedGestureInsets} to decide whether a back gesture should
+     * {@link QuickSettingsControllerImpl#mCachedGestureInsets} to decide whether a back gesture should
      * receive a horizontal swipe inwards from the left/right vertical edge of the screen.
      * We cache this on ACTION_DOWN, and query it during both ACTION_DOWN and ACTION_MOVE events.
      */
@@ -282,12 +283,6 @@
     /** The duration of the notification bounds animation. */
     private long mNotificationBoundsAnimationDuration;
 
-    /** TODO(b/273591201): remove after bug resolved */
-    private int mLastClippingTopBound;
-    private int mLastNotificationsTopPadding;
-    private int mLastNotificationsClippingTopBound;
-    private int mLastNotificationsClippingTopBoundNssl;
-
     private final Region mInterceptRegion = new Region();
     /** The end bounds of a clipping animation. */
     private final Rect mClippingAnimationEndBounds = new Rect();
@@ -310,7 +305,7 @@
     private final QS.ScrollListener mQsScrollListener = this::onScroll;
 
     @Inject
-    public QuickSettingsController(
+    public QuickSettingsControllerImpl(
             Lazy<NotificationPanelViewController> panelViewControllerLazy,
             NotificationPanelView panelView,
             QsFrameTranslateController qsFrameTranslateController,
@@ -405,23 +400,23 @@
         mQs = qs;
     }
 
-    public void setExpansionHeightListener(ExpansionHeightListener listener) {
+    void setExpansionHeightListener(ExpansionHeightListener listener) {
         mExpansionHeightListener = listener;
     }
 
-    public void setQsStateUpdateListener(QsStateUpdateListener listener) {
+    void setQsStateUpdateListener(QsStateUpdateListener listener) {
         mQsStateUpdateListener = listener;
     }
 
-    public void setApplyClippingImmediatelyListener(ApplyClippingImmediatelyListener listener) {
+    void setApplyClippingImmediatelyListener(ApplyClippingImmediatelyListener listener) {
         mApplyClippingImmediatelyListener = listener;
     }
 
-    public void setFlingQsWithoutClickListener(FlingQsWithoutClickListener listener) {
+    void setFlingQsWithoutClickListener(FlingQsWithoutClickListener listener) {
         mFlingQsWithoutClickListener = listener;
     }
 
-    public void setExpansionHeightSetToMaxListener(ExpansionHeightSetToMaxListener callback) {
+    void setExpansionHeightSetToMaxListener(ExpansionHeightSetToMaxListener callback) {
         mExpansionHeightSetToMaxListener = callback;
     }
 
@@ -513,15 +508,11 @@
                 && mPanelView.getRootWindowInsets().isVisible(ime());
     }
 
-    public boolean isExpansionEnabled() {
+    boolean isExpansionEnabled() {
         return mExpansionEnabledPolicy && mExpansionEnabledAmbient
             && !isRemoteInputActiveWithKeyboardUp();
     }
 
-    public float getTransitioningToFullShadeProgress() {
-        return mTransitioningToFullShadeProgress;
-    }
-
     /** */
     @VisibleForTesting
     boolean isExpandImmediate() {
@@ -542,7 +533,7 @@
      *  Computes (and caches) the gesture insets for the current window. Intended to be called
      *  on ACTION_DOWN, and safely queried repeatedly thereafter during ACTION_MOVE events.
      */
-    public void updateGestureInsetsCache() {
+    void updateGestureInsetsCache() {
         WindowManager wm = this.mPanelView.getContext().getSystemService(WindowManager.class);
         WindowMetrics windowMetrics = wm.getCurrentWindowMetrics();
         mCachedGestureInsets = windowMetrics.getWindowInsets().getInsets(
@@ -554,7 +545,7 @@
      *  Returns whether x coordinate lies in the vertical edges of the screen
      *  (the only place where a back gesture can be initiated).
      */
-    public boolean shouldBackBypassQuickSettings(float touchX) {
+    boolean shouldBackBypassQuickSettings(float touchX) {
         return (touchX < mCachedGestureInsets.left)
                 || (touchX > mCachedWindowWidth - mCachedGestureInsets.right);
     }
@@ -598,6 +589,7 @@
         return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag;
     }
 
+    @Override
     public boolean getExpanded() {
         return mShadeRepository.getLegacyIsQsExpanded().getValue();
     }
@@ -607,7 +599,7 @@
         return mShadeRepository.getLegacyQsTracking().getValue();
     }
 
-    public boolean getFullyExpanded() {
+    boolean getFullyExpanded() {
         return mFullyExpanded;
     }
 
@@ -629,27 +621,28 @@
         return mQs != null;
     }
 
+    @Override
     public boolean isCustomizing() {
         return isQsFragmentCreated() && mQs.isCustomizing();
     }
 
-    public float getExpansionHeight() {
+    float getExpansionHeight() {
         return mExpansionHeight;
     }
 
-    public boolean getExpandedWhenExpandingStarted() {
+    boolean getExpandedWhenExpandingStarted() {
         return mExpandedWhenExpandingStarted;
     }
 
-    public int getMinExpansionHeight() {
+    int getMinExpansionHeight() {
         return mMinExpansionHeight;
     }
 
-    public boolean isFullyExpandedAndTouchesDisallowed() {
+    boolean isFullyExpandedAndTouchesDisallowed() {
         return isQsFragmentCreated() && getFullyExpanded() && disallowTouches();
     }
 
-    public int getMaxExpansionHeight() {
+    int getMaxExpansionHeight() {
         return mMaxExpansionHeight;
     }
 
@@ -660,13 +653,14 @@
         return !mTouchAboveFalsingThreshold;
     }
 
-    public int getFalsingThreshold() {
+    int getFalsingThreshold() {
         return mFalsingThreshold;
     }
 
     /**
      * Returns Whether we should intercept a gesture to open Quick Settings.
      */
+    @Override
     public boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
         boolean keyguardShowing = mBarState == KEYGUARD;
         if (!isExpansionEnabled() || mCollapsedOnDown || (keyguardShowing
@@ -723,7 +717,7 @@
      * @param downY the y location where the touch started
      * Returns true if the panel could be collapsed because it stared on QQS
      */
-    public boolean canPanelCollapseOnQQS(float downX, float downY) {
+    boolean canPanelCollapseOnQQS(float downX, float downY) {
         if (mCollapsedOnDown || mBarState == KEYGUARD || getExpanded()) {
             return false;
         }
@@ -733,6 +727,7 @@
     }
 
     /** Closes the Qs customizer. */
+    @Override
     public void closeQsCustomizer() {
         if (mQs != null) {
             mQs.closeCustomizer();
@@ -740,7 +735,7 @@
     }
 
     /** Returns whether touches from the notification panel should be disallowed */
-    public boolean disallowTouches() {
+    boolean disallowTouches() {
         if (mQs != null) {
             return mQs.disallowPanelTouches();
         } else {
@@ -760,15 +755,11 @@
         }
     }
 
-    public void setDozing(boolean dozing) {
+    void setDozing(boolean dozing) {
         mDozing = dozing;
     }
 
-    /**
-     * This method closes QS but in split shade it should be used only in special cases: to make
-     * sure QS closes when shade is closed as well. Otherwise it will result in QS disappearing
-     * from split shade
-     */
+    @Override
     public void closeQs() {
         if (mSplitShadeEnabled) {
             mShadeLog.d("Closing QS while in split shade");
@@ -800,7 +791,7 @@
     }
 
     /** update Qs height state */
-    public void setExpansionHeight(float height) {
+    void setExpansionHeight(float height) {
         int maxHeight = getMaxExpansionHeight();
         height = Math.min(Math.max(
                 height, getMinExpansionHeight()), maxHeight);
@@ -823,7 +814,7 @@
     }
 
     /** */
-    public void setHeightOverrideToDesiredHeight() {
+    void setHeightOverrideToDesiredHeight() {
         if (isSizeChangeAnimationRunning() && isQsFragmentCreated()) {
             mQs.setHeightOverride(mQs.getDesiredHeight());
         }
@@ -925,7 +916,7 @@
     }
 
     /** Sets Qs ScrimEnabled and updates QS state. */
-    public void setScrimEnabled(boolean scrimEnabled) {
+    void setScrimEnabled(boolean scrimEnabled) {
         boolean changed = mScrimEnabled != scrimEnabled;
         mScrimEnabled = scrimEnabled;
         if (changed) {
@@ -974,7 +965,9 @@
         // Reset scroll position and apply that position to the expanded height.
         float height = mExpansionHeight;
         setExpansionHeight(height);
-        mNotificationStackScrollLayoutController.checkSnoozeLeavebehind();
+        if (!SceneContainerFlag.isEnabled()) {
+            mNotificationStackScrollLayoutController.checkSnoozeLeavebehind();
+        }
 
         // When expanding QS, let's authenticate the user if possible,
         // this will speed up notification actions.
@@ -1001,7 +994,7 @@
     }
 
     /** update expanded state of QS */
-    public void updateExpansion() {
+    void updateExpansion() {
         if (mQs == null) return;
         final float squishiness;
         if ((isExpandImmediate() || getExpanded()) && !mSplitShadeEnabled) {
@@ -1059,13 +1052,13 @@
         // mTransitioningToFullShadeProgress > 0 means we're doing regular lockscreen to shade
         // transition. If that's not the case we should follow QS expansion fraction for when
         // user is pulling from the same top to go directly to expanded QS
-        return getTransitioningToFullShadeProgress() > 0
+        return mTransitioningToFullShadeProgress > 0
                 ? mLockscreenShadeTransitionController.getQSDragProgress()
                 : computeExpansionFraction();
     }
 
     /** */
-    public void updateExpansionEnabledAmbient() {
+    void updateExpansionEnabledAmbient() {
         final float scrollRangeToTop = mAmbientState.getTopPadding() - mQuickQsHeaderHeight;
         mExpansionEnabledAmbient = mSplitShadeEnabled
                 || (mAmbientState.getScrollY() <= scrollRangeToTop);
@@ -1087,7 +1080,7 @@
     }
 
     /** Calculate fraction of current QS expansion state */
-    public float computeExpansionFraction() {
+    float computeExpansionFraction() {
         if (mAnimatingHiddenFromCollapsed) {
             // When hiding QS from collapsed state, the expansion can sometimes temporarily
             // be larger than 0 because of the timing, leading to flickers.
@@ -1118,8 +1111,10 @@
     }
 
     /** Called when shade starts expanding. */
-    public void onExpandingStarted(boolean qsFullyExpanded) {
-        mNotificationStackScrollLayoutController.onExpansionStarted();
+    void onExpandingStarted(boolean qsFullyExpanded) {
+        if (!SceneContainerFlag.isEnabled()) {
+            mNotificationStackScrollLayoutController.onExpansionStarted();
+        }
         mExpandedWhenExpandingStarted = qsFullyExpanded;
         mMediaHierarchyManager.setCollapsingShadeFromQS(mExpandedWhenExpandingStarted
                 /* We also start expanding when flinging closed Qs. Let's exclude that */
@@ -1369,7 +1364,7 @@
         }
     }
 
-    /** Calculate top padding for notifications */
+    @Override
     public float calculateNotificationsTopPadding(boolean isShadeExpanding,
             int keyguardNotificationStaticPadding, float expandedFraction) {
         float topPadding;
@@ -1410,7 +1405,7 @@
         }
     }
 
-    /** Calculate height of QS panel */
+    @Override
     public int calculatePanelHeightExpanded(int stackScrollerPadding) {
         float
                 notificationHeight =
@@ -1582,7 +1577,6 @@
         }
     }
 
-    @VisibleForTesting
     boolean isTrackingBlocked() {
         return mConflictingExpansionGesture && getExpanded();
     }
@@ -1597,7 +1591,7 @@
     }
 
     /** handles touches in Qs panel area */
-    public boolean handleTouch(MotionEvent event, boolean isFullyCollapsed,
+    boolean handleTouch(MotionEvent event, boolean isFullyCollapsed,
             boolean isShadeOrQsHeightAnimationRunning) {
         if (isSplitShadeAndTouchXOutsideQs(event.getX())) {
             return false;
@@ -1753,7 +1747,7 @@
     }
 
     /** intercepts touches on Qs panel area. */
-    public boolean onIntercept(MotionEvent event) {
+    boolean onIntercept(MotionEvent event) {
         int pointerIndex = event.findPointerIndex(mTrackingPointer);
         if (pointerIndex < 0) {
             pointerIndex = 0;
@@ -1864,7 +1858,7 @@
      *
      * @param animateAway Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
      */
-    public void animateCloseQs(boolean animateAway) {
+    void animateCloseQs(boolean animateAway) {
         if (mExpansionAnimator != null) {
             if (!mAnimatorExpand) {
                 return;
@@ -1883,7 +1877,7 @@
     }
 
     /** @see #flingQs(float, int, Runnable, boolean) */
-    public void flingQs(float vel, int type) {
+    void flingQs(float vel, int type) {
         flingQs(vel, type, null /* onFinishRunnable */, false /* isClick */);
     }
 
@@ -2141,14 +2135,6 @@
         ipw.println(mNotificationBoundsAnimationDelay);
         ipw.print("mNotificationBoundsAnimationDuration=");
         ipw.println(mNotificationBoundsAnimationDuration);
-        ipw.print("mLastClippingTopBound=");
-        ipw.println(mLastClippingTopBound);
-        ipw.print("mLastNotificationsTopPadding=");
-        ipw.println(mLastNotificationsTopPadding);
-        ipw.print("mLastNotificationsClippingTopBound=");
-        ipw.println(mLastNotificationsClippingTopBound);
-        ipw.print("mLastNotificationsClippingTopBoundNssl=");
-        ipw.println(mLastNotificationsClippingTopBoundNssl);
         ipw.print("mInterceptRegion=");
         ipw.println(mInterceptRegion);
         ipw.print("mClippingAnimationEndBounds=");
@@ -2158,7 +2144,7 @@
     }
 
     /** */
-    public FragmentHostManager.FragmentListener getQsFragmentListener() {
+    FragmentHostManager.FragmentListener getQsFragmentListener() {
         return new QsFragmentListener();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt
new file mode 100644
index 0000000..b8250cc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.qs.QSContainerController
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import javax.inject.Inject
+
+@SysUISingleton
+class QuickSettingsControllerSceneImpl
+@Inject
+constructor(
+    private val shadeInteractor: ShadeInteractor,
+    private val qsSceneAdapter: QSSceneAdapter,
+    private val qsContainerController: QSContainerController,
+) : QuickSettingsController {
+
+    override val expanded: Boolean
+        get() = shadeInteractor.isQsExpanded.value
+
+    override val isCustomizing: Boolean
+        get() = qsSceneAdapter.isCustomizing.value
+
+    @Deprecated("specific to legacy touch handling")
+    override fun shouldQuickSettingsIntercept(x: Float, y: Float, yDiff: Float): Boolean {
+        throw UnsupportedOperationException()
+    }
+
+    override fun closeQsCustomizer() {
+        qsContainerController.setCustomizerShowing(false)
+    }
+
+    @Deprecated("specific to legacy split shade")
+    override fun closeQs() {
+        // Do nothing
+    }
+
+    @Deprecated("specific to legacy DebugDrawable")
+    override fun calculateNotificationsTopPadding(
+        isShadeExpanding: Boolean,
+        keyguardNotificationStaticPadding: Int,
+        expandedFraction: Float
+    ): Float {
+        throw UnsupportedOperationException()
+    }
+
+    @Deprecated("specific to legacy DebugDrawable")
+    override fun calculatePanelHeightExpanded(stackScrollerPadding: Int): Int {
+        throw UnsupportedOperationException()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index ec4b23a..0a57b64 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -78,6 +78,14 @@
      */
     void animateCollapseShade(int flags, boolean force, boolean delayed, float speedUpFactor);
 
+    /**
+     * Collapses the shade with an animation duration in milliseconds.
+     *
+     * @deprecated use animateCollapseShade with a speed up factor instead
+     */
+    @Deprecated
+    void collapseWithDuration(int animationDuration);
+
     /** Expand the shade with an animation. */
     void animateExpandShade();
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
index 08a0c93..093690f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
@@ -33,6 +33,7 @@
         delayed: Boolean,
         speedUpFactor: Float
     ) {}
+    override fun collapseWithDuration(animationDuration: Int) {}
     override fun animateExpandShade() {}
     override fun animateExpandQs() {}
     override fun postAnimateCollapseShade() {}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index ea41912..d99d607 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -32,6 +32,7 @@
 import com.android.systemui.log.dagger.ShadeTouchLog;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -98,6 +99,7 @@
                 statusBarKeyguardViewManager,
                 notificationShadeWindowController,
                 assistManagerLazy);
+        SceneContainerFlag.assertInLegacyMode();
         mCommandQueue = commandQueue;
         mMainExecutor = mainExecutor;
         mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
@@ -145,6 +147,11 @@
     }
 
     @Override
+    public void collapseWithDuration(int animationDuration) {
+        mNpvc.get().collapseWithDuration(animationDuration);
+    }
+
+    @Override
     protected void expandToNotifications() {
         getNpvc().expandToNotifications();
     }
@@ -219,7 +226,6 @@
         }
     }
 
-
     @Override
     public void collapseShade(boolean animate) {
         if (animate) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index 6a2a6a4..177c3db 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.shade
 
 import android.view.MotionEvent
+import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.assist.AssistManager
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -25,7 +26,7 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.dagger.ShadeTouchLog
 import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.TransitionKeys.CollapseShadeInstantly
 import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse
 import com.android.systemui.shade.ShadeController.ShadeVisibilityListener
@@ -88,15 +89,14 @@
 
     override fun isShadeFullyOpen(): Boolean = shadeInteractor.isAnyFullyExpanded.value
 
-    override fun isExpandingOrCollapsing(): Boolean =
-        shadeInteractor.anyExpansion.value > 0f && shadeInteractor.anyExpansion.value < 1f
+    override fun isExpandingOrCollapsing(): Boolean = shadeInteractor.isUserInteracting.value
 
     override fun instantExpandShade() {
         // Do nothing
     }
 
     override fun instantCollapseShade() {
-        // TODO(b/315921512) add support for instant transition
+        // TODO(b/325602936) add support for instant transition
         sceneInteractor.changeScene(
             getCollapseDestinationScene(),
             "hide shade",
@@ -121,7 +121,7 @@
             // release focus immediately to kick off focus change transition
             notificationShadeWindowController.setNotificationShadeFocusable(false)
             notificationStackScrollLayout.cancelExpandHelper()
-            sceneInteractor.changeScene(SceneKey.Shade, "ShadeController.animateExpandShade")
+            sceneInteractor.changeScene(Scenes.Shade, "ShadeController.animateExpandShade")
             if (delayed) {
                 scope.launch {
                     delay(125)
@@ -133,6 +133,11 @@
         }
     }
 
+    override fun collapseWithDuration(animationDuration: Int) {
+        // TODO(b/300258424) inline this. The only caller uses the default duration.
+        animateCollapseShade()
+    }
+
     private fun animateCollapseShadeInternal() {
         sceneInteractor.changeScene(
             getCollapseDestinationScene(),
@@ -143,9 +148,9 @@
 
     private fun getCollapseDestinationScene(): SceneKey {
         return if (deviceEntryInteractor.isDeviceEntered.value) {
-            SceneKey.Gone
+            Scenes.Gone
         } else {
-            SceneKey.Lockscreen
+            Scenes.Lockscreen
         }
     }
 
@@ -183,11 +188,11 @@
     }
 
     override fun expandToNotifications() {
-        sceneInteractor.changeScene(SceneKey.Shade, "ShadeController.animateExpandShade")
+        sceneInteractor.changeScene(Scenes.Shade, "ShadeController.animateExpandShade")
     }
 
     override fun expandToQs() {
-        sceneInteractor.changeScene(SceneKey.QuickSettings, "ShadeController.animateExpandQs")
+        sceneInteractor.changeScene(Scenes.QuickSettings, "ShadeController.animateExpandQs")
     }
 
     override fun setVisibilityListener(listener: ShadeVisibilityListener) {
@@ -237,7 +242,7 @@
     }
 
     override fun isExpandedVisible(): Boolean {
-        return sceneInteractor.currentScene.value != SceneKey.Gone
+        return sceneInteractor.currentScene.value != Scenes.Gone
     }
 
     override fun onStatusBarTouch(event: MotionEvent) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
index 4054a86..25e27ae 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.shade.data.repository.PrivacyChipRepositoryImpl
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.shade.data.repository.ShadeRepositoryImpl
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorEmptyImpl
 import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
@@ -66,5 +67,11 @@
 
     @Binds
     @SysUISingleton
+    abstract fun bindsPanelExpansionInteractor(
+        sbai: ShadeViewControllerEmptyImpl
+    ): PanelExpansionInteractor
+
+    @Binds
+    @SysUISingleton
     abstract fun bindsPrivacyChipRepository(impl: PrivacyChipRepositoryImpl): PrivacyChipRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLockscreenInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLockscreenInteractor.kt
index a9ba6f9..859fce5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLockscreenInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLockscreenInteractor.kt
@@ -27,9 +27,6 @@
      */
     @Deprecated("Use ShadeInteractor instead") fun expandToNotifications()
 
-    /** Returns whether the shade is expanding or collapsing itself or quick settings. */
-    val isExpandingOrCollapsing: Boolean
-
     /**
      * Returns whether the shade height is greater than zero (i.e. partially or fully expanded),
      * there is a HUN, the shade is animating, or the shade is instantly expanding.
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 5632766..3e9a32b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -17,12 +17,16 @@
 package com.android.systemui.shade
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.qs.QSContainerController
+import com.android.systemui.qs.ui.adapter.QSSceneAdapterImpl
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.shade.data.repository.PrivacyChipRepository
 import com.android.systemui.shade.data.repository.PrivacyChipRepositoryImpl
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.shade.data.repository.ShadeRepositoryImpl
 import com.android.systemui.shade.domain.interactor.BaseShadeInteractor
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractorImpl
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorSceneContainerImpl
@@ -111,6 +115,40 @@
                 sceneContainerOff.get()
             }
         }
+
+        @Provides
+        @SysUISingleton
+        fun providePanelExpansionInteractor(
+            sceneContainerFlags: SceneContainerFlags,
+            sceneContainerOn: Provider<PanelExpansionInteractorImpl>,
+            sceneContainerOff: Provider<NotificationPanelViewController>
+        ): PanelExpansionInteractor {
+            return if (sceneContainerFlags.isEnabled()) {
+                sceneContainerOn.get()
+            } else {
+                sceneContainerOff.get()
+            }
+        }
+
+        @Provides
+        @SysUISingleton
+        fun provideQuickSettingsController(
+            sceneContainerFlags: SceneContainerFlags,
+            sceneContainerOn: Provider<QuickSettingsControllerSceneImpl>,
+            sceneContainerOff: Provider<QuickSettingsControllerImpl>,
+        ): QuickSettingsController {
+            return if (sceneContainerFlags.isEnabled()) {
+                sceneContainerOn.get()
+            } else {
+                sceneContainerOff.get()
+            }
+        }
+
+        @Provides
+        @SysUISingleton
+        fun providesQSContainerController(impl: QSSceneAdapterImpl): QSContainerController {
+            return impl
+        }
     }
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
index 941c6f3..5c276b1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.shade
 
 import android.view.ViewPropertyAnimator
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
 import com.android.systemui.statusbar.GestureRecorder
 import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -26,7 +27,11 @@
  * this class. If any method in this class is needed outside of CentralSurfacesImpl, it must be
  * pulled up into ShadeViewController.
  */
-interface ShadeSurface : ShadeViewController, ShadeBackActionInteractor, ShadeLockscreenInteractor {
+interface ShadeSurface :
+    ShadeViewController,
+    ShadeBackActionInteractor,
+    ShadeLockscreenInteractor,
+    PanelExpansionInteractor {
     /** Initialize objects instead of injecting to avoid circular dependencies. */
     fun initDependencies(
         centralSurfaces: CentralSurfaces,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 44c6a82..de21a73 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -31,43 +31,12 @@
  * @see NotificationPanelViewController
  */
 interface ShadeViewController {
-    /** Expand the shade either animated or instantly. */
-    fun expand(animate: Boolean)
-
-    /** Animates to an expanded shade with QS expanded. If the shade starts expanded, expands QS. */
-    fun expandToQs()
-
-    /** Returns whether the shade is expanding or collapsing itself or quick settings. */
-    val isExpandingOrCollapsing: Boolean
-
     /**
      * Returns whether the shade height is greater than zero or the shade is expecting a synthesized
      * down event.
      */
     val isPanelExpanded: Boolean
 
-    /** Returns whether the shade is fully expanded in either QS or QQS. */
-    val isShadeFullyExpanded: Boolean
-
-    /**
-     * Animates the collapse of a shade with the given delay and the default duration divided by
-     * speedUpFactor.
-     */
-    fun collapse(delayed: Boolean, speedUpFactor: Float)
-
-    /** Collapses the shade. */
-    fun collapse(animate: Boolean, delayed: Boolean, speedUpFactor: Float)
-
-    /** Collapses the shade with an animation duration in milliseconds. */
-    fun collapseWithDuration(animationDuration: Int)
-
-    /** Collapses the shade instantly without animation. */
-    fun instantCollapse()
-
-    /** Returns whether the shade can be collapsed. */
-    @Deprecated("Do not use outside of the shade package. Not supported by scenes.")
-    fun canBeCollapsed(): Boolean
-
     /** Returns whether the shade is in the process of collapsing. */
     val isCollapsing: Boolean
 
@@ -80,15 +49,9 @@
     /** Returns whether the shade's top level view is enabled. */
     val isViewEnabled: Boolean
 
-    /** Sets a listener to be notified when the shade starts opening or finishes closing. */
-    fun setOpenCloseListener(openCloseListener: OpenCloseListener)
-
     /** Returns whether status bar icons should be hidden when the shade is expanded. */
     fun shouldHideStatusBarIconsWhenExpanded(): Boolean
 
-    /** Sets a listener to be notified when touch tracking begins. */
-    fun setTrackingStartedListener(trackingStartedListener: TrackingStartedListener)
-
     /**
      * Disables the shade header.
      *
@@ -102,9 +65,6 @@
     /** Returns the StatusBarState. */
     val barState: Int
 
-    /** Sets the amount of progress in the status bar launch animation. */
-    fun applyLaunchAnimationProgress(linearProgress: Float)
-
     /** Sets the alpha value of the shade to a value between 0 and 255. */
     fun setAlpha(alpha: Int, animate: Boolean)
 
@@ -281,17 +241,3 @@
     /** Return the fraction of the shade that's expanded, when in lockscreen. */
     val lockscreenShadeDragProgress: Float
 }
-
-/** Listens for when touch tracking begins. */
-interface TrackingStartedListener {
-    fun onTrackingStarted()
-}
-
-/** Listens for when shade begins opening or finishes closing. */
-interface OpenCloseListener {
-    /** Called when the shade finishes closing. */
-    fun onClosingFinished()
-
-    /** Called when the shade starts opening. */
-    fun onOpenStarted()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index 7a181f1..b67156f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -19,43 +19,37 @@
 import android.view.MotionEvent
 import android.view.ViewGroup
 import android.view.ViewTreeObserver
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController
 import java.util.function.Consumer
 import javax.inject.Inject
+import kotlinx.coroutines.flow.flowOf
 
 /** Empty implementation of ShadeViewController for variants with no shade. */
 class ShadeViewControllerEmptyImpl @Inject constructor() :
-    ShadeViewController, ShadeBackActionInteractor, ShadeLockscreenInteractor {
-    override fun expand(animate: Boolean) {}
-    override fun expandToQs() {}
+    ShadeViewController,
+    ShadeBackActionInteractor,
+    ShadeLockscreenInteractor,
+    PanelExpansionInteractor {
     override fun expandToNotifications() {}
-    override val isExpandingOrCollapsing: Boolean = false
     override val isExpanded: Boolean = false
     override val isPanelExpanded: Boolean = false
-    override val isShadeFullyExpanded: Boolean = false
-    override fun collapse(delayed: Boolean, speedUpFactor: Float) {}
-    override fun collapse(animate: Boolean, delayed: Boolean, speedUpFactor: Float) {}
-    override fun collapseWithDuration(animationDuration: Int) {}
-    override fun instantCollapse() {}
     override fun animateCollapseQs(fullyCollapse: Boolean) {}
     override fun canBeCollapsed(): Boolean = false
     override val isCollapsing: Boolean = false
     override val isFullyCollapsed: Boolean = false
     override val isTracking: Boolean = false
     override val isViewEnabled: Boolean = false
-    override fun setOpenCloseListener(openCloseListener: OpenCloseListener) {}
     override fun shouldHideStatusBarIconsWhenExpanded() = false
     override fun blockExpansionForCurrentTouch() {}
-    override fun setTrackingStartedListener(trackingStartedListener: TrackingStartedListener) {}
     override fun disableHeader(state1: Int, state2: Int, animated: Boolean) {}
     override fun startExpandLatencyTracking() {}
     override fun startBouncerPreHideAnimation() {}
     override fun dozeTimeTick() {}
     override fun resetViews(animate: Boolean) {}
     override val barState: Int = 0
-    override fun applyLaunchAnimationProgress(linearProgress: Float) {}
     override fun closeUserSwitcherIfOpen(): Boolean {
         return false
     }
@@ -90,6 +84,7 @@
 
     override val shadeHeadsUpTracker = ShadeHeadsUpTrackerEmptyImpl()
     override val shadeFoldAnimator = ShadeFoldAnimatorEmptyImpl()
+    override val legacyPanelExpansion = flowOf(0f)
 }
 
 class ShadeHeadsUpTrackerEmptyImpl : ShadeHeadsUpTracker {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt
index 15ec18c..c4de78b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.CoreStartable
 import com.android.systemui.biometrics.AuthRippleController
+import com.android.systemui.shade.domain.startable.ShadeStartable
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
@@ -34,4 +35,9 @@
     @IntoMap
     @ClassKey(AuthRippleController::class)
     abstract fun bindAuthRippleController(controller: AuthRippleController): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(ShadeStartable::class)
+    abstract fun provideShadeStartable(startable: ShadeStartable): CoreStartable
 }
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt b/packages/SystemUI/src/com/android/systemui/shade/TrackingStartedListener.kt
similarity index 80%
copy from packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
copy to packages/SystemUI/src/com/android/systemui/shade/TrackingStartedListener.kt
index 8ad0a08..3803c27 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/TrackingStartedListener.kt
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel.component.mediaoutput
+package com.android.systemui.shade
 
-import dagger.Module
-
-@Module interface MediaOutputModule
+/** Listens for when touch tracking begins. */
+interface TrackingStartedListener {
+    fun onTrackingStarted()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index e5ff977..5c79e1e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.shade.data.repository
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.shared.model.ShadeMode
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -100,12 +101,16 @@
     @Deprecated("Use ShadeInteractor.isQsBypassingShade instead")
     val legacyExpandImmediate: StateFlow<Boolean>
 
+    val shadeMode: StateFlow<ShadeMode>
+
     /** True when QS is taking up the entire screen, i.e. fully expanded on a non-unfolded phone. */
     @Deprecated("Use ShadeInteractor instead") val legacyQsFullscreen: StateFlow<Boolean>
 
     /** NPVC.mClosing as a flow. */
     @Deprecated("Use ShadeAnimationInteractor instead") val legacyIsClosing: StateFlow<Boolean>
 
+    fun setShadeMode(mode: ShadeMode)
+
     /** Sets whether a closing animation is happening. */
     @Deprecated("Use ShadeAnimationInteractor instead") fun setLegacyIsClosing(isClosing: Boolean)
 
@@ -214,6 +219,13 @@
     @Deprecated("Use ShadeInteractor instead")
     override val legacyQsFullscreen: StateFlow<Boolean> = _legacyQsFullscreen.asStateFlow()
 
+    val _shadeMode = MutableStateFlow<ShadeMode>(ShadeMode.Single)
+    override val shadeMode: StateFlow<ShadeMode> = _shadeMode.asStateFlow()
+
+    override fun setShadeMode(shadeMode: ShadeMode) {
+        _shadeMode.value = shadeMode
+    }
+
     override fun setLegacyQsFullscreen(legacyQsFullscreen: Boolean) {
         _legacyQsFullscreen.value = legacyQsFullscreen
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
new file mode 100644
index 0000000..01118bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.shade.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Expansion-related methods used throughout SysUI before the addition of the scene container as the
+ * top layer component. This interface exists to allow the scene container to fulfil
+ * NotificationPanelViewController's contracts with the rest of SysUI. Once the scene container is
+ * the only shade implementation in SysUI, the remaining implementation of this should be deleted
+ * after inlining all of its method bodies. No new calls to any of these methods should be added.
+ */
+@SysUISingleton
+@Deprecated("Use ShadeInteractor instead.")
+interface PanelExpansionInteractor {
+    /**
+     * The amount by which the "panel" has been expanded (`0` when fully collapsed, `1` when fully
+     * expanded).
+     *
+     * This is a legacy concept from the time when the "panel" included the notification/QS shades
+     * as well as the keyguard (lockscreen and bouncer). This value is meant only for
+     * backwards-compatibility and should not be consumed by newer code.
+     */
+    @Deprecated("Use SceneInteractor.currentScene instead.") val legacyPanelExpansion: Flow<Float>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
new file mode 100644
index 0000000..20f73b0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.shade.domain.interactor
+
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+@SysUISingleton
+class PanelExpansionInteractorImpl
+@Inject
+constructor(
+    sceneInteractor: SceneInteractor,
+) : PanelExpansionInteractor {
+
+    /**
+     * The amount by which the "panel" has been expanded (`0` when fully collapsed, `1` when fully
+     * expanded).
+     *
+     * This is a legacy concept from the time when the "panel" included the notification/QS shades
+     * as well as the keyguard (lockscreen and bouncer). This value is meant only for
+     * backwards-compatibility and should not be consumed by newer code.
+     */
+    @Deprecated("Use SceneInteractor.currentScene instead.")
+    override val legacyPanelExpansion: Flow<Float> =
+        sceneInteractor.transitionState.flatMapLatest { state ->
+            when (state) {
+                is ObservableTransitionState.Idle ->
+                    flowOf(
+                        if (state.scene != Scenes.Gone) {
+                            // When resting on a non-Gone scene, the panel is fully expanded.
+                            1f
+                        } else {
+                            // When resting on the Gone scene, the panel is considered fully
+                            // collapsed.
+                            0f
+                        }
+                    )
+                is ObservableTransitionState.Transition ->
+                    when {
+                        state.fromScene == Scenes.Gone ->
+                            if (state.toScene.isExpandable()) {
+                                // Moving from Gone to a scene that can animate-expand has a
+                                // panel
+                                // expansion
+                                // that tracks with the transition.
+                                state.progress
+                            } else {
+                                // Moving from Gone to a scene that doesn't animate-expand
+                                // immediately makes
+                                // the panel fully expanded.
+                                flowOf(1f)
+                            }
+                        state.toScene == Scenes.Gone ->
+                            if (state.fromScene.isExpandable()) {
+                                // Moving to Gone from a scene that can animate-expand has a
+                                // panel
+                                // expansion
+                                // that tracks with the transition.
+                                state.progress.map { 1 - it }
+                            } else {
+                                // Moving to Gone from a scene that doesn't animate-expand
+                                // immediately makes
+                                // the panel fully collapsed.
+                                flowOf(0f)
+                            }
+                        else -> flowOf(1f)
+                    }
+            }
+        }
+
+    private fun SceneKey.isExpandable(): Boolean {
+        return this == Scenes.Shade || this == Scenes.QuickSettings
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
index 1ee6d38..eaac8ae 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
@@ -16,10 +16,10 @@
 
 package com.android.systemui.shade.domain.interactor
 
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.data.repository.ShadeAnimationRepository
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -44,10 +44,10 @@
                     is ObservableTransitionState.Idle -> flowOf(false)
                     is ObservableTransitionState.Transition ->
                         if (
-                            (state.fromScene == SceneKey.Shade &&
-                                state.toScene != SceneKey.QuickSettings) ||
-                                (state.fromScene == SceneKey.QuickSettings &&
-                                    state.toScene != SceneKey.Shade)
+                            (state.fromScene == Scenes.Shade &&
+                                state.toScene != Scenes.QuickSettings) ||
+                                (state.fromScene == Scenes.QuickSettings &&
+                                    state.toScene != Scenes.Shade)
                         ) {
                             state.isUserInputOngoing.map { !it }
                         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
index a2e2598..3a8ba7a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
@@ -36,12 +36,12 @@
             val key =
                 if (fullyCollapse) {
                     if (deviceEntryInteractor.isDeviceEntered.value) {
-                        SceneKey.Gone
+                        Scenes.Gone
                     } else {
-                        SceneKey.Lockscreen
+                        Scenes.Lockscreen
                     }
                 } else {
-                    SceneKey.Shade
+                    Scenes.Shade
                 }
             sceneInteractor.changeScene(key, "animateCollapseQs")
         }
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 43ede2a..bc60c83 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
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shade.domain.interactor
 
+import com.android.systemui.shade.shared.model.ShadeMode
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
@@ -102,6 +103,8 @@
      * animating.
      */
     val isUserInteractingWithQs: Flow<Boolean>
+
+    val shadeMode: StateFlow<ShadeMode>
 }
 
 fun createAnyExpansionFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
index 55dd674..e9bb4c6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.shade.domain.interactor
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.shared.model.ShadeMode
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -42,4 +43,5 @@
     override val isUserInteracting: StateFlow<Boolean> = inactiveFlowBoolean
     override val isShadeTouchable: Flow<Boolean> = inactiveFlowBoolean
     override val isExpandToQsEnabled: Flow<Boolean> = inactiveFlowBoolean
+    override val shadeMode: StateFlow<ShadeMode> = MutableStateFlow(ShadeMode.Single)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
index 2ac3193..6414af3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -99,6 +100,8 @@
     override val isUserInteractingWithQs: Flow<Boolean> =
         userInteractingFlow(repository.legacyQsTracking, repository.qsExpansion)
 
+    override val shadeMode: StateFlow<ShadeMode> = repository.shadeMode
+
     /**
      * Return a flow for whether a user is interacting with an expandable shade component using
      * tracking and expansion flows. NOTE: expansion must be a `StateFlow` to guarantee that
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
index 08f2c40..7785eda 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
@@ -16,11 +16,14 @@
 
 package com.android.systemui.shade.domain.interactor
 
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -44,10 +47,11 @@
     @Application scope: CoroutineScope,
     sceneInteractor: SceneInteractor,
     sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
+    shadeRepository: ShadeRepository,
 ) : BaseShadeInteractor {
-    override val shadeExpansion: Flow<Float> = sceneBasedExpansion(sceneInteractor, SceneKey.Shade)
+    override val shadeExpansion: Flow<Float> = sceneBasedExpansion(sceneInteractor, Scenes.Shade)
 
-    private val sceneBasedQsExpansion = sceneBasedExpansion(sceneInteractor, SceneKey.QuickSettings)
+    private val sceneBasedQsExpansion = sceneBasedExpansion(sceneInteractor, Scenes.QuickSettings)
 
     override val qsExpansion: StateFlow<Float> =
         combine(
@@ -75,7 +79,7 @@
                 when (state) {
                     is ObservableTransitionState.Idle -> false
                     is ObservableTransitionState.Transition ->
-                        state.toScene == SceneKey.QuickSettings && state.fromScene != SceneKey.Shade
+                        state.toScene == Scenes.QuickSettings && state.fromScene != Scenes.Shade
                 }
             }
             .distinctUntilChanged()
@@ -84,7 +88,7 @@
         sceneInteractor.transitionState
             .map { state ->
                 when (state) {
-                    is ObservableTransitionState.Idle -> state.scene == SceneKey.QuickSettings
+                    is ObservableTransitionState.Idle -> state.scene == Scenes.QuickSettings
                     is ObservableTransitionState.Transition -> false
                 }
             }
@@ -100,10 +104,12 @@
             .stateIn(scope, SharingStarted.Eagerly, false)
 
     override val isUserInteractingWithShade: Flow<Boolean> =
-        sceneBasedInteracting(sceneInteractor, SceneKey.Shade)
+        sceneBasedInteracting(sceneInteractor, Scenes.Shade)
 
     override val isUserInteractingWithQs: Flow<Boolean> =
-        sceneBasedInteracting(sceneInteractor, SceneKey.QuickSettings)
+        sceneBasedInteracting(sceneInteractor, Scenes.QuickSettings)
+
+    override val shadeMode: StateFlow<ShadeMode> = shadeRepository.shadeMode
 
     /**
      * Returns a flow that uses scene transition progress to and from a scene that is pulled down
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
index 21a782e..3d9337e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
@@ -19,7 +19,7 @@
 import com.android.keyguard.LockIconViewController
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.ShadeLockscreenInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -38,8 +38,6 @@
         changeToShadeScene()
     }
 
-    override val isExpandingOrCollapsing = shadeInteractor.isUserInteracting.value
-
     override val isExpanded = shadeInteractor.isAnyExpanded.value
 
     override fun startBouncerPreHideAnimation() {
@@ -87,7 +85,7 @@
 
     private fun changeToShadeScene() {
         sceneInteractor.changeScene(
-            SceneKey.Shade,
+            Scenes.Shade,
             "ShadeLockscreenInteractorImpl.expandToNotifications",
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
new file mode 100644
index 0000000..11ce818
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.startable
+
+import android.content.Context
+import com.android.systemui.CoreStartable
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.statusbar.policy.SplitShadeStateController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class ShadeStartable
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    @Application private val applicationContext: Context,
+    private val configurationRepository: ConfigurationRepository,
+    private val shadeRepository: ShadeRepository,
+    private val controller: SplitShadeStateController,
+) : CoreStartable {
+
+    override fun start() {
+        hydrateShadeMode()
+    }
+
+    private fun hydrateShadeMode() {
+        applicationScope.launch {
+            configurationRepository.onAnyConfigurationChange
+                // Force initial collection.
+                .onStart { emit(Unit) }
+                .map { applicationContext.resources }
+                .map { resources -> controller.shouldUseSplitNotificationShade(resources) }
+                .collect { isSplitShade ->
+                    shadeRepository.setShadeMode(
+                        if (isSplitShade) {
+                            ShadeMode.Split
+                        } else {
+                            ShadeMode.Single
+                        }
+                    )
+                }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
new file mode 100644
index 0000000..3451eaf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.shared.model
+
+/** Enumerates all known modes of operation of the shade. */
+sealed interface ShadeMode {
+
+    /**
+     * The single or "accordion" shade where the QS and notification parts are in two vertically
+     * stacked panels and the user can swipe up and down to expand or collapse between the two
+     * parts.
+     */
+    data object Single : ShadeMode
+
+    /**
+     * The split shade where, on large screens and unfolded foldables, the QS and notification parts
+     * are placed side-by-side and expand/collapse as a single panel.
+     */
+    data object Split : ShadeMode
+
+    /**
+     * The dual shade where the QS and notification parts each have their own independently
+     * expandable/collapsible panel on either side of the large screen / unfolded device or sharing
+     * a space on a small screen or folded device.
+     */
+    data object Dual : ShadeMode
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
index 9715070..3a5c5e1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
@@ -19,8 +19,11 @@
 import android.content.Context
 import android.content.res.Configuration
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.qs.QS
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.PanelState
 import com.android.systemui.shade.ShadeExpansionChangeEvent
 import com.android.systemui.shade.ShadeExpansionStateManager
@@ -31,21 +34,26 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.SplitShadeStateController
+import dagger.Lazy
 import java.io.PrintWriter
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 
 /** Controls the shade expansion transition on non-lockscreen. */
 @SysUISingleton
 class ShadeTransitionController
 @Inject
 constructor(
+    @Application private val applicationScope: CoroutineScope,
     configurationController: ConfigurationController,
     shadeExpansionStateManager: ShadeExpansionStateManager,
     dumpManager: DumpManager,
     private val context: Context,
     private val scrimShadeTransitionController: ScrimShadeTransitionController,
     private val statusBarStateController: SysuiStatusBarStateController,
-    private val splitShadeStateController: SplitShadeStateController
+    private val splitShadeStateController: SplitShadeStateController,
+    private val panelExpansionInteractor: Lazy<PanelExpansionInteractor>,
 ) {
 
     lateinit var shadeViewController: ShadeViewController
@@ -63,11 +71,27 @@
                 override fun onConfigChanged(newConfig: Configuration?) {
                     updateResources()
                 }
-            })
-        val currentState =
-            shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
-        onPanelExpansionChanged(currentState)
-        shadeExpansionStateManager.addStateListener(this::onPanelStateChanged)
+            }
+        )
+        if (SceneContainerFlag.isEnabled) {
+            applicationScope.launch {
+                panelExpansionInteractor.get().legacyPanelExpansion.collect { panelExpansion ->
+                    onPanelExpansionChanged(
+                        ShadeExpansionChangeEvent(
+                            fraction = panelExpansion,
+                            expanded = panelExpansion > 0f,
+                            tracking = true,
+                            dragDownPxAmount = 0f,
+                        )
+                    )
+                }
+            }
+        } else {
+            val currentState =
+                shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
+            onPanelExpansionChanged(currentState)
+            shadeExpansionStateManager.addStateListener(this::onPanelStateChanged)
+        }
         dumpManager.registerCriticalDumpable("ShadeTransitionController") { printWriter, _ ->
             dump(printWriter)
         }
@@ -98,7 +122,9 @@
                 qs.isInitialized: ${this::qs.isInitialized}
                 npvc.isInitialized: ${this::shadeViewController.isInitialized}
                 nssl.isInitialized: ${this::notificationStackScrollLayoutController.isInitialized}
-            """.trimIndent())
+            """
+                .trimIndent()
+        )
     }
 
     private fun isScreenUnlocked() =
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 38358a8..ea549f2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -14,17 +14,32 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.shade.ui.viewmodel
 
+import androidx.lifecycle.LifecycleOwner
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.qs.FooterActionsController
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
+import java.util.concurrent.atomic.AtomicBoolean
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
@@ -42,54 +57,82 @@
     val shadeHeaderViewModel: ShadeHeaderViewModel,
     val notifications: NotificationsPlaceholderViewModel,
     val mediaDataManager: MediaDataManager,
+    shadeInteractor: ShadeInteractor,
+    private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
+    private val footerActionsController: FooterActionsController,
 ) {
-    /** The key of the scene we should switch to when swiping up. */
-    val upDestinationSceneKey: StateFlow<SceneKey> =
+    val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
         combine(
                 deviceEntryInteractor.isUnlocked,
                 deviceEntryInteractor.canSwipeToEnter,
-            ) { isUnlocked, canSwipeToDismiss ->
-                upDestinationSceneKey(
+                shadeInteractor.shadeMode,
+            ) { isUnlocked, canSwipeToDismiss, shadeMode ->
+                destinationScenes(
                     isUnlocked = isUnlocked,
                     canSwipeToDismiss = canSwipeToDismiss,
+                    shadeMode = shadeMode,
                 )
             }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
                 initialValue =
-                    upDestinationSceneKey(
+                    destinationScenes(
                         isUnlocked = deviceEntryInteractor.isUnlocked.value,
                         canSwipeToDismiss = deviceEntryInteractor.canSwipeToEnter.value,
+                        shadeMode = shadeInteractor.shadeMode.value,
                     ),
             )
 
+    private val upDestinationSceneKey: Flow<SceneKey?> =
+        destinationScenes.map { it[Swipe(SwipeDirection.Up)]?.toScene }
+
     /** Whether or not the shade container should be clickable. */
     val isClickable: StateFlow<Boolean> =
         upDestinationSceneKey
-            .map { it == SceneKey.Lockscreen }
+            .map { it == Scenes.Lockscreen }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
                 initialValue = false
             )
 
+    val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
+
     /** Notifies that some content in the shade was clicked. */
     fun onContentClicked() = deviceEntryInteractor.attemptDeviceEntry()
 
-    private fun upDestinationSceneKey(
-        isUnlocked: Boolean,
-        canSwipeToDismiss: Boolean?,
-    ): SceneKey {
-        return when {
-            canSwipeToDismiss == true -> SceneKey.Lockscreen
-            isUnlocked -> SceneKey.Gone
-            else -> SceneKey.Lockscreen
-        }
-    }
-
     fun isMediaVisible(): Boolean {
         // TODO(b/296122467): handle updates to carousel visibility while scene is still visible
         return mediaDataManager.hasActiveMediaOrRecommendation()
     }
+
+    private val footerActionsControllerInitialized = AtomicBoolean(false)
+
+    fun getFooterActionsViewModel(lifecycleOwner: LifecycleOwner): FooterActionsViewModel {
+        if (footerActionsControllerInitialized.compareAndSet(false, true)) {
+            footerActionsController.init()
+        }
+        return footerActionsViewModelFactory.create(lifecycleOwner)
+    }
+
+    private fun destinationScenes(
+        isUnlocked: Boolean,
+        canSwipeToDismiss: Boolean?,
+        shadeMode: ShadeMode,
+    ): Map<UserAction, UserActionResult> {
+        val up =
+            when {
+                canSwipeToDismiss == true -> Scenes.Lockscreen
+                isUnlocked -> Scenes.Gone
+                else -> Scenes.Lockscreen
+            }
+
+        val down = Scenes.QuickSettings.takeIf { shadeMode is ShadeMode.Single }
+
+        return buildMap {
+            this[Swipe(SwipeDirection.Up)] = UserActionResult(up)
+            down?.let { this[Swipe(SwipeDirection.Down)] = UserActionResult(down) }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
index 8352250..a58ce41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
@@ -22,7 +22,9 @@
 import android.view.View
 import android.widget.FrameLayout
 import android.widget.LinearLayout
+import com.android.settingslib.flags.Flags.newStatusBarIcons
 import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.battery.unified.BatteryColors
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.events.BackgroundAnimatableView
 
@@ -39,8 +41,12 @@
         roundedContainer = requireViewById(R.id.rounded_container)
         batteryMeterView = requireViewById(R.id.battery_meter_view)
         batteryMeterView.setStaticColor(true)
-        val primaryColor = context.resources.getColor(android.R.color.black, context.theme)
-        batteryMeterView.updateColors(primaryColor, primaryColor, primaryColor)
+        if (newStatusBarIcons()) {
+            batteryMeterView.setUnifiedBatteryColors(BatteryColors.LightThemeColors)
+        } else {
+            val primaryColor = context.resources.getColor(android.R.color.black, context.theme)
+            batteryMeterView.updateColors(primaryColor, primaryColor, primaryColor)
+        }
         updateResources()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index ca19f71..4275fc6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -168,8 +168,8 @@
     private static final int MSG_UNREGISTER_NEARBY_MEDIA_DEVICE_PROVIDER = 67 << MSG_SHIFT;
     private static final int MSG_TILE_SERVICE_REQUEST_LISTENING_STATE = 68 << MSG_SHIFT;
     private static final int MSG_SHOW_REAR_DISPLAY_DIALOG = 69 << MSG_SHIFT;
-    private static final int MSG_GO_TO_FULLSCREEN_FROM_SPLIT = 70 << MSG_SHIFT;
-    private static final int MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP = 71 << MSG_SHIFT;
+    private static final int MSG_MOVE_FOCUSED_TASK_TO_FULLSCREEN = 70 << MSG_SHIFT;
+    private static final int MSG_MOVE_FOCUSED_TASK_TO_STAGE_SPLIT = 71 << MSG_SHIFT;
     private static final int MSG_SHOW_MEDIA_OUTPUT_SWITCHER = 72 << MSG_SHIFT;
     private static final int MSG_TOGGLE_TASKBAR = 73 << MSG_SHIFT;
     private static final int MSG_SETTING_CHANGED = 74 << MSG_SHIFT;
@@ -498,14 +498,14 @@
         default void showRearDisplayDialog(int currentBaseState) {}
 
         /**
-         * @see IStatusBar#goToFullscreenFromSplit
+         * @see IStatusBar#moveFocusedTaskToFullscreen
          */
-        default void goToFullscreenFromSplit() {}
+        default void moveFocusedTaskToFullscreen(int displayId) {}
 
         /**
-         * @see IStatusBar#enterStageSplitFromRunningApp
+         * @see IStatusBar#moveFocusedTaskToStageSplit
          */
-        default void enterStageSplitFromRunningApp(boolean leftOrTop) {}
+        default void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) {}
 
         /**
          * @see IStatusBar#showMediaOutputSwitcher
@@ -1338,10 +1338,13 @@
     }
 
     @Override
-    public void enterStageSplitFromRunningApp(boolean leftOrTop) {
+    public void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) {
         synchronized (mLock) {
-            mHandler.obtainMessage(MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP,
-                    leftOrTop).sendToTarget();
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = displayId;
+            args.argi2 = leftOrTop ? 1 : 0;
+            mHandler.obtainMessage(MSG_MOVE_FOCUSED_TASK_TO_STAGE_SPLIT,
+                    args).sendToTarget();
         }
     }
 
@@ -1422,8 +1425,10 @@
     }
 
     @Override
-    public void goToFullscreenFromSplit() {
-        mHandler.obtainMessage(MSG_GO_TO_FULLSCREEN_FROM_SPLIT).sendToTarget();
+    public void moveFocusedTaskToFullscreen(int displayId) {
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = displayId;
+        mHandler.obtainMessage(MSG_MOVE_FOCUSED_TASK_TO_FULLSCREEN, args).sendToTarget();
     }
 
     @Override
@@ -1897,16 +1902,23 @@
                         mCallbacks.get(i).showRearDisplayDialog((Integer) msg.obj);
                     }
                     break;
-                case MSG_GO_TO_FULLSCREEN_FROM_SPLIT:
+                case MSG_MOVE_FOCUSED_TASK_TO_FULLSCREEN: {
+                    args = (SomeArgs) msg.obj;
+                    int displayId = args.argi1;
                     for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).goToFullscreenFromSplit();
+                        mCallbacks.get(i).moveFocusedTaskToFullscreen(displayId);
                     }
                     break;
-                case MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP:
+                }
+                case MSG_MOVE_FOCUSED_TASK_TO_STAGE_SPLIT: {
+                    args = (SomeArgs) msg.obj;
+                    int displayId = args.argi1;
+                    boolean leftOrTop = args.argi2 != 0;
                     for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).enterStageSplitFromRunningApp((Boolean) msg.obj);
+                        mCallbacks.get(i).moveFocusedTaskToStageSplit(displayId, leftOrTop);
                     }
                     break;
+                }
                 case MSG_SHOW_MEDIA_OUTPUT_SWITCHER:
                     args = (SomeArgs) msg.obj;
                     String clientPackageName = (String) args.arg1;
@@ -1927,13 +1939,14 @@
                         mCallbacks.get(i).immersiveModeChanged(rootDisplayAreaId, isImmersiveMode);
                     }
                     break;
-                case MSG_ENTER_DESKTOP:
+                case MSG_ENTER_DESKTOP: {
                     args = (SomeArgs) msg.obj;
                     int displayId = args.argi1;
                     for (int i = 0; i < mCallbacks.size(); i++) {
                         mCallbacks.get(i).enterDesktop(displayId);
                     }
                     break;
+                }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index a4741a5..a12b970 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -84,6 +84,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -457,14 +458,43 @@
         List<KeyboardShortcutMultiMappingGroup> keyboardShortcutMultiMappingGroups =
                 new ArrayList<>();
         for (KeyboardShortcutGroup group : keyboardShortcutGroups) {
-            CharSequence categoryTitle = group.getLabel();
-            List<ShortcutMultiMappingInfo> shortcutMultiMappingInfos = new ArrayList<>();
-            for (KeyboardShortcutInfo info : group.getItems()) {
-                shortcutMultiMappingInfos.add(new ShortcutMultiMappingInfo(info));
-            }
-            keyboardShortcutMultiMappingGroups.add(
+            KeyboardShortcutMultiMappingGroup mappedGroup =
                     new KeyboardShortcutMultiMappingGroup(
-                            categoryTitle, shortcutMultiMappingInfos));
+                            group.getLabel(),
+                            new ArrayList<>());
+            Map<String, List<ShortcutMultiMappingInfo>> shortcutMap = new LinkedHashMap<>();
+            for (KeyboardShortcutInfo info : group.getItems()) {
+                String label = info.getLabel().toString();
+                Icon icon = info.getIcon();
+                if (shortcutMap.containsKey(label)) {
+                    List<ShortcutMultiMappingInfo> shortcuts = shortcutMap.get(label);
+                    boolean foundSameIcon = false;
+                    for (ShortcutMultiMappingInfo shortcut : shortcuts) {
+                        Icon shortcutIcon = shortcut.getIcon();
+                        if ((shortcutIcon != null
+                                && icon != null
+                                && shortcutIcon.sameAs(icon))
+                                || (shortcutIcon == null && icon == null)) {
+                            foundSameIcon = true;
+                            shortcut.addShortcutKeyGroup(new ShortcutKeyGroup(info, null));
+                            break;
+                        }
+                    }
+                    if (!foundSameIcon) {
+                        shortcuts.add(new ShortcutMultiMappingInfo(info));
+                    }
+                } else {
+                    List<ShortcutMultiMappingInfo> shortcuts = new ArrayList<>();
+                    shortcuts.add(new ShortcutMultiMappingInfo(info));
+                    shortcutMap.put(label, shortcuts);
+                }
+            }
+            for (List<ShortcutMultiMappingInfo> shortcutInfos : shortcutMap.values()) {
+                for (ShortcutMultiMappingInfo shortcutInfo : shortcutInfos) {
+                    mappedGroup.addItem(shortcutInfo);
+                }
+            }
+            keyboardShortcutMultiMappingGroups.add(mappedGroup);
         }
         return keyboardShortcutMultiMappingGroups;
     }
@@ -482,11 +512,30 @@
                         context.getString(R.string.keyboard_shortcut_group_system),
                         new ArrayList<>());
         List<ShortcutKeyGroupMultiMappingInfo> infoList = Arrays.asList(
-                /* Access notification shade: Meta + N */
+                /* Access list of all apps and search (i.e. Search/Launcher): Meta */
                 new ShortcutKeyGroupMultiMappingInfo(
-                        context.getString(R.string.group_system_access_notification_shade),
+                        context.getString(R.string.group_system_access_all_apps_search),
                         Arrays.asList(
-                                Pair.create(KeyEvent.KEYCODE_N, KeyEvent.META_META_ON))),
+                                Pair.create(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.META_META_ON))),
+                /* Access home screen: Meta + H, Meta + Enter */
+                new ShortcutKeyGroupMultiMappingInfo(
+                        context.getString(R.string.group_system_access_home_screen),
+                        Arrays.asList(
+                                Pair.create(KeyEvent.KEYCODE_H, KeyEvent.META_META_ON),
+                                Pair.create(KeyEvent.KEYCODE_ENTER, KeyEvent.META_META_ON))),
+                /* Overview of open apps: Meta + Tab */
+                new ShortcutKeyGroupMultiMappingInfo(
+                        context.getString(R.string.group_system_overview_open_apps),
+                        Arrays.asList(
+                                Pair.create(KeyEvent.KEYCODE_TAB, KeyEvent.META_META_ON))),
+                /* Back: go back to previous state (back button) */
+                /* Meta + Escape, Meta + backspace, Meta + left arrow */
+                new ShortcutKeyGroupMultiMappingInfo(
+                        context.getString(R.string.group_system_go_back),
+                        Arrays.asList(
+                                Pair.create(KeyEvent.KEYCODE_ESCAPE, KeyEvent.META_META_ON),
+                                Pair.create(KeyEvent.KEYCODE_DEL, KeyEvent.META_META_ON),
+                                Pair.create(KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.META_META_ON))),
                 /* Take a full screenshot: Meta + Ctrl + S */
                 new ShortcutKeyGroupMultiMappingInfo(
                         context.getString(R.string.group_system_full_screenshot),
@@ -499,25 +548,6 @@
                         context.getString(R.string.group_system_access_system_app_shortcuts),
                         Arrays.asList(
                                 Pair.create(KeyEvent.KEYCODE_SLASH, KeyEvent.META_META_ON))),
-                /* Back: go back to previous state (back button) */
-                /* Meta + Escape, Meta + Grave, Meta + backspace, Meta + left arrow */
-                new ShortcutKeyGroupMultiMappingInfo(
-                        context.getString(R.string.group_system_go_back),
-                        Arrays.asList(
-                                Pair.create(KeyEvent.KEYCODE_ESCAPE, KeyEvent.META_META_ON),
-                                Pair.create(KeyEvent.KEYCODE_DEL, KeyEvent.META_META_ON),
-                                Pair.create(KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.META_META_ON))),
-                /* Access home screen: Meta + H, Meta + Enter */
-                new ShortcutKeyGroupMultiMappingInfo(
-                        context.getString(R.string.group_system_access_home_screen),
-                        Arrays.asList(
-                                Pair.create(KeyEvent.KEYCODE_H, KeyEvent.META_META_ON),
-                                Pair.create(KeyEvent.KEYCODE_ENTER, KeyEvent.META_META_ON))),
-                /* Overview of open apps: Meta + Tab */
-                new ShortcutKeyGroupMultiMappingInfo(
-                        context.getString(R.string.group_system_overview_open_apps),
-                        Arrays.asList(
-                                Pair.create(KeyEvent.KEYCODE_TAB, KeyEvent.META_META_ON))),
                 /* Cycle through recent apps (forward): Alt + Tab */
                 new ShortcutKeyGroupMultiMappingInfo(
                         context.getString(R.string.group_system_cycle_forward),
@@ -530,26 +560,16 @@
                                 Pair.create(
                                         KeyEvent.KEYCODE_TAB,
                                         KeyEvent.META_SHIFT_ON | KeyEvent.META_ALT_ON))),
-                /* Access list of all apps and search (i.e. Search/Launcher): Meta */
-                new ShortcutKeyGroupMultiMappingInfo(
-                        context.getString(R.string.group_system_access_all_apps_search),
-                        Arrays.asList(
-                                Pair.create(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.META_META_ON))),
                 /* Hide and (re)show taskbar: Meta + T */
                 new ShortcutKeyGroupMultiMappingInfo(
                         context.getString(R.string.group_system_hide_reshow_taskbar),
                         Arrays.asList(
                                 Pair.create(KeyEvent.KEYCODE_T, KeyEvent.META_META_ON))),
-                /* Access system settings: Meta + I */
+                /* Access notification shade: Meta + N */
                 new ShortcutKeyGroupMultiMappingInfo(
-                        context.getString(R.string.group_system_access_system_settings),
+                        context.getString(R.string.group_system_access_notification_shade),
                         Arrays.asList(
-                                Pair.create(KeyEvent.KEYCODE_I, KeyEvent.META_META_ON))),
-                /* Access Google Assistant: Meta + A */
-                new ShortcutKeyGroupMultiMappingInfo(
-                        context.getString(R.string.group_system_access_google_assistant),
-                        Arrays.asList(
-                                Pair.create(KeyEvent.KEYCODE_A, KeyEvent.META_META_ON))),
+                                Pair.create(KeyEvent.KEYCODE_N, KeyEvent.META_META_ON))),
                 /*  Lock screen: Meta + L */
                 new ShortcutKeyGroupMultiMappingInfo(
                         context.getString(R.string.group_system_lock_screen),
@@ -561,7 +581,17 @@
                         Arrays.asList(
                                 Pair.create(
                                         KeyEvent.KEYCODE_N,
-                                        KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON)))
+                                        KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON))),
+                /* Access system settings: Meta + I */
+                new ShortcutKeyGroupMultiMappingInfo(
+                        context.getString(R.string.group_system_access_system_settings),
+                        Arrays.asList(
+                                Pair.create(KeyEvent.KEYCODE_I, KeyEvent.META_META_ON))),
+                /* Access Google Assistant: Meta + A */
+                new ShortcutKeyGroupMultiMappingInfo(
+                        context.getString(R.string.group_system_access_google_assistant),
+                        Arrays.asList(
+                                Pair.create(KeyEvent.KEYCODE_A, KeyEvent.META_META_ON)))
         );
         for (ShortcutKeyGroupMultiMappingInfo info : infoList) {
             systemGroup.addItem(info.getShortcutMultiMappingInfo());
@@ -1377,7 +1407,9 @@
         ShortcutMultiMappingInfo(KeyboardShortcutInfo info) {
             mLabel = info.getLabel();
             mIcon = info.getIcon();
-            mShortcutKeyGroups = Arrays.asList(new ShortcutKeyGroup(info, null));
+            mShortcutKeyGroups = new ArrayList<>(
+                Arrays.asList(new ShortcutKeyGroup(info, null))
+            );
         }
 
         CharSequence getLabel() {
@@ -1388,6 +1420,10 @@
             return mIcon;
         }
 
+        void addShortcutKeyGroup(ShortcutKeyGroup group) {
+            mShortcutKeyGroups.add(group);
+        }
+
         List<ShortcutKeyGroup> getShortcutKeyGroups() {
             return mShortcutKeyGroups;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 19fe60a..c904621 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -71,6 +71,7 @@
 import android.provider.DeviceConfig;
 import android.text.TextUtils;
 import android.text.format.Formatter;
+import android.util.Pair;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
@@ -146,8 +147,9 @@
     private static final int MSG_SHOW_ACTION_TO_UNLOCK = 1;
     private static final int MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON = 2;
     private static final long TRANSIENT_BIOMETRIC_ERROR_TIMEOUT = 1300;
+    public static final long DEFAULT_MESSAGE_TIME = 3500;
     public static final long DEFAULT_HIDE_DELAY_MS =
-            3500 + KeyguardIndicationTextView.Y_IN_DURATION;
+            DEFAULT_MESSAGE_TIME + KeyguardIndicationTextView.Y_IN_DURATION;
 
     private final Context mContext;
     private final BroadcastDispatcher mBroadcastDispatcher;
@@ -187,6 +189,7 @@
     private CharSequence mTransientIndication;
     private CharSequence mBiometricMessage;
     private CharSequence mBiometricMessageFollowUp;
+    private BiometricSourceType mBiometricMessageSource;
     protected ColorStateList mInitialTextColorState;
     private boolean mVisible;
     private boolean mOrganizationOwnedDevice;
@@ -206,7 +209,7 @@
     private int mBatteryLevel;
     private boolean mBatteryPresent = true;
     private long mChargingTimeRemaining;
-    private String mBiometricErrorMessageToShowOnScreenOn;
+    private Pair<String, BiometricSourceType> mBiometricErrorMessageToShowOnScreenOn;
     private final Set<Integer> mCoExFaceAcquisitionMsgIdsToShow;
     private final FaceHelpMessageDeferral mFaceAcquiredMessageDeferral;
     private boolean mInited;
@@ -225,15 +228,18 @@
                 mIsActiveDreamLockscreenHosted = isLockscreenHosted;
                 updateDeviceEntryIndication(false);
             };
-    private final ScreenLifecycle.Observer mScreenObserver =
-            new ScreenLifecycle.Observer() {
+    private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
         @Override
         public void onScreenTurnedOn() {
             mHandler.removeMessages(MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON);
             if (mBiometricErrorMessageToShowOnScreenOn != null) {
                 String followUpMessage = mFaceLockedOutThisAuthSession
                         ? faceLockedOutFollowupMessage() : null;
-                showBiometricMessage(mBiometricErrorMessageToShowOnScreenOn, followUpMessage);
+                showBiometricMessage(
+                        mBiometricErrorMessageToShowOnScreenOn.first,
+                        followUpMessage,
+                        mBiometricErrorMessageToShowOnScreenOn.second
+                );
                 // We want to keep this message around in case the screen was off
                 hideBiometricMessageDelayed(DEFAULT_HIDE_DELAY_MS);
                 mBiometricErrorMessageToShowOnScreenOn = null;
@@ -879,8 +885,35 @@
         updateTransient();
     }
 
-    private void showBiometricMessage(CharSequence biometricMessage) {
-        showBiometricMessage(biometricMessage, null);
+    private void showSuccessBiometricMessage(
+            CharSequence biometricMessage,
+            @Nullable CharSequence biometricMessageFollowUp,
+            BiometricSourceType biometricSourceType
+    ) {
+        showBiometricMessage(biometricMessage, biometricMessageFollowUp, biometricSourceType, true);
+    }
+
+    private void showSuccessBiometricMessage(CharSequence biometricMessage,
+            BiometricSourceType biometricSourceType) {
+        showSuccessBiometricMessage(biometricMessage, null, biometricSourceType);
+    }
+
+    private void showBiometricMessage(CharSequence biometricMessage,
+            BiometricSourceType biometricSourceType) {
+        showBiometricMessage(biometricMessage, null, biometricSourceType, false);
+    }
+
+    private void showBiometricMessage(
+            CharSequence biometricMessage,
+            @Nullable CharSequence biometricMessageFollowUp,
+            BiometricSourceType biometricSourceType
+    ) {
+        showBiometricMessage(
+                biometricMessage,
+                biometricMessageFollowUp,
+                biometricSourceType,
+                false
+        );
     }
 
     /**
@@ -889,15 +922,33 @@
      * by {@link KeyguardIndicationRotateTextViewController}, see class for rotating message
      * logic.
      */
-    private void showBiometricMessage(CharSequence biometricMessage,
-            @Nullable CharSequence biometricMessageFollowUp) {
+    private void showBiometricMessage(
+            CharSequence biometricMessage,
+            @Nullable CharSequence biometricMessageFollowUp,
+            BiometricSourceType biometricSourceType,
+            boolean isSuccessMessage
+    ) {
         if (TextUtils.equals(biometricMessage, mBiometricMessage)
+                && biometricSourceType == mBiometricMessageSource
                 && TextUtils.equals(biometricMessageFollowUp, mBiometricMessageFollowUp)) {
             return;
         }
 
+        if (!isSuccessMessage
+                && mBiometricMessageSource == FINGERPRINT
+                && biometricSourceType != FINGERPRINT) {
+            // drop all non-fingerprint biometric messages if there's a fingerprint message showing
+            mKeyguardLogger.logDropNonFingerprintMessage(
+                    biometricMessage,
+                    biometricMessageFollowUp,
+                    biometricSourceType
+            );
+            return;
+        }
+
         mBiometricMessage = biometricMessage;
         mBiometricMessageFollowUp = biometricMessageFollowUp;
+        mBiometricMessageSource = biometricSourceType;
 
         mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK);
         hideBiometricMessageDelayed(
@@ -914,6 +965,7 @@
         if (mBiometricMessage != null || mBiometricMessageFollowUp != null) {
             mBiometricMessage = null;
             mBiometricMessageFollowUp = null;
+            mBiometricMessageSource = null;
             mHideBiometricMessageHandler.cancel();
             updateBiometricMessage();
         }
@@ -1085,7 +1137,8 @@
                 } else {
                     message = mContext.getString(R.string.keyguard_retry);
                 }
-                mStatusBarKeyguardViewManager.setKeyguardMessage(message, mInitialTextColorState);
+                mStatusBarKeyguardViewManager.setKeyguardMessage(message, mInitialTextColorState,
+                        null);
             }
         } else {
             final boolean canSkipBouncer = mKeyguardUpdateMonitor.getUserCanSkipBouncer(
@@ -1097,34 +1150,40 @@
                         || mAccessibilityManager.isTouchExplorationEnabled();
                 if (udfpsSupported && faceAuthenticated) { // co-ex
                     if (a11yEnabled) {
-                        showBiometricMessage(
+                        showSuccessBiometricMessage(
                                 mContext.getString(R.string.keyguard_face_successful_unlock),
-                                mContext.getString(R.string.keyguard_unlock)
+                                mContext.getString(R.string.keyguard_unlock),
+                                FACE
                         );
                     } else {
-                        showBiometricMessage(
+                        showSuccessBiometricMessage(
                                 mContext.getString(R.string.keyguard_face_successful_unlock),
-                                mContext.getString(R.string.keyguard_unlock_press)
+                                mContext.getString(R.string.keyguard_unlock_press),
+                                FACE
                         );
                     }
                 } else if (faceAuthenticated) { // face-only
-                    showBiometricMessage(
+                    showSuccessBiometricMessage(
                             mContext.getString(R.string.keyguard_face_successful_unlock),
-                            mContext.getString(R.string.keyguard_unlock)
+                            mContext.getString(R.string.keyguard_unlock),
+                            FACE
                     );
                 } else if (udfpsSupported) { // udfps-only
                     if (a11yEnabled) {
-                        showBiometricMessage(mContext.getString(R.string.keyguard_unlock));
+                        showSuccessBiometricMessage(
+                                mContext.getString(R.string.keyguard_unlock),
+                                null
+                        );
                     } else {
-                        showBiometricMessage(mContext.getString(
-                                R.string.keyguard_unlock_press));
+                        showSuccessBiometricMessage(mContext.getString(
+                                R.string.keyguard_unlock_press), null);
                     }
                 } else { // no security or unlocked by a trust agent
-                    showBiometricMessage(mContext.getString(R.string.keyguard_unlock));
+                    showSuccessBiometricMessage(mContext.getString(R.string.keyguard_unlock), null);
                 }
             } else {
                 // suggest swiping up for the primary authentication bouncer
-                showBiometricMessage(mContext.getString(R.string.keyguard_unlock));
+                showBiometricMessage(mContext.getString(R.string.keyguard_unlock), null);
             }
         }
     }
@@ -1228,6 +1287,13 @@
                     && msgId != BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
             final boolean faceAuthFailed = biometricSourceType == FACE
                     && msgId == BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; // ran through matcher & failed
+            if (faceAuthFailed && mFaceLockedOutThisAuthSession) {
+                mKeyguardLogger.logBiometricMessage(
+                        "skipped showing faceAuthFailed message due to lockout",
+                        msgId,
+                        helpString);
+                return;
+            }
             final boolean fpAuthFailed = biometricSourceType == FINGERPRINT
                     && msgId == BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED; // ran matcher & failed
             final boolean isUnlockWithFingerprintPossible = canUnlockWithFingerprint();
@@ -1245,49 +1311,55 @@
                     mBouncerMessageInteractor.setFaceAcquisitionMessage(helpString);
                 }
                 mStatusBarKeyguardViewManager.setKeyguardMessage(helpString,
-                        mInitialTextColorState);
+                        mInitialTextColorState, biometricSourceType);
             } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) {
                 if (isCoExFaceAcquisitionMessage && msgId == FACE_ACQUIRED_TOO_DARK) {
                     showBiometricMessage(
                             helpString,
-                            mContext.getString(R.string.keyguard_suggest_fingerprint)
+                            mContext.getString(R.string.keyguard_suggest_fingerprint),
+                            biometricSourceType
                     );
                 } else if (faceAuthFailed && isUnlockWithFingerprintPossible) {
                     showBiometricMessage(
                             mContext.getString(R.string.keyguard_face_failed),
-                            mContext.getString(R.string.keyguard_suggest_fingerprint)
+                            mContext.getString(R.string.keyguard_suggest_fingerprint),
+                            biometricSourceType
                     );
                 } else if (fpAuthFailed
                         && mKeyguardUpdateMonitor.isCurrentUserUnlockedWithFace()) {
                     // face had already previously unlocked the device, so instead of showing a
                     // fingerprint error, tell them they have already unlocked with face auth
                     // and how to enter their device
-                    showBiometricMessage(
+                    showSuccessBiometricMessage(
                             mContext.getString(R.string.keyguard_face_successful_unlock),
-                            mContext.getString(R.string.keyguard_unlock)
+                            mContext.getString(R.string.keyguard_unlock),
+                            null
                     );
                 } else if (fpAuthFailed
                         && mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser())) {
-                    showBiometricMessage(
+                    showSuccessBiometricMessage(
                             getTrustGrantedIndication(),
-                            mContext.getString(R.string.keyguard_unlock)
+                            mContext.getString(R.string.keyguard_unlock),
+                            null
                     );
                 } else if (faceAuthUnavailable) {
                     showBiometricMessage(
                             helpString,
                             isUnlockWithFingerprintPossible
                                     ? mContext.getString(R.string.keyguard_suggest_fingerprint)
-                                    : mContext.getString(R.string.keyguard_unlock)
+                                    : mContext.getString(R.string.keyguard_unlock),
+                            biometricSourceType
                     );
                 } else {
-                    showBiometricMessage(helpString);
+                    showBiometricMessage(helpString, biometricSourceType);
                 }
             } else if (faceAuthFailed) {
                 // show action to unlock
                 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SHOW_ACTION_TO_UNLOCK),
                         TRANSIENT_BIOMETRIC_ERROR_TIMEOUT);
             } else {
-                mBiometricErrorMessageToShowOnScreenOn = helpString;
+                mBiometricErrorMessageToShowOnScreenOn =
+                        new Pair<>(helpString, biometricSourceType);
                 mHandler.sendMessageDelayed(
                         mHandler.obtainMessage(MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON),
                         1000);
@@ -1333,7 +1405,7 @@
             } else if (mIndicationHelper.isFaceLockoutErrorMsg(msgId)) {
                 handleFaceLockoutError(errString);
             } else {
-                showErrorMessageNowOrLater(errString, null);
+                showErrorMessageNowOrLater(errString, null, FACE);
             }
         }
 
@@ -1343,7 +1415,7 @@
                         msgId,
                         errString);
             } else {
-                showErrorMessageNowOrLater(errString, null);
+                showErrorMessageNowOrLater(errString, null, FINGERPRINT);
             }
         }
 
@@ -1371,7 +1443,7 @@
 
         @Override
         public void onTrustAgentErrorMessage(CharSequence message) {
-            showBiometricMessage(message);
+            showBiometricMessage(message, null);
         }
 
         @Override
@@ -1459,12 +1531,13 @@
         // had too many unsuccessful attempts.
         if (!mFaceLockedOutThisAuthSession) {
             mFaceLockedOutThisAuthSession = true;
-            showErrorMessageNowOrLater(errString, followupMessage);
+            showErrorMessageNowOrLater(errString, followupMessage, FACE);
         } else if (!mAuthController.isUdfpsFingerDown()) {
             // On subsequent lockouts, we show a more generic locked out message.
             showErrorMessageNowOrLater(
                     mContext.getString(R.string.keyguard_face_unlock_unavailable),
-                    followupMessage);
+                    followupMessage,
+                    FACE);
         }
     }
 
@@ -1484,7 +1557,8 @@
                     && !mStatusBarKeyguardViewManager.isBouncerShowing()) {
                 showBiometricMessage(
                         deferredFaceMessage,
-                        mContext.getString(R.string.keyguard_suggest_fingerprint)
+                        mContext.getString(R.string.keyguard_suggest_fingerprint),
+                        FACE
                 );
             } else {
                 // otherwise, don't show any message
@@ -1496,7 +1570,8 @@
             // user to manually retry.
             showBiometricMessage(
                     deferredFaceMessage,
-                    mContext.getString(R.string.keyguard_unlock)
+                    mContext.getString(R.string.keyguard_unlock),
+                    FACE
             );
         } else {
             // Face-only
@@ -1510,13 +1585,15 @@
                 getCurrentUser()) && mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed();
     }
 
-    private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) {
+    private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg,
+            BiometricSourceType biometricSourceType) {
         if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
-            mStatusBarKeyguardViewManager.setKeyguardMessage(errString, mInitialTextColorState);
+            mStatusBarKeyguardViewManager.setKeyguardMessage(errString, mInitialTextColorState,
+                    biometricSourceType);
         } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) {
-            showBiometricMessage(errString, followUpMsg);
+            showBiometricMessage(errString, followUpMsg, biometricSourceType);
         } else {
-            mBiometricErrorMessageToShowOnScreenOn = errString;
+            mBiometricErrorMessageToShowOnScreenOn = new Pair<>(errString, biometricSourceType);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 4ee8349..4b16126 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -352,10 +352,6 @@
     /** Called by the touch helper when the drag down was aborted and should be reset. */
     internal fun onDragDownReset() {
         logger.logDragDownAborted()
-        nsslController.setDimmed(
-            /* dimmed= */ true,
-            /* animate= */ true,
-        )
         nsslController.resetScrollPosition()
         nsslController.resetCheckSnoozeLeavebehind()
         setDragDownAmountAnimated(0f)
@@ -366,12 +362,7 @@
      *
      * @param above whether they dragged above it
      */
-    internal fun onCrossedThreshold(above: Boolean) {
-        nsslController.setDimmed(
-            /* dimmed= */ !above,
-            /* animate= */ true,
-        )
-    }
+    internal fun onCrossedThreshold(above: Boolean) {}
 
     /** Called by the touch helper when the drag down was started */
     internal fun onDragDownStarted(startingChild: ExpandableView?) {
@@ -603,6 +594,7 @@
                 }
             }
             val cancelHandler = Runnable {
+                statusBarStateController.setLeaveOpenOnKeyguardHide(false)
                 draggedDownEntry?.apply {
                     setUserLocked(false)
                     notifyHeightChanged(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
index 143fc32..3cf61e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
@@ -34,6 +34,8 @@
 import com.android.internal.widget.ImageFloatingTextView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationContentView;
+import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
+import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -253,7 +255,8 @@
         }
 
         public void init() {
-            View header = mParentRow.getNotificationViewWrapper().getNotificationHeader();
+            NotificationViewWrapper wrapper = mParentRow.getNotificationViewWrapper();
+            View header = wrapper == null ? null : wrapper.getNotificationHeader();
             mParentView = header == null ? null : header.findViewById(mId);
             mParentData = mExtractor == null ? null : mExtractor.extractData(mParentRow);
             mApply = !mComparator.isEmpty(mParentView);
@@ -326,6 +329,9 @@
 
         @Override
         public boolean isEmpty(View view) {
+            if (AsyncGroupHeaderViewInflation.isEnabled() && view == null) {
+                return true;
+            }
             if (view instanceof ImageView) {
                 return ((ImageView) view).getDrawable() == null;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 1a06eec..0091bc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -420,7 +420,7 @@
         filter.addAction(Intent.ACTION_USER_UNLOCKED);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
-        if (allowPrivateProfile()){
+        if (privateSpaceFlagsEnabled()) {
             filter.addAction(Intent.ACTION_PROFILE_AVAILABLE);
             filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE);
         }
@@ -813,13 +813,17 @@
     }
 
     private boolean profileAvailabilityActions(String action){
-        return allowPrivateProfile()?
+        return privateSpaceFlagsEnabled()?
                 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);
     }
 
+    private static boolean privateSpaceFlagsEnabled() {
+        return allowPrivateProfile() && android.multiuser.Flags.enablePrivateSpaceFeatures();
+    }
+
     @Override
     public void dump(PrintWriter pw, String[] args) {
         pw.println("NotificationLockscreenUserManager state:");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 6155348..5171a5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -39,8 +39,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.animation.ShadeInterpolation;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.RefactorFlag;
 import com.android.systemui.res.R;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.notification.ColorUpdateLogger;
@@ -95,8 +93,6 @@
     private float mCornerAnimationDistance;
     private float mActualWidth = -1;
     private int mMaxIconsOnLockscreen;
-    private final RefactorFlag mSensitiveRevealAnim =
-            RefactorFlag.forView(Flags.SENSITIVE_REVEAL_ANIM);
     private boolean mCanModifyColorOfNotifications;
     private boolean mCanInteract;
     private NotificationStackScrollLayout mHostLayout;
@@ -266,7 +262,7 @@
         }
 
         final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
-        if (mSensitiveRevealAnim.isEnabled() && viewState.hidden) {
+        if (viewState.hidden) {
             // if the shelf is hidden, position it at the end of the stack (plus the clip
             // padding), such that when it appears animated, it will smoothly move in from the
             // bottom, without jump cutting any notifications
@@ -398,10 +394,6 @@
         //  find the first view that doesn't overlap with the shelf
         int notGoneIndex = 0;
         int colorOfViewBeforeLast = NO_COLOR;
-        boolean backgroundForceHidden = false;
-        if (mHideBackground && !((ShelfState) getViewState()).hasItemsInStableShelf) {
-            backgroundForceHidden = true;
-        }
         int colorTwoBefore = NO_COLOR;
         int previousColor = NO_COLOR;
         float transitionAmount = 0.0f;
@@ -429,8 +421,7 @@
                     expandingAnimated, isLastChild, shelfClipStart);
 
             // TODO(b/172289889) scale mPaddingBetweenElements with expansion amount
-            if ((!mSensitiveRevealAnim.isEnabled() && ((isLastChild && !child.isInShelf())
-                    || backgroundForceHidden)) || aboveShelf) {
+            if (aboveShelf) {
                 notificationClipEnd = shelfStart + getIntrinsicHeight();
             } else {
                 notificationClipEnd = shelfStart - mPaddingBetweenElements;
@@ -440,8 +431,7 @@
 
             // If the current row is an ExpandableNotificationRow, update its color, roundedness,
             // and icon state.
-            if (child instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow expandableRow = (ExpandableNotificationRow) child;
+            if (child instanceof ExpandableNotificationRow expandableRow) {
                 numViewsInShelf += inShelfAmount;
                 int ownColorUntinted = expandableRow.getBackgroundColorWithoutTint();
                 if (viewStart >= shelfStart && mNotGoneIndex == -1) {
@@ -471,16 +461,8 @@
                 notGoneIndex++;
             }
 
-            if (child instanceof ActivatableNotificationView) {
-                ActivatableNotificationView anv = (ActivatableNotificationView) child;
-                // Because we show whole notifications on the lockscreen, the bottom notification is
-                // always "just about to enter the shelf" by normal scrolling rules.  This is fine
-                // if the shelf is visible, but if the shelf is hidden, it causes incorrect curling.
-                // notificationClipEnd handles the discrepancy between a visible and hidden shelf,
-                // so we use that when on the keyguard (and while animating away) to reduce curling.
-                final float keyguardSafeShelfStart = !mSensitiveRevealAnim.isEnabled()
-                        && mAmbientState.isOnKeyguard() ? notificationClipEnd : shelfStart;
-                updateCornerRoundnessOnScroll(anv, viewStart, keyguardSafeShelfStart);
+            if (child instanceof ActivatableNotificationView anv) {
+                updateCornerRoundnessOnScroll(anv, viewStart, shelfStart);
             }
         }
 
@@ -519,11 +501,10 @@
         mShelfIcons.applyIconStates();
         for (int i = 0; i < getHostLayoutChildCount(); i++) {
             View child = getHostLayoutChildAt(i);
-            if (!(child instanceof ExpandableNotificationRow)
+            if (!(child instanceof ExpandableNotificationRow row)
                     || child.getVisibility() == GONE) {
                 continue;
             }
-            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
             updateContinuousClipping(row);
         }
         boolean hideBackground = isHidden;
@@ -613,8 +594,7 @@
     private void clipTransientViews() {
         for (int i = 0; i < getHostLayoutTransientViewCount(); i++) {
             View transientView = getHostLayoutTransientView(i);
-            if (transientView instanceof ExpandableView) {
-                ExpandableView transientExpandableView = (ExpandableView) transientView;
+            if (transientView instanceof ExpandableView transientExpandableView) {
                 updateNotificationClipHeight(transientExpandableView, getTranslationY(), -1);
             }
         }
@@ -871,10 +851,9 @@
     }
 
     private void setIconTransformationAmount(ExpandableView view, float transitionAmount) {
-        if (!(view instanceof ExpandableNotificationRow)) {
+        if (!(view instanceof ExpandableNotificationRow row)) {
             return;
         }
-        ExpandableNotificationRow row = (ExpandableNotificationRow) view;
         StatusBarIconView icon = row.getShelfIcon();
         NotificationIconContainer.IconState iconState = getIconState(icon);
         if (iconState == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 36fc9bb..e0dd7f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -36,6 +36,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.app.animation.Interpolators;
+import com.android.compose.animation.scene.SceneKey;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
@@ -49,7 +50,7 @@
 import com.android.systemui.res.R;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
-import com.android.systemui.scene.shared.model.SceneKey;
+import com.android.systemui.scene.shared.model.Scenes;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.policy.CallbackController;
@@ -659,11 +660,11 @@
     }
 
     private static final Map<SceneKey, Integer> sStatusBarStateByLockedSceneKey = Map.of(
-            SceneKey.Lockscreen.INSTANCE, StatusBarState.KEYGUARD,
-            SceneKey.Bouncer.INSTANCE, StatusBarState.KEYGUARD,
-            SceneKey.Communal.INSTANCE, StatusBarState.KEYGUARD,
-            SceneKey.Shade.INSTANCE, StatusBarState.SHADE_LOCKED,
-            SceneKey.QuickSettings.INSTANCE, StatusBarState.SHADE_LOCKED
+            Scenes.Lockscreen, StatusBarState.KEYGUARD,
+            Scenes.Bouncer, StatusBarState.KEYGUARD,
+            Scenes.Communal, StatusBarState.KEYGUARD,
+            Scenes.Shade, StatusBarState.SHADE_LOCKED,
+            Scenes.QuickSettings, StatusBarState.SHADE_LOCKED
     );
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
index 642eacc..c4d9cbf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
@@ -35,6 +35,10 @@
 import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileDataInteractor
 import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileUserActionInteractor
 import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
+import com.android.systemui.qs.tiles.impl.internet.domain.InternetTileMapper
+import com.android.systemui.qs.tiles.impl.internet.domain.interactor.InternetTileDataInteractor
+import com.android.systemui.qs.tiles.impl.internet.domain.interactor.InternetTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
 import com.android.systemui.qs.tiles.impl.saver.domain.DataSaverTileMapper
 import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileDataInteractor
 import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileUserActionInteractor
@@ -90,6 +94,7 @@
 
         const val AIRPLANE_MODE_TILE_SPEC = "airplane"
         const val DATA_SAVER_TILE_SPEC = "saver"
+        const val INTERNET_TILE_SPEC = "internet"
 
         /** Inject InternetTile or InternetTileNewImpl into tileMap in QSModule */
         @Provides
@@ -168,5 +173,36 @@
                 stateInteractor,
                 mapper,
             )
+
+        @Provides
+        @IntoMap
+        @StringKey(INTERNET_TILE_SPEC)
+        fun provideInternetTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+            QSTileConfig(
+                tileSpec = TileSpec.create(INTERNET_TILE_SPEC),
+                uiConfig =
+                    QSTileUIConfig.Resource(
+                        iconRes = R.drawable.ic_qs_no_internet_available,
+                        labelRes = R.string.quick_settings_internet_label,
+                    ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+            )
+
+        /** Inject InternetTile into tileViewModelMap in QSModule */
+        @Provides
+        @IntoMap
+        @StringKey(INTERNET_TILE_SPEC)
+        fun provideInternetTileViewModel(
+            factory: QSTileViewModelFactory.Static<InternetTileModel>,
+            mapper: InternetTileMapper,
+            stateInteractor: InternetTileDataInteractor,
+            userActionInteractor: InternetTileUserActionInteractor
+        ): QSTileViewModel =
+            factory.create(
+                TileSpec.create(INTERNET_TILE_SPEC),
+                userActionInteractor,
+                stateInteractor,
+                mapper,
+            )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java
index e582a01..dbcda41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.connectivity;
 
+import static com.android.settingslib.flags.Flags.newStatusBarIcons;
+
 import com.android.settingslib.AccessibilityContentDescriptions;
 import com.android.settingslib.R;
 import com.android.settingslib.SignalIcon.IconGroup;
@@ -23,21 +25,52 @@
 /** */
 public class WifiIcons {
 
-    public static final int[] WIFI_FULL_ICONS = {
-            com.android.internal.R.drawable.ic_wifi_signal_0,
-            com.android.internal.R.drawable.ic_wifi_signal_1,
-            com.android.internal.R.drawable.ic_wifi_signal_2,
-            com.android.internal.R.drawable.ic_wifi_signal_3,
-            com.android.internal.R.drawable.ic_wifi_signal_4
-    };
+    public static final int[] WIFI_FULL_ICONS = getIconsBasedOnFlag();
 
-    public static final int[] WIFI_NO_INTERNET_ICONS = {
-            R.drawable.ic_no_internet_wifi_signal_0,
-            R.drawable.ic_no_internet_wifi_signal_1,
-            R.drawable.ic_no_internet_wifi_signal_2,
-            R.drawable.ic_no_internet_wifi_signal_3,
-            R.drawable.ic_no_internet_wifi_signal_4
-    };
+    /**
+     * Check the aconfig flag to decide on which icons to use. Can be removed once the flag is gone
+     */
+    private static int[] getIconsBasedOnFlag() {
+        if (newStatusBarIcons()) {
+            return new int[] {
+                R.drawable.ic_wifi_0,
+                R.drawable.ic_wifi_1,
+                R.drawable.ic_wifi_2,
+                R.drawable.ic_wifi_3,
+                R.drawable.ic_wifi_4
+            };
+        } else {
+            return new int[] {
+                com.android.internal.R.drawable.ic_wifi_signal_0,
+                com.android.internal.R.drawable.ic_wifi_signal_1,
+                com.android.internal.R.drawable.ic_wifi_signal_2,
+                com.android.internal.R.drawable.ic_wifi_signal_3,
+                com.android.internal.R.drawable.ic_wifi_signal_4
+            };
+        }
+    }
+
+    public static final int[] WIFI_NO_INTERNET_ICONS = getErrorIconsBasedOnFlag();
+
+    private static int [] getErrorIconsBasedOnFlag() {
+        if (newStatusBarIcons()) {
+            return new int[] {
+                R.drawable.ic_wifi_0_error,
+                R.drawable.ic_wifi_1_error,
+                R.drawable.ic_wifi_2_error,
+                R.drawable.ic_wifi_3_error,
+                R.drawable.ic_wifi_4_error
+            };
+        } else {
+            return new int[] {
+                R.drawable.ic_no_internet_wifi_signal_0,
+                R.drawable.ic_no_internet_wifi_signal_1,
+                R.drawable.ic_no_internet_wifi_signal_2,
+                R.drawable.ic_no_internet_wifi_signal_3,
+                R.drawable.ic_no_internet_wifi_signal_4
+            };
+        }
+    }
 
     public static final int[][] QS_WIFI_SIGNAL_STRENGTH = {
             WIFI_NO_INTERNET_ICONS,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt
new file mode 100644
index 0000000..1bebbfd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChangedBy
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/**
+ * Whether to set the status bar keyguard view occluded or not, and whether to animate that change.
+ */
+data class OccludedState(
+    val occluded: Boolean,
+    val animate: Boolean = false,
+)
+
+/** Handles logic around calls to [StatusBarKeyguardViewManager] in legacy code. */
+@Deprecated("Will be removed once all of SBKVM's responsibilies are refactored.")
+@SysUISingleton
+class StatusBarKeyguardViewManagerInteractor
+@Inject
+constructor(
+    keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+    powerInteractor: PowerInteractor,
+) {
+    /** Occlusion state to apply whenever a keyguard transition is STARTED, if any. */
+    private val occlusionStateFromStartedStep: Flow<OccludedState> =
+        keyguardTransitionInteractor.startedKeyguardTransitionStep
+            .sample(powerInteractor.detailedWakefulness, ::Pair)
+            .map { (startedStep, wakefulness) ->
+                val transitioningFromPowerButtonGesture =
+                    KeyguardState.deviceIsAsleepInState(startedStep.from) &&
+                        startedStep.to == KeyguardState.OCCLUDED &&
+                        wakefulness.powerButtonLaunchGestureTriggered
+
+                if (
+                    startedStep.to == KeyguardState.OCCLUDED && !transitioningFromPowerButtonGesture
+                ) {
+                    // Set occluded upon STARTED, *unless* we're transitioning from the power
+                    // button, in which case we're going to play an animation over the lockscreen UI
+                    // and need to remain unoccluded until the transition finishes.
+                    return@map OccludedState(occluded = true, animate = false)
+                }
+
+                if (
+                    startedStep.from == KeyguardState.OCCLUDED &&
+                        startedStep.to == KeyguardState.LOCKSCREEN
+                ) {
+                    // Set unoccluded immediately ONLY if we're transitioning back to the lockscreen
+                    // since we need the views visible to animate them back down. This is a special
+                    // case due to the way unocclusion remote animations are run. We can remove this
+                    // once the unocclude animation uses the return animation framework.
+                    return@map OccludedState(occluded = false, animate = false)
+                }
+
+                // Otherwise, wait for the transition to FINISH to decide.
+                return@map null
+            }
+            .filterNotNull()
+
+    /** Occlusion state to apply whenever a keyguard transition is FINISHED. */
+    private val occlusionStateFromFinishedStep =
+        keyguardTransitionInteractor.finishedKeyguardTransitionStep
+            .sample(keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop, ::Pair)
+            .map { (finishedStep, showWhenLockedOnTop) ->
+                // If we're FINISHED in OCCLUDED, we want to render as occluded. We also need to
+                // remain occluded if a SHOW_WHEN_LOCKED activity is on the top of the task stack,
+                // and we're in any state other than GONE. This is necessary, for example, when we
+                // transition from OCCLUDED to a bouncer state. Otherwise, we should not be
+                // occluded.
+                val occluded =
+                    finishedStep.to == KeyguardState.OCCLUDED ||
+                        (showWhenLockedOnTop && finishedStep.to != KeyguardState.GONE)
+                OccludedState(occluded = occluded, animate = false)
+            }
+
+    /** Occlusion state to apply to SKBVM's setOccluded call. */
+    val keyguardViewOcclusionState =
+        merge(occlusionStateFromStartedStep, occlusionStateFromFinishedStep)
+            .distinctUntilChangedBy {
+                // Don't switch 'animate' values mid-transition.
+                it.occluded
+            }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 599600d6..0fd0555 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -39,7 +39,6 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.settingslib.Utils
 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
@@ -54,6 +53,7 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.clocks.WeatherData
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.regionsampling.RegionSampler
 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DATE_SMARTSPACE_DATA_PLUGIN
@@ -68,11 +68,14 @@
 import com.android.systemui.util.time.SystemClock
 import java.io.PrintWriter
 import java.time.Instant
+import java.util.Deque
+import java.util.LinkedList
 import java.util.Optional
 import java.util.concurrent.Executor
 import javax.inject.Inject
 import javax.inject.Named
 
+
 /** Controller for managing the smartspace view on the lockscreen */
 @SysUISingleton
 class LockscreenSmartspaceController
@@ -106,6 +109,8 @@
 ) : Dumpable {
     companion object {
         private const val TAG = "LockscreenSmartspaceController"
+
+        private const val MAX_RECENT_SMARTSPACE_DATA_FOR_DUMP = 5
     }
 
     private var session: SmartspaceSession? = null
@@ -114,6 +119,9 @@
     private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
     private val configPlugin: BcSmartspaceConfigPlugin? = optionalConfigPlugin.orElse(null)
 
+    // This stores recently received Smartspace pushes to be included in dumpsys.
+    private val recentSmartspaceData: Deque<List<SmartspaceTarget>> = LinkedList()
+
     // Smartspace can be used on multiple displays, such as when the user casts their screen
     private var smartspaceViews = mutableSetOf<SmartspaceView>()
     private var regionSamplers =
@@ -173,6 +181,7 @@
 
         // The weather data plugin takes unfiltered targets and performs the filtering internally.
         weatherPlugin?.onTargetsAvailable(targets)
+
         val now = Instant.ofEpochMilli(systemClock.currentTimeMillis())
         val weatherTarget = targets.find { t ->
             t.featureType == SmartspaceTarget.FEATURE_WEATHER &&
@@ -201,6 +210,14 @@
         }
 
         val filteredTargets = targets.filter(::filterSmartspaceTarget)
+
+        synchronized(recentSmartspaceData) {
+            recentSmartspaceData.offerLast(filteredTargets)
+            if (recentSmartspaceData.size > MAX_RECENT_SMARTSPACE_DATA_FOR_DUMP) {
+                recentSmartspaceData.pollFirst()
+            }
+        }
+
         plugin?.onTargetsAvailable(filteredTargets)
     }
 
@@ -588,9 +605,26 @@
         return null
     }
 
-    override fun dump(pw: PrintWriter, args: Array<out String>) = pw.asIndenting().run {
-        printCollection("Region Samplers", regionSamplers.values) {
-            it.dump(this)
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.asIndenting().run {
+            printCollection("Region Samplers", regionSamplers.values) {
+                it.dump(this)
+            }
+        }
+
+        pw.println("Recent BC Smartspace Targets (most recent first)")
+        synchronized(recentSmartspaceData) {
+            if (recentSmartspaceData.size === 0) {
+                pw.println("   No data\n")
+                return
+            }
+            recentSmartspaceData.descendingIterator().forEachRemaining { smartspaceTargets ->
+                pw.println("   Number of targets: ${smartspaceTargets.size}")
+                for (target in smartspaceTargets) {
+                    pw.println("      $target")
+                }
+                pw.println()
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 639e23a..dfb0f9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -363,10 +363,12 @@
 
     NotifInflater.Params getInflaterParams(NotifUiAdjustment adjustment, String reason) {
         return new NotifInflater.Params(
-                adjustment.isMinimized(),
-                reason,
-                adjustment.isSnoozeEnabled(),
-                adjustment.isChildInGroup()
+                /* isLowPriority = */ adjustment.isMinimized(),
+                /* reason = */ reason,
+                /* showSnooze = */ adjustment.isSnoozeEnabled(),
+                /* isChildInGroup = */ adjustment.isChildInGroup(),
+                /* isGroupSummary = */ adjustment.isGroupSummary(),
+                /* needsRedaction = */ adjustment.getNeedsRedaction()
         );
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinator.kt
index 12de339..4a7b7ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinator.kt
@@ -54,7 +54,7 @@
     }
 
     private fun GroupEntry.calculateLatestAlertTime(): Long {
-        val lastChildAlertedTime = children.maxOf { it.lastAudiblyAlertedMs }
+        val lastChildAlertedTime = children.maxOfOrNull { it.lastAudiblyAlertedMs } ?: 0
         val summaryAlertedTime = checkNotNull(summary).lastAudiblyAlertedMs
         return max(lastChildAlertedTime, summaryAlertedTime)
     }
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 29627e1..e4db4c7 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
@@ -63,9 +63,16 @@
         SensitiveContentCoordinator,
         DynamicPrivacyController.Listener,
         OnBeforeRenderListListener {
+    private val onSensitiveStateChanged = Runnable() {
+        invalidateList("onSensitiveStateChanged")
+    }
 
     override fun attach(pipeline: NotifPipeline) {
         dynamicPrivacyController.addListener(this)
+        if (screenshareNotificationHiding()) {
+            sensitiveNotificationProtectionController
+                .registerSensitiveStateListener(onSensitiveStateChanged)
+        }
         pipeline.addOnBeforeRenderListListener(this)
         pipeline.addPreRenderInvalidator(this)
     }
@@ -119,6 +126,9 @@
             val needsRedaction = lockscreenUserManager.needsRedaction(entry)
             val isSensitive = userPublic && needsRedaction
             entry.setSensitive(isSensitive || shouldProtectNotification, deviceSensitive)
+            if (screenshareNotificationHiding()) {
+                entry.row?.setPublicExpanderVisible(!shouldProtectNotification)
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
index c0b187b..7b8a062 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
@@ -60,5 +60,7 @@
         val reason: String,
         val showSnooze: Boolean,
         val isChildInGroup: Boolean = false,
+        val isGroupSummary: Boolean = false,
+        val needsRedaction: Boolean,
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
index e1d2cdc..bab94b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
@@ -20,6 +20,7 @@
 import android.app.RemoteInput
 import android.graphics.drawable.Icon
 import android.text.TextUtils
+import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation
 import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
 
 /**
@@ -36,6 +37,7 @@
     val isMinimized: Boolean,
     val needsRedaction: Boolean,
     val isChildInGroup: Boolean,
+    val isGroupSummary: Boolean,
 ) {
     companion object {
         @JvmStatic
@@ -55,6 +57,8 @@
             //  !oldAdjustment.isChildInGroup && newAdjustment.isChildInGroup -> true
             AsyncHybridViewInflation.isEnabled &&
                     oldAdjustment.isChildInGroup != newAdjustment.isChildInGroup -> true
+            AsyncGroupHeaderViewInflation.isEnabled &&
+                !oldAdjustment.isGroupSummary && newAdjustment.isGroupSummary -> true
             else -> false
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
index 6f44c13..e0e5a35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
@@ -22,6 +22,7 @@
 import android.os.HandlerExecutor
 import android.os.UserHandle
 import android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE
+import com.android.server.notification.Flags.screenshareNotificationHiding
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.settings.UserTracker
@@ -30,6 +31,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
 import com.android.systemui.util.ListenerSet
 import com.android.systemui.util.settings.SecureSettings
 import javax.inject.Inject
@@ -43,6 +45,7 @@
     @Main private val handler: Handler,
     private val secureSettings: SecureSettings,
     private val lockscreenUserManager: NotificationLockscreenUserManager,
+    private val sensitiveNotifProtectionController: SensitiveNotificationProtectionController,
     private val sectionStyleProvider: SectionStyleProvider,
     private val userTracker: UserTracker,
     private val groupMembershipManager: GroupMembershipManager,
@@ -66,6 +69,11 @@
     fun addDirtyListener(listener: Runnable) {
         if (dirtyListeners.isEmpty()) {
             lockscreenUserManager.addNotificationStateChangedListener(notifStateChangedListener)
+            if (screenshareNotificationHiding()) {
+                sensitiveNotifProtectionController.registerSensitiveStateListener(
+                    onSensitiveStateChangedListener
+                )
+            }
             updateSnoozeEnabled()
             secureSettings.registerContentObserverForUser(
                 SHOW_NOTIFICATION_SNOOZE,
@@ -80,6 +88,11 @@
         dirtyListeners.remove(listener)
         if (dirtyListeners.isEmpty()) {
             lockscreenUserManager.removeNotificationStateChangedListener(notifStateChangedListener)
+            if (screenshareNotificationHiding()) {
+                sensitiveNotifProtectionController.unregisterSensitiveStateListener(
+                    onSensitiveStateChangedListener
+                )
+            }
             secureSettings.unregisterContentObserver(settingsObserver)
         }
     }
@@ -89,6 +102,8 @@
             dirtyListeners.forEach(Runnable::run)
         }
 
+    private val onSensitiveStateChangedListener = Runnable { dirtyListeners.forEach(Runnable::run) }
+
     private val settingsObserver = object : ContentObserver(handler) {
         override fun onChange(selfChange: Boolean) {
             updateSnoozeEnabled()
@@ -122,7 +137,11 @@
         isConversation = entry.ranking.isConversation,
         isSnoozeEnabled = isSnoozeSettingsEnabled && !entry.isCanceled,
         isMinimized = isEntryMinimized(entry),
-        needsRedaction = lockscreenUserManager.needsRedaction(entry),
-        isChildInGroup = groupMembershipManager.isChildInGroup(entry),
+        needsRedaction =
+            lockscreenUserManager.needsRedaction(entry) ||
+                (screenshareNotificationHiding() &&
+                    sensitiveNotifProtectionController.shouldProtectNotification(entry)),
+        isChildInGroup = entry.sbn.isAppOrSystemGroupChild,
+        isGroupSummary = entry.sbn.isAppOrSystemGroupSummary,
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 954e805..6400ff6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -21,6 +21,8 @@
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_GROUP_SUMMARY_HEADER;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER;
 
 import static java.util.Objects.requireNonNull;
 
@@ -50,6 +52,7 @@
 import com.android.systemui.statusbar.notification.row.RowContentBindStage;
 import com.android.systemui.statusbar.notification.row.RowInflaterTask;
 import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
+import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
 import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 
@@ -251,11 +254,9 @@
         params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
         params.setUseLowPriority(isLowPriority);
 
-        // If screenshareNotificationHiding is enabled, both public and private views should be
-        // inflated to avoid any latency associated with reinflating all notification views when
-        // screen share starts and stops
         if (screenshareNotificationHiding()
-                || mNotificationLockscreenUserManager.needsRedaction(entry)) {
+                ? inflaterParams.getNeedsRedaction()
+                : mNotificationLockscreenUserManager.needsRedaction(entry)) {
             params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC);
         } else {
             params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC);
@@ -271,6 +272,17 @@
             }
         }
 
+        if (AsyncGroupHeaderViewInflation.isEnabled()) {
+            if (inflaterParams.isGroupSummary()) {
+                params.requireContentViews(FLAG_GROUP_SUMMARY_HEADER);
+                if (isLowPriority) {
+                    params.requireContentViews(FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER);
+                }
+            } else {
+                params.markContentViewsFreeable(FLAG_GROUP_SUMMARY_HEADER);
+                params.markContentViewsFreeable(FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER);
+            }
+        }
         params.rebindAllContentViews();
         mLogger.logRequestingRebind(entry, inflaterParams);
         mRowContentBindStage.requestRebind(entry, en -> {
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 510086d..dc9eeb3 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
@@ -191,11 +191,11 @@
     public boolean shouldBubbleUp(NotificationEntry entry) {
         final StatusBarNotification sbn = entry.getSbn();
 
-        if (!canAlertCommon(entry, true)) {
+        if (!canAlertCommon(entry, false)) {
             return false;
         }
 
-        if (!canAlertAwakeCommon(entry, true)) {
+        if (!canAlertAwakeCommon(entry, false)) {
             return false;
         }
 
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 7358034..61cdea1 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
@@ -547,13 +547,7 @@
     }
 
     private void setContentAlpha(float contentAlpha) {
-        View contentView = getContentView();
-        if (contentView.hasOverlappingRendering()) {
-            int layerType = contentAlpha == 0.0f || contentAlpha == 1.0f ? LAYER_TYPE_NONE
-                    : LAYER_TYPE_HARDWARE;
-            contentView.setLayerType(layerType, null);
-        }
-        contentView.setAlpha(contentAlpha);
+        setAlphaAndLayerType(getContentView(), contentAlpha);
         // After updating the current view, reset all views.
         if (contentAlpha == 1f) {
             resetAllContentAlphas();
@@ -561,6 +555,19 @@
     }
 
     /**
+     * Set a content view's alpha value and hardware layer type for fluent animations
+     * @param contentView the view to set
+     * @param alpha the alpha value to set
+     */
+    protected void setAlphaAndLayerType(View contentView, float alpha) {
+        if (contentView.hasOverlappingRendering()) {
+            int layerType = alpha == 0.0f || alpha == 1.0f ? LAYER_TYPE_NONE : LAYER_TYPE_HARDWARE;
+            contentView.setLayerType(layerType, null);
+        }
+        contentView.setAlpha(alpha);
+    }
+
+    /**
      * If a subclass's {@link #getContentView()} returns different views depending on state,
      * this method is an opportunity to reset the alpha of ALL content views, not just the
      * current one, which may prevent a content view that is temporarily hidden from being reset.
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 decb244..c05c3c3 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
@@ -100,7 +100,9 @@
 import com.android.systemui.statusbar.notification.logging.NotificationCounters;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
+import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
@@ -218,6 +220,7 @@
     private boolean mShowingPublic;
     private boolean mSensitive;
     private boolean mSensitiveHiddenInGeneral;
+    private boolean mShowPublicExpander = true;
     private boolean mShowingPublicInitialized;
     private boolean mHideSensitiveForIntrinsicHeight;
     private float mHeaderVisibleAmount = DEFAULT_HEADER_VISIBLE_AMOUNT;
@@ -275,8 +278,6 @@
     private OnExpandClickListener mOnExpandClickListener;
     private View.OnClickListener mOnFeedbackClickListener;
     private Path mExpandingClipPath;
-    private final RefactorFlag mInlineReplyAnimation =
-            RefactorFlag.forView(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION);
 
     private static boolean shouldSimulateSlowMeasure() {
         return Compile.IS_DEBUG && RefactorFlag.forView(
@@ -352,7 +353,7 @@
                     nowExpanded = !isExpanded();
                     setUserExpanded(nowExpanded);
                 }
-                notifyHeightChanged(true);
+                notifyHeightChanged(/* needsAnimation= */ true);
                 mOnExpandClickListener.onExpandClicked(mEntry, v, nowExpanded);
                 mMetricsLogger.action(MetricsEvent.ACTION_NOTIFICATION_EXPANDER, nowExpanded);
             }
@@ -586,7 +587,11 @@
             mMenuRow.setAppName(mAppName);
         }
         if (mIsSummaryWithChildren) {
-            mChildrenContainer.recreateNotificationHeader(mExpandClickListener, isConversation());
+            if (!AsyncGroupHeaderViewInflation.isEnabled()) {
+                // We create the header from the background thread instead
+                mChildrenContainer.recreateNotificationHeader(mExpandClickListener,
+                        isConversation());
+            }
             mChildrenContainer.onNotificationUpdated();
         }
         if (mAnimationRunning) {
@@ -599,8 +604,7 @@
             mNotificationParent.updateChildrenAppearance();
         }
         onAttachedChildrenCountChanged();
-        // The public layouts expand button is always visible
-        mPublicLayout.updateExpandButtons(true);
+        mPublicLayout.updateExpandButtons(mShowPublicExpander);
         updateLimits();
         updateShelfIconColor();
         if (mUpdateSelfBackgroundOnUpdate) {
@@ -668,7 +672,9 @@
 
     public int getOriginalIconColor() {
         if (mIsSummaryWithChildren && !shouldShowPublic()) {
-            return mChildrenContainer.getVisibleWrapper().getOriginalIconColor();
+            if (!AsyncGroupHeaderViewInflation.isEnabled()) {
+                return mChildrenContainer.getVisibleWrapper().getOriginalIconColor();
+            }
         }
         int color = getShowingLayout().getOriginalIconColor();
         if (color != Notification.COLOR_INVALID) {
@@ -770,7 +776,7 @@
             mChildrenContainer.updateGroupOverflow();
         }
         if (intrinsicBefore != getIntrinsicHeight()) {
-            notifyHeightChanged(false  /* needsAnimation */);
+            notifyHeightChanged(/* needsAnimation= */ false);
         }
         if (isHeadsUp) {
             mMustStayOnScreen = true;
@@ -818,7 +824,7 @@
             if (mChildrenContainer != null) {
                 mChildrenContainer.setHeaderVisibleAmount(headerVisibleAmount);
             }
-            notifyHeightChanged(false /* needsAnimation */);
+            notifyHeightChanged(/* needsAnimation= */ false);
         }
     }
 
@@ -1080,7 +1086,7 @@
         boolean wasAboveShelf = isAboveShelf();
         mIsPinned = pinned;
         if (intrinsicHeight != getIntrinsicHeight()) {
-            notifyHeightChanged(false /* needsAnimation */);
+            notifyHeightChanged(/* needsAnimation= */ false);
         }
         if (pinned) {
             setAnimationRunning(true);
@@ -1513,6 +1519,40 @@
         return mChildrenContainer;
     }
 
+    /**
+     * @return An non-null instance of mChildrenContainer, inflate it if not yet.
+     */
+    public @NonNull NotificationChildrenContainer getChildrenContainerNonNull() {
+        if (mChildrenContainer == null) {
+            mChildrenContainerStub.inflate();
+        }
+        return mChildrenContainer;
+    }
+
+    /**
+     * Set the group notification header view
+     * @param headerView header view to set
+     */
+    public void setGroupHeader(NotificationHeaderView headerView) {
+        NotificationChildrenContainer childrenContainer = getChildrenContainerNonNull();
+        childrenContainer.setGroupHeader(
+                /* headerView= */ headerView,
+                /* onClickListener= */ mExpandClickListener
+        );
+    }
+
+    /**
+     * Set the low-priority group notification header view
+     * @param headerView header view to set
+     */
+    public void setLowPriorityGroupHeader(NotificationHeaderView headerView) {
+        NotificationChildrenContainer childrenContainer = getChildrenContainerNonNull();
+        childrenContainer.setLowPriorityGroupHeader(
+                /* headerViewLowPriority= */ headerView,
+                /* onClickListener= */ mExpandClickListener
+        );
+    }
+
     public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
         boolean wasAboveShelf = isAboveShelf();
         boolean changed = headsUpAnimatingAway != mHeadsupDisappearRunning;
@@ -1565,7 +1605,11 @@
     @Override
     public View getShelfTransformationTarget() {
         if (mIsSummaryWithChildren && !shouldShowPublic()) {
-            return mChildrenContainer.getVisibleWrapper().getShelfTransformationTarget();
+            NotificationViewWrapper viewWrapper = mChildrenContainer.getVisibleWrapper();
+            if (AsyncGroupHeaderViewInflation.isEnabled() && viewWrapper == null) {
+                return null;
+            }
+            return viewWrapper.getShelfTransformationTarget();
         }
         return getShowingLayout().getShelfTransformationTarget();
     }
@@ -1715,7 +1759,7 @@
      * Constructs an ExpandableNotificationRow. Used by layout inflation.
      *
      * @param context passed to image resolver
-     * @param attrs attributes used to initialize parent view
+     * @param attrs   attributes used to initialize parent view
      */
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
         this(context, attrs, context);
@@ -1729,9 +1773,9 @@
      * AsyncLayoutFactory} in {@link RowInflaterTask}.
      *
      * @param context context context of the view
-     * @param attrs attributes used to initialize parent view
-     * @param entry notification that the row will be associated to (determines the user for the
-     *              ImageResolver)
+     * @param attrs   attributes used to initialize parent view
+     * @param entry   notification that the row will be associated to (determines the user for the
+     *                ImageResolver)
      */
     public ExpandableNotificationRow(Context context, AttributeSet attrs, NotificationEntry entry) {
         this(context, attrs, userContextForEntry(context, entry));
@@ -1982,7 +2026,7 @@
             return traceTag;
         }
 
-        return  traceTag + "(" + getEntry().getNotificationStyle() + ")";
+        return traceTag + "(" + getEntry().getNotificationStyle() + ")";
     }
 
     @Override
@@ -2565,7 +2609,7 @@
         onExpansionChanged(true /* userAction */, wasExpanded);
         if (!wasExpanded && isExpanded()
                 && getActualHeight() != getIntrinsicHeight()) {
-            notifyHeightChanged(true /* needsAnimation */);
+            notifyHeightChanged(/* needsAnimation= */ true);
         }
     }
 
@@ -2577,7 +2621,7 @@
             if (mIsSummaryWithChildren) {
                 mChildrenContainer.onExpansionChanged();
             }
-            notifyHeightChanged(false /* needsAnimation */);
+            notifyHeightChanged(/* needsAnimation= */ false);
         }
         updateShelfIconColor();
     }
@@ -2615,7 +2659,7 @@
         if (expand != mIsSystemExpanded) {
             final boolean wasExpanded = isExpanded();
             mIsSystemExpanded = expand;
-            notifyHeightChanged(false /* needsAnimation */);
+            notifyHeightChanged(/* needsAnimation= */ false);
             onExpansionChanged(false /* userAction */, wasExpanded);
             if (mIsSummaryWithChildren) {
                 mChildrenContainer.updateGroupOverflow();
@@ -2634,7 +2678,7 @@
                 if (mIsSummaryWithChildren) {
                     mChildrenContainer.updateGroupOverflow();
                 }
-                notifyHeightChanged(false /* needsAnimation */);
+                notifyHeightChanged(/* needsAnimation= */ false);
             }
             if (isAboveShelf() != wasAboveShelf) {
                 mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
@@ -2710,10 +2754,12 @@
                 && mChildrenContainer.getNotificationChildCount() > 0;
         if (mIsSummaryWithChildren) {
             Trace.beginSection("ExpNotRow#onChildCountChanged (summary)");
-            NotificationViewWrapper wrapper = mChildrenContainer.getNotificationViewWrapper();
-            if (wrapper == null || wrapper.getNotificationHeader() == null) {
-                mChildrenContainer.recreateNotificationHeader(mExpandClickListener,
-                        isConversation());
+            if (!AsyncGroupHeaderViewInflation.isEnabled()) {
+                NotificationViewWrapper wrapper = mChildrenContainer.getNotificationViewWrapper();
+                if (wrapper == null || wrapper.getNotificationHeader() == null) {
+                    mChildrenContainer.recreateNotificationHeader(mExpandClickListener,
+                            isConversation());
+                }
             }
         }
         if (!mIsSummaryWithChildren && wasSummary) {
@@ -2789,7 +2835,7 @@
         super.onLayout(changed, left, top, right, bottom);
         if (intrinsicBefore != getIntrinsicHeight()
                 && (intrinsicBefore != 0 || getActualHeight() > 0)) {
-            notifyHeightChanged(true  /* needsAnimation */);
+            notifyHeightChanged(/* needsAnimation= */ true);
         }
         if (mMenuRow != null && mMenuRow.getMenuView() != null) {
             mMenuRow.onParentHeightUpdate();
@@ -2832,8 +2878,15 @@
         mSensitiveHiddenInGeneral = hideSensitive;
         int intrinsicAfter = getIntrinsicHeight();
         if (intrinsicBefore != intrinsicAfter) {
-            boolean needsAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
-            notifyHeightChanged(needsAnimation);
+            notifyHeightChanged(/* needsAnimation= */ true);
+        }
+    }
+
+    /** Sets whether this notification row should show the notification expander or not */
+    public void setPublicExpanderVisible(boolean showPublicExpander) {
+        if (mShowPublicExpander != showPublicExpander) {
+            mShowPublicExpander = showPublicExpander;
+            mPublicLayout.updateExpandButtons(mShowPublicExpander);
         }
     }
 
@@ -2863,6 +2916,7 @@
         if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) {
             return;
         }
+        float oldAlpha = getContentView().getAlpha();
 
         if (!animated) {
             mPublicLayout.animate().cancel();
@@ -2873,6 +2927,13 @@
             resetAllContentAlphas();
             mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
             updateChildrenVisibility();
+            if (NotificationContentAlphaOptimization.isEnabled()) {
+                // We want to set the old alpha to the now-showing layout to avoid breaking an
+                // on-going animation
+                if (oldAlpha != 1f) {
+                    setAlphaAndLayerType(mShowingPublic ? mPublicLayout : mPrivateLayout, oldAlpha);
+                }
+            }
         } else {
             animateShowingPublic(delay, duration, mShowingPublic);
         }
@@ -2954,7 +3015,7 @@
         if (isChildInGroup()) {
             mGroupExpansionManager.setGroupExpanded(mEntry, true);
         }
-        notifyHeightChanged(false /* needsAnimation */);
+        notifyHeightChanged(/* needsAnimation= */ false);
     }
 
     public void setChildrenExpanded(boolean expanded, boolean animate) {
@@ -3019,6 +3080,7 @@
                             onStartedRunnable.run();
                         }
                     }
+
                     @Override
                     public void onAnimationEnd(Animator animation) {
                         ExpandableNotificationRow.super.performRemoveAnimation(
@@ -3176,13 +3238,8 @@
             mGuts.setActualHeight(height);
             return;
         }
-        int contentHeight = Math.max(getMinHeight(), height);
         for (NotificationContentView l : mLayouts) {
-            if (mInlineReplyAnimation.isEnabled()) {
-                l.setContentHeight(height);
-            } else {
-                l.setContentHeight(contentHeight);
-            }
+            l.setContentHeight(height);
         }
         if (mIsSummaryWithChildren) {
             mChildrenContainer.setActualHeight(height);
@@ -3713,6 +3770,9 @@
             pw.print(", privateShowing: " + (showingLayout == mPrivateLayout));
             pw.print(", mShowNoBackground: " + mShowNoBackground);
             pw.println();
+            if (NotificationContentView.INCLUDE_HEIGHTS_TO_DUMP) {
+                dumpHeights(pw);
+            }
             showingLayout.dump(pw, args);
 
             if (getViewState() != null) {
@@ -3756,6 +3816,34 @@
         });
     }
 
+    private void dumpHeights(IndentingPrintWriter pw) {
+        pw.print("Heights: ");
+        pw.print("intrinsic", getIntrinsicHeight());
+        pw.print("actual", getActualHeight());
+        pw.print("maxContent", getMaxContentHeight());
+        pw.print("maxExpanded", getMaxExpandHeight());
+        pw.print("collapsed", getCollapsedHeight());
+        pw.print("headsup", getHeadsUpHeight());
+        pw.print("headsup  without header", getHeadsUpHeightWithoutHeader());
+        pw.print("minHeight", getMinHeight());
+        pw.print("pinned headsup", getPinnedHeadsUpHeight(
+                true /* atLeastMinHeight */));
+        pw.println();
+        pw.print("Intrinsic Height Factors: ");
+        pw.print("isUserLocked()", isUserLocked());
+        pw.print("isChildInGroup()", isChildInGroup());
+        pw.print("isGroupExpanded()", isGroupExpanded());
+        pw.print("sensitive", mSensitive);
+        pw.print("hideSensitiveForIntrinsicHeight", mHideSensitiveForIntrinsicHeight);
+        pw.print("isSummaryWithChildren", mIsSummaryWithChildren);
+        pw.print("canShowHeadsUp()", canShowHeadsUp());
+        pw.print("isHeadsUpState()", isHeadsUpState());
+        pw.print("isPinned()", isPinned());
+        pw.print("headsupDisappearRunning", mHeadsupDisappearRunning);
+        pw.print("isExpanded()", isExpanded());
+        pw.println();
+    }
+
     private void logKeepInParentChildDetached(ExpandableNotificationRow child) {
         if (mLogger != null) {
             mLogger.logKeepInParentChildDetached(child.getEntry(), getEntry());
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 ea9df9a..05e8717 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
@@ -275,15 +275,6 @@
         return getHeight();
     }
 
-    /**
-     * Sets the notification as dimmed. The default implementation does nothing.
-     *
-     * @param dimmed Whether the notification should be dimmed.
-     * @param fade Whether an animation should be played to change the state.
-     */
-    public void setDimmed(boolean dimmed, boolean fade) {
-    }
-
     public boolean isRemoved() {
         return false;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
index dab89c5..b90aa10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
@@ -19,7 +19,9 @@
 import android.widget.flags.Flags.notifLinearlayoutOptimized
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
 import javax.inject.Inject
+import javax.inject.Provider
 
 interface NotifRemoteViewsFactoryContainer {
     val factories: Set<NotifRemoteViewsFactory>
@@ -31,7 +33,8 @@
     featureFlags: FeatureFlags,
     precomputedTextViewFactory: PrecomputedTextViewFactory,
     bigPictureLayoutInflaterFactory: BigPictureLayoutInflaterFactory,
-    optimizedLinearLayoutFactory: NotificationOptimizedLinearLayoutFactory
+    optimizedLinearLayoutFactory: NotificationOptimizedLinearLayoutFactory,
+    notificationViewFlipperFactory: Provider<NotificationViewFlipperFactory>,
 ) : NotifRemoteViewsFactoryContainer {
     override val factories: Set<NotifRemoteViewsFactory> = buildSet {
         add(precomputedTextViewFactory)
@@ -41,5 +44,8 @@
         if (notifLinearlayoutOptimized()) {
             add(optimizedLinearLayoutFactory)
         }
+        if (NotificationViewFlipperPausing.isEnabled) {
+            add(notificationViewFlipperFactory.get())
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index e288e85..f835cca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -37,9 +37,12 @@
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
+import android.view.NotificationHeaderView;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.RemoteViews;
 
+import com.android.app.tracing.TraceUtils;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.ImageMessageConsumer;
 import com.android.systemui.dagger.SysUISingleton;
@@ -51,11 +54,13 @@
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
 import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
 import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
 import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineConversationViewBinder;
 import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder;
 import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
+import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
 import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
@@ -365,34 +370,55 @@
             ExpandableNotificationRow row,
             NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider,
             NotificationContentInflaterLogger logger) {
-        InflationProgress result = new InflationProgress();
-        final NotificationEntry entryForLogging = row.getEntry();
+        return TraceUtils.trace("NotificationContentInflater.createRemoteViews", () -> {
+            InflationProgress result = new InflationProgress();
+            final NotificationEntry entryForLogging = row.getEntry();
 
-        if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
-            logger.logAsyncTaskProgress(entryForLogging, "creating contracted remote view");
-            result.newContentView = createContentView(builder, isLowPriority, usesIncreasedHeight);
-        }
+            if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
+                logger.logAsyncTaskProgress(entryForLogging, "creating contracted remote view");
+                result.newContentView = createContentView(builder, isLowPriority,
+                        usesIncreasedHeight);
+            }
 
-        if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
-            logger.logAsyncTaskProgress(entryForLogging, "creating expanded remote view");
-            result.newExpandedView = createExpandedView(builder, isLowPriority);
-        }
+            if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
+                logger.logAsyncTaskProgress(entryForLogging, "creating expanded remote view");
+                result.newExpandedView = createExpandedView(builder, isLowPriority);
+            }
 
-        if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
-            logger.logAsyncTaskProgress(entryForLogging, "creating heads up remote view");
-            result.newHeadsUpView = builder.createHeadsUpContentView(usesIncreasedHeadsUpHeight);
-        }
+            if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
+                logger.logAsyncTaskProgress(entryForLogging, "creating heads up remote view");
+                result.newHeadsUpView = builder.createHeadsUpContentView(
+                        usesIncreasedHeadsUpHeight);
+            }
 
-        if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
-            logger.logAsyncTaskProgress(entryForLogging, "creating public remote view");
-            result.newPublicView = builder.makePublicContentView(isLowPriority);
-        }
-        setNotifsViewsInflaterFactory(result, row, notifLayoutInflaterFactoryProvider);
-        result.packageContext = packageContext;
-        result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(false /* showingPublic */);
-        result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
-                true /* showingPublic */);
-        return result;
+            if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
+                logger.logAsyncTaskProgress(entryForLogging, "creating public remote view");
+                result.newPublicView = builder.makePublicContentView(isLowPriority);
+            }
+
+            if (AsyncGroupHeaderViewInflation.isEnabled()) {
+                if ((reInflateFlags & FLAG_GROUP_SUMMARY_HEADER) != 0) {
+                    logger.logAsyncTaskProgress(entryForLogging,
+                            "creating group summary remote view");
+                    result.mNewGroupHeaderView = builder.makeNotificationGroupHeader();
+                }
+
+                if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) {
+                    logger.logAsyncTaskProgress(entryForLogging,
+                            "creating low-priority group summary remote view");
+                    result.mNewLowPriorityGroupHeaderView =
+                            builder.makeLowPriorityContentView(true /* useRegularSubtext */);
+                }
+            }
+            setNotifsViewsInflaterFactory(result, row, notifLayoutInflaterFactoryProvider);
+            result.packageContext = packageContext;
+            result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(
+                    false /* showingPublic */);
+            result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
+                    true /* showingPublic */);
+
+            return result;
+        });
     }
 
     private static void setNotifsViewsInflaterFactory(InflationProgress result,
@@ -426,6 +452,8 @@
             RemoteViews.InteractionHandler remoteViewClickHandler,
             @Nullable InflationCallback callback,
             NotificationContentInflaterLogger logger) {
+        Trace.beginAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row));
+
         NotificationContentView privateLayout = row.getPrivateLayout();
         NotificationContentView publicLayout = row.getPublicLayout();
         final HashMap<Integer, CancellationSignal> runningInflations = new HashMap<>();
@@ -534,6 +562,67 @@
                     runningInflations, applyCallback, logger);
         }
 
+        if (AsyncGroupHeaderViewInflation.isEnabled()) {
+            NotificationChildrenContainer childrenContainer = row.getChildrenContainerNonNull();
+            if ((reInflateFlags & FLAG_GROUP_SUMMARY_HEADER) != 0) {
+                boolean isNewView =
+                        !canReapplyRemoteView(
+                                /* newView = */ result.mNewGroupHeaderView,
+                                /* oldView = */ remoteViewCache
+                                        .getCachedView(entry, FLAG_GROUP_SUMMARY_HEADER));
+                ApplyCallback applyCallback = new ApplyCallback() {
+                    @Override
+                    public void setResultView(View v) {
+                        logger.logAsyncTaskProgress(entry, "group header view applied");
+                        result.mInflatedGroupHeaderView = (NotificationHeaderView) v;
+                    }
+
+                    @Override
+                    public RemoteViews getRemoteView() {
+                        return result.mNewGroupHeaderView;
+                    }
+                };
+                logger.logAsyncTaskProgress(entry, "applying group header view");
+                applyRemoteView(inflationExecutor, inflateSynchronously, result, reInflateFlags,
+                        /* inflationId = */ FLAG_GROUP_SUMMARY_HEADER,
+                        remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback,
+                        /* parentLayout = */ childrenContainer,
+                        /* existingView = */ childrenContainer.getNotificationHeader(),
+                        /* existingWrapper = */ childrenContainer.getNotificationHeaderWrapper(),
+                        runningInflations, applyCallback, logger);
+            }
+
+            if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) {
+                boolean isNewView =
+                        !canReapplyRemoteView(
+                                /* newView = */ result.mNewLowPriorityGroupHeaderView,
+                                /* oldView = */ remoteViewCache.getCachedView(
+                                        entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER));
+                ApplyCallback applyCallback = new ApplyCallback() {
+                    @Override
+                    public void setResultView(View v) {
+                        logger.logAsyncTaskProgress(entry,
+                                "low-priority group header view applied");
+                        result.mInflatedLowPriorityGroupHeaderView = (NotificationHeaderView) v;
+                    }
+
+                    @Override
+                    public RemoteViews getRemoteView() {
+                        return result.mNewLowPriorityGroupHeaderView;
+                    }
+                };
+                logger.logAsyncTaskProgress(entry, "applying low priority group header view");
+                applyRemoteView(inflationExecutor, inflateSynchronously, result, reInflateFlags,
+                        /* inflationId = */ FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
+                        remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback,
+                        /* parentLayout = */ childrenContainer,
+                        /* existingView = */ childrenContainer.getNotificationHeaderLowPriority(),
+                        /* existingWrapper = */ childrenContainer
+                                .getLowPriorityViewWrapper(),
+                        runningInflations, applyCallback, logger);
+            }
+        }
+
         // Let's try to finish, maybe nobody is even inflating anything
         finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations, callback, entry,
                 row, logger);
@@ -541,6 +630,7 @@
         cancellationSignal.setOnCancelListener(
                 () -> {
                     logger.logAsyncTaskProgress(entry, "apply cancelled");
+                    Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row));
                     runningInflations.values().forEach(CancellationSignal::cancel);
                 });
 
@@ -560,7 +650,7 @@
             boolean isNewView,
             RemoteViews.InteractionHandler remoteViewClickHandler,
             @Nullable final InflationCallback callback,
-            NotificationContentView parentLayout,
+            ViewGroup parentLayout,
             View existingView,
             NotificationViewWrapper existingWrapper,
             final HashMap<Integer, CancellationSignal> runningInflations,
@@ -689,19 +779,23 @@
         if (!requiresHeightCheck(entry)) {
             return true;
         }
-        Trace.beginSection("NotificationContentInflater#satisfiesMinHeightRequirement");
-        int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
-        int referenceWidth = resources.getDimensionPixelSize(
-                R.dimen.notification_validation_reference_width);
-        int widthSpec = View.MeasureSpec.makeMeasureSpec(referenceWidth, View.MeasureSpec.EXACTLY);
-        view.measure(widthSpec, heightSpec);
-        int minHeight = resources.getDimensionPixelSize(
-                R.dimen.notification_validation_minimum_allowed_height);
-        boolean result = view.getMeasuredHeight() >= minHeight;
-        Trace.endSection();
-        return result;
+        return TraceUtils.trace("NotificationContentInflater#satisfiesMinHeightRequirement", () -> {
+            int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+            int referenceWidth = resources.getDimensionPixelSize(
+                    R.dimen.notification_validation_reference_width);
+            int widthSpec = View.MeasureSpec.makeMeasureSpec(referenceWidth,
+                    View.MeasureSpec.EXACTLY);
+            view.measure(widthSpec, heightSpec);
+            int minHeight = resources.getDimensionPixelSize(
+                    R.dimen.notification_validation_minimum_allowed_height);
+            return view.getMeasuredHeight() >= minHeight;
+        });
     }
 
+    /**
+     * Notifications with undecorated custom views need to satisfy a minimum height to avoid visual
+     * issues.
+     */
     private static boolean requiresHeightCheck(NotificationEntry entry) {
         // Undecorated custom views are disallowed from S onwards
         if (entry.targetSdk >= Build.VERSION_CODES.S) {
@@ -749,110 +843,144 @@
             @Nullable InflationCallback endListener, NotificationEntry entry,
             ExpandableNotificationRow row, NotificationContentInflaterLogger logger) {
         Assert.isMainThread();
+        if (!runningInflations.isEmpty()) {
+            return false;
+        }
         NotificationContentView privateLayout = row.getPrivateLayout();
         NotificationContentView publicLayout = row.getPublicLayout();
-        if (runningInflations.isEmpty()) {
-            logger.logAsyncTaskProgress(entry, "finishing");
-            boolean setRepliesAndActions = true;
-            if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
-                if (result.inflatedContentView != null) {
-                    // New view case
-                    privateLayout.setContractedChild(result.inflatedContentView);
-                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
-                            result.newContentView);
-                } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) {
-                    // Reinflation case. Only update if it's still cached (i.e. view has not been
-                    // freed while inflating).
-                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
-                            result.newContentView);
-                }
-                setRepliesAndActions = true;
+        logger.logAsyncTaskProgress(entry, "finishing");
+        boolean setRepliesAndActions = true;
+        if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
+            if (result.inflatedContentView != null) {
+                // New view case
+                privateLayout.setContractedChild(result.inflatedContentView);
+                remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
+                        result.newContentView);
+            } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) {
+                // Reinflation case. Only update if it's still cached (i.e. view has not been
+                // freed while inflating).
+                remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
+                        result.newContentView);
             }
-
-            if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
-                if (result.inflatedExpandedView != null) {
-                    privateLayout.setExpandedChild(result.inflatedExpandedView);
-                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
-                            result.newExpandedView);
-                } else if (result.newExpandedView == null) {
-                    privateLayout.setExpandedChild(null);
-                    remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED);
-                } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) {
-                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
-                            result.newExpandedView);
-                }
-                if (result.newExpandedView != null) {
-                    privateLayout.setExpandedInflatedSmartReplies(
-                            result.expandedInflatedSmartReplies);
-                } else {
-                    privateLayout.setExpandedInflatedSmartReplies(null);
-                }
-                row.setExpandable(result.newExpandedView != null);
-                setRepliesAndActions = true;
-            }
-
-            if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
-                if (result.inflatedHeadsUpView != null) {
-                    privateLayout.setHeadsUpChild(result.inflatedHeadsUpView);
-                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
-                            result.newHeadsUpView);
-                } else if (result.newHeadsUpView == null) {
-                    privateLayout.setHeadsUpChild(null);
-                    remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
-                } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) {
-                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
-                            result.newHeadsUpView);
-                }
-                if (result.newHeadsUpView != null) {
-                    privateLayout.setHeadsUpInflatedSmartReplies(
-                            result.headsUpInflatedSmartReplies);
-                } else {
-                    privateLayout.setHeadsUpInflatedSmartReplies(null);
-                }
-                setRepliesAndActions = true;
-            }
-
-            if (AsyncHybridViewInflation.isEnabled()
-                    && (reInflateFlags & FLAG_CONTENT_VIEW_SINGLE_LINE) != 0) {
-                HybridNotificationView viewHolder = result.mInflatedSingleLineViewHolder;
-                SingleLineViewModel viewModel = result.mInflatedSingleLineViewModel;
-                if (viewHolder != null && viewModel != null) {
-                    if (viewModel.isConversation()) {
-                        SingleLineConversationViewBinder.bind(
-                                result.mInflatedSingleLineViewModel,
-                                result.mInflatedSingleLineViewHolder
-                        );
-                    } else {
-                        SingleLineViewBinder.bind(result.mInflatedSingleLineViewModel,
-                                result.mInflatedSingleLineViewHolder);
-                    }
-                    privateLayout.setSingleLineView(result.mInflatedSingleLineViewHolder);
-                }
-            }
-
-            if (setRepliesAndActions) {
-                privateLayout.setInflatedSmartReplyState(result.inflatedSmartReplyState);
-            }
-
-            if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
-                if (result.inflatedPublicView != null) {
-                    publicLayout.setContractedChild(result.inflatedPublicView);
-                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
-                            result.newPublicView);
-                } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) {
-                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
-                            result.newPublicView);
-                }
-            }
-
-            entry.headsUpStatusBarText = result.headsUpStatusBarText;
-            entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic;
-            if (endListener != null) {
-                endListener.onAsyncInflationFinished(entry);
-            }
-            return true;
+            setRepliesAndActions = true;
         }
-        return false;
+
+        if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
+            if (result.inflatedExpandedView != null) {
+                privateLayout.setExpandedChild(result.inflatedExpandedView);
+                remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
+                        result.newExpandedView);
+            } else if (result.newExpandedView == null) {
+                privateLayout.setExpandedChild(null);
+                remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED);
+            } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) {
+                remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
+                        result.newExpandedView);
+            }
+            if (result.newExpandedView != null) {
+                privateLayout.setExpandedInflatedSmartReplies(
+                        result.expandedInflatedSmartReplies);
+            } else {
+                privateLayout.setExpandedInflatedSmartReplies(null);
+            }
+            row.setExpandable(result.newExpandedView != null);
+            setRepliesAndActions = true;
+        }
+
+        if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
+            if (result.inflatedHeadsUpView != null) {
+                privateLayout.setHeadsUpChild(result.inflatedHeadsUpView);
+                remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
+                        result.newHeadsUpView);
+            } else if (result.newHeadsUpView == null) {
+                privateLayout.setHeadsUpChild(null);
+                remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
+            } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) {
+                remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
+                        result.newHeadsUpView);
+            }
+            if (result.newHeadsUpView != null) {
+                privateLayout.setHeadsUpInflatedSmartReplies(
+                        result.headsUpInflatedSmartReplies);
+            } else {
+                privateLayout.setHeadsUpInflatedSmartReplies(null);
+            }
+            setRepliesAndActions = true;
+        }
+
+        if (AsyncHybridViewInflation.isEnabled()
+                && (reInflateFlags & FLAG_CONTENT_VIEW_SINGLE_LINE) != 0) {
+            HybridNotificationView viewHolder = result.mInflatedSingleLineViewHolder;
+            SingleLineViewModel viewModel = result.mInflatedSingleLineViewModel;
+            if (viewHolder != null && viewModel != null) {
+                if (viewModel.isConversation()) {
+                    SingleLineConversationViewBinder.bind(
+                            result.mInflatedSingleLineViewModel,
+                            result.mInflatedSingleLineViewHolder
+                    );
+                } else {
+                    SingleLineViewBinder.bind(result.mInflatedSingleLineViewModel,
+                            result.mInflatedSingleLineViewHolder);
+                }
+                privateLayout.setSingleLineView(result.mInflatedSingleLineViewHolder);
+            }
+        }
+
+        if (setRepliesAndActions) {
+            privateLayout.setInflatedSmartReplyState(result.inflatedSmartReplyState);
+        }
+
+        if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
+            if (result.inflatedPublicView != null) {
+                publicLayout.setContractedChild(result.inflatedPublicView);
+                remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
+                        result.newPublicView);
+            } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) {
+                remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
+                        result.newPublicView);
+            }
+        }
+
+        if (AsyncGroupHeaderViewInflation.isEnabled()) {
+            if ((reInflateFlags & FLAG_GROUP_SUMMARY_HEADER) != 0) {
+                if (result.mInflatedGroupHeaderView != null) {
+                    row.setIsLowPriority(false);
+                    row.setGroupHeader(/* headerView= */ result.mInflatedGroupHeaderView);
+                    remoteViewCache.putCachedView(entry, FLAG_GROUP_SUMMARY_HEADER,
+                            result.mNewGroupHeaderView);
+                } else if (remoteViewCache.hasCachedView(entry, FLAG_GROUP_SUMMARY_HEADER)) {
+                    // Re-inflation case. Only update if it's still cached (i.e. view has not
+                    // been freed while inflating).
+                    remoteViewCache.putCachedView(entry, FLAG_GROUP_SUMMARY_HEADER,
+                            result.mNewGroupHeaderView);
+                }
+            }
+
+            if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) {
+                if (result.mInflatedLowPriorityGroupHeaderView != null) {
+                    // New view case, set row to low priority
+                    row.setIsLowPriority(true);
+                    row.setLowPriorityGroupHeader(
+                            /* headerView= */ result.mInflatedLowPriorityGroupHeaderView);
+                    remoteViewCache.putCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
+                            result.mNewLowPriorityGroupHeaderView);
+                } else if (remoteViewCache.hasCachedView(entry,
+                        FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER)) {
+                    // Re-inflation case. Only update if it's still cached (i.e. view has not
+                    // been freed while inflating).
+                    remoteViewCache.putCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
+                            result.mNewGroupHeaderView);
+                }
+            }
+        }
+
+        entry.headsUpStatusBarText = result.headsUpStatusBarText;
+        entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic;
+        Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row));
+        if (endListener != null) {
+            endListener.onAsyncInflationFinished(entry);
+        }
+        return true;
     }
 
     private static RemoteViews createExpandedView(Notification.Builder builder,
@@ -985,83 +1113,97 @@
         }
 
         @Override
+        protected void onPreExecute() {
+            Trace.beginAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this));
+        }
+
+        @Override
         protected InflationProgress doInBackground(Void... params) {
-            try {
-                final StatusBarNotification sbn = mEntry.getSbn();
-                // Ensure the ApplicationInfo is updated before a builder is recovered.
-                updateApplicationInfo(sbn);
-                final Notification.Builder recoveredBuilder
-                        = Notification.Builder.recoverBuilder(mContext,
-                        sbn.getNotification());
+            return TraceUtils.trace("NotificationContentInflater.AsyncInflationTask#doInBackground",
+                    () -> {
+                        try {
+                            return doInBackgroundInternal();
+                        } catch (Exception e) {
+                            mError = e;
+                            mLogger.logAsyncTaskException(mEntry, "inflating", e);
+                            return null;
+                        }
+                    });
+        }
 
-                Context packageContext = sbn.getPackageContext(mContext);
-                if (recoveredBuilder.usesTemplate()) {
-                    // For all of our templates, we want it to be RTL
-                    packageContext = new RtlEnabledContext(packageContext);
-                }
-                boolean isConversation = mEntry.getRanking().isConversation();
-                Notification.MessagingStyle messagingStyle = null;
-                if (isConversation) {
-                    messagingStyle = mConversationProcessor.processNotification(
-                            mEntry, recoveredBuilder, mLogger);
-                }
-                InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
-                        recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
-                        mUsesIncreasedHeadsUpHeight, packageContext, mRow,
-                        mNotifLayoutInflaterFactoryProvider, mLogger);
+        private InflationProgress doInBackgroundInternal() {
+            final StatusBarNotification sbn = mEntry.getSbn();
+            // Ensure the ApplicationInfo is updated before a builder is recovered.
+            updateApplicationInfo(sbn);
+            final Notification.Builder recoveredBuilder = Notification.Builder.recoverBuilder(
+                    mContext, sbn.getNotification());
 
-                mLogger.logAsyncTaskProgress(mEntry,
-                        "getting existing smart reply state (on wrong thread!)");
-                InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState();
-                mLogger.logAsyncTaskProgress(mEntry, "inflating smart reply views");
-                InflationProgress result = inflateSmartReplyViews(
-                        /* result = */ inflationProgress,
-                        mReInflateFlags,
-                        mEntry,
-                        mContext,
-                        packageContext,
-                        previousSmartReplyState,
-                        mSmartRepliesInflater,
-                        mLogger);
-
-                if (AsyncHybridViewInflation.isEnabled()) {
-                    // Inflate the single-line content view's ViewModel and ViewHolder from the
-                    // background thread, the ViewHolder needs to be bind with ViewModel later from
-                    // the main thread.
-                    result.mInflatedSingleLineViewModel = SingleLineViewInflater
-                            .inflateSingleLineViewModel(
-                                    mEntry.getSbn().getNotification(),
-                                    messagingStyle,
-                                    recoveredBuilder,
-                                    mContext
-                            );
-                    result.mInflatedSingleLineViewHolder =
-                            SingleLineViewInflater.inflateSingleLineViewHolder(
-                                    isConversation,
-                                    mReInflateFlags,
-                                    mEntry,
-                                    mContext,
-                                    mLogger
-                            );
-                }
-
-                mLogger.logAsyncTaskProgress(mEntry,
-                        "getting row image resolver (on wrong thread!)");
-                final NotificationInlineImageResolver imageResolver = mRow.getImageResolver();
-                // wait for image resolver to finish preloading
-                mLogger.logAsyncTaskProgress(mEntry, "waiting for preloaded images");
-                imageResolver.waitForPreloadedImages(IMG_PRELOAD_TIMEOUT_MS);
-
-                return result;
-            } catch (Exception e) {
-                mError = e;
-                mLogger.logAsyncTaskException(mEntry, "inflating", e);
-                return null;
+            Context packageContext = sbn.getPackageContext(mContext);
+            if (recoveredBuilder.usesTemplate()) {
+                // For all of our templates, we want it to be RTL
+                packageContext = new RtlEnabledContext(packageContext);
             }
+            boolean isConversation = mEntry.getRanking().isConversation();
+            Notification.MessagingStyle messagingStyle = null;
+            if (isConversation) {
+                messagingStyle = mConversationProcessor.processNotification(
+                        mEntry, recoveredBuilder, mLogger);
+            }
+            InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
+                    recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
+                    mUsesIncreasedHeadsUpHeight, packageContext, mRow,
+                    mNotifLayoutInflaterFactoryProvider, mLogger);
+
+            mLogger.logAsyncTaskProgress(mEntry,
+                    "getting existing smart reply state (on wrong thread!)");
+            InflatedSmartReplyState previousSmartReplyState =
+                    mRow.getExistingSmartReplyState();
+            mLogger.logAsyncTaskProgress(mEntry, "inflating smart reply views");
+            InflationProgress result = inflateSmartReplyViews(
+                    /* result = */ inflationProgress,
+                    mReInflateFlags,
+                    mEntry,
+                    mContext,
+                    packageContext,
+                    previousSmartReplyState,
+                    mSmartRepliesInflater,
+                    mLogger);
+
+            if (AsyncHybridViewInflation.isEnabled()) {
+                // Inflate the single-line content view's ViewModel and ViewHolder from the
+                // background thread, the ViewHolder needs to be bind with ViewModel later from
+                // the main thread.
+                result.mInflatedSingleLineViewModel = SingleLineViewInflater
+                        .inflateSingleLineViewModel(
+                                mEntry.getSbn().getNotification(),
+                                messagingStyle,
+                                recoveredBuilder,
+                                mContext
+                        );
+                result.mInflatedSingleLineViewHolder =
+                        SingleLineViewInflater.inflateSingleLineViewHolder(
+                                isConversation,
+                                mReInflateFlags,
+                                mEntry,
+                                mContext,
+                                mLogger
+                        );
+            }
+
+            mLogger.logAsyncTaskProgress(mEntry,
+                    "getting row image resolver (on wrong thread!)");
+            final NotificationInlineImageResolver imageResolver = mRow.getImageResolver();
+            // wait for image resolver to finish preloading
+            mLogger.logAsyncTaskProgress(mEntry, "waiting for preloaded images");
+            imageResolver.waitForPreloadedImages(IMG_PRELOAD_TIMEOUT_MS);
+
+            return result;
         }
 
         @Override
         protected void onPostExecute(InflationProgress result) {
+            Trace.endAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this));
+
             if (mError == null) {
                 // Logged in detail in apply.
                 mCancellationSignal = apply(
@@ -1080,6 +1222,11 @@
             }
         }
 
+        @Override
+        protected void onCancelled(InflationProgress result) {
+            Trace.endAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this));
+        }
+
         private void handleError(Exception e) {
             mEntry.onInflationTaskFinished();
             StatusBarNotification sbn = mEntry.getSbn();
@@ -1147,6 +1294,8 @@
         private RemoteViews newHeadsUpView;
         private RemoteViews newExpandedView;
         private RemoteViews newPublicView;
+        private RemoteViews mNewGroupHeaderView;
+        private RemoteViews mNewLowPriorityGroupHeaderView;
 
         @VisibleForTesting
         Context packageContext;
@@ -1155,6 +1304,8 @@
         private View inflatedHeadsUpView;
         private View inflatedExpandedView;
         private View inflatedPublicView;
+        private NotificationHeaderView mInflatedGroupHeaderView;
+        private NotificationHeaderView mInflatedLowPriorityGroupHeaderView;
         private CharSequence headsUpStatusBarText;
         private CharSequence headsUpStatusBarTextPublic;
 
@@ -1173,4 +1324,8 @@
         public abstract void setResultView(View v);
         public abstract RemoteViews getRemoteView();
     }
+
+    private static final String ASYNC_TASK_TRACE_METHOD =
+            "NotificationContentInflater.AsyncInflationTask";
+    private static final String APPLY_TRACE_METHOD = "NotificationContentInflater#apply";
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt
index ee9462c..15c7055 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt
@@ -27,6 +27,8 @@
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_GROUP_SUMMARY_HEADER
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
 import javax.inject.Inject
 
@@ -145,6 +147,12 @@
             if (flag and FLAG_CONTENT_VIEW_SINGLE_LINE != 0) {
                 l.add("SINGLE_LINE")
             }
+            if (flag and FLAG_GROUP_SUMMARY_HEADER != 0) {
+                l.add("GROUP_SUMMARY_HEADER")
+            }
+            if (flag and FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER != 0) {
+                l.add("LOW_PRIORITY_GROUP_SUMMARY_HEADER")
+            }
             return l.joinToString("|")
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 3742482..8a3e7e8 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
@@ -70,6 +70,7 @@
 import com.android.systemui.statusbar.policy.SmartReplyView;
 import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
 import com.android.systemui.util.Compile;
+import com.android.systemui.util.DumpUtilsKt;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -97,6 +98,8 @@
 
     private static final int UNDEFINED = -1;
 
+    protected static final boolean INCLUDE_HEIGHTS_TO_DUMP = true;
+
     private final Rect mClipBounds = new Rect();
 
     private int mMinContractedHeight;
@@ -238,7 +241,10 @@
         mMinContractedHeight = getResources().getDimensionPixelSize(
                 R.dimen.min_notification_layout_height);
         if (AsyncHybridViewInflation.isEnabled()) {
-            //TODO: set the height with a more reasonable min single-line height
+            //TODO (b/217799515): single-line view height is the greater of two heights: text view
+            // height and icon height (when there's an icon). icon height is fixed to be
+            // conversation_single_line_face_pile_size (24dp), the text view's height is 16sp,
+            // its pixel height changes with the system's font scaling factor.
             mMinSingleLineHeight = getResources().getDimensionPixelSize(
                     R.dimen.conversation_single_line_face_pile_size);
         }
@@ -693,8 +699,7 @@
         int hint;
         if (mHeadsUpChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_HEADSUP)) {
             hint = getViewHeight(VISIBLE_TYPE_HEADSUP);
-            if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isAnimatingAppearance()
-                    && mHeadsUpRemoteInputController.isFocusAnimationFlagActive()) {
+            if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isAnimatingAppearance()) {
                 // While the RemoteInputView is animating its appearance, it should be allowed
                 // to overlap the hint, therefore no space is reserved for the hint during the
                 // appearance animation of the RemoteInputView
@@ -843,7 +848,7 @@
                 if (mSingleLineView != null) {
                     return getViewHeight(VISIBLE_TYPE_SINGLELINE);
                 } else {
-                    Log.wtf(TAG, "getMinHeight: mSingleLineView == null");
+                    //TODO(b/217799515): investigate the impact of min-height value
                     return mMinSingleLineHeight;
                 }
             } else {
@@ -2193,6 +2198,11 @@
             pw.print("null");
         }
         pw.println();
+
+        if (INCLUDE_HEIGHTS_TO_DUMP) {
+            dumpContentDimensions(DumpUtilsKt.asIndenting(pw));
+        }
+
         pw.println("mBubblesEnabledForUser: " + mBubblesEnabledForUser);
 
         pw.print("RemoteInputViews { ");
@@ -2213,6 +2223,69 @@
         pw.println(" }");
     }
 
+    private String visibleTypeToString(int visibleType) {
+        return switch (visibleType) {
+            case VISIBLE_TYPE_CONTRACTED -> "CONTRACTED";
+            case VISIBLE_TYPE_EXPANDED -> "EXPANDED";
+            case VISIBLE_TYPE_HEADSUP -> "HEADSUP";
+            case VISIBLE_TYPE_SINGLELINE -> "SINGLELINE";
+            default -> "NONE";
+        };
+    }
+
+    /** Add content views to dump */
+    private void dumpContentDimensions(IndentingPrintWriter pw) {
+        pw.print("ContentDimensions: ");
+        pw.print("visibleType(String)", visibleTypeToString(mVisibleType));
+        pw.print("measured width", getMeasuredWidth());
+        pw.print("measured height", getMeasuredHeight());
+        pw.print("maxHeight", getMaxHeight());
+        pw.print("minHeight", getMinHeight());
+        pw.println();
+        pw.println("ChildViews:");
+        DumpUtilsKt.withIncreasedIndent(pw, () -> {
+            final View contractedChild = mContractedChild;
+            if (contractedChild != null) {
+                dumpChildViewDimensions(pw, contractedChild, "Contracted Child:");
+                pw.println();
+            }
+
+            final View expandedChild = mExpandedChild;
+            if (expandedChild != null) {
+                dumpChildViewDimensions(pw, expandedChild, "Expanded Child:");
+                pw.println();
+            }
+
+            final View headsUpChild = mHeadsUpChild;
+            if (headsUpChild != null) {
+                dumpChildViewDimensions(pw, headsUpChild, "HeadsUp Child:");
+                pw.println();
+            }
+            final View singleLineView = mSingleLineView;
+            if (singleLineView != null) {
+                dumpChildViewDimensions(pw, singleLineView, "Single Line  View:");
+                pw.println();
+            }
+
+        });
+        final int expandedRemoteInputHeight = getExtraRemoteInputHeight(mExpandedRemoteInput);
+        final int headsUpRemoteInputHeight = getExtraRemoteInputHeight(mHeadsUpRemoteInput);
+        pw.print("expandedRemoteInputHeight", expandedRemoteInputHeight);
+        pw.print("headsUpRemoteInputHeight", headsUpRemoteInputHeight);
+        pw.println();
+    }
+
+    private void dumpChildViewDimensions(IndentingPrintWriter pw, View view,
+            String name) {
+        pw.print(name + " ");
+        DumpUtilsKt.withIncreasedIndent(pw, () -> {
+            pw.print("width", view.getWidth());
+            pw.print("height", view.getHeight());
+            pw.print("measuredWidth", view.getMeasuredWidth());
+            pw.print("measuredHeight", view.getMeasuredHeight());
+        });
+    }
+
     /** Add any existing SmartReplyView to the dump */
     public void dumpSmartReplies(IndentingPrintWriter pw) {
         if (mHeadsUpSmartReplyView != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 14593cb..e141b7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
@@ -353,18 +354,15 @@
     }
 
     private void bindPackage() {
-        ApplicationInfo info;
-        try {
-            info = mPm.getApplicationInfo(
-                    mPackageName,
-                    PackageManager.MATCH_UNINSTALLED_PACKAGES
-                            | PackageManager.MATCH_DISABLED_COMPONENTS
-                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                            | PackageManager.MATCH_DIRECT_BOOT_AWARE);
-            if (info != null) {
+        // filled in if missing during notification inflation, which must have happened if
+        // we have a notification to long press on
+        ApplicationInfo info =
+                mSbn.getNotification().extras.getParcelable(EXTRA_BUILDER_APPLICATION_INFO,
+                        ApplicationInfo.class);
+        if (info != null) {
+            try {
                 mAppName = String.valueOf(mPm.getApplicationLabel(info));
-            }
-        } catch (PackageManager.NameNotFoundException e) {
+            } catch (Exception ignored) {}
         }
         ((TextView) findViewById(R.id.pkg_name)).setText(mAppName);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index d8ebd42..2012099 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
@@ -66,7 +67,6 @@
 
 import java.lang.annotation.Retention;
 import java.util.List;
-import java.util.Set;
 
 /**
  * The guts of a notification revealed when performing a long press.
@@ -297,19 +297,18 @@
     private void bindHeader() {
         // Package name
         mPkgIcon = null;
-        ApplicationInfo info;
-        try {
-            info = mPm.getApplicationInfo(
-                    mPackageName,
-                    PackageManager.MATCH_UNINSTALLED_PACKAGES
-                            | PackageManager.MATCH_DISABLED_COMPONENTS
-                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                            | PackageManager.MATCH_DIRECT_BOOT_AWARE);
-            if (info != null) {
+        // filled in if missing during notification inflation, which must have happened if
+        // we have a notification to long press on
+        ApplicationInfo info =
+                mSbn.getNotification().extras.getParcelable(EXTRA_BUILDER_APPLICATION_INFO,
+                        ApplicationInfo.class);
+        if (info != null) {
+            try {
                 mAppName = String.valueOf(mPm.getApplicationLabel(info));
                 mPkgIcon = mPm.getApplicationIcon(info);
-            }
-        } catch (PackageManager.NameNotFoundException e) {
+            } catch (Exception ignored) {}
+        }
+        if (mPkgIcon == null) {
             // app is gone, just show package name and generic icon
             mPkgIcon = mPm.getDefaultActivityIcon();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
index 736140c..b0fd475 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
@@ -81,6 +81,8 @@
                     FLAG_CONTENT_VIEW_HEADS_UP,
                     FLAG_CONTENT_VIEW_PUBLIC,
                     FLAG_CONTENT_VIEW_SINGLE_LINE,
+                    FLAG_GROUP_SUMMARY_HEADER,
+                    FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
                     FLAG_CONTENT_VIEW_ALL})
     @interface InflationFlag {}
     /**
@@ -108,7 +110,17 @@
      */
     int FLAG_CONTENT_VIEW_SINGLE_LINE = 1 << 4;
 
-    int FLAG_CONTENT_VIEW_ALL = (1 << 5) - 1;
+    /**
+     * The notification group summary header view
+     */
+    int FLAG_GROUP_SUMMARY_HEADER = 1 << 5;
+
+    /**
+     * The notification low-priority group summary header view
+     */
+    int FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER = 1 << 6;
+
+    int FLAG_CONTENT_VIEW_ALL = (1 << 7) - 1;
 
     /**
      * Parameters for content view binding
@@ -129,6 +141,11 @@
          * Use increased height when binding heads up views.
          */
         public boolean usesIncreasedHeadsUpHeight;
+
+        /**
+         * Is group summary notification
+         */
+        public boolean mIsGroupSummary;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationViewFlipperFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationViewFlipperFactory.kt
new file mode 100644
index 0000000..0594c12
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationViewFlipperFactory.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.ViewFlipper
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.NotificationViewFlipperBinder
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.NotificationViewFlipperViewModel
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
+import javax.inject.Inject
+
+/**
+ * A factory which owns the construction of any ViewFlipper inside of Notifications, and binds it
+ * with a view model. This ensures that ViewFlippers are paused when the keyguard is showing.
+ */
+class NotificationViewFlipperFactory
+@Inject
+constructor(
+    private val viewModel: NotificationViewFlipperViewModel,
+) : NotifRemoteViewsFactory {
+    init {
+        /* check if */ NotificationViewFlipperPausing.isUnexpectedlyInLegacyMode()
+    }
+
+    override fun instantiate(
+        row: ExpandableNotificationRow,
+        @InflationFlag layoutType: Int,
+        parent: View?,
+        name: String,
+        context: Context,
+        attrs: AttributeSet
+    ): View? {
+        return when (name) {
+            ViewFlipper::class.java.name,
+            ViewFlipper::class.java.simpleName ->
+                ViewFlipper(context, attrs).also { viewFlipper ->
+                    NotificationViewFlipperBinder.bindWhileAttached(viewFlipper, viewModel)
+                }
+            else -> null
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
index 9445d56..ea3036e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
@@ -31,6 +31,7 @@
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.time.SystemClock;
 
 import javax.inject.Inject;
 
@@ -46,9 +47,14 @@
     private NotificationEntry mEntry;
     private boolean mCancelled;
     private Throwable mInflateOrigin;
+    private final SystemClock mSystemClock;
+    private final RowInflaterTaskLogger mLogger;
+    private long mInflateStartTimeMs;
 
     @Inject
-    public RowInflaterTask() {
+    public RowInflaterTask(SystemClock systemClock, RowInflaterTaskLogger logger) {
+        mSystemClock = systemClock;
+        mLogger = logger;
     }
 
     /**
@@ -61,29 +67,49 @@
         }
         mListener = listener;
         AsyncLayoutInflater inflater = com.android.systemui.Flags.notificationRowUserContext()
-                ? new AsyncLayoutInflater(context, new RowAsyncLayoutInflater(entry))
+                ? new AsyncLayoutInflater(context, makeRowInflater(entry))
                 : new AsyncLayoutInflater(context);
         mEntry = entry;
         entry.setInflationTask(this);
+
+        mLogger.logInflateStart(entry);
+        mInflateStartTimeMs = mSystemClock.elapsedRealtime();
         inflater.inflate(R.layout.status_bar_notification_row, parent, this);
     }
 
+    private RowAsyncLayoutInflater makeRowInflater(NotificationEntry entry) {
+        return new RowAsyncLayoutInflater(entry, mSystemClock, mLogger);
+    }
+
     @VisibleForTesting
     static class RowAsyncLayoutInflater implements AsyncLayoutFactory {
         private final NotificationEntry mEntry;
+        private final SystemClock mSystemClock;
+        private final RowInflaterTaskLogger mLogger;
 
-        RowAsyncLayoutInflater(NotificationEntry entry) {
+        RowAsyncLayoutInflater(NotificationEntry entry, SystemClock systemClock,
+                RowInflaterTaskLogger logger) {
             mEntry = entry;
+            mSystemClock = systemClock;
+            mLogger = logger;
         }
 
         @Nullable
         @Override
         public View onCreateView(@Nullable View parent, @NonNull String name,
                 @NonNull Context context, @NonNull AttributeSet attrs) {
-            if (name.equals(ExpandableNotificationRow.class.getName())) {
-                return new ExpandableNotificationRow(context, attrs, mEntry);
+            if (!name.equals(ExpandableNotificationRow.class.getName())) {
+                return null;
             }
-            return null;
+
+            final long startMs = mSystemClock.elapsedRealtime();
+            final ExpandableNotificationRow row =
+                    new ExpandableNotificationRow(context, attrs, mEntry);
+            final long elapsedMs = mSystemClock.elapsedRealtime() - startMs;
+
+            mLogger.logCreatedRow(mEntry, elapsedMs);
+
+            return row;
         }
 
         @Nullable
@@ -101,6 +127,9 @@
 
     @Override
     public void onInflateFinished(View view, int resid, ViewGroup parent) {
+        final long elapsedMs = mSystemClock.elapsedRealtime() - mInflateStartTimeMs;
+        mLogger.logInflateFinish(mEntry, elapsedMs, mCancelled);
+
         if (!mCancelled) {
             try {
                 mEntry.onInflationTaskFinished();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTaskLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTaskLogger.kt
new file mode 100644
index 0000000..94da6be
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTaskLogger.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.NotifInflationLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
+import javax.inject.Inject
+
+class RowInflaterTaskLogger @Inject constructor(@NotifInflationLog private val buffer: LogBuffer) {
+    fun logInflateStart(entry: NotificationEntry) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { str1 = entry.logKey },
+            { "started row inflation for $str1" }
+        )
+    }
+
+    fun logCreatedRow(entry: NotificationEntry, elapsedMs: Long) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = entry.logKey
+                long1 = elapsedMs
+            },
+            { "created row in $long1 ms for $str1" }
+        )
+    }
+
+    fun logInflateFinish(entry: NotificationEntry, elapsedMs: Long, cancelled: Boolean) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = entry.logKey
+                long1 = elapsedMs
+                bool1 = cancelled
+            },
+            { "finished ${if (bool1) "cancelled " else ""}row inflation in $long1 ms for $str1" }
+        )
+    }
+}
+
+private const val TAG = "RowInflaterTask"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/NotificationViewFlipperBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/NotificationViewFlipperBinder.kt
new file mode 100644
index 0000000..133d3e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/NotificationViewFlipperBinder.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewbinder
+
+import android.widget.ViewFlipper
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.NotificationViewFlipperViewModel
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+/** Binds a [NotificationViewFlipper] to its [view model][NotificationViewFlipperViewModel]. */
+object NotificationViewFlipperBinder {
+    fun bindWhileAttached(
+        viewFlipper: ViewFlipper,
+        viewModel: NotificationViewFlipperViewModel,
+    ): DisposableHandle {
+        if (viewFlipper.isAutoStart) {
+            // If the ViewFlipper is not set to AutoStart, the pause binding is meaningless
+            return DisposableHandle {}
+        }
+        return viewFlipper.repeatWhenAttached {
+            lifecycleScope.launch { bind(viewFlipper, viewModel) }
+        }
+    }
+
+    suspend fun bind(
+        viewFlipper: ViewFlipper,
+        viewModel: NotificationViewFlipperViewModel,
+    ) = coroutineScope { launch { viewModel.isPaused.collect { viewFlipper.setPaused(it) } } }
+
+    private fun ViewFlipper.setPaused(paused: Boolean) {
+        if (paused) {
+            stopFlipping()
+        } else if (isAutoStart) {
+            startFlipping()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModel.kt
new file mode 100644
index 0000000..7694e58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModel.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
+import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackInteractor
+import com.android.systemui.util.kotlin.FlowDumperImpl
+import javax.inject.Inject
+
+/** A model which represents whether ViewFlippers inside notifications should be paused. */
+@SysUISingleton
+class NotificationViewFlipperViewModel
+@Inject
+constructor(
+    dumpManager: DumpManager,
+    stackInteractor: NotificationStackInteractor,
+) : FlowDumperImpl(dumpManager) {
+    init {
+        /* check if */ NotificationViewFlipperPausing.isUnexpectedlyInLegacyMode()
+    }
+
+    val isPaused = stackInteractor.isShowingOnLockscreen.dumpWhileCollecting("isPaused")
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationContentAlphaOptimization.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationContentAlphaOptimization.kt
new file mode 100644
index 0000000..c703595
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationContentAlphaOptimization.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the Async Group Header Inflation flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationContentAlphaOptimization {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_NOTIFICATION_CONTENT_ALPHA_OPTIMIZATION
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the async inflation of group header views enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.notificationContentAlphaOptimization()
+
+    /**
+     * 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/shared/NotificationViewFlipperPausing.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationViewFlipperPausing.kt
new file mode 100644
index 0000000..cea6a2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationViewFlipperPausing.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notification view flipper pausing flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationViewFlipperPausing {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_NOTIFICATION_VIEW_FLIPPER_PAUSING
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the refactor enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.notificationViewFlipperPausing()
+
+    /**
+     * 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/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index c90acee..ac44b3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -61,7 +61,6 @@
      */
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private int mScrollY;
-    private boolean mDimmed;
     private float mOverScrollTopAmount;
     private float mOverScrollBottomAmount;
     private boolean mDozing;
@@ -263,9 +262,6 @@
         return mStackHeight;
     }
 
-    /** Tracks the state from HeadsUpManager#hasNotifications() */
-    private boolean mHasHeadsUpEntries;
-
     @Inject
     public AmbientState(
             @NonNull Context context,
@@ -344,14 +340,6 @@
         this.mScrollY = Math.max(scrollY, 0);
     }
 
-    /**
-     * @param dimmed Whether we are in a dimmed state (on the lockscreen), where the backgrounds are
-     *               translucent and everything is scaled back a bit.
-     */
-    public void setDimmed(boolean dimmed) {
-        mDimmed = dimmed;
-    }
-
     /** While dozing, we draw as little as possible, assuming a black background */
     public void setDozing(boolean dozing) {
         mDozing = dozing;
@@ -375,12 +363,6 @@
         mHideSensitive = hideSensitive;
     }
 
-    public boolean isDimmed() {
-        // While we are expanding from pulse, we want the notifications not to be dimmed, otherwise
-        // you'd see the difference to the pulsing notification
-        return mDimmed && !(isPulseExpanding() && mDozeAmount == 1.0f);
-    }
-
     public boolean isDozing() {
         return mDozing;
     }
@@ -562,10 +544,6 @@
         mPanelTracking = panelTracking;
     }
 
-    public boolean hasPulsingNotifications() {
-        return mPulsing && mHasHeadsUpEntries;
-    }
-
     public void setPulsing(boolean hasPulsing) {
         mPulsing = hasPulsing;
     }
@@ -716,10 +694,6 @@
         return mAppearFraction;
     }
 
-    public void setHasHeadsUpEntries(boolean hasHeadsUpEntries) {
-        mHasHeadsUpEntries = hasHeadsUpEntries;
-    }
-
     public void setStackTopMargin(int stackTopMargin) {
         mStackTopMargin = stackTopMargin;
     }
@@ -768,7 +742,6 @@
         pw.println("mHideSensitive=" + mHideSensitive);
         pw.println("mShadeExpanded=" + mShadeExpanded);
         pw.println("mClearAllInProgress=" + mClearAllInProgress);
-        pw.println("mDimmed=" + mDimmed);
         pw.println("mStatusBarState=" + mStatusBarState);
         pw.println("mExpansionChanging=" + mExpansionChanging);
         pw.println("mPanelFullWidth=" + mIsSmallScreen);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
index 5343cbf..03a1082 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
@@ -35,7 +35,6 @@
     boolean animateZ;
     boolean animateHeight;
     boolean animateTopInset;
-    boolean animateDimmed;
     boolean animateHideSensitive;
     boolean hasDelays;
     boolean hasGoToFullShadeEvent;
@@ -83,11 +82,6 @@
         return this;
     }
 
-    public AnimationFilter animateDimmed() {
-        animateDimmed = true;
-        return this;
-    }
-
     public AnimationFilter animateHideSensitive() {
         animateHideSensitive = true;
         return this;
@@ -128,7 +122,6 @@
         animateZ |= filter.animateZ;
         animateHeight |= filter.animateHeight;
         animateTopInset |= filter.animateTopInset;
-        animateDimmed |= filter.animateDimmed;
         animateHideSensitive |= filter.animateHideSensitive;
         hasDelays |= filter.hasDelays;
         mAnimatedProperties.addAll(filter.mAnimatedProperties);
@@ -142,7 +135,6 @@
         animateZ = false;
         animateHeight = false;
         animateTopInset = false;
-        animateDimmed = false;
         animateHideSensitive = false;
         hasDelays = false;
         hasGoToFullShadeEvent = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
index d0c5c82..d1e5ab0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
@@ -88,7 +88,6 @@
             | ExpandableViewState.LOCATION_MAIN_AREA;
 
     public int height;
-    public boolean dimmed;
     public boolean hideSensitive;
     public boolean belowSpeedBump;
     public boolean inShelf;
@@ -128,7 +127,6 @@
         if (viewState instanceof ExpandableViewState) {
             ExpandableViewState svs = (ExpandableViewState) viewState;
             height = svs.height;
-            dimmed = svs.dimmed;
             hideSensitive = svs.hideSensitive;
             belowSpeedBump = svs.belowSpeedBump;
             clipTopAmount = svs.clipTopAmount;
@@ -155,9 +153,6 @@
                 expandableView.setActualHeight(newHeight, false /* notifyListeners */);
             }
 
-            // apply dimming
-            expandableView.setDimmed(this.dimmed, false /* animate */);
-
             // apply hiding sensitive
             expandableView.setHideSensitive(
                     this.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
@@ -216,9 +211,6 @@
             abortAnimation(child, TAG_ANIMATOR_BOTTOM_INSET);
         }
 
-        // start dimmed animation
-        expandableView.setDimmed(this.dimmed, animationFilter.animateDimmed);
-
         // apply below the speed bump
         if (!NotificationIconContainerRefactor.isEnabled()) {
             expandableView.setBelowSpeedBump(this.belowSpeedBump);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index abf6c27..28f874d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -55,6 +55,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.HybridGroupManager;
 import com.android.systemui.statusbar.notification.row.HybridNotificationView;
+import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
 
@@ -131,6 +132,7 @@
     private int mUntruncatedChildCount;
     private boolean mContainingNotificationIsFaded = false;
     private RoundableState mRoundableState;
+    private int mMinSingleLineHeight;
 
     private NotificationChildrenContainerLogger mLogger;
 
@@ -183,6 +185,8 @@
                 com.android.internal.R.dimen.notification_content_margin)
                 - mNotificationHeaderMargin;
         mHybridGroupManager.initDimens();
+        mMinSingleLineHeight = getResources().getDimensionPixelSize(
+                R.dimen.conversation_single_line_face_pile_size);
     }
 
     @NonNull
@@ -385,16 +389,31 @@
         return mAttachedChildren.size();
     }
 
-    public void recreateNotificationHeader(OnClickListener listener, boolean isConversation) {
+    /**
+     * Re-create the Notification header view
+     * @param listener OnClickListener of the header view
+     * @param isConversation if the notification group is a conversation group
+     */
+    public void recreateNotificationHeader(
+            OnClickListener listener,
+            boolean isConversation
+    ) {
+        // We don't want to inflate headers from the main thread when async inflation enabled
+        AsyncGroupHeaderViewInflation.assertInLegacyMode();
+        // TODO(b/217799515): remove traces from this function in a follow-up change
         Trace.beginSection("NotifChildCont#recreateHeader");
         mHeaderClickListener = listener;
         mIsConversation = isConversation;
         StatusBarNotification notification = mContainingNotification.getEntry().getSbn();
         final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(),
                 notification.getNotification());
+        Trace.beginSection("recreateHeader#makeNotificationGroupHeader");
         RemoteViews header = builder.makeNotificationGroupHeader();
+        Trace.endSection();
         if (mNotificationHeader == null) {
+            Trace.beginSection("recreateHeader#apply");
             mNotificationHeader = (NotificationHeaderView) header.apply(getContext(), this);
+            Trace.endSection();
             mNotificationHeader.findViewById(com.android.internal.R.id.expand_button)
                     .setVisibility(VISIBLE);
             mNotificationHeader.setOnClickListener(mHeaderClickListener);
@@ -407,7 +426,9 @@
             addView(mNotificationHeader, 0);
             invalidate();
         } else {
+            Trace.beginSection("recreateHeader#reapply");
             header.reapply(getContext(), mNotificationHeader);
+            Trace.endSection();
         }
         mNotificationHeaderWrapper.setExpanded(mChildrenExpanded);
         mNotificationHeaderWrapper.onContentUpdated(mContainingNotification);
@@ -417,12 +438,105 @@
         Trace.endSection();
     }
 
+    private void removeGroupHeader() {
+        if (mNotificationHeader == null) {
+            return;
+        }
+        removeView(mNotificationHeader);
+        mNotificationHeader = null;
+        mNotificationHeaderWrapper = null;
+    }
+
+    private void removeLowPriorityGroupHeader() {
+        if (mNotificationHeaderLowPriority == null) {
+            return;
+        }
+        removeView(mNotificationHeaderLowPriority);
+        mNotificationHeaderLowPriority = null;
+        mNotificationHeaderWrapperLowPriority = null;
+    }
+
+    /**
+     * Set the group header view
+     * @param headerView view to set
+     * @param onClickListener OnClickListener of the header view
+     */
+    public void setGroupHeader(
+            NotificationHeaderView headerView,
+            OnClickListener onClickListener
+    ) {
+        if (AsyncGroupHeaderViewInflation.isUnexpectedlyInLegacyMode()) return;
+        mHeaderClickListener = onClickListener;
+
+        removeGroupHeader();
+
+        if (headerView == null) {
+            return;
+        }
+
+        mNotificationHeader = headerView;
+        mNotificationHeader.findViewById(com.android.internal.R.id.expand_button)
+                .setVisibility(VISIBLE);
+        mNotificationHeader.setOnClickListener(mHeaderClickListener);
+        mNotificationHeaderWrapper =
+                (NotificationHeaderViewWrapper) NotificationViewWrapper.wrap(
+                        getContext(),
+                        mNotificationHeader,
+                        mContainingNotification);
+        mNotificationHeaderWrapper.setOnRoundnessChangedListener(this::invalidate);
+        addView(mNotificationHeader, 0);
+        invalidate();
+
+        mNotificationHeaderWrapper.setExpanded(mChildrenExpanded);
+        mNotificationHeaderWrapper.onContentUpdated(mContainingNotification);
+
+        updateHeaderVisibility(false /* animate */);
+        updateChildrenAppearance();
+
+        Trace.endSection();
+    }
+
+    /**
+     * Set the low-priority group header view
+     * @param headerViewLowPriority header view to set
+     * @param onClickListener OnClickListener of the header view
+     */
+    public void setLowPriorityGroupHeader(
+            NotificationHeaderView headerViewLowPriority,
+            OnClickListener onClickListener
+    ) {
+        if (AsyncGroupHeaderViewInflation.isUnexpectedlyInLegacyMode()) return;
+        removeLowPriorityGroupHeader();
+        if (headerViewLowPriority == null) {
+            return;
+        }
+
+        mNotificationHeaderLowPriority = headerViewLowPriority;
+        mNotificationHeaderLowPriority.findViewById(com.android.internal.R.id.expand_button)
+                .setVisibility(VISIBLE);
+        mNotificationHeaderLowPriority.setOnClickListener(onClickListener);
+        mNotificationHeaderWrapperLowPriority =
+                (NotificationHeaderViewWrapper) NotificationViewWrapper.wrap(
+                        getContext(),
+                        mNotificationHeaderLowPriority,
+                        mContainingNotification);
+        mNotificationHeaderWrapperLowPriority.setOnRoundnessChangedListener(this::invalidate);
+        addView(mNotificationHeaderLowPriority, 0);
+        invalidate();
+
+        mNotificationHeaderWrapperLowPriority.onContentUpdated(mContainingNotification);
+        updateHeaderVisibility(false /* animate */);
+        updateChildrenAppearance();
+    }
+
     /**
      * Recreate the low-priority header.
      *
      * @param builder a builder to reuse. Otherwise the builder will be recovered.
      */
-    private void recreateLowPriorityHeader(Notification.Builder builder, boolean isConversation) {
+    @VisibleForTesting
+    void recreateLowPriorityHeader(Notification.Builder builder, boolean isConversation) {
+        AsyncGroupHeaderViewInflation.assertInLegacyMode();
         RemoteViews header;
         StatusBarNotification notification = mContainingNotification.getEntry().getSbn();
         if (mIsLowPriority) {
@@ -527,8 +641,10 @@
      * @param alpha alpha value to apply to the content
      */
     public void setContentAlpha(float alpha) {
-        for (int i = 0; i < mNotificationHeader.getChildCount(); i++) {
-            mNotificationHeader.getChildAt(i).setAlpha(alpha);
+        if (mNotificationHeader != null) {
+            for (int i = 0; i < mNotificationHeader.getChildCount(); i++) {
+                mNotificationHeader.getChildAt(i).setAlpha(alpha);
+            }
         }
         for (ExpandableNotificationRow child : getAttachedChildren()) {
             child.setContentAlpha(alpha);
@@ -564,7 +680,11 @@
      */
     private int getIntrinsicHeight(float maxAllowedVisibleChildren) {
         if (showingAsLowPriority()) {
-            return mNotificationHeaderLowPriority.getHeight();
+            if (AsyncGroupHeaderViewInflation.isEnabled()) {
+                return mHeaderHeight;
+            } else {
+                return mNotificationHeaderLowPriority.getHeight();
+            }
         }
         int intrinsicHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation;
         int visibleChildren = 0;
@@ -675,7 +795,6 @@
             } else {
                 childState.setZTranslation(0);
             }
-            childState.dimmed = parentState.dimmed;
             childState.hideSensitive = parentState.hideSensitive;
             childState.belowSpeedBump = parentState.belowSpeedBump;
             childState.clipTopAmount = 0;
@@ -1023,6 +1142,14 @@
         return mCurrentHeader;
     }
 
+    public NotificationHeaderView getNotificationHeader() {
+        return mNotificationHeader;
+    }
+
+    public NotificationHeaderView getNotificationHeaderLowPriority() {
+        return mNotificationHeaderLowPriority;
+    }
+
     private void updateHeaderVisibility(boolean animate) {
         ViewGroup desiredHeader;
         ViewGroup currentHeader = mCurrentHeader;
@@ -1032,6 +1159,10 @@
             return;
         }
 
+        if (AsyncGroupHeaderViewInflation.isEnabled() && desiredHeader == null) {
+            return;
+        }
+
         if (animate) {
             if (desiredHeader != null && currentHeader != null) {
                 currentHeader.setVisibility(VISIBLE);
@@ -1271,6 +1402,9 @@
             boolean likeHighPriority,
             int headerTranslation) {
         if (!likeHighPriority && showingAsLowPriority()) {
+            if (AsyncGroupHeaderViewInflation.isEnabled()) {
+                return mHeaderHeight;
+            }
             if (mNotificationHeaderLowPriority == null) {
                 Log.e(TAG, "getMinHeight: low priority header is null", new Exception());
                 return 0;
@@ -1295,8 +1429,12 @@
             if (singleLineView != null) {
                 minExpandHeight += singleLineView.getHeight();
             } else {
-                Log.e(TAG, "getMinHeight: child " + child.getEntry().getKey()
-                                + " single line view is null", new Exception());
+                if (AsyncGroupHeaderViewInflation.isEnabled()) {
+                    minExpandHeight += mMinSingleLineHeight;
+                } else {
+                    Log.e(TAG, "getMinHeight: child " + child.getEntry().getKey()
+                            + " single line view is null", new Exception());
+                }
             }
             visibleChildren++;
         }
@@ -1309,15 +1447,19 @@
     }
 
     public void reInflateViews(OnClickListener listener, StatusBarNotification notification) {
-        if (mNotificationHeader != null) {
-            removeView(mNotificationHeader);
-            mNotificationHeader = null;
+        if (!AsyncGroupHeaderViewInflation.isEnabled()) {
+            // When Async header inflation is enabled, we do not reinflate headers because they are
+            // inflated from the background thread
+            if (mNotificationHeader != null) {
+                removeView(mNotificationHeader);
+                mNotificationHeader = null;
+            }
+            if (mNotificationHeaderLowPriority != null) {
+                removeView(mNotificationHeaderLowPriority);
+                mNotificationHeaderLowPriority = null;
+            }
+            recreateNotificationHeader(listener, mIsConversation);
         }
-        if (mNotificationHeaderLowPriority != null) {
-            removeView(mNotificationHeaderLowPriority);
-            mNotificationHeaderLowPriority = null;
-        }
-        recreateNotificationHeader(listener, mIsConversation);
         initDimens();
         for (int i = 0; i < mDividers.size(); i++) {
             View prevDivider = mDividers.get(i);
@@ -1395,7 +1537,9 @@
     public void setIsLowPriority(boolean isLowPriority) {
         mIsLowPriority = isLowPriority;
         if (mContainingNotification != null) { /* we're not yet set up yet otherwise */
-            recreateLowPriorityHeader(null /* existingBuilder */, mIsConversation);
+            if (!AsyncGroupHeaderViewInflation.isEnabled()) {
+                recreateLowPriorityHeader(null /* existingBuilder */, mIsConversation);
+            }
             updateHeaderVisibility(false /* animate */);
         }
         if (mUserLocked) {
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 e397a70..77e9425 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
@@ -17,6 +17,8 @@
 package com.android.systemui.statusbar.notification.stack;
 
 import static android.os.Trace.TRACE_TAG_APP;
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL;
@@ -29,10 +31,6 @@
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeAnimator;
-import android.animation.ValueAnimator;
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
 import android.annotation.FloatRange;
@@ -79,7 +77,6 @@
 
 import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.ColorUtils;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.policy.SystemBarUtils;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
@@ -90,7 +87,6 @@
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-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;
@@ -124,6 +120,7 @@
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
 import com.android.systemui.util.Assert;
 import com.android.systemui.util.ColorUtilKt;
+import com.android.systemui.util.Compile;
 import com.android.systemui.util.DumpUtilsKt;
 
 import com.google.errorprone.annotations.CompileTimeConstant;
@@ -150,6 +147,7 @@
     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
     private static final String TAG = "StackScroller";
     private static final boolean SPEW = Log.isLoggable(TAG, Log.VERBOSE);
+    private static final boolean DEBUG_UPDATE_SIDE_PADDING = Compile.IS_DEBUG;
 
     private boolean mShadeNeedsToClose = false;
 
@@ -161,18 +159,11 @@
      * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
      */
     private static final int INVALID_POINTER = -1;
-    /**
-     * The distance in pixels between sections when the sections are directly adjacent (no visible
-     * gap is drawn between them). In this case we don't want to round their corners.
-     */
-    private static final int DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX = 1;
     private boolean mKeyguardBypassEnabled;
 
     private final ExpandHelper mExpandHelper;
     private NotificationSwipeHelper mSwipeHelper;
     private int mCurrentStackHeight = Integer.MAX_VALUE;
-    private final Paint mBackgroundPaint = new Paint();
-    private final boolean mShouldDrawNotificationBackground;
     private boolean mHighPriorityBeforeSpeedBump;
 
     private float mExpandedHeight;
@@ -205,8 +196,6 @@
      */
     private Set<Integer> mDebugTextUsedYPositions;
     private final boolean mDebugRemoveAnimation;
-    private final boolean mSensitiveRevealAnimEndabled;
-    private final RefactorFlag mAnimatedInsets;
     private int mContentHeight;
     private float mIntrinsicContentHeight;
     private int mPaddingBetweenElements;
@@ -218,6 +207,7 @@
     int mBottomInset = 0;
     private float mQsExpansionFraction;
     private final int mSplitShadeMinContentHeight;
+    private String mLastUpdateSidePaddingDumpString;
 
     /**
      * The algorithm which calculates the properties for our children
@@ -260,7 +250,6 @@
     private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
     private boolean mNeedsAnimation;
     private boolean mTopPaddingNeedsAnimation;
-    private boolean mDimmedNeedsAnimation;
     private boolean mHideSensitiveNeedsAnimation;
     private boolean mActivateNeedsAnimation;
     private boolean mGoToFullShadeNeedsAnimation;
@@ -316,6 +305,10 @@
             = new ViewTreeObserver.OnPreDrawListener() {
         @Override
         public boolean onPreDraw() {
+            if (SceneContainerFlag.isEnabled() && !mChildrenUpdateRequested) {
+                getViewTreeObserver().removeOnPreDrawListener(this);
+                return true;
+            }
             updateForcedScroll();
             updateChildren();
             mChildrenUpdateRequested = false;
@@ -343,40 +336,15 @@
         }
     };
     private final NotificationSection[] mSections;
-    private boolean mAnimateNextBackgroundTop;
-    private boolean mAnimateNextBackgroundBottom;
-    private boolean mAnimateNextSectionBoundsChange;
-    private @ColorInt int mBgColor;
-    private float mDimAmount;
-    private ValueAnimator mDimAnimator;
     private final ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
-    private final Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() {
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            mDimAnimator = null;
-        }
-    };
-    private final ValueAnimator.AnimatorUpdateListener mDimUpdateListener
-            = new ValueAnimator.AnimatorUpdateListener() {
-
-        @Override
-        public void onAnimationUpdate(ValueAnimator animation) {
-            setDimAmount((Float) animation.getAnimatedValue());
-        }
-    };
     protected ViewGroup mQsHeader;
     // Rect of QsHeader. Kept as a field just to avoid creating a new one each time.
     private final Rect mQsHeaderBound = new Rect();
     private boolean mContinuousShadowUpdate;
-    private boolean mContinuousBackgroundUpdate;
     private final ViewTreeObserver.OnPreDrawListener mShadowUpdater = () -> {
         updateViewShadows();
         return true;
     };
-    private final ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> {
-        updateBackground();
-        return true;
-    };
     private final Comparator<ExpandableView> mViewPositionComparator = (view, otherView) -> {
         float endY = view.getTranslationY() + view.getActualHeight();
         float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
@@ -474,7 +442,6 @@
     private boolean mHeadsUpAnimatingAway;
     private int mStatusBarState;
     private int mUpcomingStatusBarState;
-    private int mCachedBackgroundColor;
     private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
     private final Runnable mReflingAndAnimateScroll = this::animateScroll;
     private int mCornerRadius;
@@ -574,7 +541,6 @@
      */
     private boolean mDismissUsingRowTranslationX = true;
     private ExpandableNotificationRow mTopHeadsUpRow;
-    private long mNumHeadsUp;
     private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private boolean mShouldUseSplitNotificationShade;
@@ -588,7 +554,7 @@
         mSplitShadeStateController = splitShadeStateController;
         updateSplitNotificationShade();
     }
-    private FeatureFlags mFeatureFlags;
+    private final FeatureFlags mFeatureFlags;
 
     private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener =
             new ExpandableView.OnHeightChangedListener() {
@@ -650,9 +616,6 @@
                 Flags.LOCKSCREEN_ENABLE_LANDSCAPE);
         mDebugLines = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
         mDebugRemoveAnimation = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
-        mSensitiveRevealAnimEndabled = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
-        mAnimatedInsets =
-                new RefactorFlag(mFeatureFlags, Flags.ANIMATED_NOTIFICATION_SHADE_INSETS);
         mSectionsManager = Dependency.get(NotificationSectionsManager.class);
         mScreenOffAnimationController =
                 Dependency.get(ScreenOffAnimationController.class);
@@ -660,8 +623,6 @@
         mSections = mSectionsManager.createSectionsForBuckets();
 
         mAmbientState = Dependency.get(AmbientState.class);
-        mBgColor = Utils.getColorAttr(mContext,
-                com.android.internal.R.attr.materialColorSurfaceContainerHigh).getDefaultColor();
         int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
         int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height);
         mSplitShadeMinContentHeight = res.getDimensionPixelSize(
@@ -673,13 +634,12 @@
 
         mStackScrollAlgorithm = createStackScrollAlgorithm(context);
         mStateAnimator = new StackStateAnimator(context, this);
-        mShouldDrawNotificationBackground =
-                res.getBoolean(R.bool.config_drawNotificationBackground);
         setOutlineProvider(mOutlineProvider);
 
-        boolean willDraw = mShouldDrawNotificationBackground || mDebugLines;
+        // We could set this whenever we 'requestChildUpdate' much like the viewTreeObserver, but
+        // that adds a bunch of complexity, and drawing nothing isn't *that* expensive.
+        boolean willDraw = SceneContainerFlag.isEnabled() || mDebugLines;
         setWillNotDraw(!willDraw);
-        mBackgroundPaint.setAntiAlias(true);
         if (mDebugLines) {
             mDebugPaint = new Paint();
             mDebugPaint.setColor(0xffff0000);
@@ -690,9 +650,7 @@
         mGroupMembershipManager = Dependency.get(GroupMembershipManager.class);
         mGroupExpansionManager = Dependency.get(GroupExpansionManager.class);
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
-        if (mAnimatedInsets.isEnabled()) {
-            setWindowInsetsAnimationCallback(mInsetsCallback);
-        }
+        setWindowInsetsAnimationCallback(mInsetsCallback);
     }
 
     /**
@@ -802,9 +760,6 @@
     }
 
     void updateBgColor() {
-        mBgColor = Utils.getColorAttr(mContext,
-                com.android.internal.R.attr.materialColorSurfaceContainerHigh).getDefaultColor();
-        updateBackgroundDimming();
         for (int i = 0; i < getChildCount(); i++) {
             View child = getChildAt(i);
             if (child instanceof ActivatableNotificationView activatableView) {
@@ -813,15 +768,18 @@
         }
     }
 
-    protected void onDraw(Canvas canvas) {
-        if (mShouldDrawNotificationBackground
-                && (mSections[0].getCurrentBounds().top
-                < mSections[mSections.length - 1].getCurrentBounds().bottom
-                || mAmbientState.isDozing())) {
-            drawBackground(canvas);
-        } else if (mInHeadsUpPinnedMode || mHeadsUpAnimatingAway) {
-            drawHeadsUpBackground(canvas);
+    private void onJustBeforeDraw() {
+        if (SceneContainerFlag.isEnabled()) {
+            if (mChildrenUpdateRequested) {
+                updateForcedScroll();
+                updateChildren();
+                mChildrenUpdateRequested = false;
+            }
         }
+    }
+
+    protected void onDraw(Canvas canvas) {
+        onJustBeforeDraw();
 
         if (mDebugLines) {
             onDrawDebug(canvas);
@@ -909,150 +867,6 @@
         return textY;
     }
 
-    private void drawBackground(Canvas canvas) {
-        int lockScreenLeft = mSidePaddings;
-        int lockScreenRight = getWidth() - mSidePaddings;
-        int lockScreenTop = mSections[0].getCurrentBounds().top;
-        int lockScreenBottom = mSections[mSections.length - 1].getCurrentBounds().bottom;
-        int hiddenLeft = getWidth() / 2;
-        int hiddenTop = mTopPadding;
-
-        float yProgress = 1 - mInterpolatedHideAmount;
-        float xProgress = mHideXInterpolator.getInterpolation(
-                (1 - mLinearHideAmount) * mBackgroundXFactor);
-
-        int left = (int) MathUtils.lerp(hiddenLeft, lockScreenLeft, xProgress);
-        int right = (int) MathUtils.lerp(hiddenLeft, lockScreenRight, xProgress);
-        int top = (int) MathUtils.lerp(hiddenTop, lockScreenTop, yProgress);
-        int bottom = (int) MathUtils.lerp(hiddenTop, lockScreenBottom, yProgress);
-        mBackgroundAnimationRect.set(
-                left,
-                top,
-                right,
-                bottom);
-
-        int backgroundTopAnimationOffset = top - lockScreenTop;
-        // TODO(kprevas): this may not be necessary any more since we don't display the shelf in AOD
-        boolean anySectionHasVisibleChild = false;
-        for (NotificationSection section : mSections) {
-            if (section.needsBackground()) {
-                anySectionHasVisibleChild = true;
-                break;
-            }
-        }
-        boolean shouldDrawBackground;
-        if (mKeyguardBypassEnabled && onKeyguard()) {
-            shouldDrawBackground = isPulseExpanding();
-        } else {
-            shouldDrawBackground = !mAmbientState.isDozing() || anySectionHasVisibleChild;
-        }
-        if (shouldDrawBackground) {
-            drawBackgroundRects(canvas, left, right, top, backgroundTopAnimationOffset);
-        }
-
-        updateClipping();
-    }
-
-    /**
-     * Draws round rects for each background section.
-     * <p>
-     * We want to draw a round rect for each background section as defined by {@link #mSections}.
-     * However, if two sections are directly adjacent with no gap between them (e.g. on the
-     * lockscreen where the shelf can appear directly below the high priority section, or while
-     * scrolling the shade so that the top of the shelf is right at the bottom of the high priority
-     * section), we don't want to round the adjacent corners.
-     * <p>
-     * Since {@link Canvas} doesn't provide a way to draw a half-rounded rect, this means that we
-     * need to coalesce the backgrounds for adjacent sections and draw them as a single round rect.
-     * This method tracks the top of each rect we need to draw, then iterates through the visible
-     * sections.  If a section is not adjacent to the previous section, we draw the previous rect
-     * behind the sections we've accumulated up to that point, then start a new rect at the top of
-     * the current section.  When we're done iterating we will always have one rect left to draw.
-     */
-    private void drawBackgroundRects(Canvas canvas, int left, int right, int top,
-                                     int animationYOffset) {
-        int backgroundRectTop = top;
-        int lastSectionBottom =
-                mSections[0].getCurrentBounds().bottom + animationYOffset;
-        int currentLeft = left;
-        int currentRight = right;
-        boolean first = true;
-        for (NotificationSection section : mSections) {
-            if (!section.needsBackground()) {
-                continue;
-            }
-            int sectionTop = section.getCurrentBounds().top + animationYOffset;
-            int ownLeft = Math.min(Math.max(left, section.getCurrentBounds().left), right);
-            int ownRight = Math.max(Math.min(right, section.getCurrentBounds().right), ownLeft);
-            // If sections are directly adjacent to each other, we don't want to draw them
-            // as separate roundrects, as the rounded corners right next to each other look
-            // bad.
-            if (sectionTop - lastSectionBottom > DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX
-                    || ((currentLeft != ownLeft || currentRight != ownRight) && !first)) {
-                canvas.drawRoundRect(currentLeft,
-                        backgroundRectTop,
-                        currentRight,
-                        lastSectionBottom,
-                        mCornerRadius, mCornerRadius, mBackgroundPaint);
-                backgroundRectTop = sectionTop;
-            }
-            currentLeft = ownLeft;
-            currentRight = ownRight;
-            lastSectionBottom =
-                    section.getCurrentBounds().bottom + animationYOffset;
-            first = false;
-        }
-        canvas.drawRoundRect(currentLeft,
-                backgroundRectTop,
-                currentRight,
-                lastSectionBottom,
-                mCornerRadius, mCornerRadius, mBackgroundPaint);
-    }
-
-    private void drawHeadsUpBackground(Canvas canvas) {
-        int left = mSidePaddings;
-        int right = getWidth() - mSidePaddings;
-
-        float top = getHeight();
-        float bottom = 0;
-        int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View child = getChildAt(i);
-            if (child.getVisibility() != View.GONE
-                    && child instanceof ExpandableNotificationRow row) {
-                if ((row.isPinned() || row.isHeadsUpAnimatingAway()) && row.getTranslation() < 0
-                        && row.getProvider().shouldShowGutsOnSnapOpen()) {
-                    top = Math.min(top, row.getTranslationY());
-                    bottom = Math.max(bottom, row.getTranslationY() + row.getActualHeight());
-                }
-            }
-        }
-
-        if (top < bottom) {
-            canvas.drawRoundRect(
-                    left, top, right, bottom,
-                    mCornerRadius, mCornerRadius, mBackgroundPaint);
-        }
-    }
-
-    void updateBackgroundDimming() {
-        // No need to update the background color if it's not being drawn.
-        if (!mShouldDrawNotificationBackground) {
-            return;
-        }
-        // Interpolate between semi-transparent notification panel background color
-        // and white AOD separator.
-        float colorInterpolation = MathUtils.smoothStep(0.4f /* start */, 1f /* end */,
-                mLinearHideAmount);
-        int color = ColorUtils.blendARGB(mBgColor, Color.WHITE, colorInterpolation);
-
-        if (mCachedBackgroundColor != color) {
-            mCachedBackgroundColor = color;
-            mBackgroundPaint.setColor(color);
-            invalidate();
-        }
-    }
-
     private void reinitView() {
         initView(getContext(), mSwipeHelper, mNotificationStackSizeCalculator);
     }
@@ -1106,15 +920,27 @@
     }
 
     void updateSidePadding(int viewWidth) {
+        final int orientation = getResources().getConfiguration().orientation;
+
+        mLastUpdateSidePaddingDumpString = "viewWidth=" + viewWidth
+                + " skinnyNotifsInLandscape=" + mSkinnyNotifsInLandscape
+                + " orientation=" + orientation;
+
+        if (DEBUG_UPDATE_SIDE_PADDING) {
+            Log.v(TAG, "updateSidePadding: " + mLastUpdateSidePaddingDumpString);
+        }
+
         if (viewWidth == 0 || !mSkinnyNotifsInLandscape) {
             mSidePaddings = mMinimumPaddings;
             return;
         }
+
         // Portrait is easy, just use the dimen for paddings
-        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
+        if (orientation == Configuration.ORIENTATION_PORTRAIT) {
             mSidePaddings = mMinimumPaddings;
             return;
         }
+
         final int innerWidth = viewWidth - mMinimumPaddings * 2;
         final int qsTileWidth = (innerWidth - mQsTilePadding * 3) / 4;
         mSidePaddings = mMinimumPaddings + qsTileWidth + mQsTilePadding;
@@ -1325,9 +1151,6 @@
 
     private void onPreDrawDuringAnimation() {
         mShelf.updateAppearance();
-        if (!mNeedsAnimation && !mChildrenUpdateRequested) {
-            updateBackground();
-        }
     }
 
     private void updateScrollStateForAddedChildren() {
@@ -1903,11 +1726,7 @@
             return;
         }
         mForcedScroll = v;
-        if (mAnimatedInsets.isEnabled()) {
-            updateForcedScroll();
-        } else {
-            scrollTo(v);
-        }
+        updateForcedScroll();
     }
 
     public boolean scrollTo(View v) {
@@ -1952,31 +1771,15 @@
 
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        if (!mAnimatedInsets.isEnabled()) {
-            mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom;
-        }
         mWaterfallTopInset = 0;
         final DisplayCutout cutout = insets.getDisplayCutout();
         if (cutout != null) {
             mWaterfallTopInset = cutout.getWaterfallInsets().top;
         }
-        if (mAnimatedInsets.isEnabled() && !mIsInsetAnimationRunning) {
+        if (!mIsInsetAnimationRunning) {
             // update bottom inset e.g. after rotation
             updateBottomInset(insets);
         }
-        if (!mAnimatedInsets.isEnabled()) {
-            int range = getScrollRange();
-            if (mOwnScrollY > range) {
-                // HACK: We're repeatedly getting staggered insets here while the IME is
-                // animating away. To work around that we'll wait until things have settled.
-                removeCallbacks(mReclamp);
-                postDelayed(mReclamp, 50);
-            } else if (mForcedScroll != null) {
-                // The scroll was requested before we got the actual inset - in case we need
-                // to scroll up some more do so now.
-                scrollTo(mForcedScroll);
-            }
-        }
         return insets;
     }
 
@@ -2531,125 +2334,6 @@
         }
     }
 
-    private void updateBackground() {
-        // No need to update the background color if it's not being drawn.
-        if (!mShouldDrawNotificationBackground) {
-            return;
-        }
-
-        updateBackgroundBounds();
-        if (didSectionBoundsChange()) {
-            boolean animate = mAnimateNextSectionBoundsChange || mAnimateNextBackgroundTop
-                    || mAnimateNextBackgroundBottom || areSectionBoundsAnimating();
-            if (!isExpanded()) {
-                abortBackgroundAnimators();
-                animate = false;
-            }
-            if (animate) {
-                startBackgroundAnimation();
-            } else {
-                for (NotificationSection section : mSections) {
-                    section.resetCurrentBounds();
-                }
-                invalidate();
-            }
-        } else {
-            abortBackgroundAnimators();
-        }
-        mAnimateNextBackgroundTop = false;
-        mAnimateNextBackgroundBottom = false;
-        mAnimateNextSectionBoundsChange = false;
-    }
-
-    private void abortBackgroundAnimators() {
-        for (NotificationSection section : mSections) {
-            section.cancelAnimators();
-        }
-    }
-
-    private boolean didSectionBoundsChange() {
-        for (NotificationSection section : mSections) {
-            if (section.didBoundsChange()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private boolean areSectionBoundsAnimating() {
-        for (NotificationSection section : mSections) {
-            if (section.areBoundsAnimating()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private void startBackgroundAnimation() {
-        // TODO(kprevas): do we still need separate fields for top/bottom?
-        // or can each section manage its own animation state?
-        NotificationSection firstVisibleSection = getFirstVisibleSection();
-        NotificationSection lastVisibleSection = getLastVisibleSection();
-        for (NotificationSection section : mSections) {
-            section.startBackgroundAnimation(
-                    section == firstVisibleSection
-                            ? mAnimateNextBackgroundTop
-                            : mAnimateNextSectionBoundsChange,
-                    section == lastVisibleSection
-                            ? mAnimateNextBackgroundBottom
-                            : mAnimateNextSectionBoundsChange);
-        }
-    }
-
-    /**
-     * Update the background bounds to the new desired bounds
-     */
-    private void updateBackgroundBounds() {
-        int left = mSidePaddings;
-        int right = getWidth() - mSidePaddings;
-        for (NotificationSection section : mSections) {
-            section.getBounds().left = left;
-            section.getBounds().right = right;
-        }
-
-        if (!mIsExpanded) {
-            for (NotificationSection section : mSections) {
-                section.getBounds().top = 0;
-                section.getBounds().bottom = 0;
-            }
-            return;
-        }
-        int minTopPosition;
-        NotificationSection lastSection = getLastVisibleSection();
-        boolean onKeyguard = mStatusBarState == StatusBarState.KEYGUARD;
-        if (!onKeyguard) {
-            minTopPosition = (int) (mTopPadding + mStackTranslation);
-        } else if (lastSection == null) {
-            minTopPosition = mTopPadding;
-        } else {
-            // The first sections could be empty while there could still be elements in later
-            // sections. The position of these first few sections is determined by the position of
-            // the first visible section.
-            NotificationSection firstVisibleSection = getFirstVisibleSection();
-            firstVisibleSection.updateBounds(0 /* minTopPosition*/, 0 /* minBottomPosition */,
-                    false /* shiftPulsingWithFirst */);
-            minTopPosition = firstVisibleSection.getBounds().top;
-        }
-        boolean shiftPulsingWithFirst = mNumHeadsUp <= 1
-                && (mAmbientState.isDozing() || (mKeyguardBypassEnabled && onKeyguard));
-        for (NotificationSection section : mSections) {
-            int minBottomPosition = minTopPosition;
-            if (section == lastSection) {
-                // We need to make sure the section goes all the way to the shelf
-                minBottomPosition = (int) (ViewState.getFinalTranslationY(mShelf)
-                        + mShelf.getIntrinsicHeight());
-            }
-            minTopPosition = section.updateBounds(minTopPosition, minBottomPosition,
-                    shiftPulsingWithFirst);
-            shiftPulsingWithFirst = false;
-        }
-    }
-
     private NotificationSection getFirstVisibleSection() {
         for (NotificationSection section : mSections) {
             if (section.getFirstVisibleChild() != null) {
@@ -2864,7 +2548,7 @@
             return;
         }
         child.setOnHeightChangedListener(null);
-        if (child instanceof ExpandableNotificationRow && mSensitiveRevealAnimEndabled) {
+        if (child instanceof ExpandableNotificationRow) {
             NotificationEntry entry = ((ExpandableNotificationRow) child).getEntry();
             entry.removeOnSensitivityChangedListener(mOnChildSensitivityChangedListener);
         }
@@ -3150,13 +2834,7 @@
                 mSections, getChildrenWithBackground());
 
         if (mAnimationsEnabled && mIsExpanded) {
-            mAnimateNextBackgroundTop = firstChild != previousFirstChild;
-            mAnimateNextBackgroundBottom = lastChild != previousLastChild || mAnimateBottomOnLayout;
-            mAnimateNextSectionBoundsChange = sectionViewsChanged;
         } else {
-            mAnimateNextBackgroundTop = false;
-            mAnimateNextBackgroundBottom = false;
-            mAnimateNextSectionBoundsChange = false;
         }
         mAmbientState.setLastVisibleBackgroundChild(lastChild);
         mAnimateBottomOnLayout = false;
@@ -3166,7 +2844,7 @@
     private void onViewAddedInternal(ExpandableView child) {
         updateHideSensitiveForChild(child);
         child.setOnHeightChangedListener(mOnChildHeightChangedListener);
-        if (child instanceof ExpandableNotificationRow && mSensitiveRevealAnimEndabled) {
+        if (child instanceof ExpandableNotificationRow) {
             NotificationEntry entry = ((ExpandableNotificationRow) child).getEntry();
             entry.addOnSensitivityChangedListener(mOnChildSensitivityChangedListener);
         }
@@ -3310,7 +2988,6 @@
             setAnimationRunning(true);
             mStateAnimator.startAnimationForEvents(mAnimationEvents, mGoToFullShadeDelay);
             mAnimationEvents.clear();
-            updateBackground();
             updateViewShadows();
         } else {
             applyCurrentState();
@@ -3325,7 +3002,6 @@
         generatePositionChangeEvents();
         generateTopPaddingEvent();
         generateActivateEvent();
-        generateDimmedEvent();
         generateHideSensitiveEvent();
         generateGoToFullShadeEvent();
         generateViewResizeEvent();
@@ -3543,14 +3219,6 @@
         mEverythingNeedsAnimation = false;
     }
 
-    private void generateDimmedEvent() {
-        if (mDimmedNeedsAnimation) {
-            mAnimationEvents.add(
-                    new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
-        }
-        mDimmedNeedsAnimation = false;
-    }
-
     private void generateHideSensitiveEvent() {
         if (mHideSensitiveNeedsAnimation) {
             mAnimationEvents.add(
@@ -3611,7 +3279,11 @@
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         if (SceneContainerFlag.isEnabled() && mIsBeingDragged) {
-            if (!mSendingTouchesToSceneFramework) {
+            int action = ev.getActionMasked();
+            boolean isUpOrCancel = action == ACTION_UP || action == ACTION_CANCEL;
+            if (mSendingTouchesToSceneFramework) {
+                mController.sendTouchToSceneFramework(ev);
+            } else if (!isUpOrCancel) {
                 // if this is the first touch being sent to the scene framework,
                 // convert it into a synthetic DOWN event.
                 mSendingTouchesToSceneFramework = true;
@@ -3619,14 +3291,9 @@
                 downEvent.setAction(MotionEvent.ACTION_DOWN);
                 mController.sendTouchToSceneFramework(downEvent);
                 downEvent.recycle();
-            } else {
-                mController.sendTouchToSceneFramework(ev);
             }
 
-            if (
-                    ev.getActionMasked() == MotionEvent.ACTION_UP
-                    || ev.getActionMasked() == MotionEvent.ACTION_CANCEL
-            ) {
+            if (isUpOrCancel) {
                 setIsBeingDragged(false);
             }
             return false;
@@ -3783,7 +3450,7 @@
                     }
                 }
                 break;
-            case MotionEvent.ACTION_UP:
+            case ACTION_UP:
                 if (mIsBeingDragged) {
                     final VelocityTracker velocityTracker = mVelocityTracker;
                     velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
@@ -3820,7 +3487,7 @@
                 }
 
                 break;
-            case MotionEvent.ACTION_CANCEL:
+            case ACTION_CANCEL:
                 if (mIsBeingDragged && getChildCount() > 0) {
                     if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
                             getScrollRange())) {
@@ -3929,7 +3596,7 @@
                     mTouchIsClick = false;
                 }
                 break;
-            case MotionEvent.ACTION_UP:
+            case ACTION_UP:
                 if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
                         isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
                     debugShadeLog("handleEmptySpaceClick: touch event propagated further");
@@ -4070,8 +3737,8 @@
                 break;
             }
 
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_UP:
+            case ACTION_CANCEL:
+            case ACTION_UP:
                 /* Release the drag */
                 setIsBeingDragged(false);
                 mActivePointerId = INVALID_POINTER;
@@ -4452,48 +4119,6 @@
         mAnimationFinishedRunnables.clear();
     }
 
-    /**
-     * See {@link AmbientState#setDimmed}.
-     */
-    void setDimmed(boolean dimmed, boolean animate) {
-        dimmed &= onKeyguard();
-        mAmbientState.setDimmed(dimmed);
-        if (animate && mAnimationsEnabled) {
-            mDimmedNeedsAnimation = true;
-            mNeedsAnimation = true;
-            animateDimmed(dimmed);
-        } else {
-            setDimAmount(dimmed ? 1.0f : 0.0f);
-        }
-        requestChildrenUpdate();
-    }
-
-    @VisibleForTesting
-    boolean isDimmed() {
-        return mAmbientState.isDimmed();
-    }
-
-    private void setDimAmount(float dimAmount) {
-        mDimAmount = dimAmount;
-        updateBackgroundDimming();
-    }
-
-    private void animateDimmed(boolean dimmed) {
-        if (mDimAnimator != null) {
-            mDimAnimator.cancel();
-        }
-        float target = dimmed ? 1.0f : 0.0f;
-        if (target == mDimAmount) {
-            return;
-        }
-        mDimAnimator = TimeAnimator.ofFloat(mDimAmount, target);
-        mDimAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED);
-        mDimAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-        mDimAnimator.addListener(mDimEndListener);
-        mDimAnimator.addUpdateListener(mDimUpdateListener);
-        mDimAnimator.start();
-    }
-
     void updateSensitiveness(boolean animate, boolean hideSensitive) {
         if (hideSensitive != mAmbientState.isHideSensitive()) {
             int childCount = getChildCount();
@@ -4530,7 +4155,6 @@
 
         runAnimationFinishedRunnables();
         setAnimationRunning(false);
-        updateBackground();
         updateViewShadows();
     }
 
@@ -4680,7 +4304,6 @@
             invalidateOutline();
         }
         updateAlgorithmHeightAndPadding();
-        updateBackgroundDimming();
         requestChildrenUpdate();
         updateOwnTranslationZ();
     }
@@ -4713,21 +4336,6 @@
         }
     }
 
-    private int getNotGoneIndex(View child) {
-        int count = getChildCount();
-        int notGoneIndex = 0;
-        for (int i = 0; i < count; i++) {
-            View v = getChildAt(i);
-            if (child == v) {
-                return notGoneIndex;
-            }
-            if (v.getVisibility() != View.GONE) {
-                notGoneIndex++;
-            }
-        }
-        return -1;
-    }
-
     /**
      * Returns whether or not a History button is shown in the footer. If there is no footer, then
      * this will return false.
@@ -4992,6 +4600,17 @@
 
     public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
         ExpandableNotificationRow row = entry.getHeadsUpAnimationView();
+        generateHeadsUpAnimation(row, isHeadsUp);
+    }
+
+    /**
+     * Notifies the NSSL, that the given view would need a HeadsUp animation, when it is being
+     * added to this container.
+     *
+     * @param row to animate
+     * @param isHeadsUp true for appear, false for disappear animations
+     */
+    public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
         final boolean add = mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed);
         if (SPEW) {
             Log.v(TAG, "generateHeadsUpAnimation:"
@@ -5221,13 +4840,10 @@
     void onStatePostChange(boolean fromShadeLocked) {
         boolean onKeyguard = onKeyguard();
 
-        mAmbientState.setDimmed(onKeyguard);
-
         if (mHeadsUpAppearanceController != null) {
             mHeadsUpAppearanceController.onStateChanged();
         }
 
-        setDimmed(onKeyguard, fromShadeLocked);
         setExpandingEnabled(!onKeyguard);
         if (!FooterViewRefactor.isEnabled()) {
             updateFooter();
@@ -5294,6 +4910,11 @@
             println(pw, "translationX", getTranslationX());
             println(pw, "translationY", getTranslationY());
             println(pw, "translationZ", getTranslationZ());
+            println(pw, "skinnyNotifsInLandscape", mSkinnyNotifsInLandscape);
+            println(pw, "minimumPaddings", mMinimumPaddings);
+            println(pw, "qsTilePadding", mQsTilePadding);
+            println(pw, "sidePaddings", mSidePaddings);
+            println(pw, "lastUpdateSidePadding", mLastUpdateSidePaddingDumpString);
             mNotificationStackSizeCalculator.dump(pw, args);
         });
         pw.println();
@@ -5626,7 +5247,6 @@
      */
     public void setDozeAmount(float dozeAmount) {
         mAmbientState.setDozeAmount(dozeAmount);
-        updateContinuousBackgroundDrawing();
         updateStackPosition();
         requestChildrenUpdate();
     }
@@ -5661,7 +5281,6 @@
                 view.setTranslationY(wakeUplocation);
             }
         }
-        mDimmedNeedsAnimation = true;
     }
 
     void setAnimateBottomOnLayout(boolean animateBottomOnLayout) {
@@ -5713,7 +5332,6 @@
         updateFirstAndLastBackgroundViews();
         requestDisallowInterceptTouchEvent(true);
         updateContinuousShadowDrawing();
-        updateContinuousBackgroundDrawing();
         requestChildrenUpdate();
     }
 
@@ -5725,15 +5343,13 @@
         mShelf.updateAppearance();
     }
 
-    void setTopHeadsUpRow(ExpandableNotificationRow topHeadsUpRow) {
+    /**
+     * @param topHeadsUpRow the first headsUp row in z-order.
+     */
+    public void setTopHeadsUpRow(ExpandableNotificationRow topHeadsUpRow) {
         mTopHeadsUpRow = topHeadsUpRow;
     }
 
-    void setNumHeadsUp(long numHeadsUp) {
-        mNumHeadsUp = numHeadsUp;
-        mAmbientState.setHasHeadsUpEntries(numHeadsUp > 0);
-    }
-
     public boolean getIsExpanded() {
         return mIsExpanded;
     }
@@ -6104,19 +5720,6 @@
         mSpeedBumpIndexDirty = true;
     }
 
-    void updateContinuousBackgroundDrawing() {
-        boolean continuousBackground = !mAmbientState.isFullyAwake()
-                && mSwipeHelper.isSwiping();
-        if (continuousBackground != mContinuousBackgroundUpdate) {
-            mContinuousBackgroundUpdate = continuousBackground;
-            if (continuousBackground) {
-                getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater);
-            } else {
-                getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater);
-            }
-        }
-    }
-
     private void resetAllSwipeState() {
         Trace.beginSection("NSSL.resetAllSwipeState()");
         mSwipeHelper.resetTouchState();
@@ -6203,7 +5806,6 @@
                         .animateHeight()
                         .animateTopInset()
                         .animateY()
-                        .animateDimmed()
                         .animateZ(),
 
                 // ANIMATION_TYPE_ACTIVATED_CHILD
@@ -6211,8 +5813,7 @@
                         .animateZ(),
 
                 // ANIMATION_TYPE_DIMMED
-                new AnimationFilter()
-                        .animateDimmed(),
+                new AnimationFilter(),
 
                 // ANIMATION_TYPE_CHANGE_POSITION
                 new AnimationFilter()
@@ -6227,7 +5828,6 @@
                         .animateHeight()
                         .animateTopInset()
                         .animateY()
-                        .animateDimmed()
                         .animateZ()
                         .hasDelays(),
 
@@ -6283,7 +5883,6 @@
                 // ANIMATION_TYPE_EVERYTHING
                 new AnimationFilter()
                         .animateAlpha()
-                        .animateDimmed()
                         .animateHideSensitive()
                         .animateHeight()
                         .animateTopInset()
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 7c13877..6553193 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
@@ -363,7 +363,8 @@
     };
 
     private NotifStats mNotifStats = NotifStats.getEmpty();
-    private float mMaxAlphaForExpansion = 1.0f;
+    private float mMaxAlphaForKeyguard = 1.0f;
+    private String mMaxAlphaForKeyguardSource = "constructor";
     private float mMaxAlphaForUnhide = 1.0f;
 
     /**
@@ -689,9 +690,7 @@
 
                 @Override
                 public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
-                    long numEntries = mHeadsUpManager.getAllEntries().count();
                     NotificationEntry topEntry = mHeadsUpManager.getTopEntry();
-                    mView.setNumHeadsUp(numEntries);
                     mView.setTopHeadsUpRow(topEntry != null ? topEntry.getRow() : null);
                     generateHeadsUpAnimation(entry, isHeadsUp);
                 }
@@ -875,8 +874,6 @@
         mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed);
         mDynamicPrivacyController.addListener(mDynamicPrivacyControllerListener);
 
-        mScrimController.setScrimBehindChangeRunnable(mView::updateBackgroundDimming);
-
         mLockscreenShadeTransitionController.setStackScroller(this);
 
         mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
@@ -1324,9 +1321,14 @@
         return mView.getEmptyShadeViewHeight();
     }
 
-    public void setMaxAlphaForExpansion(float alpha) {
-        mMaxAlphaForExpansion = alpha;
+    /** Set the max alpha for keyguard */
+    public void setMaxAlphaForKeyguard(float alpha, String source) {
+        mMaxAlphaForKeyguard = alpha;
+        mMaxAlphaForKeyguardSource = source;
         updateAlpha();
+        if (DEBUG) {
+            Log.d(TAG, "setMaxAlphaForKeyguard=" + alpha + " --- from: " + source);
+        }
     }
 
     private void setMaxAlphaForUnhide(float alpha) {
@@ -1345,7 +1347,7 @@
 
     private void updateAlpha() {
         if (mView != null) {
-            mView.setAlpha(Math.min(mMaxAlphaForExpansion,
+            mView.setAlpha(Math.min(mMaxAlphaForKeyguard,
                     Math.min(mMaxAlphaForUnhide, mMaxAlphaForGlanceableHub)));
         }
     }
@@ -1743,13 +1745,6 @@
     }
 
     /**
-     * Set the dimmed state for all of the notification views.
-     */
-    public void setDimmed(boolean dimmed, boolean animate) {
-        mView.setDimmed(dimmed, animate);
-    }
-
-    /**
      * @return the inset during the full shade transition, that needs to be added to the position
      * of the quick settings edge. This is relevant for media, that is transitioning
      * from the keyguard host to the quick settings one.
@@ -1842,9 +1837,10 @@
 
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
-        pw.println("mMaxAlphaForExpansion=" + mMaxAlphaForExpansion);
         pw.println("mMaxAlphaForUnhide=" + mMaxAlphaForUnhide);
         pw.println("mMaxAlphaForGlanceableHub=" + mMaxAlphaForGlanceableHub);
+        pw.println("mMaxAlphaForKeyguard=" + mMaxAlphaForKeyguard);
+        pw.println("mMaxAlphaForKeyguardSource=" + mMaxAlphaForKeyguardSource);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 1ef9a8f..9b1952b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -369,13 +369,11 @@
     /** Updates the dimmed and hiding sensitive states of the children. */
     private void updateDimmedAndHideSensitive(AmbientState ambientState,
             StackScrollAlgorithmState algorithmState) {
-        boolean dimmed = ambientState.isDimmed();
         boolean hideSensitive = ambientState.isHideSensitive();
         int childCount = algorithmState.visibleChildren.size();
         for (int i = 0; i < childCount; i++) {
             ExpandableView child = algorithmState.visibleChildren.get(i);
             ExpandableViewState childViewState = child.getViewState();
-            childViewState.dimmed = dimmed;
             childViewState.hideSensitive = hideSensitive;
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractor.kt
new file mode 100644
index 0000000..9cd46f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractor.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/** Interactor exposing states related to the stack's context */
+@SysUISingleton
+class NotificationStackInteractor
+@Inject
+constructor(
+    keyguardInteractor: KeyguardInteractor,
+    powerInteractor: PowerInteractor,
+) {
+    val isShowingOnLockscreen: Flow<Boolean> =
+        combine(
+                // Non-notification UI elements of the notification list should not be visible
+                // on the lockscreen (incl. AOD and bouncer), except if the shade is opened on
+                // top. See b/219680200 for the footer and b/228790482, b/267060171 for the
+                // empty shade.
+                // TODO(b/323187006): There's a plan to eventually get rid of StatusBarState
+                //  entirely, so this will have to be replaced at some point.
+                keyguardInteractor.statusBarState.map { it == StatusBarState.KEYGUARD },
+                // The StatusBarState is unfortunately not updated quickly enough when the power
+                // button is pressed, so this is necessary in addition to the KEYGUARD check to
+                // cover the transition to AOD while going to sleep (b/190227875).
+                powerInteractor.isAsleep,
+            ) { (isOnKeyguard, isAsleep) ->
+                isOnKeyguard || isAsleep
+            }
+            .distinctUntilChanged()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
index 76495cb..8b1b06e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
@@ -66,15 +66,18 @@
                 }
 
                 launch {
+                    var wasExpanding = false
                     viewModel.expandFraction.collect { expandFraction ->
+                        val nowExpanding = expandFraction != 0f && expandFraction != 1f
+                        if (nowExpanding && !wasExpanding) {
+                            controller.onExpansionStarted()
+                        }
                         ambientState.expansionFraction = expandFraction
                         controller.expandedHeight = expandFraction * controller.view.height
-                        controller.setMaxAlphaForExpansion(
-                            ((expandFraction - 0.5f) / 0.5f).coerceAtLeast(0f)
-                        )
-                        if (expandFraction == 0f || expandFraction == 1f) {
+                        if (!nowExpanding && wasExpanding) {
                             controller.onExpansionStopped()
                         }
+                        wasExpanding = nowExpanding
                     }
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 566c030..ece7a7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -106,27 +106,26 @@
         val disposableHandleMainImmediate =
             view.repeatWhenAttached(mainImmediateDispatcher) {
                 repeatOnLifecycle(Lifecycle.State.CREATED) {
-                    if (!sceneContainerFlags.flexiNotifsEnabled()) {
-                        launch {
-                            // Only temporarily needed, until flexi notifs go live
-                            viewModel.shadeCollapseFadeIn.collect { fadeIn ->
-                                if (fadeIn) {
-                                    android.animation.ValueAnimator.ofFloat(0f, 1f).apply {
-                                        duration = 250
-                                        addUpdateListener { animation ->
-                                            controller.setMaxAlphaForExpansion(
-                                                animation.getAnimatedFraction()
-                                            )
-                                        }
-                                        addListener(
-                                            object : AnimatorListenerAdapter() {
-                                                override fun onAnimationEnd(animation: Animator) {
-                                                    viewModel.setShadeCollapseFadeInComplete(true)
-                                                }
-                                            }
+                    launch {
+                        // Only temporarily needed, until flexi notifs go live
+                        viewModel.shadeCollapseFadeIn.collect { fadeIn ->
+                            if (fadeIn) {
+                                android.animation.ValueAnimator.ofFloat(0f, 1f).apply {
+                                    duration = 250
+                                    addUpdateListener { animation ->
+                                        controller.setMaxAlphaForKeyguard(
+                                            animation.animatedFraction,
+                                            "SharedNotificationContainerVB (collapseFadeIn)"
                                         )
-                                        start()
                                     }
+                                    addListener(
+                                        object : AnimatorListenerAdapter() {
+                                            override fun onAnimationEnd(animation: Animator) {
+                                                viewModel.setShadeCollapseFadeInComplete(true)
+                                            }
+                                        }
+                                    )
+                                    start()
                                 }
                             }
                         }
@@ -164,13 +163,12 @@
 
                     launch { viewModel.translationX.collect { x -> controller.translationX = x } }
 
-                    if (!sceneContainerFlags.isEnabled()) {
-                        launch {
-                            viewModel.expansionAlpha(viewState).collect {
-                                controller.setMaxAlphaForExpansion(it)
-                            }
+                    launch {
+                        viewModel.keyguardAlpha(viewState).collect {
+                            controller.setMaxAlphaForKeyguard(it, "SharedNotificationContainerVB")
                         }
                     }
+
                     launch {
                         viewModel.glanceableHubAlpha.collect {
                             controller.setMaxAlphaForGlanceableHub(it)
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 7b50256..c85a18a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -16,9 +16,6 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
@@ -26,6 +23,7 @@
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
 import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
+import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackInteractor
 import com.android.systemui.statusbar.policy.domain.interactor.UserSetupInteractor
 import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
 import com.android.systemui.util.kotlin.combine
@@ -51,8 +49,7 @@
     val footer: Optional<FooterViewModel>,
     val logger: Optional<NotificationLoggerViewModel>,
     activeNotificationsInteractor: ActiveNotificationsInteractor,
-    keyguardInteractor: KeyguardInteractor,
-    powerInteractor: PowerInteractor,
+    notificationStackInteractor: NotificationStackInteractor,
     remoteInputInteractor: RemoteInputInteractor,
     seenNotificationsInteractor: SeenNotificationsInteractor,
     shadeInteractor: ShadeInteractor,
@@ -71,7 +68,7 @@
         } else {
             combine(
                     activeNotificationsInteractor.areAnyNotificationsPresent,
-                    isShowingOnLockscreen,
+                    notificationStackInteractor.isShowingOnLockscreen,
                 ) { hasNotifications, isShowingOnLockscreen ->
                     hasNotifications || !isShowingOnLockscreen
                 }
@@ -86,7 +83,7 @@
             combine(
                     activeNotificationsInteractor.areAnyNotificationsPresent,
                     shadeInteractor.isQsFullscreen,
-                    isShowingOnLockscreen,
+                    notificationStackInteractor.isShowingOnLockscreen,
                 ) { hasNotifications, isQsFullScreen, isShowingOnLockscreen ->
                     when {
                         hasNotifications -> false
@@ -109,7 +106,7 @@
             combine(
                     activeNotificationsInteractor.areAnyNotificationsPresent,
                     userSetupInteractor.isUserSetUp,
-                    isShowingOnLockscreen,
+                    notificationStackInteractor.isShowingOnLockscreen,
                     shadeInteractor.qsExpansion,
                     shadeInteractor.isQsFullscreen,
                     remoteInputInteractor.isRemoteInputActive,
@@ -177,29 +174,6 @@
         SHOW_WITH_ANIMATION(visible = true, canAnimate = true)
     }
 
-    private val isShowingOnLockscreen: Flow<Boolean> by lazy {
-        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
-            flowOf(false)
-        } else {
-            combine(
-                    // Non-notification UI elements of the notification list should not be visible
-                    // on the lockscreen (incl. AOD and bouncer), except if the shade is opened on
-                    // top. See b/219680200 for the footer and b/228790482, b/267060171 for the
-                    // empty shade.
-                    // TODO(b/323187006): There's a plan to eventually get rid of StatusBarState
-                    //  entirely, so this will have to be replaced at some point.
-                    keyguardInteractor.statusBarState.map { it == StatusBarState.KEYGUARD },
-                    // The StatusBarState is unfortunately not updated quickly enough when the power
-                    // button is pressed, so this is necessary in addition to the KEYGUARD check to
-                    // cover the transition to AOD while going to sleep (b/190227875).
-                    powerInteractor.isAsleep,
-                ) { (isOnKeyguard, isAsleep) ->
-                    isOnKeyguard || isAsleep
-                }
-                .distinctUntilChanged()
-        }
-    }
-
     // TODO(b/308591475): This should be tracked separately by the empty shade.
     val areNotificationsHiddenInShade: Flow<Boolean> by lazy {
         if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
index 3a0f03f..8d1cdfa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
@@ -17,13 +17,15 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
+import com.android.systemui.util.kotlin.FlowDumperImpl
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
@@ -35,10 +37,11 @@
 class NotificationStackAppearanceViewModel
 @Inject
 constructor(
+    dumpManager: DumpManager,
     stackAppearanceInteractor: NotificationStackAppearanceInteractor,
     shadeInteractor: ShadeInteractor,
     sceneInteractor: SceneInteractor,
-) {
+) : FlowDumperImpl(dumpManager) {
     /**
      * The expansion fraction of the notification stack. It should go from 0 to 1 when transitioning
      * from Gone to Shade scenes, and remain at 1 when in Lockscreen or Shade scenes and while
@@ -51,7 +54,7 @@
             ) { shadeExpansion, transitionState ->
                 when (transitionState) {
                     is ObservableTransitionState.Idle -> {
-                        if (transitionState.scene == SceneKey.Lockscreen) {
+                        if (transitionState.scene == Scenes.Lockscreen) {
                             1f
                         } else {
                             shadeExpansion
@@ -59,10 +62,10 @@
                     }
                     is ObservableTransitionState.Transition -> {
                         if (
-                            (transitionState.fromScene == SceneKey.Shade &&
-                                transitionState.toScene == SceneKey.QuickSettings) ||
-                                (transitionState.fromScene == SceneKey.QuickSettings &&
-                                    transitionState.toScene == SceneKey.Shade)
+                            (transitionState.fromScene == Scenes.Shade &&
+                                transitionState.toScene == Scenes.QuickSettings) ||
+                                (transitionState.fromScene == Scenes.QuickSettings &&
+                                    transitionState.toScene == Scenes.Shade)
                         ) {
                             1f
                         } else {
@@ -72,10 +75,12 @@
                 }
             }
             .distinctUntilChanged()
+            .dumpWhileCollecting("expandFraction")
 
     /** The bounds of the notification stack in the current scene. */
-    val stackBounds: Flow<NotificationContainerBounds> = stackAppearanceInteractor.stackBounds
+    val stackBounds: Flow<NotificationContainerBounds> =
+        stackAppearanceInteractor.stackBounds.dumpValue("stackBounds")
 
     /** The y-coordinate in px of top of the contents of the notification stack. */
-    val contentTop: StateFlow<Float> = stackAppearanceInteractor.contentTop
+    val contentTop: StateFlow<Float> = stackAppearanceInteractor.contentTop.dumpValue("contentTop")
 }
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 052e35c..2745817 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -20,9 +20,9 @@
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
 import com.android.systemui.common.shared.model.NotificationContainerBounds
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -36,13 +36,13 @@
 import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE
 import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
 import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
-import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
-import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel
@@ -60,6 +60,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import com.android.systemui.util.kotlin.FlowDumperImpl
 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -79,6 +80,7 @@
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transformWhile
 import kotlinx.coroutines.isActive
 
 /** View-model for the shared notification container, used by both the shade and keyguard spaces */
@@ -87,15 +89,17 @@
 @Inject
 constructor(
     private val interactor: SharedNotificationContainerInteractor,
+    dumpManager: DumpManager,
     @Application applicationScope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val shadeInteractor: ShadeInteractor,
-    communalInteractor: CommunalInteractor,
     private val alternateBouncerToGoneTransitionViewModel:
         AlternateBouncerToGoneTransitionViewModel,
     private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+    private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
     private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
+    private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
     private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
     private val glanceableHubToLockscreenTransitionViewModel:
         GlanceableHubToLockscreenTransitionViewModel,
@@ -115,24 +119,10 @@
     private val primaryBouncerToLockscreenTransitionViewModel:
         PrimaryBouncerToLockscreenTransitionViewModel,
     private val aodBurnInViewModel: AodBurnInViewModel,
-) {
+) : FlowDumperImpl(dumpManager) {
     private val statesForConstrainedNotifications: Set<KeyguardState> =
         setOf(AOD, LOCKSCREEN, DOZING, ALTERNATE_BOUNCER, PRIMARY_BOUNCER)
 
-    private val lockscreenToGlanceableHubRunning =
-        keyguardTransitionInteractor
-            .transition(LOCKSCREEN, GLANCEABLE_HUB)
-            .map { it.transitionState == STARTED || it.transitionState == RUNNING }
-            .distinctUntilChanged()
-            .onStart { emit(false) }
-
-    private val glanceableHubToLockscreenRunning =
-        keyguardTransitionInteractor
-            .transition(GLANCEABLE_HUB, LOCKSCREEN)
-            .map { it.transitionState == STARTED || it.transitionState == RUNNING }
-            .distinctUntilChanged()
-            .onStart { emit(false) }
-
     /**
      * Shade locked is a legacy concept, but necessary to mimic current functionality. Listen for
      * both SHADE_LOCKED and shade/qs expansion in order to determine lock state, as one can arrive
@@ -147,8 +137,10 @@
                 isShadeLocked && (isQsExpanded || isShadeExpanded)
             }
             .distinctUntilChanged()
+            .dumpWhileCollecting("isShadeLocked")
 
-    val shadeCollapseFadeInComplete = MutableStateFlow(false)
+    private val shadeCollapseFadeInComplete =
+        MutableStateFlow(false).dumpValue("shadeCollapseFadeInComplete")
 
     val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
         interactor.configurationBasedDimensions
@@ -170,6 +162,7 @@
                 )
             }
             .distinctUntilChanged()
+            .dumpWhileCollecting("configurationBasedDimensions")
 
     /** If the user is visually on one of the unoccluded lockscreen states. */
     val isOnLockscreen: Flow<Boolean> =
@@ -178,13 +171,13 @@
                     statesForConstrainedNotifications.contains(it)
                 },
                 keyguardTransitionInteractor
-                    .transitionValue(LOCKSCREEN)
-                    .onStart { emit(0f) }
-                    .map { it > 0 }
+                    .isInTransitionWhere { from, to -> from == LOCKSCREEN || to == LOCKSCREEN }
+                    .onStart { emit(false) }
             ) { constrainedNotificationState, transitioningToOrFromLockscreen ->
                 constrainedNotificationState || transitioningToOrFromLockscreen
             }
             .distinctUntilChanged()
+            .dumpWhileCollecting("isOnLockscreen")
 
     /** Are we purely on the keyguard without the shade/qs? */
     val isOnLockscreenWithoutShade: Flow<Boolean> =
@@ -203,38 +196,98 @@
                 started = SharingStarted.Eagerly,
                 initialValue = false,
             )
+            .dumpValue("isOnLockscreenWithoutShade")
+
+    /** If the user is visually on the glanceable hub or transitioning to/from it */
+    private val isOnGlanceableHub: Flow<Boolean> =
+        combine(
+                keyguardTransitionInteractor.finishedKeyguardState.map { state ->
+                    state == GLANCEABLE_HUB
+                },
+                keyguardTransitionInteractor
+                    .isInTransitionWhere { from, to ->
+                        from == GLANCEABLE_HUB || to == GLANCEABLE_HUB
+                    }
+                    .onStart { emit(false) }
+            ) { isOnGlanceableHub, transitioningToOrFromHub ->
+                isOnGlanceableHub || transitioningToOrFromHub
+            }
+            .distinctUntilChanged()
+            .dumpWhileCollecting("isOnGlanceableHub")
 
     /** Are we purely on the glanceable hub without the shade/qs? */
     val isOnGlanceableHubWithoutShade: Flow<Boolean> =
         combine(
-                communalInteractor.isIdleOnCommunal,
+                isOnGlanceableHub,
                 // Shade with notifications
                 shadeInteractor.shadeExpansion.map { it > 0f },
                 // Shade without notifications, quick settings only (pull down from very top on
                 // lockscreen)
                 shadeInteractor.qsExpansion.map { it > 0f },
-            ) { isIdleOnCommunal, isShadeVisible, qsExpansion ->
-                isIdleOnCommunal && !(isShadeVisible || qsExpansion)
+            ) { isGlanceableHub, isShadeVisible, qsExpansion ->
+                isGlanceableHub && !(isShadeVisible || qsExpansion)
             }
             .stateIn(
                 scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
+                started = SharingStarted.Eagerly,
                 initialValue = false,
             )
+            .dumpWhileCollecting("isOnGlanceableHubWithoutShade")
+
+    /**
+     * Fade in if the user swipes the shade back up, not if collapsed by going to AOD. This is
+     * needed due to the lack of a SHADE state with existing keyguard transitions.
+     */
+    private fun awaitCollapse(): Flow<Boolean> {
+        var aodTransitionIsComplete = true
+        return combine(
+                isOnLockscreenWithoutShade,
+                keyguardTransitionInteractor
+                    .isInTransitionWhere(
+                        fromStatePredicate = { it == LOCKSCREEN },
+                        toStatePredicate = { it == AOD }
+                    )
+                    .onStart { emit(false) },
+                ::Pair
+            )
+            .transformWhile { (isOnLockscreenWithoutShade, aodTransitionIsRunning) ->
+                // Wait until the AOD transition is complete before terminating
+                if (!aodTransitionIsComplete && !aodTransitionIsRunning) {
+                    aodTransitionIsComplete = true
+                    emit(false) // do not fade in
+                    false
+                } else if (aodTransitionIsRunning) {
+                    aodTransitionIsComplete = false
+                    true
+                } else if (isOnLockscreenWithoutShade) {
+                    // Shade is closed, fade in and terminate
+                    emit(true)
+                    false
+                } else {
+                    true
+                }
+            }
+    }
 
     /** Fade in only for use after the shade collapses */
     val shadeCollapseFadeIn: Flow<Boolean> =
         flow {
                 while (currentCoroutineContext().isActive) {
+                    // Ensure shade is collapsed
+                    isShadeLocked.first { !it }
                     emit(false)
                     // Wait for shade to be fully expanded
                     isShadeLocked.first { it }
-                    // ... and then for it to be collapsed
-                    isOnLockscreenWithoutShade.first { it }
-                    emit(true)
-                    // ... and then for the animation to complete
-                    shadeCollapseFadeInComplete.first { it }
-                    shadeCollapseFadeInComplete.value = false
+                    // ... and then for it to be collapsed OR a transition to AOD begins.
+                    // If AOD, do not fade in (a fade out occurs instead).
+                    awaitCollapse().collect { doFadeIn ->
+                        if (doFadeIn) {
+                            emit(true)
+                            // ... and then for the animation to complete
+                            shadeCollapseFadeInComplete.first { it }
+                            shadeCollapseFadeInComplete.value = false
+                        }
+                    }
                 }
             }
             .stateIn(
@@ -242,6 +295,7 @@
                 started = SharingStarted.WhileSubscribed(),
                 initialValue = false,
             )
+            .dumpWhileCollecting("shadeCollapseFadeIn")
 
     /**
      * The container occupies the entire screen, and must be positioned relative to other elements.
@@ -280,6 +334,7 @@
                 started = SharingStarted.Lazily,
                 initialValue = NotificationContainerBounds(),
             )
+            .dumpValue("bounds")
 
     /**
      * Ensure view is visible when the shade/qs are expanded. Also, as QS is expanding, fade out
@@ -303,26 +358,30 @@
                 }
             }
             .onStart { emit(0f) }
+            .dumpWhileCollecting("alphaForShadeAndQsExpansion")
 
     private val alphaWhenGoneAndShadeState: Flow<Float> =
         combineTransform(
-            keyguardTransitionInteractor.transitions
-                .map { step -> step.to == GONE && step.transitionState == FINISHED }
-                .distinctUntilChanged(),
-            keyguardInteractor.statusBarState,
-        ) { isGoneTransitionFinished, statusBarState ->
-            if (isGoneTransitionFinished && statusBarState == SHADE) {
-                emit(1f)
+                keyguardTransitionInteractor.transitions
+                    .map { step -> step.to == GONE && step.transitionState == FINISHED }
+                    .distinctUntilChanged(),
+                keyguardInteractor.statusBarState,
+            ) { isGoneTransitionFinished, statusBarState ->
+                if (isGoneTransitionFinished && statusBarState == SHADE) {
+                    emit(1f)
+                }
             }
-        }
+            .dumpWhileCollecting("alphaWhenGoneAndShadeState")
 
-    fun expansionAlpha(viewState: ViewStateAccessor): Flow<Float> {
+    fun keyguardAlpha(viewState: ViewStateAccessor): Flow<Float> {
         // All transition view models are mututally exclusive, and safe to merge
         val alphaTransitions =
             merge(
                 alternateBouncerToGoneTransitionViewModel.lockscreenAlpha,
                 aodToLockscreenTransitionViewModel.notificationAlpha,
+                aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
                 dozingToLockscreenTransitionViewModel.lockscreenAlpha,
+                dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState),
                 dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
                 goneToAodTransitionViewModel.notificationAlpha,
                 goneToDreamingTransitionViewModel.lockscreenAlpha,
@@ -333,7 +392,7 @@
                 lockscreenToPrimaryBouncerTransitionViewModel.lockscreenAlpha,
                 occludedToAodTransitionViewModel.lockscreenAlpha,
                 occludedToLockscreenTransitionViewModel.lockscreenAlpha,
-                primaryBouncerToGoneTransitionViewModel.lockscreenAlpha,
+                primaryBouncerToGoneTransitionViewModel.notificationAlpha,
                 primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha,
             )
 
@@ -347,7 +406,9 @@
                     isOnLockscreenWithoutShade,
                     shadeCollapseFadeIn,
                     alphaForShadeAndQsExpansion,
-                    keyguardInteractor.dismissAlpha,
+                    keyguardInteractor.dismissAlpha.dumpWhileCollecting(
+                        "keyguardInteractor.keyguardAlpha"
+                    ),
                 ) {
                     isOnLockscreenWithoutShade,
                     shadeCollapseFadeIn,
@@ -363,39 +424,42 @@
                 },
             )
             .distinctUntilChanged()
+            .dumpWhileCollecting("keyguardAlpha")
     }
 
     /**
-     * Returns a flow of the expected alpha while running a LOCKSCREEN<->GLANCEABLE_HUB transition
-     * or idle on the glanceable hub.
+     * Returns a flow of the expected alpha while running a LOCKSCREEN<->GLANCEABLE_HUB or
+     * DREAMING<->GLANCEABLE_HUB transition or idle on the hub.
      *
      * Must return 1.0f when not controlling the alpha since notifications does a min of all the
      * alpha sources.
      */
     val glanceableHubAlpha: Flow<Float> =
-        isOnGlanceableHubWithoutShade.flatMapLatest { isOnGlanceableHubWithoutShade ->
-            combineTransform(
-                lockscreenToGlanceableHubRunning,
-                glanceableHubToLockscreenRunning,
+        combineTransform(
+                isOnGlanceableHubWithoutShade,
+                isOnLockscreen,
                 merge(
                     lockscreenToGlanceableHubTransitionViewModel.notificationAlpha,
                     glanceableHubToLockscreenTransitionViewModel.notificationAlpha,
                 )
-            ) { lockscreenToGlanceableHubRunning, glanceableHubToLockscreenRunning, alpha ->
-                if (isOnGlanceableHubWithoutShade) {
+            ) { isOnGlanceableHubWithoutShade, isOnLockscreen, alpha,
+                ->
+                if (isOnGlanceableHubWithoutShade && !isOnLockscreen) {
                     // Notifications should not be visible on the glanceable hub.
-                    // TODO(b/321075734): implement a way to actually set the notifications to gone
-                    //  while on the hub instead of just adjusting alpha
+                    // TODO(b/321075734): implement a way to actually set the notifications to
+                    // gone while on the hub instead of just adjusting alpha
                     emit(0f)
-                } else if (lockscreenToGlanceableHubRunning || glanceableHubToLockscreenRunning) {
+                } else if (isOnGlanceableHubWithoutShade) {
+                    // We are transitioning between hub and lockscreen, so set the alpha for the
+                    // transition animation.
                     emit(alpha)
                 } else {
-                    // Not on the hub and no transitions running, return full visibility so we don't
-                    // block the notifications from showing.
+                    // Not on the hub and no transitions running, return full visibility so we
+                    // don't block the notifications from showing.
                     emit(1f)
                 }
             }
-        }
+            .dumpWhileCollecting("glanceableHubAlpha")
 
     /**
      * Under certain scenarios, such as swiping up on the lockscreen, the container will need to be
@@ -403,19 +467,23 @@
      */
     fun translationY(params: BurnInParameters): Flow<Float> {
         return combine(
-            aodBurnInViewModel.translationY(params).onStart { emit(0f) },
-            isOnLockscreenWithoutShade,
-            merge(
-                keyguardInteractor.keyguardTranslationY,
-                occludedToLockscreenTransitionViewModel.lockscreenTranslationY,
-            )
-        ) { burnInY, isOnLockscreenWithoutShade, translationY ->
-            if (isOnLockscreenWithoutShade) {
-                burnInY + translationY
-            } else {
-                0f
+                aodBurnInViewModel
+                    .movement(params)
+                    .map { it.translationY.toFloat() }
+                    .onStart { emit(0f) },
+                isOnLockscreenWithoutShade,
+                merge(
+                    keyguardInteractor.keyguardTranslationY,
+                    occludedToLockscreenTransitionViewModel.lockscreenTranslationY,
+                )
+            ) { burnInY, isOnLockscreenWithoutShade, translationY ->
+                if (isOnLockscreenWithoutShade) {
+                    burnInY + translationY
+                } else {
+                    0f
+                }
             }
-        }
+            .dumpWhileCollecting("translationY")
     }
 
     /**
@@ -424,9 +492,10 @@
      */
     val translationX: Flow<Float> =
         merge(
-            lockscreenToGlanceableHubTransitionViewModel.notificationTranslationX,
-            glanceableHubToLockscreenTransitionViewModel.notificationTranslationX,
-        )
+                lockscreenToGlanceableHubTransitionViewModel.notificationTranslationX,
+                glanceableHubToLockscreenTransitionViewModel.notificationTranslationX,
+            )
+            .dumpWhileCollecting("translationX")
 
     /**
      * When on keyguard, there is limited space to display notifications so calculate how many could
@@ -468,6 +537,7 @@
                 }
             }
             .distinctUntilChanged()
+            .dumpWhileCollecting("maxNotifications")
     }
 
     fun notificationStackChanged() {
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 270b94b..a55de25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -45,8 +45,8 @@
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shade.ShadeController
-import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
+import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -71,7 +71,7 @@
     private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
     private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
     private val shadeControllerLazy: Lazy<ShadeController>,
-    private val shadeViewControllerLazy: Lazy<ShadeViewController>,
+    private val commandQueue: CommandQueue,
     private val shadeAnimationInteractor: ShadeAnimationInteractor,
     private val statusBarKeyguardViewManagerLazy: Lazy<StatusBarKeyguardViewManager>,
     private val notifShadeWindowControllerLazy: Lazy<NotificationShadeWindowController>,
@@ -648,6 +648,10 @@
                 }
                 if (intent.isActivity) {
                     assistManagerLazy.get().hideAssist()
+                    // This activity could have started while the device is dreaming, in which case
+                    // the dream would occlude the activity. In order to show the newly started
+                    // activity, we wake from the dream.
+                    keyguardUpdateMonitor.awakenFromDream()
                 }
                 intentSentUiThreadCallback?.let { postOnUiThread(runnable = it) }
             }
@@ -853,10 +857,11 @@
                 if (dismissShade) {
                     return StatusBarTransitionAnimatorController(
                         animationController,
-                        shadeViewControllerLazy.get(),
                         shadeAnimationInteractor,
                         shadeControllerLazy.get(),
                         notifShadeWindowControllerLazy.get(),
+                        commandQueue,
+                        displayId,
                         isLaunchForActivity
                     )
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 3669ba8..ab9ecab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -301,22 +301,20 @@
 
         if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP == key.getKeyCode()) {
             mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_UP);
-            mShadeViewController.collapse(
-                    false /* delayed */, 1.0f /* speedUpFactor */);
+            mShadeController.animateCollapseShade();
         } else if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN == key.getKeyCode()) {
             mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_DOWN);
             if (mShadeViewController.isFullyCollapsed()) {
                 if (mVibrateOnOpening) {
                     vibrateOnNavigationKeyDown();
                 }
-                mShadeViewController.expand(true /* animate */);
+                mShadeController.animateExpandShade();
                 mNotificationStackScrollLayoutController.setWillExpand(true);
                 mHeadsUpManager.unpinAll(true /* userUnpinned */);
                 mMetricsLogger.count("panel_open", 1);
             } else if (!mQsController.getExpanded()
-                    && !mShadeViewController.isExpandingOrCollapsing()) {
-                mQsController.flingQs(0 /* velocity */,
-                        ShadeViewController.FLING_EXPAND);
+                    && !mShadeController.isExpandingOrCollapsing()) {
+                mShadeController.animateExpandQs();
                 mMetricsLogger.count("panel_open_qs", 1);
             }
         }
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 db15144..d32e88b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2790,6 +2790,9 @@
                         || mKeyguardStateController.isKeyguardGoingAway()
                         || mKeyguardViewMediator.requestedShowSurfaceBehindKeyguard()
                         || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind());
+        boolean dreaming =
+                mKeyguardStateController.isShowing() && mKeyguardUpdateMonitor.isDreaming()
+                        && !unlocking;
 
         mScrimController.setExpansionAffectsAlpha(!unlocking);
 
@@ -2834,13 +2837,16 @@
             // this as otherwise it can remain pending and leave keyguard in a weird state.
             mUnlockScrimCallback.onCancelled();
         } else if (mIsIdleOnCommunal) {
-            mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
+            if (dreaming) {
+                mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
+            } else {
+                mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
+            }
         } else if (mKeyguardStateController.isShowing()
                 && !mKeyguardStateController.isOccluded()
                 && !unlocking) {
             mScrimController.transitionTo(ScrimState.KEYGUARD);
-        } else if (mKeyguardStateController.isShowing() && mKeyguardUpdateMonitor.isDreaming()
-                && !unlocking) {
+        } else if (dreaming) {
             mScrimController.transitionTo(ScrimState.DREAMING);
         } else {
             mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
@@ -3043,8 +3049,7 @@
             if (userSetup != mUserSetup) {
                 mUserSetup = userSetup;
                 if (!mUserSetup && mState == StatusBarState.SHADE) {
-                    mShadeSurface.collapse(true /* animate */, false  /* delayed */,
-                            1.0f  /* speedUpFactor */);
+                    mShadeController.animateCollapseShade();
                 }
             }
         }
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 6f992ac..d513f8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
@@ -15,10 +15,12 @@
 package com.android.systemui.statusbar.phone;
 
 import static com.android.systemui.plugins.DarkIconDispatcher.getTint;
+import static com.android.settingslib.flags.Flags.newStatusBarIcons;
 
 import android.animation.ArgbEvaluator;
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.graphics.Color;
 import android.graphics.Rect;
 import android.util.ArrayMap;
 import android.widget.ImageView;
@@ -66,10 +68,16 @@
             Context context,
             LightBarTransitionsController.Factory lightBarTransitionsControllerFactory,
             DumpManager dumpManager) {
-        mDarkModeIconColorSingleTone = context.getColor(
-                com.android.settingslib.R.color.dark_mode_icon_color_single_tone);
-        mLightModeIconColorSingleTone = context.getColor(
-                com.android.settingslib.R.color.light_mode_icon_color_single_tone);
+
+        if (newStatusBarIcons()) {
+            mDarkModeIconColorSingleTone = Color.BLACK;
+            mLightModeIconColorSingleTone = Color.WHITE;
+        } else {
+            mDarkModeIconColorSingleTone = context.getColor(
+                    com.android.settingslib.R.color.dark_mode_icon_color_single_tone);
+            mLightModeIconColorSingleTone = context.getColor(
+                    com.android.settingslib.R.color.light_mode_icon_color_single_tone);
+        }
 
         mTransitionsController = lightBarTransitionsControllerFactory.create(this);
 
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 b07ba3c..a155e94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -67,7 +67,7 @@
     private static final String TAG = "HeadsUpManagerPhone";
 
     @VisibleForTesting
-    final int mExtensionTime;
+    public final int mExtensionTime;
     private final KeyguardBypassController mBypassController;
     private final GroupMembershipManager mGroupMembershipManager;
     private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>();
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 5a8b636..d975009 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -22,6 +22,7 @@
 import android.app.ActivityTaskManager;
 import android.app.AlarmManager;
 import android.app.AlarmManager.AlarmClockInfo;
+import android.app.NotificationManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -379,7 +380,7 @@
     }
 
     @Override
-    public void onConfigChanged(ZenModeConfig config) {
+    public void onConsolidatedPolicyChanged(NotificationManager.Policy policy) {
         updateVolumeZen();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index d10ca3d..6b47ac1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -239,10 +239,22 @@
         ViewGroup.LayoutParams layoutParams = getLayoutParams();
         mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
         layoutParams.height = mStatusBarHeight - waterfallTopInset;
+        updateSystemIconsContainerHeight();
         updatePaddings();
         setLayoutParams(layoutParams);
     }
 
+    private void updateSystemIconsContainerHeight() {
+        View systemIconsContainer = findViewById(R.id.system_icons);
+        ViewGroup.LayoutParams layoutParams = systemIconsContainer.getLayoutParams();
+        int newSystemIconsHeight =
+                getResources().getDimensionPixelSize(R.dimen.status_bar_system_icons_height);
+        if (layoutParams.height != newSystemIconsHeight) {
+            layoutParams.height = newSystemIconsHeight;
+            systemIconsContainer.setLayoutParams(layoutParams);
+        }
+    }
+
     private void updatePaddings() {
         int statusBarPaddingStart = getResources().getDimensionPixelSize(
                 R.dimen.status_bar_padding_start);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index a39bfe0..f29ec8f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -18,6 +18,7 @@
 import android.app.StatusBarManager.WINDOW_STATUS_BAR
 import android.graphics.Point
 import android.util.Log
+import android.view.InputDevice
 import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
@@ -81,7 +82,22 @@
         statusContainer.setOnHoverListener(
             statusOverlayHoverListenerFactory.createDarkAwareListener(statusContainer)
         )
-        statusContainer.setOnClickListener { shadeViewController.expand(/* animate= */true) }
+        statusContainer.setOnTouchListener(object : View.OnTouchListener {
+            override fun onTouch(v: View, event: MotionEvent): Boolean {
+                // We want to handle only mouse events here to avoid stealing finger touches from
+                // status bar which expands shade when swiped down on. We're using onTouchListener
+                // instead of onClickListener as the later will lead to isClickable being set to
+                // true and hence ALL touches always being intercepted. See [View.OnTouchEvent]
+                if (event.source == InputDevice.SOURCE_MOUSE) {
+                    if (event.action == MotionEvent.ACTION_UP) {
+                        v.performClick()
+                        shadeController.animateExpandShade()
+                    }
+                    return true
+                }
+                return false
+            }
+        })
 
         progressProvider?.setReadyToHandleTransition(true)
         configurationController.addCallback(configurationListener)
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 d2e36b8..02293a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -207,8 +207,6 @@
     private ScrimView mNotificationsScrim;
     private ScrimView mScrimBehind;
 
-    private Runnable mScrimBehindChangeRunnable;
-
     private final KeyguardStateController mKeyguardStateController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final DozeParameters mDozeParameters;
@@ -415,11 +413,6 @@
         behindScrim.enableBottomEdgeConcave(mClipsQsScrim);
         mNotificationsScrim.enableRoundedCorners(true);
 
-        if (mScrimBehindChangeRunnable != null) {
-            mScrimBehind.setChangeRunnable(mScrimBehindChangeRunnable, mMainExecutor);
-            mScrimBehindChangeRunnable = null;
-        }
-
         final ScrimState[] states = ScrimState.values();
         for (int i = 0; i < states.length; i++) {
             states[i].init(mScrimInFront, mScrimBehind, mDozeParameters, mDockManager);
@@ -543,6 +536,16 @@
         mAnimateChange = state.getAnimateChange();
         mAnimationDuration = state.getAnimationDuration();
 
+        if (mState == ScrimState.GLANCEABLE_HUB_OVER_DREAM) {
+            // When the device is docked while on GLANCEABLE_HUB, the dream starts underneath the
+            // hub and the ScrimState transitions to GLANCEABLE_HUB_OVER_DREAM. To prevent the
+            // scrims from flickering in during this transition, we set the panel expansion
+            // fraction, which is 1 when idle on GLANCEABLE_HUB, to 0. This only occurs when the hub
+            // is open because the hub lives in the same window as the shade, which is not visible
+            // when transitioning from KEYGUARD to DREAMING.
+            mPanelExpansionFraction = 0f;
+        }
+
         applyState();
 
         mScrimInFront.setBlendWithMainColor(state.shouldBlendWithMainColor());
@@ -745,6 +748,7 @@
             boolean relevantState = (mState == ScrimState.UNLOCKED
                     || mState == ScrimState.KEYGUARD
                     || mState == ScrimState.DREAMING
+                    || mState == ScrimState.GLANCEABLE_HUB_OVER_DREAM
                     || mState == ScrimState.SHADE_LOCKED
                     || mState == ScrimState.PULSING);
             if (!(relevantState && mExpansionAffectsAlpha) || mAnimatingPanelExpansionOnUnlock) {
@@ -851,7 +855,8 @@
             return;
         }
         mBouncerHiddenFraction = bouncerHiddenAmount;
-        if (mState == ScrimState.DREAMING || mState == ScrimState.GLANCEABLE_HUB) {
+        if (mState == ScrimState.DREAMING || mState == ScrimState.GLANCEABLE_HUB
+                || mState == ScrimState.GLANCEABLE_HUB_OVER_DREAM) {
             // The dreaming and glanceable hub states requires this for the scrim calculation, so we
             // should only trigger an update in those states.
             applyAndDispatchState();
@@ -933,7 +938,8 @@
             return;
         }
 
-        if (mState == ScrimState.UNLOCKED || mState == ScrimState.DREAMING) {
+        if (mState == ScrimState.UNLOCKED || mState == ScrimState.DREAMING
+                || mState == ScrimState.GLANCEABLE_HUB_OVER_DREAM) {
             final boolean occluding =
                     mOccludeAnimationPlaying || mState.mLaunchingAffordanceWithPreview;
             // Darken scrim as it's pulled down while unlocked. If we're unlocked but playing the
@@ -961,8 +967,9 @@
                 mInFrontAlpha = 0;
             }
 
-            if (mState == ScrimState.DREAMING
+            if ((mState == ScrimState.DREAMING || mState == ScrimState.GLANCEABLE_HUB_OVER_DREAM)
                     && mBouncerHiddenFraction != KeyguardBouncerConstants.EXPANSION_HIDDEN) {
+                // Bouncer is opening over dream or glanceable hub over dream.
                 final float interpolatedFraction =
                         BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(
                                 mBouncerHiddenFraction);
@@ -1542,16 +1549,6 @@
         mScrimBehind.postOnAnimationDelayed(callback, 32 /* delayMillis */);
     }
 
-    public void setScrimBehindChangeRunnable(Runnable changeRunnable) {
-        // TODO: remove this. This is necessary because of an order-of-operations limitation.
-        // The fix is to move more of these class into @SysUISingleton.
-        if (mScrimBehind == null) {
-            mScrimBehindChangeRunnable = changeRunnable;
-        } else {
-            mScrimBehind.setChangeRunnable(changeRunnable, mMainExecutor);
-        }
-    }
-
     private void updateThemeColors() {
         if (mScrimBehind == null) return;
         int background = Utils.getColorAttr(mScrimBehind.getContext(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index f2a649b..d4960d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -299,9 +299,9 @@
     },
 
     /**
-     * Device is locked or on dream and user has swiped from the right edge to enter the glanceable
-     * hub UI. From this state, the user can swipe from the left edge to go back to the lock screen
-     * or dream, as well as swipe down for the notifications and up for the bouncer.
+     * Device is on the lockscreen and user has swiped from the right edge to enter the glanceable
+     * hub UI. From this state, the user can swipe from the left edge to go back to the lock screen,
+     * as well as swipe down for the notifications and up for the bouncer.
      */
     GLANCEABLE_HUB {
         @Override
@@ -310,6 +310,25 @@
             mBehindAlpha = 0;
             mNotifAlpha = 0;
             mFrontAlpha = 0;
+
+            mFrontTint = Color.TRANSPARENT;
+            mBehindTint = mBackgroundColor;
+            mNotifTint = mClipQsScrim ? mBackgroundColor : Color.TRANSPARENT;
+        }
+    },
+
+    /**
+     * Device is dreaming and user has swiped from the right edge to enter the glanceable hub UI.
+     * From this state, the user can swipe from the left edge to go back to the  dream, as well as
+     * swipe down for the notifications and up for the bouncer.
+     *
+     * This is a separate state from {@link #GLANCEABLE_HUB} because the scrims behave differently
+     * when the dream is running.
+     */
+    GLANCEABLE_HUB_OVER_DREAM {
+        @Override
+        public void prepare(ScrimState previousState) {
+            GLANCEABLE_HUB.prepare(previousState);
         }
     };
 
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 29fd225..7dd328a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -23,7 +23,6 @@
 import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
-import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
 
 import android.content.Context;
@@ -82,6 +81,9 @@
 import com.android.systemui.navigationbar.TaskbarDelegate;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.scene.shared.model.Scenes;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shade.ShadeExpansionListener;
@@ -93,11 +95,13 @@
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.domain.interactor.StatusBarKeyguardViewManagerInteractor;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 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 com.android.systemui.util.kotlin.JavaAdapter;
 
 import dagger.Lazy;
 
@@ -157,6 +161,7 @@
     private final AlternateBouncerInteractor mAlternateBouncerInteractor;
     private final BouncerView mPrimaryBouncerView;
     private final Lazy<ShadeController> mShadeController;
+    private final Lazy<SceneInteractor> mSceneInteractorLazy;
 
     // Local cache of expansion events, to avoid duplicates
     private float mFraction = -1f;
@@ -347,6 +352,8 @@
     private Lazy<WindowManagerLockscreenVisibilityInteractor> mWmLockscreenVisibilityInteractor;
     private Lazy<KeyguardSurfaceBehindInteractor> mSurfaceBehindInteractor;
     private Lazy<KeyguardDismissActionInteractor> mKeyguardDismissActionInteractor;
+    private final JavaAdapter mJavaAdapter;
+    private StatusBarKeyguardViewManagerInteractor mStatusBarKeyguardViewManagerInteractor;
 
     @Inject
     public StatusBarKeyguardViewManager(
@@ -378,7 +385,10 @@
             Lazy<WindowManagerLockscreenVisibilityInteractor> wmLockscreenVisibilityInteractor,
             Lazy<KeyguardDismissActionInteractor> keyguardDismissActionInteractorLazy,
             SelectedUserInteractor selectedUserInteractor,
-            Lazy<KeyguardSurfaceBehindInteractor> surfaceBehindInteractor
+            Lazy<KeyguardSurfaceBehindInteractor> surfaceBehindInteractor,
+            JavaAdapter javaAdapter,
+            Lazy<SceneInteractor> sceneInteractorLazy,
+            StatusBarKeyguardViewManagerInteractor statusBarKeyguardViewManagerInteractor
     ) {
         mContext = context;
         mViewMediatorCallback = callback;
@@ -411,6 +421,9 @@
         mKeyguardDismissActionInteractor = keyguardDismissActionInteractorLazy;
         mSelectedUserInteractor = selectedUserInteractor;
         mSurfaceBehindInteractor = surfaceBehindInteractor;
+        mJavaAdapter = javaAdapter;
+        mSceneInteractorLazy = sceneInteractorLazy;
+        mStatusBarKeyguardViewManagerInteractor = statusBarKeyguardViewManagerInteractor;
     }
 
     KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -481,8 +494,7 @@
 
         if (KeyguardWmStateRefactor.isEnabled()) {
             // Show the keyguard views whenever we've told WM that the lockscreen is visible.
-            collectFlow(
-                    getViewRootImpl().getView(),
+            mJavaAdapter.alwaysCollectFlow(
                     combineFlows(
                             mWmLockscreenVisibilityInteractor.get().getLockscreenVisibility(),
                             mSurfaceBehindInteractor.get().isAnimatingSurface(),
@@ -494,6 +506,11 @@
                                     lockscreenVis || animatingSurface
                     ),
                     this::consumeShowStatusBarKeyguardView);
+
+            mJavaAdapter.alwaysCollectFlow(
+                    mStatusBarKeyguardViewManagerInteractor.getKeyguardViewOcclusionState(),
+                    (occlusionState) -> setOccluded(
+                            occlusionState.getOccluded(), occlusionState.getAnimate()));
         }
     }
 
@@ -562,7 +579,7 @@
         final boolean hideBouncerOverDream =
                 mDreamOverlayStateController.isOverlayActive()
                         && (mShadeLockscreenInteractor.isExpanded()
-                        || mShadeLockscreenInteractor.isExpandingOrCollapsing());
+                        || mShadeController.get().isExpandingOrCollapsing());
 
         final boolean isUserTrackingStarted =
                 event.getFraction() != EXPANSION_HIDDEN && event.getTracking();
@@ -630,8 +647,11 @@
     public void show(Bundle options) {
         Trace.beginSection("StatusBarKeyguardViewManager#show");
         mNotificationShadeWindowController.setKeyguardShowing(true);
-        mKeyguardStateController.notifyKeyguardState(true,
-                mKeyguardStateController.isOccluded());
+        if (SceneContainerFlag.isEnabled()) {
+            mSceneInteractorLazy.get().changeScene(
+                    Scenes.Lockscreen, "StatusBarKeyguardViewManager.show");
+        }
+        mKeyguardStateController.notifyKeyguardState(true, mKeyguardStateController.isOccluded());
         reset(true /* hideBouncerWhenShowing */);
         SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
                 SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
@@ -781,7 +801,7 @@
                     }
 
                     updateAlternateBouncerShowing(mAlternateBouncerInteractor.show());
-                    setKeyguardMessage(message, null);
+                    setKeyguardMessage(message, null, null);
                     return;
                 }
 
@@ -1441,14 +1461,19 @@
             hideAlternateBouncer(false);
             executeAfterKeyguardGoneAction();
         }
+
+        if (KeyguardWmStateRefactor.isEnabled()) {
+            mKeyguardTransitionInteractor.startDismissKeyguardTransition();
+        }
     }
 
     /** Display security message to relevant KeyguardMessageArea. */
-    public void setKeyguardMessage(String message, ColorStateList colorState) {
+    public void setKeyguardMessage(String message, ColorStateList colorState,
+            BiometricSourceType biometricSourceType) {
         if (mAlternateBouncerInteractor.isVisibleState()) {
             if (mKeyguardMessageAreaController != null) {
                 DeviceEntryUdfpsRefactor.assertInLegacyMode();
-                mKeyguardMessageAreaController.setMessage(message);
+                mKeyguardMessageAreaController.setMessage(message, biometricSourceType);
             }
         } else {
             mPrimaryBouncerInteractor.showMessage(message, colorState);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 5610ed9..b5ab4e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -63,6 +63,7 @@
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationClickNotifier;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
@@ -105,6 +106,7 @@
     private final NotificationVisibilityProvider mVisibilityProvider;
     private final HeadsUpManager mHeadsUpManager;
     private final ActivityStarter mActivityStarter;
+    private final CommandQueue mCommandQueue;
     private final NotificationClickNotifier mClickNotifier;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final KeyguardManager mKeyguardManager;
@@ -143,6 +145,7 @@
             NotificationVisibilityProvider visibilityProvider,
             HeadsUpManager headsUpManager,
             ActivityStarter activityStarter,
+            CommandQueue commandQueue,
             NotificationClickNotifier clickNotifier,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             KeyguardManager keyguardManager,
@@ -175,6 +178,7 @@
         mVisibilityProvider = visibilityProvider;
         mHeadsUpManager = headsUpManager;
         mActivityStarter = activityStarter;
+        mCommandQueue = commandQueue;
         mClickNotifier = clickNotifier;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mKeyguardManager = keyguardManager;
@@ -444,10 +448,11 @@
             ActivityTransitionAnimator.Controller animationController =
                     new StatusBarTransitionAnimatorController(
                             mNotificationAnimationProvider.getAnimatorController(row, null),
-                            mShadeViewController,
                             mShadeAnimationInteractor,
                             mShadeController,
                             mNotificationShadeWindowController,
+                            mCommandQueue,
+                            mDisplayId,
                             isActivityIntent);
             mActivityTransitionAnimator.startPendingIntentWithAnimation(
                     animationController,
@@ -486,10 +491,11 @@
                     ActivityTransitionAnimator.Controller animationController =
                             new StatusBarTransitionAnimatorController(
                                     mNotificationAnimationProvider.getAnimatorController(row),
-                                    mShadeViewController,
                                     mShadeAnimationInteractor,
                                     mShadeController,
                                     mNotificationShadeWindowController,
+                                    mCommandQueue,
+                                    mDisplayId,
                                     true /* isActivityIntent */);
 
                     mActivityTransitionAnimator.startIntentWithAnimation(
@@ -537,10 +543,11 @@
                             viewController == null ? null
                                 : new StatusBarTransitionAnimatorController(
                                         viewController,
-                                        mShadeViewController,
                                         mShadeAnimationInteractor,
                                         mShadeController,
                                         mNotificationShadeWindowController,
+                                        mCommandQueue,
+                                        mDisplayId,
                                         true /* isActivityIntent */);
 
                     mActivityTransitionAnimator.startIntentWithAnimation(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt
index 7e907d8..705a11d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt
@@ -3,10 +3,13 @@
 import android.view.View
 import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.animation.TransitionAnimator
+import com.android.systemui.animation.TransitionAnimator.Companion.getProgress
+import com.android.systemui.dagger.qualifiers.DisplayId
 import com.android.systemui.shade.ShadeController
-import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
+import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
 
 /**
  * A [ActivityTransitionAnimator.Controller] that takes care of collapsing the status bar at the
@@ -14,12 +17,15 @@
  */
 class StatusBarTransitionAnimatorController(
     private val delegate: ActivityTransitionAnimator.Controller,
-    private val shadeViewController: ShadeViewController,
     private val shadeAnimationInteractor: ShadeAnimationInteractor,
     private val shadeController: ShadeController,
     private val notificationShadeWindowController: NotificationShadeWindowController,
+    private val commandQueue: CommandQueue,
+    @DisplayId private val displayId: Int,
     private val isLaunchForActivity: Boolean = true
 ) : ActivityTransitionAnimator.Controller by delegate {
+    private var hideIconsDuringLaunchAnimation: Boolean = true
+
     // Always sync the opening window with the shade, given that we draw a hole punch in the shade
     // of the same size and position as the opening app to make it visible.
     override val openingWindowSyncView: View?
@@ -38,7 +44,7 @@
         delegate.onTransitionAnimationStart(isExpandingFullyAbove)
         shadeAnimationInteractor.setIsLaunchingActivity(true)
         if (!isExpandingFullyAbove) {
-            shadeViewController.collapseWithDuration(
+            shadeController.collapseWithDuration(
                 ActivityTransitionAnimator.TIMINGS.totalDuration.toInt()
             )
         }
@@ -56,7 +62,19 @@
         linearProgress: Float
     ) {
         delegate.onTransitionAnimationProgress(state, progress, linearProgress)
-        shadeViewController.applyLaunchAnimationProgress(linearProgress)
+        val hideIcons =
+            getProgress(
+                ActivityTransitionAnimator.TIMINGS,
+                linearProgress,
+                ANIMATION_DELAY_ICON_FADE_IN,
+                100
+            ) == 0.0f
+        if (hideIcons != hideIconsDuringLaunchAnimation) {
+            hideIconsDuringLaunchAnimation = hideIcons
+            if (!hideIcons) {
+                commandQueue.recomputeDisableFlags(displayId, true /* animate */)
+            }
+        }
     }
 
     override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) {
@@ -64,4 +82,12 @@
         shadeAnimationInteractor.setIsLaunchingActivity(false)
         shadeController.onLaunchAnimationCancelled(isLaunchForActivity)
     }
+
+    companion object {
+        val ANIMATION_DELAY_ICON_FADE_IN =
+            (ActivityTransitionAnimator.TIMINGS.totalDuration -
+                CollapsedStatusBarFragment.FADE_IN_DURATION -
+                CollapsedStatusBarFragment.FADE_IN_DELAY -
+                48)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
index 71e25e9..541ac48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
@@ -23,6 +23,9 @@
 import android.os.Bundle
 import android.view.Gravity
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.WindowInsets
+import android.view.WindowInsets.Type.InsetsType
+import android.view.WindowInsetsAnimation
 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
@@ -70,16 +73,43 @@
     override fun onStart() {
         super.onStart()
         configurationController?.addCallback(onConfigChanged)
+        window?.decorView?.setWindowInsetsAnimationCallback(insetsAnimationCallback)
     }
 
     override fun onStop() {
         super.onStop()
         configurationController?.removeCallback(onConfigChanged)
+        window?.decorView?.setWindowInsetsAnimationCallback(null)
     }
 
+    /** Called after any insets change. */
+    open fun onInsetsChanged(@InsetsType changedTypes: Int, insets: WindowInsets) {}
+
     /** Can be overridden by subclasses to receive config changed events. */
     open fun onConfigurationChanged() {}
 
+    private val insetsAnimationCallback =
+        object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
+
+            private var lastInsets: WindowInsets? = null
+
+            override fun onEnd(animation: WindowInsetsAnimation) {
+                lastInsets?.let { onInsetsChanged(animation.typeMask, it) }
+            }
+
+            override fun onProgress(
+                insets: WindowInsets,
+                animations: MutableList<WindowInsetsAnimation>,
+            ): WindowInsets {
+                lastInsets = insets
+                onInsetsChanged(changedTypes = allAnimationMasks(animations), insets)
+                return insets
+            }
+
+            private fun allAnimationMasks(animations: List<WindowInsetsAnimation>): Int =
+                animations.fold(0) { acc: Int, it -> acc or it.typeMask }
+        }
+
     private val onConfigChanged =
         object : ConfigurationListener {
             override fun onConfigChanged(newConfig: Configuration?) {
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 f12a09b..82d9fc7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -93,7 +93,7 @@
 
     /**
      * @deprecated Don't subclass SystemUIDialog. Please subclass {@link Delegate} and pass it to
-     *             {@link Factory#create(DialogDelegate)} to create a custom dialog.
+     *             {@link Factory#create(Delegate)} to create a custom dialog.
      */
     @Deprecated
     public SystemUIDialog(Context context) {
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 223eaf7..88374d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.statusbar.notification.stack.AnimationProperties
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.Flags.lightRevealMigration
 import com.android.app.tracing.namedRunnable
 import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
@@ -45,7 +46,7 @@
 /**
  * Duration for the light reveal portion of the animation.
  */
-private const val LIGHT_REVEAL_ANIMATION_DURATION = 750L
+private const val LIGHT_REVEAL_ANIMATION_DURATION = 500L
 
 /**
  * Controller for the unlocked screen off animation, which runs when the device is going to sleep
@@ -66,7 +67,7 @@
     private val notifShadeWindowControllerLazy: dagger.Lazy<NotificationShadeWindowController>,
     private val interactionJankMonitor: InteractionJankMonitor,
     private val powerManager: PowerManager,
-    private val handler: Handler = Handler(),
+    private val handler: Handler = Handler()
 ) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
     private lateinit var centralSurfaces: CentralSurfaces
     private lateinit var shadeViewController: ShadeViewController
@@ -95,6 +96,7 @@
         duration = LIGHT_REVEAL_ANIMATION_DURATION
         interpolator = Interpolators.LINEAR
         addUpdateListener {
+            if (lightRevealMigration()) return@addUpdateListener
             if (lightRevealScrim.revealEffect !is CircleReveal) {
                 lightRevealScrim.revealAmount = it.animatedValue as Float
             }
@@ -107,6 +109,7 @@
         }
         addListener(object : AnimatorListenerAdapter() {
             override fun onAnimationCancel(animation: Animator) {
+                if (lightRevealMigration()) return
                 if (lightRevealScrim.revealEffect !is CircleReveal) {
                     lightRevealScrim.revealAmount = 1f
                 }
@@ -371,7 +374,7 @@
      * AOD UI.
      */
     override fun isAnimationPlaying(): Boolean {
-        return lightRevealAnimationPlaying || aodUiAnimationPlaying
+        return isScreenOffLightRevealAnimationPlaying() || aodUiAnimationPlaying
     }
 
     override fun shouldAnimateInKeyguard(): Boolean =
@@ -395,6 +398,9 @@
     /**
      * Whether the light reveal animation is playing. The second part of the screen off animation,
      * where AOD animates in, might still be playing if this returns false.
+     *
+     * Note: This only refers to the specific light reveal animation that is playing during lock
+     * therefore LightRevealScrimInteractor.isAnimating is not the desired response.
      */
     fun isScreenOffLightRevealAnimationPlaying(): Boolean {
         return lightRevealAnimationPlaying
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
index efdce06..016ba5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
@@ -55,7 +55,7 @@
 @Inject
 constructor(
     broadcastDispatcher: BroadcastDispatcher,
-    private val carrierConfigManager: CarrierConfigManager,
+    private val carrierConfigManager: CarrierConfigManager?,
     dumpManager: DumpManager,
     logger: MobileInputLogger,
     @Application scope: CoroutineScope,
@@ -87,7 +87,7 @@
             .onEach { logger.logCarrierConfigChanged(it) }
             .filter { SubscriptionManager.isValidSubscriptionId(it) }
             .mapNotNull { subId ->
-                val config = carrierConfigManager.getConfigForSubId(subId)
+                val config = carrierConfigManager?.getConfigForSubId(subId)
                 config?.let { subId to it }
             }
             .shareIn(scope, SharingStarted.WhileSubscribed())
@@ -111,7 +111,7 @@
     fun getOrCreateConfigForSubId(subId: Int): SystemUiCarrierConfig {
         return configs.getOrElse(subId) {
             val config = SystemUiCarrierConfig(subId, defaultConfig)
-            val carrierConfig = carrierConfigManager.getConfigForSubId(subId)
+            val carrierConfig = carrierConfigManager?.getConfigForSubId(subId)
             if (carrierConfig != null) config.processNewCarrierConfig(carrierConfig)
             configs.put(subId, config)
             config
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 a608be3..8f3b0e7 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
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
+import android.telephony.CellSignalStrength
 import android.telephony.SubscriptionInfo
 import android.telephony.TelephonyManager
 import com.android.systemui.log.table.TableLogBuffer
@@ -149,6 +150,6 @@
 
     companion object {
         /** The default number of levels to use for [numberOfLevels]. */
-        const val DEFAULT_NUM_LEVELS = 4
+        val DEFAULT_NUM_LEVELS = CellSignalStrength.getNumSignalStrengthLevels()
     }
 }
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 6de7a00..3cb138bd 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
@@ -25,6 +25,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_CARRIER_ID
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_CARRIER_NETWORK_CHANGE
@@ -223,6 +224,9 @@
 
         _carrierId.value = event.carrierId ?: INVALID_SUBSCRIPTION_ID
 
+        numberOfLevels.value =
+            if (event.inflateStrength) DEFAULT_NUM_LEVELS + 1 else DEFAULT_NUM_LEVELS
+
         cdmaRoaming.value = event.roaming
         _isRoaming.value = event.roaming
         // TODO(b/261029387): not yet supported
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 11a61a9..dbfc576 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
@@ -71,7 +71,7 @@
         val dataType = getString("datatype")?.toDataType()
         val slot = getString("slot")?.toInt()
         val carrierId = getString("carrierid")?.toInt()
-        val inflateStrength = getString("inflate")?.toBoolean()
+        val inflateStrength = getString("inflate").toBoolean()
         val activity = getString("activity")?.toActivity()
         val carrierNetworkChange = getString("carriernetworkchange") == "show"
         val roaming = getString("roam") == "show"
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 4836abe..42171d0 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
@@ -31,7 +31,7 @@
         // Null means the default (chosen by the repository)
         val subId: Int?,
         val carrierId: Int?,
-        val inflateStrength: Boolean?,
+        val inflateStrength: Boolean = false,
         @DataActivityType val activity: Int?,
         val carrierNetworkChange: Boolean,
         val roaming: Boolean,
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 6b30326..adcf736 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
@@ -69,6 +69,9 @@
     /** True if we consider this connection to be in service, i.e. can make calls */
     val isInService: StateFlow<Boolean>
 
+    /** True if this connection is emergency only */
+    val isEmergencyOnly: StateFlow<Boolean>
+
     /** Observable for the data enabled state of this connection */
     val isDataEnabled: StateFlow<Boolean>
 
@@ -292,12 +295,7 @@
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
 
-    private val numberOfLevels: StateFlow<Int> =
-        connectionRepository.numberOfLevels.stateIn(
-            scope,
-            SharingStarted.WhileSubscribed(),
-            connectionRepository.numberOfLevels.value,
-        )
+    private val numberOfLevels: StateFlow<Int> = connectionRepository.numberOfLevels
 
     override val isDataConnected: StateFlow<Boolean> =
         connectionRepository.dataConnectionState
@@ -306,6 +304,8 @@
 
     override val isInService = connectionRepository.isInService
 
+    override val isEmergencyOnly: StateFlow<Boolean> = connectionRepository.isEmergencyOnly
+
     override val isAllowedDuringAirplaneMode = connectionRepository.isAllowedDuringAirplaneMode
 
     /** Whether or not to show the error state of [SignalDrawable] */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
index 900a920..fd5ab13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
@@ -19,6 +19,8 @@
 import android.content.Context
 import android.util.AttributeSet
 import android.view.LayoutInflater
+import android.widget.ImageView
+import com.android.settingslib.flags.Flags.newStatusBarIcons
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView.getVisibleStateString
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
@@ -59,6 +61,18 @@
                     .inflate(R.layout.status_bar_mobile_signal_group_new, null)
                     as ModernStatusBarMobileView)
                 .also {
+                    // Flag-specific configuration
+                    if (newStatusBarIcons()) {
+                        // New icon (with no embedded whitespace) is slightly shorter
+                        // (but actually taller)
+                        val iconView = it.requireViewById<ImageView>(R.id.mobile_signal)
+                        val lp = iconView.layoutParams
+                        lp.height =
+                            context.resources.getDimensionPixelSize(
+                                R.dimen.status_bar_mobile_signal_size_updated
+                            )
+                    }
+
                     it.subId = viewModel.subscriptionId
                     it.initView(slot) {
                         MobileIconBinder.bind(view = it, viewModel = viewModel, logger = logger)
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 be843ba..deae576 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
@@ -22,7 +22,6 @@
 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
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
 import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger
@@ -31,6 +30,8 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.flatMapLatest
@@ -58,9 +59,8 @@
     private val flags: FeatureFlagsClassic,
     @Application private val scope: CoroutineScope,
 ) {
-    @VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>()
     @VisibleForTesting
-    val mobileIconInteractorSubIdCache = mutableMapOf<Int, MobileIconInteractor>()
+    val reuseCache = mutableMapOf<Int, Pair<MobileIconViewModel, CoroutineScope>>()
 
     val subscriptionIdsFlow: StateFlow<List<Int>> =
         interactor.filteredSubscriptions
@@ -109,24 +109,37 @@
     }
 
     private fun commonViewModelForSub(subId: Int): MobileIconViewModelCommon {
-        return mobileIconSubIdCache[subId]
-            ?: MobileIconViewModel(
-                    subId,
-                    interactor.getMobileConnectionInteractorForSubId(subId),
-                    airplaneModeInteractor,
-                    constants,
-                    flags,
-                    scope,
-                )
-                .also { mobileIconSubIdCache[subId] = it }
+        return reuseCache.getOrPut(subId) { createViewModel(subId) }.first
     }
 
-    private fun invalidateCaches(subIds: List<Int>) {
-        val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) }
-        subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) }
+    private fun createViewModel(subId: Int): Pair<MobileIconViewModel, CoroutineScope> {
+        // Create a child scope so we can cancel it
+        val vmScope = scope.createChildScope()
+        val vm =
+            MobileIconViewModel(
+                subId,
+                interactor.getMobileConnectionInteractorForSubId(subId),
+                airplaneModeInteractor,
+                constants,
+                flags,
+                vmScope,
+            )
 
-        mobileIconInteractorSubIdCache.keys
+        return Pair(vm, vmScope)
+    }
+
+    private fun CoroutineScope.createChildScope() =
+        CoroutineScope(coroutineContext + Job(coroutineContext[Job]))
+
+    private fun invalidateCaches(subIds: List<Int>) {
+        reuseCache.keys
             .filter { !subIds.contains(it) }
-            .forEach { subId -> mobileIconInteractorSubIdCache.remove(subId) }
+            .forEach { id ->
+                reuseCache
+                    .remove(id)
+                    // Cancel the view model's scope after removing it
+                    ?.second
+                    ?.cancel()
+            }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
index 6e1114c..3f89d04b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
@@ -72,9 +72,16 @@
     /** When all connections are considered OOS, satellite connectivity is potentially valid */
     val areAllConnectionsOutOfService =
         if (Flags.oemEnabledSatelliteFlag()) {
-                iconsInteractor.icons.aggregateOver(selector = { intr -> intr.isInService }) {
-                    isInServiceList ->
-                    isInServiceList.all { !it }
+                iconsInteractor.icons.aggregateOver(
+                    selector = { intr ->
+                        combine(intr.isInService, intr.isEmergencyOnly) {
+                            isInService,
+                            isEmergencyOnly ->
+                            !isInService && !isEmergencyOnly
+                        }
+                    }
+                ) { isOosAndIsNotEmergencyOnly ->
+                    isOosAndIsNotEmergencyOnly.all { it }
                 }
             } else {
                 flowOf(false)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index 4cd3484..ba55aad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -20,6 +20,9 @@
 import android.content.Context
 import android.util.AttributeSet
 import android.view.LayoutInflater
+import android.view.ViewGroup
+import android.widget.ImageView
+import com.android.settingslib.flags.Flags.newStatusBarIcons
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView
@@ -57,7 +60,18 @@
         ): ModernStatusBarWifiView {
             return (LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
                     as ModernStatusBarWifiView)
-                .also { it.initView(slot) { WifiViewBinder.bind(it, wifiViewModel) } }
+                .also {
+                    // Flag-specific configuration
+                    if (newStatusBarIcons()) {
+                        // The newer asset does not embed whitespace around it, and is therefore
+                        // rectangular. Use wrap_content for the width in this case
+                        val iconView = it.requireViewById<ImageView>(R.id.wifi_signal)
+                        val lp = iconView.layoutParams
+                        lp.width = ViewGroup.LayoutParams.WRAP_CONTENT
+                    }
+
+                    it.initView(slot) { WifiViewBinder.bind(it, wifiViewModel) }
+                }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 2c1780d..530e49c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -304,14 +304,15 @@
         if (!isPinned) {
             headsUpEntry.mWasUnpinned = true;
         }
-        if (entry.isRowPinned() != isPinned) {
-            entry.setRowPinned(isPinned);
+        if (headsUpEntry.isPinned() != isPinned) {
+            headsUpEntry.setPinned(isPinned);
             updatePinnedMode();
             if (isPinned && entry.getSbn() != null) {
-                mUiEventLogger.logWithInstanceId(
+               mUiEventLogger.logWithInstanceId(
                         NotificationPeekEvent.NOTIFICATION_PEEK, entry.getSbn().getUid(),
                         entry.getSbn().getPackageName(), entry.getSbn().getInstanceId());
             }
+            // TODO(b/325936094) convert these listeners to collecting a flow
             for (OnHeadsUpChangedListener listener : mListeners) {
                 if (isPinned) {
                     listener.onHeadsUpPinned(entry);
@@ -662,6 +663,14 @@
             updateEntry(true /* updatePostTime */, "setEntry");
         }
 
+        public boolean isPinned() {
+            return mEntry != null && mEntry.isRowPinned();
+        }
+
+        public void setPinned(boolean pinned) {
+            if (mEntry != null) mEntry.setRowPinned(pinned);
+        }
+
         /**
          * Updates an entry's removal time.
          * @param updatePostTime whether or not to refresh the post time
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 5bced93..9633cb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -135,7 +135,6 @@
     @Nullable
     private RevealParams mRevealParams;
     private Rect mContentBackgroundBounds;
-    private boolean mIsFocusAnimationFlagActive;
     private boolean mIsAnimatingAppearance = false;
 
     // TODO(b/193539698): move these to a Controller
@@ -432,7 +431,7 @@
         // case to prevent flicker.
         if (!mRemoved) {
             ViewGroup parent = (ViewGroup) getParent();
-            if (animate && parent != null && mIsFocusAnimationFlagActive) {
+            if (animate && parent != null) {
 
                 ViewGroup grandParent = (ViewGroup) parent.getParent();
                 View actionsContainer = getActionsContainerLayout();
@@ -497,8 +496,7 @@
     }
 
     private void setTopMargin(int topMargin) {
-        if (!(getLayoutParams() instanceof FrameLayout.LayoutParams)) return;
-        final FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
+        if (!(getLayoutParams() instanceof FrameLayout.LayoutParams layoutParams)) return;
         layoutParams.topMargin = topMargin;
         setLayoutParams(layoutParams);
     }
@@ -608,24 +606,10 @@
     }
 
     /**
-     * Sets whether the feature flag for the revised inline reply animation is active or not.
-     * @param active
-     */
-    public void setIsFocusAnimationFlagActive(boolean active) {
-        mIsFocusAnimationFlagActive = active;
-    }
-
-    /**
      * Focuses the RemoteInputView and animates its appearance
      */
     public void focusAnimated() {
-        if (!mIsFocusAnimationFlagActive && getVisibility() != VISIBLE
-                && mRevealParams != null) {
-            android.animation.Animator animator = mRevealParams.createCircularRevealAnimator(this);
-            animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-            animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
-            animator.start();
-        } else if (mIsFocusAnimationFlagActive && getVisibility() != VISIBLE) {
+        if (getVisibility() != VISIBLE) {
             mIsAnimatingAppearance = true;
             setAlpha(0f);
             Animator focusAnimator = getFocusAnimator(getActionsContainerLayout());
@@ -680,37 +664,19 @@
     }
 
     private void reset() {
-        if (mIsFocusAnimationFlagActive) {
-            mProgressBar.setVisibility(INVISIBLE);
-            mResetting = true;
-            mSending = false;
-            mController.removeSpinning(mEntry.getKey(), mToken);
-            onDefocus(true /* animate */, false /* logClose */, () -> {
-                mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
-                mEditText.getText().clear();
-                mEditText.setEnabled(isAggregatedVisible());
-                mSendButton.setVisibility(VISIBLE);
-                updateSendButton();
-                setAttachment(null);
-                mResetting = false;
-            });
-            return;
-        }
-
+        mProgressBar.setVisibility(INVISIBLE);
         mResetting = true;
         mSending = false;
-        mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
-
-        mEditText.getText().clear();
-        mEditText.setEnabled(isAggregatedVisible());
-        mSendButton.setVisibility(VISIBLE);
-        mProgressBar.setVisibility(INVISIBLE);
         mController.removeSpinning(mEntry.getKey(), mToken);
-        updateSendButton();
-        onDefocus(false /* animate */, false /* logClose */, null /* doAfterDefocus */);
-        setAttachment(null);
-
-        mResetting = false;
+        onDefocus(true /* animate */, false /* logClose */, () -> {
+            mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
+            mEditText.getText().clear();
+            mEditText.setEnabled(isAggregatedVisible());
+            mSendButton.setVisibility(VISIBLE);
+            updateSendButton();
+            setAttachment(null);
+            mResetting = false;
+        });
     }
 
     @Override
@@ -854,7 +820,7 @@
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
-        if (mIsFocusAnimationFlagActive) setPivotY(getMeasuredHeight());
+        setPivotY(getMeasuredHeight());
         if (mContentBackgroundBounds != null) {
             mContentBackground.setBounds(mContentBackgroundBounds);
         }
@@ -1015,9 +981,9 @@
 
         private RemoteInputView mRemoteInputView;
         boolean mShowImeOnInputConnection;
-        private LightBarController mLightBarController;
+        private final LightBarController mLightBarController;
         private InputMethodManager mInputMethodManager;
-        private ArraySet<String> mSupportedMimes = new ArraySet<>();
+        private final ArraySet<String> mSupportedMimes = new ArraySet<>();
         UserHandle mUser;
 
         public RemoteEditText(Context context, AttributeSet attrs) {
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 6c0d433..bfee9ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
@@ -32,7 +32,6 @@
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.res.R
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.NOTIFICATION_INLINE_REPLY_ANIMATION
 import com.android.systemui.statusbar.NotificationRemoteInputManager
 import com.android.systemui.statusbar.RemoteInputController
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -64,8 +63,6 @@
 
     var revealParams: RevealParams?
 
-    val isFocusAnimationFlagActive: Boolean
-
     /**
      * Sets the smart reply that should be inserted in the remote input, or `null` if the user is
      * not editing a smart reply.
@@ -155,9 +152,6 @@
 
     override val isActive: Boolean get() = view.isActive
 
-    override val isFocusAnimationFlagActive: Boolean
-        get() = mFlags.isEnabled(NOTIFICATION_INLINE_REPLY_ANIMATION)
-
     override fun bind() {
         if (isBound) return
         isBound = true
@@ -168,7 +162,6 @@
             view.setSupportedMimeTypes(it.allowedDataTypes)
         }
         view.setRevealParameters(revealParams)
-        view.setIsFocusAnimationFlagActive(isFocusAnimationFlagActive)
 
         view.addOnEditTextFocusChangedListener(onFocusChangeListener)
         view.addOnSendRemoteInputListener(onSendRemoteInputListener)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ResourcesSplitShadeStateController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ResourcesSplitShadeStateController.kt
index 859e636..213324a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ResourcesSplitShadeStateController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ResourcesSplitShadeStateController.kt
@@ -26,6 +26,15 @@
  * based solely on resources, no extra flag logic.
  */
 class ResourcesSplitShadeStateController @Inject constructor() : SplitShadeStateController {
+
+    @Deprecated(
+        message = "This is deprecated, please use ShadeInteractor#isSplitShade instead",
+        replaceWith =
+            ReplaceWith(
+                "shadeInteractor.isSplitShade",
+                "com.android.systemui.shade.domain.interactor.ShadeInteractor",
+            ),
+    )
     override fun shouldUseSplitNotificationShade(resources: Resources): Boolean {
         return resources.getBoolean(R.bool.config_use_split_notification_shade)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt
index f64d4c6..d120a1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt
@@ -19,6 +19,15 @@
 
 /** Source of truth for split shade state: should or should not use split shade. */
 interface SplitShadeStateController {
+
     /** Returns true if the device should use the split notification shade. */
+    @Deprecated(
+        message = "This is deprecated, please use ShadeInteractor#isSplitShade instead",
+        replaceWith =
+            ReplaceWith(
+                "shadeInteractor.isSplitShade",
+                "com.android.systemui.shade.domain.interactor.ShadeInteractor",
+            ),
+    )
     fun shouldUseSplitNotificationShade(resources: Resources): Boolean
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateControllerImpl.kt
index 43905c5..5c5b17e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateControllerImpl.kt
@@ -16,10 +16,10 @@
 package com.android.systemui.statusbar.policy
 
 import android.content.res.Resources
-import com.android.systemui.res.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.res.R
 import javax.inject.Inject
 
 /**
@@ -29,10 +29,15 @@
 @SysUISingleton
 class SplitShadeStateControllerImpl @Inject constructor(private val featureFlags: FeatureFlags) :
     SplitShadeStateController {
-    /**
-     * Returns true if the device should use the split notification shade. Based on orientation,
-     * screen width, and flags.
-     */
+
+    @Deprecated(
+        message = "This is deprecated, please use ShadeInteractor#isSplitShade instead",
+        replaceWith =
+            ReplaceWith(
+                "shadeInteractor.isSplitShade",
+                "com.android.systemui.shade.domain.interactor.ShadeInteractor",
+            ),
+    )
     override fun shouldUseSplitNotificationShade(resources: Resources): Boolean {
         return (resources.getBoolean(R.bool.config_use_split_notification_shade) ||
             (featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE) &&
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 44c684c..b5efc44 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -379,7 +379,9 @@
                             + " was received. Deferring... Managed profile? " + isManagedProfile);
                     return;
                 }
-                if (android.os.Flags.allowPrivateProfile() && isPrivateProfile(newUserHandle)) {
+                if (android.os.Flags.allowPrivateProfile()
+                        && android.multiuser.Flags.enablePrivateSpaceFeatures()
+                        && isPrivateProfile(newUserHandle)) {
                     mDeferredThemeEvaluation = true;
                     Log.i(TAG, "Deferring theme for private profile till user setup is complete");
                     return;
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
index 5c53ff9..ac1d280 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.unfold
 
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
 import android.animation.ValueAnimator
 import android.annotation.BinderThread
 import android.content.Context
@@ -23,7 +25,6 @@
 import android.os.SystemProperties
 import android.util.Log
 import android.view.animation.DecelerateInterpolator
-import androidx.core.animation.addListener
 import com.android.internal.foldables.FoldLockSettingAvailabilityProvider
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.display.data.repository.DeviceStateRepository
@@ -36,17 +37,25 @@
 import com.android.systemui.unfold.dagger.UnfoldBg
 import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
 import javax.inject.Inject
+import kotlin.coroutines.resume
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.TimeoutCancellationException
 import kotlinx.coroutines.android.asCoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onCompletion
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withTimeout
 
+@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
 class FoldLightRevealOverlayAnimation
 @Inject
 constructor(
@@ -61,6 +70,9 @@
 
     private val revealProgressValueAnimator: ValueAnimator =
         ValueAnimator.ofFloat(ALPHA_OPAQUE, ALPHA_TRANSPARENT)
+    private val areAnimationEnabled: Flow<Boolean>
+        get() = animationStatusRepository.areAnimationsEnabled()
+
     private lateinit var controller: FullscreenLightRevealAnimationController
     @Volatile private var readyCallback: CompletableDeferred<Runnable>? = null
 
@@ -89,33 +101,31 @@
 
         applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
             deviceStateRepository.state
-                .map { it != DeviceStateRepository.DeviceState.FOLDED }
+                .map { it == DeviceStateRepository.DeviceState.FOLDED }
                 .distinctUntilChanged()
-                .filter { isUnfolded -> isUnfolded }
-                .collect { controller.ensureOverlayRemoved() }
-        }
-
-        applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
-            deviceStateRepository.state
-                .filter {
-                    animationStatusRepository.areAnimationsEnabled().first() &&
-                        it == DeviceStateRepository.DeviceState.FOLDED
-                }
-                .collect {
-                    try {
-                        withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) {
-                            readyCallback = CompletableDeferred()
-                            val onReady = readyCallback?.await()
-                            readyCallback = null
-                            controller.addOverlay(ALPHA_OPAQUE, onReady)
-                            waitForScreenTurnedOn()
+                .flatMapLatest { isFolded ->
+                    flow<Nothing> {
+                            if (!areAnimationEnabled.first() || !isFolded) {
+                                return@flow
+                            }
+                            withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) {
+                                readyCallback = CompletableDeferred()
+                                val onReady = readyCallback?.await()
+                                readyCallback = null
+                                controller.addOverlay(ALPHA_OPAQUE, onReady)
+                                waitForScreenTurnedOn()
+                            }
                             playFoldLightRevealOverlayAnimation()
                         }
-                    } catch (e: TimeoutCancellationException) {
-                        Log.e(TAG, "Fold light reveal animation timed out")
-                        ensureOverlayRemovedInternal()
-                    }
+                        .catchTimeoutAndLog()
+                        .onCompletion {
+                            controller.ensureOverlayRemoved()
+                            val onReady = readyCallback?.takeIf { it.isCompleted }?.getCompleted()
+                            onReady?.run()
+                            readyCallback = null
+                        }
                 }
+                .collect {}
         }
     }
 
@@ -128,19 +138,34 @@
         powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
     }
 
-    private fun ensureOverlayRemovedInternal() {
-        revealProgressValueAnimator.cancel()
-        controller.ensureOverlayRemoved()
-    }
-
-    private fun playFoldLightRevealOverlayAnimation() {
+    private suspend fun playFoldLightRevealOverlayAnimation() {
         revealProgressValueAnimator.duration = ANIMATION_DURATION
         revealProgressValueAnimator.interpolator = DecelerateInterpolator()
         revealProgressValueAnimator.addUpdateListener { animation ->
             controller.updateRevealAmount(animation.animatedFraction)
         }
-        revealProgressValueAnimator.addListener(onEnd = { controller.ensureOverlayRemoved() })
-        revealProgressValueAnimator.start()
+        revealProgressValueAnimator.startAndAwaitCompletion()
+    }
+
+    private suspend fun ValueAnimator.startAndAwaitCompletion(): Unit =
+        suspendCancellableCoroutine { continuation ->
+            val listener =
+                object : AnimatorListenerAdapter() {
+                    override fun onAnimationEnd(animation: Animator) {
+                        continuation.resume(Unit)
+                        removeListener(this)
+                    }
+                }
+            addListener(listener)
+            continuation.invokeOnCancellation { removeListener(listener) }
+            start()
+        }
+
+    private fun <T> Flow<T>.catchTimeoutAndLog() = catch { exception ->
+        when (exception) {
+            is TimeoutCancellationException -> Log.e(TAG, "Fold light reveal animation timed out")
+            else -> throw exception
+        }
     }
 
     private companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
index a925e38..f755feb 100644
--- a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
@@ -27,6 +27,7 @@
 import android.util.SparseBooleanArray;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -72,7 +73,7 @@
 
     @Inject
     public CarrierConfigTracker(
-            CarrierConfigManager carrierConfigManager,
+            @Nullable CarrierConfigManager carrierConfigManager,
             BroadcastDispatcher broadcastDispatcher) {
         mCarrierConfigManager = carrierConfigManager;
         IntentFilter filter = new IntentFilter();
@@ -95,6 +96,9 @@
         final int subId = intent.getIntExtra(
                 CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        if (mCarrierConfigManager == null) {
+            return;
+        }
         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt
new file mode 100644
index 0000000..80ccd64
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.statusbar.policy.BatteryController
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.onStart
+
+fun BatteryController.isDevicePluggedIn(): Flow<Boolean> {
+    return conflatedCallbackFlow {
+            val batteryCallback =
+                object : BatteryController.BatteryStateChangeCallback {
+                    override fun onBatteryLevelChanged(
+                        level: Int,
+                        pluggedIn: Boolean,
+                        charging: Boolean
+                    ) {
+                        trySend(pluggedIn)
+                    }
+                }
+            addCallback(batteryCallback)
+            awaitClose { removeCallback(batteryCallback) }
+        }
+        .onStart { emit(isPluggedIn) }
+}
+
+fun BatteryController.isBatteryPowerSaveEnabled(): Flow<Boolean> {
+    return conflatedCallbackFlow {
+            val batteryCallback =
+                object : BatteryController.BatteryStateChangeCallback {
+                    override fun onPowerSaveChanged(isPowerSave: Boolean) {
+                        trySend(isPowerSave)
+                    }
+                }
+            addCallback(batteryCallback)
+            awaitClose { removeCallback(batteryCallback) }
+        }
+        .onStart { emit(isPowerSave) }
+}
+
+fun BatteryController.getBatteryLevel(): Flow<Int> {
+    return conflatedCallbackFlow {
+            val batteryCallback =
+                object : BatteryController.BatteryStateChangeCallback {
+                    override fun onBatteryLevelChanged(
+                        level: Int,
+                        pluggedIn: Boolean,
+                        charging: Boolean
+                    ) {
+                        trySend(level)
+                    }
+                }
+            addCallback(batteryCallback)
+            awaitClose { removeCallback(batteryCallback) }
+        }
+        .onStart { emit(0) }
+}
+
+fun BatteryController.isExtremePowerSaverEnabled(): Flow<Boolean> {
+    return conflatedCallbackFlow {
+            val batteryCallback =
+                object : BatteryController.BatteryStateChangeCallback {
+                    override fun onExtremeBatterySaverChanged(isExtreme: Boolean) {
+                        trySend(isExtreme)
+                    }
+                }
+            addCallback(batteryCallback)
+            awaitClose { removeCallback(batteryCallback) }
+        }
+        .onStart { emit(isExtremeSaverOn) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/RotationLockControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/RotationLockControllerExt.kt
new file mode 100644
index 0000000..22cc8dd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/RotationLockControllerExt.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.statusbar.policy.RotationLockController
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.onStart
+
+fun RotationLockController.isRotationLockEnabled(): Flow<Boolean> {
+    return conflatedCallbackFlow {
+            val rotationLockCallback =
+                RotationLockController.RotationLockControllerCallback { rotationLocked, _ ->
+                    trySend(rotationLocked)
+                }
+            addCallback(rotationLockCallback)
+            awaitClose { removeCallback(rotationLockCallback) }
+        }
+        .onStart { emit(isRotationLocked) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java
index 0d6c0f5..10cf082 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java
@@ -25,8 +25,11 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 
+import com.android.app.tracing.TraceUtils;
 import com.android.systemui.settings.UserTracker;
 
+import kotlin.Unit;
+
 /**
  * Used to interact with per-user Settings.Secure and Settings.System settings (but not
  * Settings.Global, since those do not vary per-user)
@@ -123,8 +126,16 @@
     default void registerContentObserverForUser(
             Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver,
             int userHandle) {
-        getContentResolver().registerContentObserver(
-                uri, notifyForDescendants, settingsObserver, getRealUserHandle(userHandle));
+        TraceUtils.trace(
+                () -> {
+                    // The limit for trace tags length is 127 chars, which leaves us 90 for Uri.
+                    return "USP#registerObserver#[" + uri.toString() + "]";
+                }, () -> {
+                    getContentResolver().registerContentObserver(
+                            uri, notifyForDescendants, settingsObserver,
+                            getRealUserHandle(userHandle));
+                    return Unit.INSTANCE;
+                });
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/ClientTrackingWakeLock.kt b/packages/SystemUI/src/com/android/systemui/util/wakelock/ClientTrackingWakeLock.kt
new file mode 100644
index 0000000..2157fafa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/ClientTrackingWakeLock.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.wakelock
+
+import android.os.PowerManager
+import android.util.Log
+import com.android.systemui.util.wakelock.WakeLock.Builder.NO_TIMEOUT
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.atomic.AtomicInteger
+
+/**
+ * [PowerManager.WakeLock] wrapper that tracks acquire/release reasons and logs them if owning
+ * logger is enabled.
+ */
+class ClientTrackingWakeLock(
+    private val pmWakeLock: PowerManager.WakeLock,
+    private val logger: WakeLockLogger?,
+    private val maxTimeout: Long
+) : WakeLock {
+
+    private val activeClients = ConcurrentHashMap<String, AtomicInteger>()
+
+    override fun acquire(why: String) {
+        val count = activeClients.computeIfAbsent(why) { _ -> AtomicInteger(0) }.incrementAndGet()
+        logger?.logAcquire(pmWakeLock, why, count)
+        if (maxTimeout == NO_TIMEOUT) {
+            pmWakeLock.acquire()
+        } else {
+            pmWakeLock.acquire(maxTimeout)
+        }
+    }
+
+    override fun release(why: String) {
+        val count = activeClients[why]?.decrementAndGet() ?: -1
+        if (count < 0) {
+            Log.wtf(WakeLock.TAG, "Releasing WakeLock with invalid reason: $why")
+            // Restore count just in case.
+            activeClients[why]?.incrementAndGet()
+            return
+        }
+
+        logger?.logRelease(pmWakeLock, why, count)
+        pmWakeLock.release()
+    }
+
+    override fun wrap(r: Runnable): Runnable = WakeLock.wrapImpl(this, r)
+
+    fun activeClients(): Int =
+        activeClients.reduceValuesToInt(Long.MAX_VALUE, AtomicInteger::get, 0, Integer::sum)
+
+    override fun toString(): String {
+        return "active clients=${activeClients()}"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java
index 039109e..d2ed71c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java
@@ -19,8 +19,11 @@
 import android.content.Context;
 import android.os.Handler;
 
+import com.android.systemui.Flags;
 import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
 
+import dagger.Lazy;
 import dagger.assisted.Assisted;
 import dagger.assisted.AssistedFactory;
 import dagger.assisted.AssistedInject;
@@ -37,10 +40,13 @@
     private final WakeLock mInner;
 
     @AssistedInject
-    public DelayedWakeLock(@Background Handler handler, Context context, WakeLockLogger logger,
+    public DelayedWakeLock(@Background Lazy<Handler> bgHandler,
+                           @Main Lazy<Handler> mainHandler,
+                           Context context, WakeLockLogger logger,
             @Assisted String tag) {
         mInner = WakeLock.createPartial(context, logger, tag);
-        mHandler = handler;
+        mHandler = Flags.delayedWakelockReleaseOnBackgroundThread() ? bgHandler.get()
+                : mainHandler.get();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
index 6128fee..f763ee4 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
@@ -22,6 +22,8 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.systemui.Flags;
+
 import java.util.HashMap;
 
 import javax.inject.Inject;
@@ -112,6 +114,11 @@
     @VisibleForTesting
     static WakeLock wrap(
             final PowerManager.WakeLock inner, WakeLockLogger logger, long maxTimeout) {
+        if (Flags.delayedWakelockReleaseOnBackgroundThread()) {
+            return new ClientTrackingWakeLock(inner, logger, maxTimeout);
+        }
+
+        // Non-thread safe implementation, remove when flag above is removed.
         return new WakeLock() {
             private final HashMap<String, Integer> mActiveClients = new HashMap<>();
 
@@ -123,7 +130,11 @@
                 if (logger != null) {
                     logger.logAcquire(inner, why, count);
                 }
-                inner.acquire(maxTimeout);
+                if (maxTimeout == Builder.NO_TIMEOUT) {
+                    inner.acquire();
+                } else {
+                    inner.acquire(maxTimeout);
+                }
             }
 
             /** @see PowerManager.WakeLock#release() */
@@ -162,6 +173,7 @@
      * An injectable Builder that wraps {@link #createPartial(Context, String, long)}.
      */
     class Builder {
+        public static final long NO_TIMEOUT = -1;
         private final Context mContext;
         private final WakeLockLogger mLogger;
         private String mTag;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 4045630..c3274477 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -120,7 +120,7 @@
 import com.android.systemui.haptics.slider.HapticSliderViewBinder;
 import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin;
 import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
 import com.android.systemui.plugins.VolumeDialog;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.plugins.VolumeDialogController.State;
@@ -173,6 +173,9 @@
     private static final String TYPE_DISMISS = "dismiss";
     /** Volume dialog slider animation. */
     private static final String TYPE_UPDATE = "update";
+    static final short PROGRESS_HAPTICS_DISABLED = 0;
+    static final short PROGRESS_HAPTICS_EAGER = 1;
+    static final short PROGRESS_HAPTICS_ANIMATED = 2;
 
     /**
      *  TODO(b/290612381): remove lingering animations or tolerate them
@@ -265,7 +268,7 @@
     private final Object mSafetyWarningLock = new Object();
     private final Accessibility mAccessibility = new Accessibility();
     private final ConfigurationController mConfigurationController;
-    private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+    private final MediaOutputDialogManager mMediaOutputDialogManager;
     private final CsdWarningDialog.Factory mCsdWarningDialogFactory;
     private final VolumePanelNavigationInteractor mVolumePanelNavigationInteractor;
     private final VolumeNavigator mVolumeNavigator;
@@ -316,7 +319,7 @@
             AccessibilityManagerWrapper accessibilityManagerWrapper,
             DeviceProvisionedController deviceProvisionedController,
             ConfigurationController configurationController,
-            MediaOutputDialogFactory mediaOutputDialogFactory,
+            MediaOutputDialogManager mediaOutputDialogManager,
             InteractionJankMonitor interactionJankMonitor,
             VolumePanelNavigationInteractor volumePanelNavigationInteractor,
             VolumeNavigator volumeNavigator,
@@ -340,7 +343,7 @@
         mAccessibilityMgr = accessibilityManagerWrapper;
         mDeviceProvisionedController = deviceProvisionedController;
         mConfigurationController = configurationController;
-        mMediaOutputDialogFactory = mediaOutputDialogFactory;
+        mMediaOutputDialogManager = mediaOutputDialogManager;
         mCsdWarningDialogFactory = csdWarningDialogFactory;
         mShowActiveStreamOnly = showActiveStreamOnly();
         mHasSeenODICaptionsTooltip =
@@ -1199,7 +1202,7 @@
             mSettingsIcon.setOnClickListener(v -> {
                 Events.writeEvent(Events.EVENT_SETTINGS_CLICK);
                 dismissH(DISMISS_REASON_SETTINGS_CLICKED);
-                mMediaOutputDialogFactory.dismiss();
+                mMediaOutputDialogManager.dismiss();
                 mVolumeNavigator.openVolumePanel(
                         mVolumePanelNavigationInteractor.getVolumePanelRoute());
             });
@@ -2077,11 +2080,17 @@
                 if (row.anim == null) {
                     row.anim = ObjectAnimator.ofInt(row.slider, "progress", progress, newProgress);
                     row.anim.setInterpolator(new DecelerateInterpolator());
-                    row.anim.addListener(
-                        getJankListener(row.view, TYPE_UPDATE, UPDATE_ANIMATION_DURATION));
+                    Animator.AnimatorListener listener =
+                            getJankListener(row.view, TYPE_UPDATE, UPDATE_ANIMATION_DURATION);
+                    if (listener != null) {
+                        row.anim.addListener(listener);
+                    }
                 } else {
                     row.anim.cancel();
                     row.anim.setIntValues(progress, newProgress);
+                    // The animator can't keep up with the volume changes so haptics need to be
+                    // triggered here. This happens when the volume keys are continuously pressed.
+                    row.deliverOnProgressChangedHaptics(false, newProgress, PROGRESS_HAPTICS_EAGER);
                 }
                 row.animTargetProgress = newProgress;
                 row.anim.setDuration(UPDATE_ANIMATION_DURATION);
@@ -2096,6 +2105,15 @@
         }
     }
 
+    @VisibleForTesting short progressHapticsForStream(int stream) {
+        for (VolumeRow row: mRows) {
+            if (row.stream == stream) {
+                return row.mProgressHapticsType;
+            }
+        }
+        return PROGRESS_HAPTICS_DISABLED;
+    }
+
     private void recheckH(VolumeRow row) {
         if (row == null) {
             if (D.BUG) Log.d(TAG, "recheckH ALL");
@@ -2483,13 +2501,12 @@
         @Override
         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
             if (mRow.ss == null) return;
-            if (getActiveRow().equals(mRow)
-                    && mRow.slider.getVisibility() == VISIBLE
-                    && mRow.mHapticPlugin != null) {
-                mRow.mHapticPlugin.onProgressChanged(seekBar, progress, fromUser);
-                if (!fromUser) {
-                    // Consider a change from program as the volume key being continuously pressed
-                    mRow.mHapticPlugin.onKeyDown();
+            if (getActiveRow().equals(mRow) && mRow.slider.getVisibility() == VISIBLE) {
+                if (fromUser || mRow.animTargetProgress == progress) {
+                    // Deliver user-generated slider haptics immediately, or when the animation
+                    // completes
+                    mRow.deliverOnProgressChangedHaptics(
+                            fromUser, progress, PROGRESS_HAPTICS_ANIMATED);
                 }
             }
             if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream)
@@ -2571,11 +2588,11 @@
                 /* progressInterpolatorFactor= */ 1f,
                 /* progressBasedDragMinScale= */ 0f,
                 /* progressBasedDragMaxScale= */ 0.2f,
-                /* additionalVelocityMaxBump= */ 0.15f,
+                /* additionalVelocityMaxBump= */ 0.25f,
                 /* deltaMillisForDragInterval= */ 0f,
-                /* deltaProgressForDragThreshold= */ 0.015f,
-                /* numberOfLowTicks= */ 5,
-                /* maxVelocityToScale= */ 300f,
+                /* deltaProgressForDragThreshold= */ 0.05f,
+                /* numberOfLowTicks= */ 4,
+                /* maxVelocityToScale= */ 200,
                 /* velocityAxis= */ MotionEvent.AXIS_Y,
                 /* upperBookendScale= */ 1f,
                 /* lowerBookendScale= */ 0.05f,
@@ -2602,6 +2619,7 @@
         private int animTargetProgress;
         private int lastAudibleLevel = 1;
         private SeekableSliderHapticPlugin mHapticPlugin;
+        private short mProgressHapticsType = PROGRESS_HAPTICS_DISABLED;
 
         void setIcon(int iconRes, Resources.Theme theme) {
             if (icon != null) {
@@ -2642,6 +2660,17 @@
         void removeHaptics() {
             slider.setOnTouchListener(null);
         }
+
+        void deliverOnProgressChangedHaptics(boolean fromUser, int progress, short hapticsType) {
+            if (mHapticPlugin == null) return;
+
+            mHapticPlugin.onProgressChanged(slider, progress, fromUser);
+            if (!fromUser) {
+                // Consider a change from program as the volume key being continuously pressed
+                mHapticPlugin.onKeyDown();
+            }
+            mProgressHapticsType = hapticsType;
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt
index 18a9161..4ba7cbb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt
@@ -29,7 +29,9 @@
 /** Spatializer module. */
 @Module
 interface SpatializerModule {
+
     companion object {
+
         @Provides
         fun provideSpatializer(
             audioManager: AudioManager,
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 64a5644..1f87ec8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -24,7 +24,7 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
 import com.android.systemui.plugins.VolumeDialog;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.statusbar.VibratorHelper;
@@ -100,7 +100,7 @@
             AccessibilityManagerWrapper accessibilityManagerWrapper,
             DeviceProvisionedController deviceProvisionedController,
             ConfigurationController configurationController,
-            MediaOutputDialogFactory mediaOutputDialogFactory,
+            MediaOutputDialogManager mediaOutputDialogManager,
             InteractionJankMonitor interactionJankMonitor,
             VolumePanelNavigationInteractor volumePanelNavigationInteractor,
             VolumeNavigator volumeNavigator,
@@ -116,7 +116,7 @@
                 accessibilityManagerWrapper,
                 deviceProvisionedController,
                 configurationController,
-                mediaOutputDialogFactory,
+                mediaOutputDialogManager,
                 interactionJankMonitor,
                 volumePanelNavigationInteractor,
                 volumeNavigator,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt
index 8ab563a..6c47aec 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt
@@ -23,3 +23,6 @@
     val icon: Icon,
     val label: CharSequence,
 )
+
+fun ToggleButtonViewModel.toButtonViewModel(): ButtonViewModel =
+    ButtonViewModel(icon = icon, label = label)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
index 170b32c..cb16abe 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
@@ -16,14 +16,11 @@
 
 package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
 
-import android.content.Intent
-import android.provider.Settings
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.animation.Expandable
-import com.android.systemui.media.dialog.MediaOutputDialogFactory
-import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.media.dialog.MediaOutputDialogManager
 import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import javax.inject.Inject
@@ -33,29 +30,22 @@
 class MediaOutputActionsInteractor
 @Inject
 constructor(
-    private val mediaOutputDialogFactory: MediaOutputDialogFactory,
-    private val activityStarter: ActivityStarter,
+    private val mediaOutputDialogManager: MediaOutputDialogManager,
 ) {
 
-    fun onDeviceClick(expandable: Expandable) {
-        activityStarter.startActivity(
-            Intent(Settings.ACTION_BLUETOOTH_SETTINGS),
-            true,
-            expandable.activityTransitionController(),
-        )
-    }
-
     fun onBarClick(session: MediaDeviceSession, expandable: Expandable) {
         when (session) {
             is MediaDeviceSession.Active -> {
-                mediaOutputDialogFactory.createWithController(
+                mediaOutputDialogManager.createAndShowWithController(
                     session.packageName,
                     false,
                     expandable.dialogController()
                 )
             }
             is MediaDeviceSession.Inactive -> {
-                mediaOutputDialogFactory.createDialogForSystemRouting(expandable.dialogController())
+                mediaOutputDialogManager.createAndShowForSystemRouting(
+                    expandable.dialogController()
+                )
             }
             else -> {
                 /* do nothing */
@@ -68,7 +58,7 @@
             cuj =
                 DialogCuj(
                     InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
-                    MediaOutputDialogFactory.INTERACTION_JANK_TAG
+                    MediaOutputDialogManager.INTERACTION_JANK_TAG
                 )
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
index 71df8e5..1bceee9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
@@ -36,3 +36,7 @@
     /** Current media state is unknown yet. */
     data object Unknown : MediaDeviceSession
 }
+
+/** Returns true when the audio is playing for the [MediaDeviceSession]. */
+fun MediaDeviceSession.isPlaying(): Boolean =
+    this is MediaDeviceSession.Active && playbackState?.isActive == true
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt
index e518ed0..37bf661 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt
@@ -26,13 +26,13 @@
     val iconColor: Color
     val backgroundColor: Color
 
-    class IsPlaying(
+    data class IsPlaying(
         override val icon: Icon,
         override val iconColor: Color,
         override val backgroundColor: Color,
     ) : DeviceIconViewModel
 
-    class IsNotPlaying(
+    data class IsNotPlaying(
         override val icon: Icon,
         override val iconColor: Color,
         override val backgroundColor: Color,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index 85d6c9e..d49cb1e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
 import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.isPlaying
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
 import javax.inject.Inject
@@ -110,14 +111,6 @@
                 null,
             )
 
-    private fun MediaDeviceSession.isPlaying(): Boolean =
-        this is MediaDeviceSession.Active && playbackState?.isActive == true
-
-    fun onDeviceClick(expandable: Expandable) {
-        actionsInteractor.onDeviceClick(expandable)
-        volumePanelViewModel.dismissPanel()
-    }
-
     fun onBarClick(expandable: Expandable) {
         actionsInteractor.onBarClick(mediaDeviceSession.value, expandable)
         volumePanelViewModel.dismissPanel()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
index 9d801fc..9ef07fa 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
@@ -24,5 +24,6 @@
     const val BOTTOM_BAR: VolumePanelComponentKey = "bottom_bar"
     const val VOLUME_SLIDERS: VolumePanelComponentKey = "volume_sliders"
     const val CAPTIONING: VolumePanelComponentKey = "captioning"
+    const val SPATIAL_AUDIO: VolumePanelComponentKey = "spatial_audio"
     const val ANC: VolumePanelComponentKey = "anc"
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt
new file mode 100644
index 0000000..71bce5e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.spatial.domain
+
+import com.android.systemui.volume.panel.component.spatial.domain.interactor.SpatialAudioComponentInteractor
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+@VolumePanelScope
+class SpatialAudioAvailabilityCriteria
+@Inject
+constructor(private val interactor: SpatialAudioComponentInteractor) :
+    ComponentAvailabilityCriteria {
+
+    override fun isAvailable(): Flow<Boolean> =
+        interactor.isAvailable.map { it is SpatialAudioAvailabilityModel.SpatialAudio }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
new file mode 100644
index 0000000..6032bfe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.spatial.domain.interactor
+
+import android.media.AudioDeviceAttributes
+import android.media.AudioDeviceInfo
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.media.BluetoothMediaDevice
+import com.android.settingslib.media.domain.interactor.SpatializerInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Provides an ability to access and update spatial audio and head tracking state.
+ *
+ * Head tracking is a sub-feature of spatial audio. This means that it requires spatial audio to be
+ * available for it to be available. And spatial audio to be enabled for it to be enabled.
+ */
+@VolumePanelScope
+class SpatialAudioComponentInteractor
+@Inject
+constructor(
+    mediaOutputInteractor: MediaOutputInteractor,
+    private val spatializerInteractor: SpatializerInteractor,
+    @VolumePanelScope private val coroutineScope: CoroutineScope,
+) {
+
+    private val changes = MutableSharedFlow<Unit>()
+    private val currentAudioDeviceAttributes: StateFlow<AudioDeviceAttributes?> =
+        mediaOutputInteractor.currentConnectedDevice
+            .map { mediaDevice ->
+                mediaDevice ?: return@map null
+                val btDevice: CachedBluetoothDevice =
+                    (mediaDevice as? BluetoothMediaDevice)?.cachedDevice ?: return@map null
+                btDevice.getAudioDeviceAttributes()
+            }
+            .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
+
+    /**
+     * Returns spatial audio availability model. It can be:
+     * - unavailable
+     * - only spatial audio is available
+     * - spatial audio and head tracking are available
+     */
+    val isAvailable: StateFlow<SpatialAudioAvailabilityModel> =
+        combine(
+                currentAudioDeviceAttributes,
+                changes.onStart { emit(Unit) },
+            ) { attributes, _,
+                ->
+                attributes ?: return@combine SpatialAudioAvailabilityModel.Unavailable
+                if (spatializerInteractor.isHeadTrackingAvailable(attributes)) {
+                    return@combine SpatialAudioAvailabilityModel.HeadTracking
+                }
+                if (spatializerInteractor.isSpatialAudioAvailable(attributes)) {
+                    return@combine SpatialAudioAvailabilityModel.SpatialAudio
+                }
+                SpatialAudioAvailabilityModel.Unavailable
+            }
+            .stateIn(
+                coroutineScope,
+                SharingStarted.Eagerly,
+                SpatialAudioAvailabilityModel.Unavailable,
+            )
+
+    /**
+     * Returns spatial audio enabled/disabled model. It can be
+     * - disabled
+     * - only spatial audio is enabled
+     * - spatial audio and head tracking are enabled
+     */
+    val isEnabled: StateFlow<SpatialAudioEnabledModel> =
+        combine(
+                changes.onStart { emit(Unit) },
+                currentAudioDeviceAttributes,
+                isAvailable,
+            ) { _, attributes, isAvailable ->
+                if (isAvailable is SpatialAudioAvailabilityModel.Unavailable) {
+                    return@combine SpatialAudioEnabledModel.Disabled
+                }
+                attributes ?: return@combine SpatialAudioEnabledModel.Disabled
+                if (spatializerInteractor.isHeadTrackingEnabled(attributes)) {
+                    return@combine SpatialAudioEnabledModel.HeadTrackingEnabled
+                }
+                if (spatializerInteractor.isSpatialAudioEnabled(attributes)) {
+                    return@combine SpatialAudioEnabledModel.SpatialAudioEnabled
+                }
+                SpatialAudioEnabledModel.Disabled
+            }
+            .stateIn(
+                coroutineScope,
+                SharingStarted.Eagerly,
+                SpatialAudioEnabledModel.Disabled,
+            )
+
+    /**
+     * Sets current [isEnabled] to a specific [SpatialAudioEnabledModel]. It
+     * - disables both spatial audio and head tracking
+     * - enables only spatial audio
+     * - enables both spatial audio and head tracking
+     */
+    suspend fun setEnabled(model: SpatialAudioEnabledModel) {
+        val attributes = currentAudioDeviceAttributes.value ?: return
+        spatializerInteractor.setSpatialAudioEnabled(
+            attributes,
+            model is SpatialAudioEnabledModel.SpatialAudioEnabled,
+        )
+        spatializerInteractor.setHeadTrackingEnabled(
+            attributes,
+            model is SpatialAudioEnabledModel.HeadTrackingEnabled,
+        )
+        changes.emit(Unit)
+    }
+
+    private suspend fun CachedBluetoothDevice.getAudioDeviceAttributes(): AudioDeviceAttributes? {
+        return listOf(
+                AudioDeviceAttributes(
+                    AudioDeviceAttributes.ROLE_OUTPUT,
+                    AudioDeviceInfo.TYPE_BLE_HEADSET,
+                    address
+                ),
+                AudioDeviceAttributes(
+                    AudioDeviceAttributes.ROLE_OUTPUT,
+                    AudioDeviceInfo.TYPE_BLE_SPEAKER,
+                    address
+                ),
+                AudioDeviceAttributes(
+                    AudioDeviceAttributes.ROLE_OUTPUT,
+                    AudioDeviceInfo.TYPE_BLE_BROADCAST,
+                    address
+                ),
+                AudioDeviceAttributes(
+                    AudioDeviceAttributes.ROLE_OUTPUT,
+                    AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+                    address
+                ),
+                AudioDeviceAttributes(
+                    AudioDeviceAttributes.ROLE_OUTPUT,
+                    AudioDeviceInfo.TYPE_HEARING_AID,
+                    address
+                )
+            )
+            .firstOrNull { spatializerInteractor.isSpatialAudioAvailable(it) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioAvailabilityModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioAvailabilityModel.kt
new file mode 100644
index 0000000..cf14546
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioAvailabilityModel.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.spatial.domain.model
+
+/** Models spatial audio and head tracking availability. */
+interface SpatialAudioAvailabilityModel {
+
+    /** Spatial audio is unavailable. */
+    data object Unavailable : SpatialAudioAvailabilityModel
+
+    /** Spatial audio is available. */
+    interface SpatialAudio : SpatialAudioAvailabilityModel {
+        companion object : SpatialAudio
+    }
+
+    /** Head tracking is available. This also means that [SpatialAudio] is available. */
+    data object HeadTracking : SpatialAudio
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
new file mode 100644
index 0000000..9735e5c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.spatial.domain.model
+
+/** Models spatial audio and head tracking enabled/disabled state. */
+interface SpatialAudioEnabledModel {
+
+    companion object {
+        /** All possible SpatialAudioEnabledModel implementations. */
+        val values =
+            listOf(
+                Disabled,
+                SpatialAudioEnabled,
+                HeadTrackingEnabled,
+            )
+    }
+
+    /** Spatial audio is disabled. */
+    data object Disabled : SpatialAudioEnabledModel
+
+    /** Spatial audio is enabled. */
+    interface SpatialAudioEnabled : SpatialAudioEnabledModel {
+        companion object : SpatialAudioEnabled
+    }
+
+    /** Head tracking is enabled. This also means that [SpatialAudioEnabled]. */
+    data object HeadTrackingEnabled : SpatialAudioEnabled
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt
new file mode 100644
index 0000000..9f9275b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.spatial.ui.viewmodel
+
+import com.android.systemui.common.shared.model.Color
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
+
+data class SpatialAudioButtonViewModel(
+    val model: SpatialAudioEnabledModel,
+    val button: ToggleButtonViewModel,
+    val iconColor: Color,
+    val labelColor: Color,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
new file mode 100644
index 0000000..30715d1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.spatial.ui.viewmodel
+
+import android.content.Context
+import com.android.systemui.common.shared.model.Color
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.toButtonViewModel
+import com.android.systemui.volume.panel.component.spatial.domain.SpatialAudioAvailabilityCriteria
+import com.android.systemui.volume.panel.component.spatial.domain.interactor.SpatialAudioComponentInteractor
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+@VolumePanelScope
+class SpatialAudioViewModel
+@Inject
+constructor(
+    @Application private val context: Context,
+    @VolumePanelScope private val scope: CoroutineScope,
+    availabilityCriteria: SpatialAudioAvailabilityCriteria,
+    private val interactor: SpatialAudioComponentInteractor,
+) {
+
+    val spatialAudioButton: StateFlow<ButtonViewModel?> =
+        interactor.isEnabled
+            .map { it.toViewModel(true).toButtonViewModel() }
+            .stateIn(scope, SharingStarted.Eagerly, null)
+
+    val isAvailable: StateFlow<Boolean> =
+        availabilityCriteria.isAvailable().stateIn(scope, SharingStarted.Eagerly, true)
+
+    val spatialAudioButtonByEnabled: StateFlow<List<SpatialAudioButtonViewModel>> =
+        combine(interactor.isEnabled, interactor.isAvailable) { currentIsEnabled, isAvailable ->
+                SpatialAudioEnabledModel.values
+                    .filter {
+                        if (it is SpatialAudioEnabledModel.HeadTrackingEnabled) {
+                            // Spatial audio control can be visible when there is spatial audio
+                            // setting available but not the head tracking.
+                            isAvailable is SpatialAudioAvailabilityModel.HeadTracking
+                        } else {
+                            true
+                        }
+                    }
+                    .map { isEnabled ->
+                        val isChecked = isEnabled == currentIsEnabled
+                        val buttonViewModel: ToggleButtonViewModel =
+                            isEnabled.toViewModel(isChecked)
+                        SpatialAudioButtonViewModel(
+                            button = buttonViewModel,
+                            model = isEnabled,
+                            iconColor =
+                                Color.Attribute(
+                                    if (isChecked)
+                                        com.android.internal.R.attr.materialColorOnPrimaryContainer
+                                    else com.android.internal.R.attr.materialColorOnSurfaceVariant
+                                ),
+                            labelColor =
+                                Color.Attribute(
+                                    if (isChecked)
+                                        com.android.internal.R.attr.materialColorOnSurface
+                                    else com.android.internal.R.attr.materialColorOutline
+                                ),
+                        )
+                    }
+            }
+            .stateIn(scope, SharingStarted.Eagerly, emptyList())
+
+    fun setEnabled(model: SpatialAudioEnabledModel) {
+        scope.launch { interactor.setEnabled(model) }
+    }
+
+    private fun SpatialAudioEnabledModel.toViewModel(isChecked: Boolean): ToggleButtonViewModel {
+        if (this is SpatialAudioEnabledModel.HeadTrackingEnabled) {
+            return ToggleButtonViewModel(
+                isChecked = isChecked,
+                icon = Icon.Resource(R.drawable.ic_head_tracking, contentDescription = null),
+                label = context.getString(R.string.volume_panel_spatial_audio_tracking)
+            )
+        }
+
+        if (this is SpatialAudioEnabledModel.SpatialAudioEnabled) {
+            return ToggleButtonViewModel(
+                isChecked = isChecked,
+                icon = Icon.Resource(R.drawable.ic_spatial_audio, contentDescription = null),
+                label = context.getString(R.string.volume_panel_spatial_audio_fixed)
+            )
+        }
+
+        if (this is SpatialAudioEnabledModel.Disabled) {
+            return ToggleButtonViewModel(
+                isChecked = isChecked,
+                icon = Icon.Resource(R.drawable.ic_spatial_audio_off, contentDescription = null),
+                label = context.getString(R.string.volume_panel_spatial_audio_off)
+            )
+        }
+
+        error("Unsupported model: $this")
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index faf7434..532e517 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -21,6 +21,7 @@
 import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
 import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.res.R
 import com.android.systemui.volume.panel.component.volume.domain.interactor.VolumeSliderInteractor
@@ -54,14 +55,6 @@
             AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_ringer,
             AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_alarm,
         )
-    private val mutedIconsByStream =
-        mapOf(
-            AudioStream(AudioManager.STREAM_MUSIC) to R.drawable.ic_volume_off,
-            AudioStream(AudioManager.STREAM_VOICE_CALL) to R.drawable.ic_volume_off,
-            AudioStream(AudioManager.STREAM_RING) to R.drawable.ic_volume_off,
-            AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_off,
-            AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_off,
-        )
     private val labelsByStream =
         mapOf(
             AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_music,
@@ -74,6 +67,8 @@
         mapOf(
             AudioStream(AudioManager.STREAM_NOTIFICATION) to
                 R.string.stream_notification_unavailable,
+            AudioStream(AudioManager.STREAM_ALARM) to R.string.stream_alarm_unavailable,
+            AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_media_unavailable,
         )
 
     private var value = 0f
@@ -81,8 +76,9 @@
         combine(
                 audioVolumeInteractor.getAudioStream(audioStream),
                 audioVolumeInteractor.canChangeVolume(audioStream),
-            ) { model, isEnabled ->
-                model.toState(value, isEnabled)
+                audioVolumeInteractor.ringerMode,
+            ) { model, isEnabled, ringerMode ->
+                model.toState(value, isEnabled, ringerMode)
             }
             .stateIn(coroutineScope, SharingStarted.Eagerly, EmptyState)
 
@@ -100,7 +96,11 @@
         }
     }
 
-    private fun AudioStreamModel.toState(value: Float, isEnabled: Boolean): State {
+    private fun AudioStreamModel.toState(
+        value: Float,
+        isEnabled: Boolean,
+        ringerMode: RingerMode,
+    ): State {
         return State(
             value =
                 volumeSliderInteractor.processVolumeToValue(
@@ -110,7 +110,7 @@
                     isMuted,
                 ),
             valueRange = volumeSliderInteractor.displayValueRange,
-            icon = getIcon(this),
+            icon = getIcon(ringerMode),
             label = labelsByStream[audioStream]?.let(context::getString)
                     ?: error("No label for the stream: $audioStream"),
             disabledMessage = disabledTextByStream[audioStream]?.let(context::getString),
@@ -119,14 +119,31 @@
         )
     }
 
-    private fun getIcon(model: AudioStreamModel): Icon {
-        val isMutedOrNoVolume = model.isMuted || model.volume == model.minVolume
+    private fun AudioStreamModel.getIcon(ringerMode: RingerMode): Icon {
+        val isMutedOrNoVolume = isMuted || volume == minVolume
         val iconRes =
             if (isMutedOrNoVolume) {
-                mutedIconsByStream
+                when (audioStream.value) {
+                    AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_off
+                    AudioManager.STREAM_VOICE_CALL -> R.drawable.ic_volume_off
+                    AudioManager.STREAM_RING ->
+                        if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
+                            R.drawable.ic_volume_ringer_vibrate
+                        } else {
+                            R.drawable.ic_volume_off
+                        }
+                    AudioManager.STREAM_NOTIFICATION ->
+                        if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
+                            R.drawable.ic_volume_ringer_vibrate
+                        } else {
+                            R.drawable.ic_volume_off
+                        }
+                    AudioManager.STREAM_ALARM -> R.drawable.ic_volume_off
+                    else -> null
+                }
             } else {
-                iconsByStream
-            }[audioStream]
+                iconsByStream[audioStream]
+            }
                 ?: error("No icon for the stream: $audioStream")
         return Icon.Resource(iconRes, null)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
index 2824323..aaee24b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
@@ -18,6 +18,8 @@
 
 import android.media.AudioManager
 import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.isPlaying
 import com.android.systemui.volume.panel.component.volume.domain.interactor.CastVolumeInteractor
 import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.AudioStreamSliderViewModel
 import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.CastVolumeSliderViewModel
@@ -28,12 +30,17 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.flow.transformLatest
+import kotlinx.coroutines.launch
 
 /**
  * Controls the behaviour of the whole audio
@@ -46,6 +53,7 @@
 constructor(
     @VolumePanelScope private val scope: CoroutineScope,
     castVolumeInteractor: CastVolumeInteractor,
+    mediaOutputInteractor: MediaOutputInteractor,
     private val streamSliderViewModelFactory: AudioStreamSliderViewModel.Factory,
     private val castVolumeSliderViewModelFactory: CastVolumeSliderViewModel.Factory,
 ) {
@@ -90,4 +98,17 @@
                 remoteSessionsViewModels + streamViewModels
             }
             .stateIn(scope, SharingStarted.Eagerly, emptyList())
+
+    private val mutableIsExpanded = MutableSharedFlow<Boolean>()
+
+    val isExpanded: StateFlow<Boolean> =
+        merge(
+                mutableIsExpanded.onStart { emit(false) },
+                mediaOutputInteractor.mediaDeviceSession.map { !it.isPlaying() },
+            )
+            .stateIn(scope, SharingStarted.Eagerly, false)
+
+    fun onExpandedChanged(isExpanded: Boolean) {
+        scope.launch { mutableIsExpanded.emit(isExpanded) }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
index f31ee86..d868c33 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.volume.panel.component.bottombar.BottomBarModule
 import com.android.systemui.volume.panel.component.captioning.CaptioningModule
 import com.android.systemui.volume.panel.component.mediaoutput.MediaOutputModule
+import com.android.systemui.volume.panel.component.spatialaudio.SpatialAudioModule
 import com.android.systemui.volume.panel.component.volume.VolumeSlidersModule
 import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
@@ -49,6 +50,7 @@
             // Components modules
             BottomBarModule::class,
             AncModule::class,
+            SpatialAudioModule::class,
             VolumeSlidersModule::class,
             CaptioningModule::class,
             MediaOutputModule::class,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
index 57ea997..999f4c1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
@@ -51,6 +51,7 @@
         fun provideEnabledComponents(): Collection<VolumePanelComponentKey> {
             return setOf(
                 VolumePanelComponents.ANC,
+                VolumePanelComponents.SPATIAL_AUDIO,
                 VolumePanelComponents.CAPTIONING,
                 VolumePanelComponents.VOLUME_SLIDERS,
                 VolumePanelComponents.MEDIA_OUTPUT,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt
index d90a9c7..485f4b5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt
@@ -17,23 +17,18 @@
 package com.android.systemui.volume.panel.shared.flag
 
 import com.android.systemui.Flags
-import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.flags.RefactorFlagUtils
 import javax.inject.Inject
 
 /** Provides a flag to check for the new Compose based Volume Panel availability. */
 class VolumePanelFlag @Inject constructor() {
 
-    /**
-     * Returns true when the new Volume Panel is available and false the otherwise. The new panel
-     * can only be available when [ComposeFacade.isComposeAvailable] is true.
-     */
+    /** Returns true when the new Volume Panel is available and false the otherwise. */
     fun canUseNewVolumePanel(): Boolean {
-        return ComposeFacade.isComposeAvailable() && Flags.newVolumePanel()
+        return Flags.newVolumePanel()
     }
 
     fun assertNewVolumePanel() {
-        require(ComposeFacade.isComposeAvailable())
         RefactorFlagUtils.assertInNewMode(Flags.newVolumePanel(), Flags.FLAG_NEW_VOLUME_PANEL)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
index ec4da06..8ba06e1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
@@ -49,6 +49,7 @@
         fun provideFooterComponents(): Collection<VolumePanelComponentKey> {
             return listOf(
                 VolumePanelComponents.ANC,
+                VolumePanelComponents.SPATIAL_AUDIO,
                 VolumePanelComponents.CAPTIONING,
             )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt
index 53e1b8b..d430e65 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt
@@ -18,11 +18,12 @@
 
 import android.os.Bundle
 import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
 import androidx.activity.enableEdgeToEdge
 import androidx.activity.viewModels
-import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag
+import com.android.systemui.volume.panel.ui.composable.VolumePanelRoot
 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
 import javax.inject.Inject
 import javax.inject.Provider
@@ -44,7 +45,7 @@
 
         volumePanelFlag.assertNewVolumePanel()
 
-        ComposeFacade.setVolumePanelActivityContent(this, viewModel) { finish() }
+        setContent { VolumePanelRoot(viewModel = viewModel, onDismiss = ::finish) }
     }
 
     override fun onContentChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
index f498520..1a39934 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
@@ -20,6 +20,7 @@
 import static com.android.systemui.wallet.ui.WalletCardCarousel.CARD_ANIM_ALPHA_DURATION;
 
 import android.annotation.Nullable;
+import android.app.ActivityOptions;
 import android.app.BroadcastOptions;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -40,8 +41,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.Utils;
-import com.android.systemui.res.R;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.res.R;
 
 import java.util.List;
 
@@ -308,7 +309,7 @@
                             BroadcastOptions options = BroadcastOptions.makeBasic();
                             options.setInteractive(true);
                             options.setPendingIntentBackgroundActivityStartMode(
-                                    BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+                                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
                             walletCard.getPendingIntent().send(options.toBundle());
                         } catch (PendingIntent.CanceledException e) {
                             Log.w(TAG, "Error sending pending intent for wallet card.");
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 65dede8..cb61534 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -28,6 +28,7 @@
 import static com.android.server.notification.Flags.screenshareNotificationHiding;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
 
 import android.app.INotificationManager;
 import android.app.Notification;
@@ -50,6 +51,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.flags.FeatureFlags;
@@ -97,6 +99,7 @@
     private final Context mContext;
     private final Bubbles mBubbles;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
+    private final KeyguardStateController mKeyguardStateController;
     private final ShadeController mShadeController;
     private final IStatusBarService mBarService;
     private final INotificationManager mNotificationManager;
@@ -109,6 +112,7 @@
     private final NotifPipeline mNotifPipeline;
     private final NotifPipelineFlags mNotifPipelineFlags;
     private final Executor mSysuiMainExecutor;
+    private final Executor mSysuiUiBgExecutor;
 
     private final Bubbles.SysuiProxy mSysuiProxy;
     // TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline
@@ -116,6 +120,8 @@
     private final StatusBarWindowCallback mStatusBarWindowCallback;
     private final Runnable mSensitiveStateChangedListener;
     private boolean mPanelExpanded;
+    private boolean mKeyguardShowing;
+    private boolean mDreamingOrInPreview;
 
     /**
      * Creates {@link BubblesManager}, returns {@code null} if Optional {@link Bubbles} not present
@@ -140,7 +146,8 @@
             SysUiState sysUiState,
             FeatureFlags featureFlags,
             NotifPipelineFlags notifPipelineFlags,
-            Executor sysuiMainExecutor) {
+            Executor sysuiMainExecutor,
+            Executor sysuiUiBgExecutor) {
         if (bubblesOptional.isPresent()) {
             return new BubblesManager(context,
                     bubblesOptional.get(),
@@ -160,7 +167,8 @@
                     sysUiState,
                     featureFlags,
                     notifPipelineFlags,
-                    sysuiMainExecutor);
+                    sysuiMainExecutor,
+                    sysuiUiBgExecutor);
         } else {
             return null;
         }
@@ -185,10 +193,12 @@
             SysUiState sysUiState,
             FeatureFlags featureFlags,
             NotifPipelineFlags notifPipelineFlags,
-            Executor sysuiMainExecutor) {
+            Executor sysuiMainExecutor,
+            Executor sysuiUiBgExecutor) {
         mContext = context;
         mBubbles = bubbles;
         mNotificationShadeWindowController = notificationShadeWindowController;
+        mKeyguardStateController = keyguardStateController;
         mShadeController = shadeController;
         mNotificationManager = notificationManager;
         mDreamManager = dreamManager;
@@ -200,6 +210,7 @@
         mNotifPipeline = notifPipeline;
         mNotifPipelineFlags = notifPipelineFlags;
         mSysuiMainExecutor = sysuiMainExecutor;
+        mSysuiUiBgExecutor = sysuiUiBgExecutor;
 
         mBarService = statusBarService == null
                 ? IStatusBarService.Stub.asInterface(
@@ -208,12 +219,11 @@
 
         setupNotifPipeline();
 
-        keyguardStateController.addCallback(new KeyguardStateController.Callback() {
+        // TODO(b/327410864): use KeyguardTransitionInteractor to listen for keyguard changes
+        mKeyguardStateController.addCallback(new KeyguardStateController.Callback() {
             @Override
             public void onKeyguardShowingChanged() {
-                boolean isUnlockedShade = !keyguardStateController.isShowing()
-                        && !isDreamingOrInPreview();
-                bubbles.onStatusBarStateChanged(isUnlockedShade);
+                updateKeyguardAndDreamingState();
             }
         });
 
@@ -256,6 +266,14 @@
                         mPanelExpanded = panelExpanded;
                         mBubbles.onNotificationPanelExpandedChanged(panelExpanded);
                     }
+                    if (!mKeyguardShowing && mDreamingOrInPreview && !isDreaming) {
+                        // We check for dreaming state changes when keyguard status changes.
+                        // This causes us to miss events if dreaming state changes after keyguard.
+                        // Add a check here for the case where keyguard is dismissed before
+                        // dreaming state changes. Otherwise bubbles remain invisible.
+                        // TODO(b/327410864): use KeyguardTransitionInteractor for dreaming changes
+                        updateKeyguardAndDreamingState();
+                    }
                 };
         notificationShadeWindowController.registerCallback(mStatusBarWindowCallback);
 
@@ -395,6 +413,19 @@
         mBubbles.setSysuiProxy(mSysuiProxy);
     }
 
+    private void updateKeyguardAndDreamingState() {
+        mSysuiUiBgExecutor.execute(() -> {
+            mKeyguardShowing = mKeyguardStateController.isShowing();
+            mDreamingOrInPreview = isDreamingOrInPreview();
+            boolean isUnlockedShade = !mKeyguardShowing && !mDreamingOrInPreview;
+            ProtoLog.d(WM_SHELL_BUBBLES,
+                    "handleKeyguardOrDreamChange isUnlockedShade=%b keyguardShowing=%b "
+                            + "dreamingOrInPreview=%b",
+                    isUnlockedShade, mKeyguardShowing, mDreamingOrInPreview);
+            mBubbles.onStatusBarStateChanged(isUnlockedShade);
+        });
+    }
+
     private boolean isDreamingOrInPreview() {
         try {
             return mDreamManager.isDreamingOrInPreview();
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 15e0965..7674fe9 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -256,7 +256,7 @@
         });
         mCommandQueue.addCallback(new CommandQueue.Callbacks() {
             @Override
-            public void goToFullscreenFromSplit() {
+            public void moveFocusedTaskToFullscreen(int displayId) {
                 splitScreen.goToFullscreenFromSplit();
             }
         });
@@ -361,6 +361,14 @@
             public void enterDesktop(int displayId) {
                 desktopMode.enterDesktop(displayId);
             }
+            @Override
+            public void moveFocusedTaskToFullscreen(int displayId) {
+                desktopMode.moveFocusedTaskToFullscreen(displayId);
+            }
+            @Override
+            public void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) {
+                desktopMode.moveFocusedTaskToStageSplit(displayId, leftOrTop);
+            }
         });
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 3026966..3d0d8fb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -21,8 +21,8 @@
 import android.view.ViewTreeObserver
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
 import com.android.systemui.Flags as AConfigFlags
+import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -319,7 +319,10 @@
     fun listenForDozeAmountTransition_updatesClockDozeAmount() =
         runBlocking(IMMEDIATE) {
             val transitionStep = MutableStateFlow(TransitionStep())
-            whenever(keyguardTransitionInteractor.dozeAmountTransition).thenReturn(transitionStep)
+            whenever(keyguardTransitionInteractor.lockscreenToAodTransition)
+                .thenReturn(transitionStep)
+            whenever(keyguardTransitionInteractor.aodToLockscreenTransition)
+                .thenReturn(transitionStep)
 
             val job = underTest.listenForDozeAmountTransition(this)
             transitionStep.value =
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
index d4522d0..93e7602 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
@@ -19,11 +19,14 @@
 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.anyLong;
 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.hardware.biometrics.BiometricSourceType;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -119,4 +122,51 @@
         when(mKeyguardMessageArea.getText()).thenReturn(msg);
         assertThat(mMessageAreaController.getMessage()).isEqualTo(msg);
     }
+
+    @Test
+    public void testFingerprintMessageUpdate() {
+        String msg = "fpMessage";
+        mMessageAreaController.setMessage(
+                msg, BiometricSourceType.FINGERPRINT
+        );
+        verify(mKeyguardMessageArea).setMessage(msg, /* animate= */ true);
+
+        String msg2 = "fpMessage2";
+        mMessageAreaController.setMessage(
+                msg2, BiometricSourceType.FINGERPRINT
+        );
+        verify(mKeyguardMessageArea).setMessage(msg2, /* animate= */ true);
+    }
+
+    @Test
+    public void testFaceMessageDroppedWhileFingerprintMessageShowing() {
+        String fpMsg = "fpMessage";
+        mMessageAreaController.setMessage(
+                fpMsg, BiometricSourceType.FINGERPRINT
+        );
+        verify(mKeyguardMessageArea).setMessage(eq(fpMsg), /* animate= */ anyBoolean());
+
+        String faceMessage = "faceMessage";
+        mMessageAreaController.setMessage(
+                faceMessage, BiometricSourceType.FACE
+        );
+        verify(mKeyguardMessageArea, never())
+                .setMessage(eq(faceMessage), /* animate= */ anyBoolean());
+    }
+
+    @Test
+    public void testGenericMessageShowsAfterFingerprintMessageShowing() {
+        String fpMsg = "fpMessage";
+        mMessageAreaController.setMessage(
+                fpMsg, BiometricSourceType.FINGERPRINT
+        );
+        verify(mKeyguardMessageArea).setMessage(eq(fpMsg), /* animate= */ anyBoolean());
+
+        String genericMessage = "genericMessage";
+        mMessageAreaController.setMessage(
+                genericMessage, null
+        );
+        verify(mKeyguardMessageArea)
+                .setMessage(eq(genericMessage), /* animate= */ anyBoolean());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index 13fb42c..90587d7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -16,8 +16,6 @@
 
 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;
@@ -32,7 +30,6 @@
 import com.android.systemui.dump.DumpManager;
 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.res.R;
@@ -62,7 +59,6 @@
     @Mock protected KeyguardStatusViewController mControllerMock;
     @Mock protected InteractionJankMonitor mInteractionJankMonitor;
     @Mock protected ViewTreeObserver mViewTreeObserver;
-    @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     @Mock protected DumpManager mDumpManager;
     protected FakeKeyguardRepository mFakeKeyguardRepository;
     protected FakePowerRepository mFakePowerRepository;
@@ -93,7 +89,6 @@
                 mKeyguardLogger,
                 mInteractionJankMonitor,
                 deps.getKeyguardInteractor(),
-                mKeyguardTransitionInteractor,
                 mDumpManager,
                 PowerInteractorFactory.create(
                         mFakePowerRepository
@@ -110,7 +105,6 @@
 
         when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
         when(mKeyguardClockSwitchController.getView()).thenReturn(mKeyguardClockSwitch);
-        when(mKeyguardTransitionInteractor.getGoneToAodTransition()).thenReturn(emptyFlow());
         when(mKeyguardStatusView.findViewById(R.id.keyguard_status_area))
                 .thenReturn(mKeyguardStatusAreaView);
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 538daee..336a97e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -26,6 +26,8 @@
 import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
 import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
 import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
+import static android.telephony.SubscriptionManager.PROFILE_CLASS_DEFAULT;
+import static android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING;
 
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
@@ -187,6 +189,11 @@
             TEST_CARRIER, TEST_CARRIER_2, NAME_SOURCE_CARRIER_ID, 0xFFFFFF, "",
             DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", true, TEST_GROUP_UUID,
             TEST_CARRIER_ID, 0);
+    private static final SubscriptionInfo TEST_SUBSCRIPTION_PROVISIONING = new SubscriptionInfo(
+            1, "", 0,
+            TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_CARRIER_ID, 0xFFFFFF, "",
+            DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, TEST_GROUP_UUID,
+            TEST_CARRIER_ID, PROFILE_CLASS_PROVISIONING);
     private static final int FINGERPRINT_SENSOR_ID = 1;
 
     @Mock
@@ -1204,7 +1211,8 @@
         assertThat(mKeyguardUpdateMonitor.mSimDatas.get(TEST_SUBSCRIPTION.getSubscriptionId()))
                 .isNotNull();
 
-        when(mSubscriptionManager.getCompleteActiveSubscriptionInfoList()).thenReturn(null);
+        when(mSubscriptionManager.getCompleteActiveSubscriptionInfoList())
+                .thenReturn(new ArrayList<>());
         mKeyguardUpdateMonitor.mPhoneStateListener.onActiveDataSubscriptionIdChanged(
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
         mTestableLooper.processAllMessages();
@@ -1216,6 +1224,37 @@
     }
 
     @Test
+    public void testActiveSubscriptionList_filtersProvisioningNetworks() {
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION_PROVISIONING);
+        when(mSubscriptionManager.getCompleteActiveSubscriptionInfoList()).thenReturn(list);
+        mKeyguardUpdateMonitor.mSubscriptionListener.onSubscriptionsChanged();
+
+        assertThat(mKeyguardUpdateMonitor.getSubscriptionInfo(true)).isEmpty();
+
+        SubscriptionInfo.Builder b = new SubscriptionInfo.Builder(TEST_SUBSCRIPTION_PROVISIONING);
+        b.setProfileClass(PROFILE_CLASS_DEFAULT);
+        SubscriptionInfo validInfo = b.build();
+
+        list.clear();
+        list.add(validInfo);
+        mKeyguardUpdateMonitor.mSubscriptionListener.onSubscriptionsChanged();
+
+        assertThat(mKeyguardUpdateMonitor.getSubscriptionInfo(true)).hasSize(1);
+    }
+
+    @Test
+    public void testActiveSubscriptionList_filtersProvisioningNetworks_untilValid() {
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION_PROVISIONING);
+        when(mSubscriptionManager.getCompleteActiveSubscriptionInfoList()).thenReturn(list);
+        mKeyguardUpdateMonitor.mSubscriptionListener.onSubscriptionsChanged();
+
+        assertThat(mKeyguardUpdateMonitor.getSubscriptionInfo(true)).isEmpty();
+
+    }
+
+    @Test
     public void testIsUserUnlocked() {
         // mUserManager will report the user as unlocked on @Before
         assertThat(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
index 24a5e80..2e04007 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
@@ -15,14 +15,11 @@
 package com.android.systemui;
 
 import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
 
 import android.os.Looper;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.statusbar.policy.FlashlightController;
-
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -33,9 +30,9 @@
 
     @Test
     public void testClassDependency() {
-        FlashlightController f = mock(FlashlightController.class);
-        mDependency.injectTestDependency(FlashlightController.class, f);
-        Assert.assertEquals(f, Dependency.get(FlashlightController.class));
+        FakeClass f = new FakeClass();
+        mDependency.injectTestDependency(FakeClass.class, f);
+        Assert.assertEquals(f, Dependency.get(FakeClass.class));
     }
 
     @Test
@@ -53,4 +50,8 @@
         Dependency dependency = initializer.getSysUIComponent().createDependency();
         dependency.start();
     }
+
+    private static class FakeClass {
+
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
index dd428f5..ccdcee5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
@@ -32,7 +32,6 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.animation.AnimatorTestRule;
 import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -57,7 +56,6 @@
 
     @Before
     public void setUp() throws Exception {
-        mFeatureFlags.setDefault(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION);
         mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mDependency.injectMockDependency(NotificationMediaManager.class);
         allowTestableLooperAsMainThread();
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..095c945 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
@@ -22,10 +22,9 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
-import android.app.ActivityManager;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.hardware.display.DisplayManager;
@@ -75,13 +74,13 @@
     private AccessibilityManager mAccessibilityManager;
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private AccessibilityFloatingMenuController mController;
+    @Mock
     private AccessibilityButtonTargetsObserver mTargetsObserver;
+    @Mock
     private AccessibilityButtonModeObserver mModeObserver;
     @Captor
     private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor;
     private KeyguardUpdateMonitorCallback mKeyguardCallback;
-    private int mLastButtonMode;
-    private String mLastButtonTargets;
     @Mock
     private SecureSettings mSecureSettings;
 
@@ -97,10 +96,14 @@
 
         mWindowManager = mContext.getSystemService(WindowManager.class);
         mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
-        mLastButtonTargets = Settings.Secure.getStringForUser(mContextWrapper.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
-        mLastButtonMode = Settings.Secure.getIntForUser(mContextWrapper.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, UserHandle.USER_CURRENT);
+
+        when(mTargetsObserver.getCurrentAccessibilityButtonTargets())
+                .thenReturn(Settings.Secure.getStringForUser(mContextWrapper.getContentResolver(),
+                        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT));
+
+        when(mModeObserver.getCurrentAccessibilityButtonMode())
+                .thenReturn(Settings.Secure.getIntForUser(mContextWrapper.getContentResolver(),
+                        Settings.Secure.ACCESSIBILITY_BUTTON_MODE, UserHandle.USER_CURRENT));
     }
 
     @After
@@ -109,13 +112,6 @@
             mController.onAccessibilityButtonTargetsChanged("");
             mController = null;
         }
-
-        Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, mLastButtonTargets,
-                UserHandle.USER_CURRENT);
-        Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, mLastButtonMode,
-                UserHandle.USER_CURRENT);
     }
 
     @Test
@@ -227,9 +223,8 @@
 
     @Test
     public void onAccessibilityButtonModeChanged_floatingModeAndHasButtonTargets_showWidget() {
-        Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
-                ActivityManager.getCurrentUser());
+        when(mTargetsObserver.getCurrentAccessibilityButtonTargets())
+                .thenReturn(TEST_A11Y_BTN_TARGETS);
         mController = setUpController();
 
         mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
@@ -239,8 +234,8 @@
 
     @Test
     public void onAccessibilityButtonModeChanged_floatingModeAndNoButtonTargets_destroyWidget() {
-        Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", ActivityManager.getCurrentUser());
+        when(mTargetsObserver.getCurrentAccessibilityButtonTargets()).thenReturn("");
+
         mController = setUpController();
 
         mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
@@ -250,9 +245,8 @@
 
     @Test
     public void onAccessibilityButtonModeChanged_navBarModeAndHasButtonTargets_destroyWidget() {
-        Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
-                ActivityManager.getCurrentUser());
+        when(mTargetsObserver.getCurrentAccessibilityButtonTargets())
+                .thenReturn(TEST_A11Y_BTN_TARGETS);
         mController = setUpController();
 
         mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
@@ -262,8 +256,7 @@
 
     @Test
     public void onAccessibilityButtonModeChanged_navBarModeAndNoButtonTargets_destroyWidget() {
-        Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", ActivityManager.getCurrentUser());
+        when(mTargetsObserver.getCurrentAccessibilityButtonTargets()).thenReturn("");
         mController = setUpController();
 
         mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
@@ -273,9 +266,8 @@
 
     @Test
     public void onAccessibilityButtonTargetsChanged_floatingModeAndHasButtonTargets_showWidget() {
-        Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
-                ActivityManager.getCurrentUser());
+        when(mModeObserver.getCurrentAccessibilityButtonMode())
+                .thenReturn(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
         mController = setUpController();
 
         mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
@@ -285,9 +277,8 @@
 
     @Test
     public void onAccessibilityButtonTargetsChanged_floatingModeAndNoButtonTargets_destroyWidget() {
-        Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
-                ActivityManager.getCurrentUser());
+        when(mModeObserver.getCurrentAccessibilityButtonMode())
+                .thenReturn(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
         mController = setUpController();
 
         mController.onAccessibilityButtonTargetsChanged("");
@@ -297,9 +288,8 @@
 
     @Test
     public void onAccessibilityButtonTargetsChanged_navBarModeAndHasButtonTargets_destroyWidget() {
-        Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
-                ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, ActivityManager.getCurrentUser());
+        when(mModeObserver.getCurrentAccessibilityButtonMode())
+                .thenReturn(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
         mController = setUpController();
 
         mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
@@ -309,9 +299,8 @@
 
     @Test
     public void onAccessibilityButtonTargetsChanged_navBarModeAndNoButtonTargets_destroyWidget() {
-        Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
-                ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, ActivityManager.getCurrentUser());
+        when(mModeObserver.getCurrentAccessibilityButtonMode())
+                .thenReturn(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
         mController = setUpController();
 
         mController.onAccessibilityButtonTargetsChanged("");
@@ -321,9 +310,8 @@
 
     @Test
     public void onTargetsChanged_isFloatingViewLayerControllerCreated() {
-        Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
-                UserHandle.USER_CURRENT);
+        when(mModeObserver.getCurrentAccessibilityButtonMode())
+                .thenReturn(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
 
         mController = setUpController();
         mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
@@ -335,8 +323,6 @@
         final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
         final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
         final FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext);
-        mTargetsObserver = spy(Dependency.get(AccessibilityButtonTargetsObserver.class));
-        mModeObserver = spy(Dependency.get(AccessibilityButtonModeObserver.class));
         mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         final AccessibilityFloatingMenuController controller =
                 new AccessibilityFloatingMenuController(mContextWrapper, windowManager,
@@ -348,12 +334,11 @@
     }
 
     private void enableAccessibilityFloatingMenuConfig() {
-        Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
-                ActivityManager.getCurrentUser());
-        Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
-                ActivityManager.getCurrentUser());
+        when(mTargetsObserver.getCurrentAccessibilityButtonTargets())
+                .thenReturn(TEST_A11Y_BTN_TARGETS);
+
+        when(mModeObserver.getCurrentAccessibilityButtonMode())
+                .thenReturn(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
     }
 
     private void captureKeyguardUpdateMonitorCallback() {
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 3862b0f..de795a7 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
@@ -61,6 +61,7 @@
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.util.ArraySet;
 import android.view.View;
 import android.view.WindowInsets;
 import android.view.WindowManager;
@@ -71,6 +72,7 @@
 import androidx.dynamicanimation.animation.SpringAnimation;
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.dialog.AccessibilityTarget;
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.systemui.Flags;
@@ -220,6 +222,24 @@
     }
 
     @Test
+    @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+    public void triggerDismissMenuAction_callsA11yManagerEnableShortcutsForTargets() {
+        final List<String> stubShortcutTargets = new ArrayList<>();
+        stubShortcutTargets.add(TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
+        when(mStubAccessibilityManager.getAccessibilityShortcutTargets(
+                AccessibilityManager.ACCESSIBILITY_BUTTON)).thenReturn(stubShortcutTargets);
+
+        mMenuViewLayer.mDismissMenuAction.run();
+
+        verify(mStubAccessibilityManager).enableShortcutsForTargets(
+                /* enable= */ false,
+                ShortcutConstants.UserShortcutType.SOFTWARE,
+                new ArraySet<>(stubShortcutTargets),
+                mSecureSettings.getRealUserHandle(UserHandle.USER_CURRENT));
+    }
+
+    @Test
+    @DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
     public void triggerDismissMenuAction_matchA11yButtonTargetsResult() {
         mMenuViewLayer.mDismissMenuAction.run();
         verify(mSecureSettings).putStringForUser(
@@ -228,6 +248,7 @@
     }
 
     @Test
+    @DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
     public void triggerDismissMenuAction_matchEnabledA11yServicesResult() {
         setupEnabledAccessibilityServiceList();
 
@@ -239,6 +260,7 @@
     }
 
     @Test
+    @DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
     public void triggerDismissMenuAction_hasHardwareKeyShortcut_keepEnabledStatus() {
         setupEnabledAccessibilityServiceList();
         final List<String> stubShortcutTargets = new ArrayList<>();
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 1ce6525..eced465 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
@@ -22,11 +22,15 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.UiModeManager;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.graphics.Rect;
 import android.graphics.drawable.GradientDrawable;
 import android.platform.test.annotations.EnableFlags;
@@ -54,6 +58,8 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.ArrayList;
+
 /** Tests for {@link MenuView}. */
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -73,6 +79,8 @@
     private AccessibilityManager mAccessibilityManager;
 
     private SysuiTestableContext mSpyContext;
+    @Mock
+    private PackageManager mMockPackageManager;
 
     @Before
     public void setUp() throws Exception {
@@ -82,6 +90,8 @@
 
         mSpyContext = spy(mContext);
         doNothing().when(mSpyContext).startActivity(any());
+
+        when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
         final SecureSettings secureSettings = TestUtils.mockSecureSettings();
         final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
                 secureSettings);
@@ -181,10 +191,19 @@
     @Test
     @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
     public void gotoEditScreen_sendsIntent() {
+        mockActivityQuery(true);
         mMenuView.gotoEditScreen();
         verify(mSpyContext).startActivity(any());
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+    public void gotoEditScreen_noResolve_doesNotStart() {
+        mockActivityQuery(false);
+        mMenuView.gotoEditScreen();
+        verify(mSpyContext, never()).startActivity(any());
+    }
+
     private InstantInsetLayerDrawable getMenuViewInsetLayer() {
         return (InstantInsetLayerDrawable) mMenuView.getBackground();
     }
@@ -207,4 +226,14 @@
         mUiModeManager.setNightMode(mNightMode);
         Prefs.putString(mContext, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, mLastPosition);
     }
+
+    private void mockActivityQuery(boolean successfulQuery) {
+        // Query just needs to return a non-empty set to be successful.
+        ArrayList<ResolveInfo> resolveInfos = new ArrayList<>();
+        if (successfulQuery) {
+            resolveInfos.add(new ResolveInfo());
+        }
+        when(mMockPackageManager.queryIntentActivities(
+                any(), any(PackageManager.ResolveInfoFlags.class))).thenReturn(resolveInfos);
+    }
 }
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 f07932c..e3be3822 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(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
index 4ab7ab4..3c073d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
@@ -15,10 +15,13 @@
  */
 package com.android.systemui.battery
 
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.widget.ImageView
 import androidx.test.filters.SmallTest
+import com.android.settingslib.flags.Flags.FLAG_NEW_STATUS_BAR_ICONS
 import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.battery.BatteryMeterView.BatteryEstimateFetcher
@@ -141,7 +144,8 @@
     }
 
     @Test
-    fun changesFromEstimateToPercent_textAndContentDescriptionChanges() {
+    @DisableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+    fun changesFromEstimateToPercent_textAndContentDescriptionChanges_flagOff() {
         mBatteryMeterView.onBatteryLevelChanged(15, false)
         mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
         mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
@@ -164,6 +168,59 @@
     }
 
     @Test
+    @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+    fun changesFromEstimateToPercent_textAndContentDescriptionChanges_flagOn() {
+        mBatteryMeterView.onBatteryLevelChanged(15, false)
+        mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+        mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+        mBatteryMeterView.updatePercentText()
+
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+            context.getString(
+                R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE
+            )
+        )
+
+        // Update the show mode from estimate to percent
+        mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
+
+        assertThat(mBatteryMeterView.batteryPercentView).isNull()
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+            context.getString(R.string.accessibility_battery_level, 15)
+        )
+        assertThat(mBatteryMeterView.unifiedBatteryState.showPercent).isTrue()
+    }
+
+    @Test
+    @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+    fun modeEstimate_batteryPercentView_isNotNull_flagOn() {
+        mBatteryMeterView.onBatteryLevelChanged(15, false)
+        mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+        mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+        mBatteryMeterView.updatePercentText()
+
+        // New battery icon only uses the percent view for the estimate text
+        assertThat(mBatteryMeterView.batteryPercentView).isNotNull()
+        // Make sure that it was added to the view hierarchy
+        assertThat(mBatteryMeterView.batteryPercentView.parent).isNotNull()
+    }
+
+    @Test
+    @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+    fun modePercent_batteryPercentView_isNull_flagOn() {
+        mBatteryMeterView.onBatteryLevelChanged(15, false)
+        mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
+        mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+        mBatteryMeterView.updatePercentText()
+
+        // New battery icon only uses the percent view for the estimate text
+        assertThat(mBatteryMeterView.batteryPercentView).isNull()
+    }
+
+    @Test
     fun contentDescription_manyUpdates_alwaysUpdated() {
         // BatteryDefender
         mBatteryMeterView.onBatteryLevelChanged(90, false)
@@ -208,7 +265,8 @@
     }
 
     @Test
-    fun isBatteryDefenderChanged_true_drawableGetsTrue() {
+    @DisableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+    fun isBatteryDefenderChanged_true_drawableGetsTrue_flagOff() {
         mBatteryMeterView.setDisplayShieldEnabled(true)
         val drawable = getBatteryDrawable()
 
@@ -218,7 +276,18 @@
     }
 
     @Test
-    fun isBatteryDefenderChanged_false_drawableGetsFalse() {
+    @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+    fun isBatteryDefenderChanged_true_drawableGetsTrue_flagOn() {
+        mBatteryMeterView.setDisplayShieldEnabled(true)
+
+        mBatteryMeterView.onIsBatteryDefenderChanged(true)
+
+        assertThat(mBatteryMeterView.unifiedBatteryState.attribution).isNotNull()
+    }
+
+    @Test
+    @DisableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+    fun isBatteryDefenderChanged_false_drawableGetsFalse_flagOff() {
         mBatteryMeterView.setDisplayShieldEnabled(true)
         val drawable = getBatteryDrawable()
 
@@ -232,7 +301,22 @@
     }
 
     @Test
-    fun isBatteryDefenderChanged_true_featureflagOff_drawableGetsFalse() {
+    @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+    fun isBatteryDefenderChanged_false_drawableGetsFalse_flagOn() {
+        mBatteryMeterView.setDisplayShieldEnabled(true)
+
+        // Start as true
+        mBatteryMeterView.onIsBatteryDefenderChanged(true)
+
+        // Update to false
+        mBatteryMeterView.onIsBatteryDefenderChanged(false)
+
+        assertThat(mBatteryMeterView.unifiedBatteryState.attribution).isNull()
+    }
+
+    @Test
+    @DisableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+    fun isBatteryDefenderChanged_true_featureflagOff_drawableGetsFalse_flagOff() {
         mBatteryMeterView.setDisplayShieldEnabled(false)
         val drawable = getBatteryDrawable()
 
@@ -242,17 +326,41 @@
     }
 
     @Test
-    fun isIncompatibleChargingChanged_true_drawableGetsChargingFalse() {
+    @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+    fun isBatteryDefenderChanged_true_featureflagOff_drawableGetsFalse_flagOn() {
+        mBatteryMeterView.setDisplayShieldEnabled(false)
+
+        mBatteryMeterView.onIsBatteryDefenderChanged(true)
+
+        assertThat(mBatteryMeterView.unifiedBatteryState.attribution).isNull()
+    }
+
+    @Test
+    @DisableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+    fun isIncompatibleChargingChanged_true_drawableGetsChargingFalse_flagOff() {
         mBatteryMeterView.onBatteryLevelChanged(45, true)
         val drawable = getBatteryDrawable()
 
         mBatteryMeterView.onIsIncompatibleChargingChanged(true)
 
         assertThat(drawable.getCharging()).isFalse()
+        assertThat(mBatteryMeterView.isCharging).isFalse()
     }
 
     @Test
-    fun isIncompatibleChargingChanged_false_drawableGetsChargingTrue() {
+    @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+    fun isIncompatibleChargingChanged_true_drawableGetsChargingFalse_flagOn() {
+        mBatteryMeterView.onBatteryLevelChanged(45, true)
+
+        mBatteryMeterView.onIsIncompatibleChargingChanged(true)
+
+        assertThat(mBatteryMeterView.unifiedBatteryState.attribution).isNull()
+        assertThat(mBatteryMeterView.isCharging).isFalse()
+    }
+
+    @Test
+    @DisableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+    fun isIncompatibleChargingChanged_false_drawableGetsChargingTrue_flagOff() {
         mBatteryMeterView.onBatteryLevelChanged(45, true)
         val drawable = getBatteryDrawable()
 
@@ -261,6 +369,17 @@
         assertThat(drawable.getCharging()).isTrue()
     }
 
+    @Test
+    @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+    fun isIncompatibleChargingChanged_false_drawableGetsChargingTrue_flagOn() {
+        mBatteryMeterView.onBatteryLevelChanged(45, true)
+
+        mBatteryMeterView.onIsIncompatibleChargingChanged(false)
+
+        assertThat(mBatteryMeterView.isCharging).isTrue()
+        assertThat(mBatteryMeterView.unifiedBatteryState.attribution).isNotNull()
+    }
+
     private fun getBatteryDrawable(): AccessorizedBatteryDrawable {
         return (mBatteryMeterView.getChildAt(0) as ImageView)
                 .drawable as AccessorizedBatteryDrawable
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 2c1a87d..10b86ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -21,8 +21,8 @@
 import android.hardware.biometrics.BiometricConstants
 import android.hardware.biometrics.BiometricManager
 import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT
-import android.hardware.biometrics.Flags.customBiometricPrompt
 import android.hardware.biometrics.PromptInfo
+import android.hardware.biometrics.PromptVerticalListContentView
 import android.hardware.face.FaceSensorPropertiesInternal
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
 import android.os.Handler
@@ -40,7 +40,6 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
@@ -148,8 +147,6 @@
 
     @Before
     fun setup() {
-        mSetFlagsRule.disableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-        mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP)
         displayRepository = FakeDisplayRepository()
 
         displayStateInteractor =
@@ -388,9 +385,10 @@
     }
 
     @Test
-    fun testShowCredentialUI() {
+    fun testShowCredentialUI_withDescription() {
+        mSetFlagsRule.disableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
         val container = initializeFingerprintContainer(
-            authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
+                authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
         )
         waitForIdleSync()
 
@@ -399,16 +397,12 @@
     }
 
     @Test
-    fun testShowBiometricUIWhenCustomBpEnabledAndNoSensors() {
-        mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+    fun testShowCredentialUI_withCustomBp() {
         val container = initializeFingerprintContainer(
-                authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
+                authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
+                isUsingContentView = true
         )
-        waitForIdleSync()
-
-        assertThat(customBiometricPrompt()).isTrue()
-        assertThat(container.hasBiometricPrompt()).isTrue()
-        assertThat(container.hasCredentialView()).isFalse()
+        checkBpShowsForCredentialAndGoToCredential(container)
     }
 
     @Test
@@ -512,11 +506,13 @@
 
     private fun initializeFingerprintContainer(
         authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
-        addToView: Boolean = true
+        addToView: Boolean = true,
+        isUsingContentView: Boolean = false,
     ) = initializeContainer(
         TestAuthContainerView(
             authenticators = authenticators,
-            fingerprintProps = fingerprintSensorPropertiesInternal()
+            fingerprintProps = fingerprintSensorPropertiesInternal(),
+                isUsingContentView = isUsingContentView,
         ),
         addToView
     )
@@ -549,7 +545,8 @@
     private inner class TestAuthContainerView(
         authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
         fingerprintProps: List<FingerprintSensorPropertiesInternal> = listOf(),
-        faceProps: List<FaceSensorPropertiesInternal> = listOf()
+        faceProps: List<FaceSensorPropertiesInternal> = listOf(),
+        isUsingContentView: Boolean = false,
     ) : AuthContainerView(
         Config().apply {
             mContext = this@AuthContainerViewTest.context
@@ -559,6 +556,9 @@
             mSkipAnimation = true
             mPromptInfo = PromptInfo().apply {
                 this.authenticators = authenticators
+                if (isUsingContentView) {
+                    this.contentView = PromptVerticalListContentView.Builder().build()
+                }
             }
             mOpPackageName = OP_PACKAGE_NAME
         },
@@ -614,6 +614,17 @@
         val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
         assertThat((layoutParams.fitInsetsTypes and WindowInsets.Type.systemBars()) == 0).isTrue()
     }
+
+    private fun checkBpShowsForCredentialAndGoToCredential(container: TestAuthContainerView) {
+        waitForIdleSync()
+        assertThat(container.hasBiometricPrompt()).isTrue()
+        assertThat(container.hasCredentialView()).isFalse()
+
+        container.animateToCredentialUI(false)
+        waitForIdleSync()
+        assertThat(container.hasBiometricPrompt()).isFalse()
+        assertThat(container.hasCredentialView()).isTrue()
+    }
 }
 
 private fun AuthContainerView.hasBiometricPrompt() =
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 54d6b53..67ca9a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.SysUITestComponent
 import com.android.systemui.SysUITestModule
 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
@@ -45,6 +46,7 @@
             [
                 SysUITestModule::class,
                 UserDomainLayerModule::class,
+                BiometricsDomainLayerModule::class,
             ]
     )
     interface TestComponent : SysUITestComponent<AuthDialogPanelInteractionDetector> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
index dc438d7..7808c41 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
@@ -84,8 +84,8 @@
             val sensorType by collectLastValue(repository.sensorType)
             val sensorLocations by collectLastValue(repository.sensorLocations)
 
-            // Assert default properties.
-            assertThat(sensorId).isEqualTo(-1)
+            // Assert non-initialized properties.
+            assertThat(sensorId).isEqualTo(-2)
             assertThat(strength).isEqualTo(SensorStrength.CONVENIENCE)
             assertThat(sensorType).isEqualTo(FingerprintSensorType.UNKNOWN)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
index b39e09d..7b972d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
@@ -16,7 +16,9 @@
 
 package com.android.systemui.biometrics.data.repository
 
+import android.hardware.biometrics.BiometricManager
 import android.hardware.biometrics.PromptInfo
+import android.hardware.biometrics.PromptVerticalListContentView
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
@@ -131,6 +133,52 @@
         }
 
     @Test
+    fun showBpWithoutIconForCredential_withCustomBp() =
+        testScope.runTest {
+            for (case in
+                listOf(
+                    PromptKind.Biometric(),
+                    PromptKind.Pin,
+                    PromptKind.Password,
+                    PromptKind.Pattern
+                )) {
+                val hasCredentialViewShown = case !is PromptKind.Biometric
+                val promptInfo =
+                    PromptInfo().apply {
+                        authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
+                        contentView = PromptVerticalListContentView.Builder().build()
+                    }
+                repository.setPrompt(promptInfo, USER_ID, CHALLENGE, case, OP_PACKAGE_NAME)
+                repository.setShouldShowBpWithoutIconForCredential(promptInfo)
+
+                assertThat(repository.showBpWithoutIconForCredential.value)
+                    .isEqualTo(!hasCredentialViewShown)
+            }
+        }
+
+    @Test
+    fun showBpWithoutIconForCredential_withDescription() =
+        testScope.runTest {
+            for (case in
+                listOf(
+                    PromptKind.Biometric(),
+                    PromptKind.Pin,
+                    PromptKind.Password,
+                    PromptKind.Pattern
+                )) {
+                val promptInfo =
+                    PromptInfo().apply {
+                        authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
+                        description = "description"
+                    }
+                repository.setPrompt(promptInfo, USER_ID, CHALLENGE, case, OP_PACKAGE_NAME)
+                repository.setShouldShowBpWithoutIconForCredential(promptInfo)
+
+                assertThat(repository.showBpWithoutIconForCredential.value).isFalse()
+            }
+        }
+
+    @Test
     fun setsAndUnsetsPrompt() =
         testScope.runTest {
             val kind = PromptKind.Pin
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
index 52b4275..2817780 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
@@ -147,7 +147,7 @@
         testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PIN) }
 
     @Test
-    fun usePattermCredentialAndReset() =
+    fun usePatternCredentialAndReset() =
         testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PATTERN) }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
index fa17672..5caa146 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.TestMocksModule
+import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
 import com.android.systemui.collectLastValue
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FakeFeatureFlagsClassicModule
@@ -65,6 +66,7 @@
             [
                 SysUITestModule::class,
                 UserDomainLayerModule::class,
+                BiometricsDomainLayerModule::class,
             ]
     )
     interface TestComponent : SysUITestComponent<DefaultUdfpsTouchOverlayViewModel> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
index 7a18477..a569cee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
@@ -42,7 +42,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogTransitionAnimator;
 import com.android.systemui.broadcast.BroadcastSender;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -77,7 +77,8 @@
     @Mock SysUiState mSysUiState;
     @Mock
     DialogTransitionAnimator mDialogTransitionAnimator;
-    @Mock MediaOutputDialogFactory mMediaOutputDialogFactory;
+    @Mock
+    MediaOutputDialogManager mMediaOutputDialogManager;
     private SystemUIDialog mDialog;
     private TextView mTitle;
     private TextView mSubTitle;
@@ -96,7 +97,7 @@
 
         mBroadcastDialogDelegate = new BroadcastDialogDelegate(
                 mContext,
-                mMediaOutputDialogFactory,
+                mMediaOutputDialogManager,
                 mLocalBluetoothManager,
                 new UiEventLoggerFake(),
                 mFakeExecutor,
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 e796303..701b703 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
@@ -401,7 +401,7 @@
                 LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
                     Pair("Enter PIN", "PIN is required after lockdown"),
                 LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
-                    Pair("Enter PIN", "Update will install when device not in use"),
+                    Pair("Enter PIN", "PIN required for additional security"),
                 LockPatternUtils.StrongAuthTracker
                     .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
                     Pair(
@@ -439,7 +439,7 @@
                 LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
                     Pair("Enter PIN", "PIN is required after lockdown"),
                 LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
-                    Pair("Enter PIN", "Update will install when device not in use"),
+                    Pair("Enter PIN", "PIN required for additional security"),
                 LockPatternUtils.StrongAuthTracker
                     .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
                     Pair(
@@ -481,7 +481,7 @@
                 LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
                     Pair("Enter PIN", "PIN is required after lockdown"),
                 LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
-                    Pair("Enter PIN", "Update will install when device not in use"),
+                    Pair("Enter PIN", "PIN required for additional security"),
                 LockPatternUtils.StrongAuthTracker
                     .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
                     Pair(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt
index 3e6cc3b..03e4f9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt
@@ -32,10 +32,6 @@
 class ComposeInitializerTest : SysuiTestCase() {
     @Test
     fun testCanAddComposeViewInInitializedWindow() {
-        if (!ComposeFacade.isComposeAvailable()) {
-            return
-        }
-
         val root = TestWindowRoot(context)
         try {
             runOnMainThreadAndWaitForIdleSync { ViewUtils.attachView(root) }
@@ -55,12 +51,12 @@
     class TestWindowRoot(context: Context) : FrameLayout(context) {
         override fun onAttachedToWindow() {
             super.onAttachedToWindow()
-            ComposeFacade.composeInitializer().onAttachedToWindow(this)
+            ComposeInitializer.onAttachedToWindow(this)
         }
 
         override fun onDetachedFromWindow() {
             super.onDetachedFromWindow()
-            ComposeFacade.composeInitializer().onDetachedFromWindow(this)
+            ComposeInitializer.onDetachedFromWindow(this)
         }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt
index e8eda80..d5839b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt
@@ -21,9 +21,15 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.fingerprintPropertyInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
@@ -35,6 +41,8 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class DeviceEntryUdfpsInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
     private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
     private lateinit var fingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository
     private lateinit var biometricsRepository: FakeBiometricSettingsRepository
@@ -43,77 +51,85 @@
 
     @Before
     fun setUp() {
-        fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
-        fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
-        biometricsRepository = FakeBiometricSettingsRepository()
+        fingerprintPropertyRepository = kosmos.fakeFingerprintPropertyRepository
+        fingerprintAuthRepository = kosmos.fakeDeviceEntryFingerprintAuthRepository
+        biometricsRepository = kosmos.fakeBiometricSettingsRepository
 
         underTest =
             DeviceEntryUdfpsInteractor(
-                fingerprintPropertyRepository = fingerprintPropertyRepository,
+                fingerprintPropertyInteractor = kosmos.fingerprintPropertyInteractor,
                 fingerprintAuthRepository = fingerprintAuthRepository,
                 biometricSettingsRepository = biometricsRepository,
             )
     }
 
     @Test
-    fun udfpsSupported_rearFp_false() = runTest {
-        val isUdfpsSupported by collectLastValue(underTest.isUdfpsSupported)
-        fingerprintPropertyRepository.supportsRearFps()
-        assertThat(isUdfpsSupported).isFalse()
-    }
+    fun udfpsSupported_rearFp_false() =
+        testScope.runTest {
+            val isUdfpsSupported by collectLastValue(underTest.isUdfpsSupported)
+            fingerprintPropertyRepository.supportsRearFps()
+            assertThat(isUdfpsSupported).isFalse()
+        }
 
     @Test
-    fun udfpsSupoprted() = runTest {
-        val isUdfpsSupported by collectLastValue(underTest.isUdfpsSupported)
-        fingerprintPropertyRepository.supportsUdfps()
-        assertThat(isUdfpsSupported).isTrue()
-    }
+    fun udfpsSupoprted() =
+        testScope.runTest {
+            val isUdfpsSupported by collectLastValue(underTest.isUdfpsSupported)
+            fingerprintPropertyRepository.supportsUdfps()
+            assertThat(isUdfpsSupported).isTrue()
+        }
 
     @Test
-    fun udfpsEnrolledAndEnabled() = runTest {
-        val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled)
-        fingerprintPropertyRepository.supportsUdfps()
-        biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
-        assertThat(isUdfpsEnrolledAndEnabled).isTrue()
-    }
+    fun udfpsEnrolledAndEnabled() =
+        testScope.runTest {
+            val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled)
+            fingerprintPropertyRepository.supportsUdfps()
+            biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+            assertThat(isUdfpsEnrolledAndEnabled).isTrue()
+        }
 
     @Test
-    fun udfpsEnrolledAndEnabled_rearFp_false() = runTest {
-        val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled)
-        fingerprintPropertyRepository.supportsRearFps()
-        biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
-        assertThat(isUdfpsEnrolledAndEnabled).isFalse()
-    }
+    fun udfpsEnrolledAndEnabled_rearFp_false() =
+        testScope.runTest {
+            val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled)
+            fingerprintPropertyRepository.supportsRearFps()
+            biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+            assertThat(isUdfpsEnrolledAndEnabled).isFalse()
+        }
 
     @Test
-    fun udfpsEnrolledAndEnabled_notEnrolledOrEnabled_false() = runTest {
-        val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled)
-        fingerprintPropertyRepository.supportsUdfps()
-        biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
-        assertThat(isUdfpsEnrolledAndEnabled).isFalse()
-    }
+    fun udfpsEnrolledAndEnabled_notEnrolledOrEnabled_false() =
+        testScope.runTest {
+            val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled)
+            fingerprintPropertyRepository.supportsUdfps()
+            biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+            assertThat(isUdfpsEnrolledAndEnabled).isFalse()
+        }
 
     @Test
-    fun isListeningForUdfps() = runTest {
-        val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps)
-        fingerprintPropertyRepository.supportsUdfps()
-        fingerprintAuthRepository.setIsRunning(true)
-        assertThat(isListeningForUdfps).isTrue()
-    }
+    fun isListeningForUdfps() =
+        testScope.runTest {
+            val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps)
+            fingerprintPropertyRepository.supportsUdfps()
+            fingerprintAuthRepository.setIsRunning(true)
+            assertThat(isListeningForUdfps).isTrue()
+        }
 
     @Test
-    fun isListeningForUdfps_rearFp_false() = runTest {
-        val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps)
-        fingerprintPropertyRepository.supportsRearFps()
-        fingerprintAuthRepository.setIsRunning(true)
-        assertThat(isListeningForUdfps).isFalse()
-    }
+    fun isListeningForUdfps_rearFp_false() =
+        testScope.runTest {
+            val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps)
+            fingerprintPropertyRepository.supportsRearFps()
+            fingerprintAuthRepository.setIsRunning(true)
+            assertThat(isListeningForUdfps).isFalse()
+        }
 
     @Test
-    fun isListeningForUdfps_notRunning_false() = runTest {
-        val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps)
-        fingerprintPropertyRepository.supportsUdfps()
-        fingerprintAuthRepository.setIsRunning(false)
-        assertThat(isListeningForUdfps).isFalse()
-    }
+    fun isListeningForUdfps_notRunning_false() =
+        testScope.runTest {
+            val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps)
+            fingerprintPropertyRepository.supportsUdfps()
+            fingerprintAuthRepository.setIsRunning(false)
+            assertThat(isListeningForUdfps).isFalse()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
index e97edcb..9b8cf59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
@@ -27,7 +27,7 @@
 import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -39,6 +39,7 @@
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.runner.RunWith
 
@@ -52,7 +53,7 @@
         }
     private val deviceEntryIconTransition = kosmos.fakeDeviceEntryIconViewModelTransition
     private val testScope = kosmos.testScope
-    private val keyguardRepository = kosmos.fakeKeyguardRepository
+    private val biometricSettingsRepository = kosmos.fakeBiometricSettingsRepository
     private val accessibilityRepository = kosmos.fakeAccessibilityRepository
     private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
     private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
@@ -85,22 +86,12 @@
             setupVisibleStateOnLockscreen()
 
             // AOD
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.AOD,
-                    value = 0f,
-                    transitionState = TransitionState.STARTED,
-                )
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                this,
             )
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.AOD,
-                    value = 1f,
-                    transitionState = TransitionState.FINISHED,
-                )
-            )
+            runCurrent()
             assertThat(visible).isFalse()
         }
     fun fpNotRunning_overlayNotVisible() =
@@ -129,6 +120,7 @@
 
         // Listening for UDFPS
         fingerprintPropertyRepository.supportsUdfps()
+        biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
         deviceEntryFingerprintAuthRepository.setIsRunning(true)
         deviceEntryRepository.setUnlocked(false)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
index b25fb6e..30519b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
@@ -16,14 +16,17 @@
 
 package com.android.systemui.display.ui.view
 
+import android.graphics.Insets
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
+import android.view.WindowInsets
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.res.R
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -41,6 +44,7 @@
 
     private val onStartMirroringCallback = mock<View.OnClickListener>()
     private val onCancelCallback = mock<View.OnClickListener>()
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -96,10 +100,40 @@
         verify(onStartMirroringCallback).onClick(any())
     }
 
+    @Test
+    fun onInsetsChanged_navBarInsets_updatesBottomPadding() {
+        dialog.show()
+
+        val insets = buildInsets(WindowInsets.Type.navigationBars(), TEST_BOTTOM_INSETS)
+        dialog.onInsetsChanged(WindowInsets.Type.navigationBars(), insets)
+
+        assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
+            .isEqualTo(TEST_BOTTOM_INSETS)
+    }
+
+    @Test
+    fun onInsetsChanged_otherType_doesNotUpdateBottomPadding() {
+        dialog.show()
+
+        val insets = buildInsets(WindowInsets.Type.ime(), TEST_BOTTOM_INSETS)
+        dialog.onInsetsChanged(WindowInsets.Type.ime(), insets)
+
+        assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
+            .isNotEqualTo(TEST_BOTTOM_INSETS)
+    }
+
+    private fun buildInsets(@WindowInsets.Type.InsetsType type: Int, bottom: Int): WindowInsets {
+        return WindowInsets.Builder().setInsets(type, Insets.of(0, 0, 0, bottom)).build()
+    }
+
     @After
     fun teardown() {
         if (::dialog.isInitialized) {
             dialog.dismiss()
         }
     }
+
+    private companion object {
+        const val TEST_BOTTOM_INSETS = 1000 // arbitrarily high number
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java
index 9d9b263..2d3ca60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java
@@ -19,7 +19,13 @@
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNull;
 
+import static org.mockito.Mockito.when;
+
+import android.nearby.NearbyManager;
+import android.net.platform.flags.Flags;
 import android.os.PowerManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.filters.SmallTest;
@@ -32,6 +38,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 
 @SmallTest
@@ -41,10 +48,13 @@
     ShutdownUi mShutdownUi;
     @Mock
     BlurUtils mBlurUtils;
+    @Mock
+    NearbyManager mNearbyManager;
 
     @Before
     public void setUp() throws Exception {
-        mShutdownUi = new ShutdownUi(getContext(), mBlurUtils);
+        MockitoAnnotations.initMocks(this);
+        mShutdownUi = new ShutdownUi(getContext(), mBlurUtils, mNearbyManager);
     }
 
     @Test
@@ -82,4 +92,53 @@
         String message = mShutdownUi.getReasonMessage("anything-else");
         assertNull(message);
     }
+
+    @EnableFlags(Flags.FLAG_POWERED_OFF_FINDING_PLATFORM)
+    @Test
+    public void getDialog_whenPowerOffFindingModeEnabled_returnsFinderDialog() {
+        when(mNearbyManager.getPoweredOffFindingMode()).thenReturn(
+                NearbyManager.POWERED_OFF_FINDING_MODE_ENABLED);
+
+        int actualLayout = mShutdownUi.getShutdownDialogContent(false);
+
+        int expectedLayout = com.android.systemui.res.R.layout.shutdown_dialog_finder_active;
+        assertEquals(actualLayout, expectedLayout);
+    }
+
+    @DisableFlags(Flags.FLAG_POWERED_OFF_FINDING_PLATFORM)
+    @Test
+    public void getDialog_whenPowerOffFindingModeEnabledFlagDisabled_returnsFinderDialog() {
+        when(mNearbyManager.getPoweredOffFindingMode()).thenReturn(
+                NearbyManager.POWERED_OFF_FINDING_MODE_ENABLED);
+
+        int actualLayout = mShutdownUi.getShutdownDialogContent(false);
+
+        int expectedLayout = R.layout.shutdown_dialog;
+        assertEquals(actualLayout, expectedLayout);
+    }
+
+    @EnableFlags(Flags.FLAG_POWERED_OFF_FINDING_PLATFORM)
+    @Test
+    public void getDialog_whenPowerOffFindingModeDisabled_returnsDefaultDialog() {
+        when(mNearbyManager.getPoweredOffFindingMode()).thenReturn(
+                NearbyManager.POWERED_OFF_FINDING_MODE_DISABLED);
+
+        int actualLayout = mShutdownUi.getShutdownDialogContent(false);
+
+        int expectedLayout = R.layout.shutdown_dialog;
+        assertEquals(actualLayout, expectedLayout);
+    }
+
+    @EnableFlags(Flags.FLAG_POWERED_OFF_FINDING_PLATFORM)
+    @Test
+    public void getDialog_whenPowerOffFindingModeEnabledAndIsReboot_returnsDefaultDialog() {
+        when(mNearbyManager.getPoweredOffFindingMode()).thenReturn(
+                NearbyManager.POWERED_OFF_FINDING_MODE_ENABLED);
+
+        int actualLayout = mShutdownUi.getShutdownDialogContent(true);
+
+        int expectedLayout = R.layout.shutdown_dialog;
+        assertEquals(actualLayout, expectedLayout);
+    }
+
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt
index a992956..59d8fc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt
@@ -19,7 +19,6 @@
 import android.app.Dialog
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.keyboard.data.repository.FakeStickyKeysRepository
 import com.android.systemui.keyboard.data.repository.keyboardRepository
 import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
@@ -34,7 +33,6 @@
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
-import org.junit.Assume
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -54,19 +52,22 @@
 
     @Before
     fun setup() {
-        Assume.assumeTrue(ComposeFacade.isComposeAvailable())
         val dialogFactory = mock<StickyKeyDialogFactory>()
         whenever(dialogFactory.create(any())).thenReturn(dialog)
         val keyboardRepository = Kosmos().keyboardRepository
-        val viewModel = StickyKeysIndicatorViewModel(
+        val viewModel =
+            StickyKeysIndicatorViewModel(
                 stickyKeysRepository,
                 keyboardRepository,
-                testScope.backgroundScope)
-        coordinator = StickyKeysIndicatorCoordinator(
+                testScope.backgroundScope
+            )
+        coordinator =
+            StickyKeysIndicatorCoordinator(
                 testScope.backgroundScope,
                 dialogFactory,
                 viewModel,
-                mock<StickyKeysLogger>())
+                mock<StickyKeysLogger>()
+            )
         coordinator.startListening()
         keyboardRepository.setIsAnyKeyboardConnected(true)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 915522d..1a6da76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -53,9 +53,11 @@
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
@@ -101,6 +103,8 @@
     private lateinit var underTest: CustomizationProvider
     private lateinit var testScope: TestScope
 
+    private val kosmos = testKosmos()
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -185,6 +189,7 @@
                                 },
                         )
                         .keyguardInteractor,
+                shadeInteractor = kosmos.shadeInteractor,
                 lockPatternUtils = lockPatternUtils,
                 keyguardStateController = keyguardStateController,
                 userTracker = userTracker,
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 489665c..51828c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -6,6 +6,7 @@
 import android.graphics.Point
 import android.graphics.Rect
 import android.os.PowerManager
+import android.platform.test.annotations.DisableFlags
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.RemoteAnimationTarget
@@ -15,6 +16,7 @@
 import android.view.ViewRootImpl
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardViewController
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
@@ -130,6 +132,7 @@
      * surface, or the user will see the wallpaper briefly as the app animates in.
      */
     @Test
+    @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun noSurfaceAnimation_ifWakeAndUnlocking() {
         whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
 
@@ -320,6 +323,7 @@
      * If we are not wake and unlocking, we expect the unlock animation to play normally.
      */
     @Test
+    @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun surfaceAnimation_multipleTargets() {
         keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
                 arrayOf(remoteTarget1, remoteTarget2),
@@ -358,6 +362,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun surfaceBehindAlphaOverriddenTo0_ifNotInteractive() {
         whenever(powerManager.isInteractive).thenReturn(false)
 
@@ -389,6 +394,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun surfaceBehindAlphaNotOverriddenTo0_ifInteractive() {
         whenever(powerManager.isInteractive).thenReturn(true)
 
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 0957748..1849245 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -93,11 +93,11 @@
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.ui.viewmodel.DreamViewModel;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.SystemPropertiesHelper;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.navigationbar.NavigationModeController;
@@ -220,7 +220,7 @@
     private boolean mKeyguardGoingAway = false;
 
     private @Mock CoroutineDispatcher mDispatcher;
-    private @Mock DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
+    private @Mock DreamViewModel mDreamViewModel;
     private @Mock SystemPropertiesHelper mSystemPropertiesHelper;
     private @Mock SceneContainerFlags mSceneContainerFlags;
 
@@ -241,9 +241,9 @@
         final ViewRootImpl testViewRoot = mock(ViewRootImpl.class);
         when(testViewRoot.getView()).thenReturn(mock(View.class));
         when(mStatusBarKeyguardViewManager.getViewRootImpl()).thenReturn(testViewRoot);
-        when(mDreamingToLockscreenTransitionViewModel.getDreamOverlayAlpha())
+        when(mDreamViewModel.getDreamAlpha())
                 .thenReturn(mock(Flow.class));
-        when(mDreamingToLockscreenTransitionViewModel.getTransitionEnded())
+        when(mDreamViewModel.getTransitionEnded())
                 .thenReturn(mock(Flow.class));
         when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mDefaultUserId);
         when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mDefaultUserId);
@@ -1259,11 +1259,12 @@
                 mSystemSettings,
                 mSystemClock,
                 mDispatcher,
-                () -> mDreamingToLockscreenTransitionViewModel,
+                () -> mDreamViewModel,
                 mSystemPropertiesHelper,
                 () -> mock(WindowManagerLockscreenVisibilityManager.class),
                 mSelectedUserInteractor,
-                mKeyguardInteractor);
+                mKeyguardInteractor,
+                mock(WindowManagerOcclusionManager.class));
         mViewMediator.start();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
index 37836a5..bcaad01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
@@ -62,7 +62,6 @@
             whenever(defaultLockscreenBlueprint.id).thenReturn(DEFAULT)
             underTest =
                 KeyguardBlueprintRepository(
-                    configurationRepository,
                     setOf(defaultLockscreenBlueprint),
                     fakeExecutorHandler,
                     threadAssert,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
index df52265..0bd541c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
@@ -20,16 +20,19 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -43,41 +46,35 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class BurnInInteractorTest : SysuiTestCase() {
+    val kosmos = testKosmos()
+    val testScope = kosmos.testScope
+    val configurationRepository = kosmos.fakeConfigurationRepository
+    val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
+
     private val burnInOffset = 7
     private var burnInProgress = 0f
 
     @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
 
-    private lateinit var configurationRepository: FakeConfigurationRepository
-    private lateinit var keyguardInteractor: KeyguardInteractor
-    private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
-    private lateinit var testScope: TestScope
     private lateinit var underTest: BurnInInteractor
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        configurationRepository = FakeConfigurationRepository()
 
         context
             .getOrCreateTestableResources()
             .addOverride(R.dimen.burn_in_prevention_offset_y, burnInOffset)
-
-        KeyguardInteractorFactory.create().let {
-            keyguardInteractor = it.keyguardInteractor
-            fakeKeyguardRepository = it.repository
-        }
         whenever(burnInHelperWrapper.burnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset)
         setBurnInProgress(.65f)
 
-        testScope = TestScope()
         underTest =
             BurnInInteractor(
                 context,
                 burnInHelperWrapper,
-                testScope.backgroundScope,
-                configurationRepository,
-                keyguardInteractor,
+                kosmos.applicationCoroutineScope,
+                kosmos.configurationInteractor,
+                kosmos.keyguardInteractor,
             )
     }
 
@@ -122,7 +119,13 @@
         testScope.runTest {
             whenever(burnInHelperWrapper.burnInScale()).thenReturn(0.5f)
 
-            val burnInModel by collectLastValue(underTest.keyguardBurnIn)
+            val burnInModel by
+                collectLastValue(
+                    underTest.burnIn(
+                        xDimenResourceId = R.dimen.burn_in_prevention_offset_x,
+                        yDimenResourceId = R.dimen.burn_in_prevention_offset_y
+                    )
+                )
 
             // After time tick, returns the configured values
             fakeKeyguardRepository.dozeTimeTick(10)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
new file mode 100644
index 0000000..7bef01a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.os.PowerManager
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.testKosmos
+import junit.framework.Assert.assertEquals
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromAodTransitionInteractorTest : SysuiTestCase() {
+    private val kosmos =
+        testKosmos().apply {
+            this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+        }
+
+    private val testScope = kosmos.testScope
+    private val underTest = kosmos.fromAodTransitionInteractor
+
+    private val powerInteractor = kosmos.powerInteractor
+    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+    @Before
+    fun setup() {
+        underTest.start()
+
+        // Transition to AOD and set the power interactor asleep.
+        powerInteractor.setAsleepForTest()
+        runBlocking {
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                testScope
+            )
+            kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
+            reset(transitionRepository)
+        }
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionToLockscreen_onWakeup() =
+        testScope.runTest {
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            // Under default conditions, we should transition to LOCKSCREEN when waking up.
+            assertThat(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionToOccluded_onWakeup_whenOccludingActivityOnTop() =
+        testScope.runTest {
+            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED.
+            assertThat(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.OCCLUDED,
+                )
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromAod_nonDismissableKeyguard() =
+        testScope.runTest {
+            powerInteractor.onCameraLaunchGestureDetected()
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            // We should head back to GONE since we started there.
+            assertThat(transitionRepository)
+                .startedTransition(from = KeyguardState.AOD, to = KeyguardState.OCCLUDED)
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromAod_dismissableKeyguard() =
+        testScope.runTest {
+            kosmos.fakeKeyguardRepository.setKeyguardDismissible(true)
+            powerInteractor.onCameraLaunchGestureDetected()
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            // We should head back to GONE since we started there.
+            assertThat(transitionRepository)
+                .startedTransition(from = KeyguardState.AOD, to = KeyguardState.GONE)
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromGone() =
+        testScope.runTest {
+            powerInteractor.setAwakeForTest()
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.GONE,
+                testScope,
+            )
+            runCurrent()
+
+            // Make sure we're GONE.
+            assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+
+            // Get part way to AOD.
+            powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+            runCurrent()
+
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.AOD,
+                testScope = testScope,
+                throughTransitionState = TransitionState.RUNNING,
+            )
+
+            // Detect a power gesture and then wake up.
+            reset(transitionRepository)
+            powerInteractor.onCameraLaunchGestureDetected()
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            // We should head back to GONE since we started there.
+            assertThat(transitionRepository)
+                .startedTransition(from = KeyguardState.AOD, to = KeyguardState.GONE)
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetectedAfterFinishedInAod_fromGone() =
+        testScope.runTest {
+            powerInteractor.setAwakeForTest()
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.GONE,
+                testScope,
+            )
+            runCurrent()
+
+            // Make sure we're GONE.
+            assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+
+            // Get all the way to AOD
+            powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.AOD,
+                testScope = testScope,
+            )
+
+            // Detect a power gesture and then wake up.
+            reset(transitionRepository)
+            powerInteractor.onCameraLaunchGestureDetected()
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            // We should go to OCCLUDED - we came from GONE, but we finished in AOD, so this is no
+            // longer an insecure camera launch and it would be bad if we unlocked now.
+            assertThat(transitionRepository)
+                .startedTransition(from = KeyguardState.AOD, to = KeyguardState.OCCLUDED)
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromLockscreen() =
+        testScope.runTest {
+            powerInteractor.setAwakeForTest()
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.LOCKSCREEN,
+                testScope,
+            )
+            runCurrent()
+
+            // Make sure we're in LOCKSCREEN.
+            assertEquals(
+                KeyguardState.LOCKSCREEN,
+                kosmos.keyguardTransitionInteractor.getFinishedState()
+            )
+
+            // Get part way to AOD.
+            powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+            runCurrent()
+
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                testScope = testScope,
+                throughTransitionState = TransitionState.RUNNING,
+            )
+
+            // Detect a power gesture and then wake up.
+            reset(transitionRepository)
+            powerInteractor.onCameraLaunchGestureDetected()
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            // We should head back to GONE since we started there.
+            assertThat(transitionRepository)
+                .startedTransition(from = KeyguardState.AOD, to = KeyguardState.OCCLUDED)
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testWakeAndUnlock_transitionsToGone_onlyAfterDismissCallPostWakeup() =
+        testScope.runTest {
+            kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            // Waking up from wake and unlock should not start any transitions, we'll wait for the
+            // dismiss call.
+            assertThat(transitionRepository).noTransitionsStarted()
+
+            underTest.dismissAod()
+            runCurrent()
+
+            assertThat(transitionRepository)
+                .startedTransition(from = KeyguardState.AOD, to = KeyguardState.GONE)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
new file mode 100644
index 0000000..258dbf3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.os.PowerManager
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.testKosmos
+import junit.framework.Assert.assertEquals
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromDozingTransitionInteractorTest : SysuiTestCase() {
+    private val kosmos =
+        testKosmos().apply {
+            this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+        }
+
+    private val testScope = kosmos.testScope
+    private val underTest = kosmos.fromDozingTransitionInteractor
+
+    private val powerInteractor = kosmos.powerInteractor
+    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+    @Before
+    fun setup() {
+        underTest.start()
+
+        // Transition to DOZING and set the power interactor asleep.
+        powerInteractor.setAsleepForTest()
+        runBlocking {
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.DOZING,
+                testScope
+            )
+            kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
+            reset(transitionRepository)
+        }
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionToLockscreen_onWakeup() =
+        testScope.runTest {
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            // Under default conditions, we should transition to LOCKSCREEN when waking up.
+            assertThat(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.DOZING,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionToGlanceableHub_onWakeup_ifIdleOnCommunal_noOccludingActivity() =
+        testScope.runTest {
+            kosmos.fakeCommunalRepository.setTransitionState(
+                flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+            )
+            runCurrent()
+
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            // Under default conditions, we should transition to LOCKSCREEN when waking up.
+            assertThat(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.DOZING,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                )
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionToOccluded_onWakeup_whenOccludingActivityOnTop() =
+        testScope.runTest {
+            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED.
+            assertThat(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.DOZING,
+                    to = KeyguardState.OCCLUDED,
+                )
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionToOccluded_onWakeup_whenOccludingActivityOnTop_evenIfIdleOnCommunal() =
+        testScope.runTest {
+            kosmos.fakeCommunalRepository.setTransitionState(
+                flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+            )
+            runCurrent()
+
+            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED.
+            assertThat(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.DOZING,
+                    to = KeyguardState.OCCLUDED,
+                )
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromAod_nonDismissableKeyguard() =
+        testScope.runTest {
+            powerInteractor.onCameraLaunchGestureDetected()
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            // We should head back to GONE since we started there.
+            assertThat(transitionRepository)
+                .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED)
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromAod_dismissableKeyguard() =
+        testScope.runTest {
+            kosmos.fakeKeyguardRepository.setKeyguardDismissible(true)
+            powerInteractor.onCameraLaunchGestureDetected()
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            // We should head back to GONE since we started there.
+            assertThat(transitionRepository)
+                .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GONE)
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromGone() =
+        testScope.runTest {
+            powerInteractor.setAwakeForTest()
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.DOZING,
+                to = KeyguardState.GONE,
+                testScope,
+            )
+            runCurrent()
+
+            // Make sure we're GONE.
+            assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+
+            // Get part way to AOD.
+            powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+            runCurrent()
+
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.DOZING,
+                testScope = testScope,
+                throughTransitionState = TransitionState.RUNNING,
+            )
+
+            // Detect a power gesture and then wake up.
+            reset(transitionRepository)
+            powerInteractor.onCameraLaunchGestureDetected()
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            // We should head back to GONE since we started there.
+            assertThat(transitionRepository)
+                .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GONE)
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetectedAfterFinishedInAod_fromGone() =
+        testScope.runTest {
+            powerInteractor.setAwakeForTest()
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.DOZING,
+                to = KeyguardState.GONE,
+                testScope,
+            )
+            runCurrent()
+
+            // Make sure we're GONE.
+            assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+
+            // Get all the way to AOD
+            powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.DOZING,
+                testScope = testScope,
+            )
+
+            // Detect a power gesture and then wake up.
+            reset(transitionRepository)
+            powerInteractor.onCameraLaunchGestureDetected()
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            // We should go to OCCLUDED - we came from GONE, but we finished in AOD, so this is no
+            // longer an insecure camera launch and it would be bad if we unlocked now.
+            assertThat(transitionRepository)
+                .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED)
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromLockscreen() =
+        testScope.runTest {
+            powerInteractor.setAwakeForTest()
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.DOZING,
+                to = KeyguardState.LOCKSCREEN,
+                testScope,
+            )
+            runCurrent()
+
+            // Make sure we're in LOCKSCREEN.
+            assertEquals(
+                KeyguardState.LOCKSCREEN,
+                kosmos.keyguardTransitionInteractor.getFinishedState()
+            )
+
+            // Get part way to AOD.
+            powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+            runCurrent()
+
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.DOZING,
+                testScope = testScope,
+                throughTransitionState = TransitionState.RUNNING,
+            )
+
+            // Detect a power gesture and then wake up.
+            reset(transitionRepository)
+            powerInteractor.onCameraLaunchGestureDetected()
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            // We should head back to GONE since we started there.
+            assertThat(transitionRepository)
+                .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
new file mode 100644
index 0000000..f534ba5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.testKosmos
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromDreamingTransitionInteractorTest : SysuiTestCase() {
+    private val kosmos =
+        testKosmos().apply {
+            this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+        }
+
+    private val testScope = kosmos.testScope
+    private val underTest = kosmos.fromDreamingTransitionInteractor
+
+    private val powerInteractor = kosmos.powerInteractor
+    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+    @Before
+    fun setup() {
+        underTest.start()
+
+        // Transition to DOZING and set the power interactor asleep.
+        powerInteractor.setAsleepForTest()
+        runBlocking {
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.DREAMING,
+                testScope
+            )
+            kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
+            reset(transitionRepository)
+        }
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionToOccluded_ifDreamEnds_occludingActivityOnTop() =
+        testScope.runTest {
+            kosmos.fakeKeyguardRepository.setDreaming(true)
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.DREAMING,
+                testScope,
+            )
+            runCurrent()
+
+            reset(transitionRepository)
+
+            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true)
+            kosmos.fakeKeyguardRepository.setDreaming(false)
+            runCurrent()
+
+            assertThat(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.DREAMING,
+                    to = KeyguardState.OCCLUDED,
+                )
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testDoesNotTransitionToOccluded_occludingActivityOnTop_whileStillDreaming() =
+        testScope.runTest {
+            kosmos.fakeKeyguardRepository.setDreaming(true)
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.DREAMING,
+                testScope,
+            )
+            runCurrent()
+
+            reset(transitionRepository)
+
+            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true)
+            runCurrent()
+
+            assertThat(transitionRepository).noTransitionsStarted()
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionsToLockscreen_whenOccludingActivityEnds() =
+        testScope.runTest {
+            kosmos.fakeKeyguardRepository.setDreaming(true)
+            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true)
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.DREAMING,
+                testScope,
+            )
+            runCurrent()
+
+            reset(transitionRepository)
+
+            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = false)
+            runCurrent()
+
+            assertThat(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.DREAMING,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
index 6d8e7aa..c3e24d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.app.ActivityManager
+import android.app.WindowConfiguration
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -26,6 +28,7 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -42,6 +45,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 
@@ -126,7 +130,7 @@
             keyguardRepository.setKeyguardDismissible(true)
             runCurrent()
             shadeRepository.setCurrentFling(
-                FlingInfo(expand = true) // Not a dismiss fling (expand = true).
+                FlingInfo(expand = false) // Is a dismiss fling upward (expand = false).
             )
             runCurrent()
 
@@ -153,4 +157,67 @@
 
             assertThatRepository(transitionRepository).noTransitionsStarted()
         }
+
+    @Test
+    @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testDoesNotTransitionToGone_whenDismissFling_emitsNull() =
+        testScope.runTest {
+            underTest.start()
+            verify(transitionRepository, never()).startTransition(any())
+
+            keyguardRepository.setKeyguardDismissible(true)
+            runCurrent()
+
+            // The fling is null when it a) initializes b) ends and in either case we should not
+            // swipe to unlock.
+            shadeRepository.setCurrentFling(null)
+            runCurrent()
+
+            assertThatRepository(transitionRepository).noTransitionsStarted()
+        }
+
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionsToOccluded_whenShowWhenLockedActivityOnTop() =
+        testScope.runTest {
+            underTest.start()
+            runCurrent()
+
+            reset(transitionRepository)
+            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(
+                true,
+                ActivityManager.RunningTaskInfo().apply {
+                    topActivityType = WindowConfiguration.ACTIVITY_TYPE_STANDARD
+                }
+            )
+            runCurrent()
+
+            assertThatRepository(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.OCCLUDED,
+                )
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionsToDream_whenDreamActivityOnTop() =
+        testScope.runTest {
+            underTest.start()
+            runCurrent()
+
+            reset(transitionRepository)
+            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(
+                true,
+                ActivityManager.RunningTaskInfo().apply {
+                    topActivityType = WindowConfiguration.ACTIVITY_TYPE_DREAM
+                }
+            )
+            runCurrent()
+
+            assertThatRepository(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.DREAMING,
+                )
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
new file mode 100644
index 0000000..d3c4848
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.testKosmos
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromOccludedTransitionInteractorTest : SysuiTestCase() {
+    private val kosmos =
+        testKosmos().apply {
+            this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+        }
+
+    private val testScope = kosmos.testScope
+    private val underTest = kosmos.fromOccludedTransitionInteractor
+
+    private val powerInteractor = kosmos.powerInteractor
+    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+    @Before
+    fun setup() {
+        underTest.start()
+
+        // Transition to OCCLUDED and set up PowerInteractor and the occlusion repository.
+        powerInteractor.setAwakeForTest()
+        runBlocking {
+            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true)
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.OCCLUDED,
+                testScope
+            )
+            reset(transitionRepository)
+        }
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testShowWhenLockedActivity_noLongerOnTop_transitionsToLockscreen() =
+        testScope.runTest {
+            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = false)
+            runCurrent()
+
+            assertThat(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testShowWhenLockedActivity_noLongerOnTop_transitionsToGlanceableHub_ifIdleOnCommunal() =
+        testScope.runTest {
+            kosmos.fakeCommunalRepository.setTransitionState(
+                flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+            )
+            runCurrent()
+
+            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = false)
+            runCurrent()
+
+            assertThat(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                )
+        }
+}
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 f33a5c9..7ee8963 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
@@ -16,14 +16,23 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
 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.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
 import com.android.systemui.user.domain.interactor.selectedUserInteractor
@@ -31,20 +40,27 @@
 import junit.framework.Assert.assertTrue
 import junit.framework.Assert.fail
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class FromPrimaryBouncerTransitionInteractorTest : SysuiTestCase() {
-    val kosmos = testKosmos()
+    val kosmos =
+        testKosmos().apply {
+            this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+        }
     val underTest = kosmos.fromPrimaryBouncerTransitionInteractor
     val testScope = kosmos.testScope
     val selectedUserInteractor = kosmos.selectedUserInteractor
     val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+    val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
 
     @Test
     fun testSurfaceBehindVisibility() =
@@ -193,4 +209,85 @@
                 fail("surfaceBehindModel was unexpectedly null.")
             }
         }
+
+    @Test
+    @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testReturnToLockscreen_whenBouncerHides() =
+        testScope.runTest {
+            underTest.start()
+            bouncerRepository.setPrimaryShow(true)
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.PRIMARY_BOUNCER,
+                testScope
+            )
+
+            reset(transitionRepository)
+
+            bouncerRepository.setPrimaryShow(false)
+            runCurrent()
+
+            assertThat(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.LOCKSCREEN
+                )
+        }
+
+    @Test
+    @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testReturnToGlanceableHub_whenBouncerHides_ifIdleOnCommunal() =
+        testScope.runTest {
+            underTest.start()
+            kosmos.fakeCommunalRepository.setTransitionState(
+                flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+            )
+            bouncerRepository.setPrimaryShow(true)
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.PRIMARY_BOUNCER,
+                testScope
+            )
+
+            reset(transitionRepository)
+
+            bouncerRepository.setPrimaryShow(false)
+            runCurrent()
+
+            assertThat(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GLANCEABLE_HUB
+                )
+        }
+
+    @Test
+    @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionToOccluded_bouncerHide_occludingActivityOnTop() =
+        testScope.runTest {
+            underTest.start()
+            bouncerRepository.setPrimaryShow(true)
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.PRIMARY_BOUNCER,
+                testScope
+            )
+
+            reset(transitionRepository)
+
+            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+            runCurrent()
+
+            // Shouldn't transition to OCCLUDED until the bouncer hides.
+            assertThat(transitionRepository).noTransitionsStarted()
+
+            bouncerRepository.setPrimaryShow(false)
+            runCurrent()
+
+            assertThat(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.OCCLUDED
+                )
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
index b0d8de3..170d348 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
@@ -21,95 +21,80 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
+import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID
 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.WEATHER_CLOCK_BLUEPRINT_ID
 import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
 import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
-import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition
-import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config
-import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.clocks.ClockConfig
 import com.android.systemui.plugins.clocks.ClockController
-import com.android.systemui.statusbar.policy.SplitShadeStateController
-import com.android.systemui.util.mockito.any
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.whenever
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
+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.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
+@ExperimentalCoroutinesApi
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class KeyguardBlueprintInteractorTest : SysuiTestCase() {
-    private val configurationFlow = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
-    private lateinit var underTest: KeyguardBlueprintInteractor
-    private lateinit var testScope: TestScope
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val underTest by lazy { kosmos.keyguardBlueprintInteractor }
+    private val clockRepository by lazy { kosmos.fakeKeyguardClockRepository }
+    private val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
+    private val fingerprintPropertyRepository by lazy { kosmos.fakeFingerprintPropertyRepository }
 
-    val refreshTransition: MutableSharedFlow<IntraBlueprintTransition.Config> =
-        MutableSharedFlow(extraBufferCapacity = 1)
-
-    @Mock private lateinit var splitShadeStateController: SplitShadeStateController
-    @Mock private lateinit var keyguardBlueprintRepository: KeyguardBlueprintRepository
-    @Mock private lateinit var clockInteractor: KeyguardClockInteractor
     @Mock private lateinit var clockController: ClockController
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        testScope = TestScope(StandardTestDispatcher())
-        whenever(keyguardBlueprintRepository.configurationChange).thenReturn(configurationFlow)
-        whenever(keyguardBlueprintRepository.refreshTransition).thenReturn(refreshTransition)
-        whenever(clockInteractor.currentClock).thenReturn(MutableStateFlow(clockController))
-        clockInteractor.currentClock
-
-        underTest =
-            KeyguardBlueprintInteractor(
-                keyguardBlueprintRepository,
-                testScope.backgroundScope,
-                mContext,
-                splitShadeStateController,
-                clockInteractor,
-            )
     }
 
     @Test
     fun testAppliesDefaultBlueprint() {
         testScope.runTest {
-            whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
-                .thenReturn(false)
-
-            reset(keyguardBlueprintRepository)
-            configurationFlow.tryEmit(Unit)
+            val blueprint by collectLastValue(underTest.blueprint)
+            overrideResource(R.bool.config_use_split_notification_shade, false)
+            configurationRepository.onConfigurationChange()
             runCurrent()
 
-            verify(keyguardBlueprintRepository)
-                .applyBlueprint(DefaultKeyguardBlueprint.Companion.DEFAULT)
+            assertThat(blueprint?.id).isEqualTo(DefaultKeyguardBlueprint.Companion.DEFAULT)
         }
     }
 
     @Test
     fun testAppliesSplitShadeBlueprint() {
         testScope.runTest {
-            whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
-                .thenReturn(true)
-
-            reset(keyguardBlueprintRepository)
-            configurationFlow.tryEmit(Unit)
+            val blueprint by collectLastValue(underTest.blueprint)
+            overrideResource(R.bool.config_use_split_notification_shade, true)
+            configurationRepository.onConfigurationChange()
             runCurrent()
 
-            verify(keyguardBlueprintRepository)
-                .applyBlueprint(SplitShadeKeyguardBlueprint.Companion.ID)
+            assertThat(blueprint?.id).isEqualTo(SplitShadeKeyguardBlueprint.Companion.ID)
+        }
+    }
+
+    @Test
+    fun fingerprintPropertyInitialized_updatesBlueprint() {
+        testScope.runTest {
+            val blueprint by collectLastValue(underTest.blueprint)
+            overrideResource(R.bool.config_use_split_notification_shade, true)
+            fingerprintPropertyRepository.supportsUdfps() // initialize properties
+            runCurrent()
+            assertThat(blueprint?.id).isEqualTo(SplitShadeKeyguardBlueprint.Companion.ID)
         }
     }
 
@@ -117,6 +102,7 @@
     fun composeLockscreenOff_DoesAppliesSplitShadeWeatherClockBlueprint() {
         testScope.runTest {
             mSetFlagsRule.disableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
+            val blueprint by collectLastValue(underTest.blueprint)
             whenever(clockController.config)
                 .thenReturn(
                     ClockConfig(
@@ -125,15 +111,12 @@
                         description = "clock",
                     )
                 )
-            whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
-                .thenReturn(true)
-
-            reset(keyguardBlueprintRepository)
-            configurationFlow.tryEmit(Unit)
+            clockRepository.setCurrentClock(clockController)
+            overrideResource(R.bool.config_use_split_notification_shade, true)
+            configurationRepository.onConfigurationChange()
             runCurrent()
 
-            verify(keyguardBlueprintRepository, never())
-                .applyBlueprint(SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID)
+            assertThat(blueprint?.id).isNotEqualTo(SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID)
         }
     }
 
@@ -141,6 +124,7 @@
     fun testDoesAppliesSplitShadeWeatherClockBlueprint() {
         testScope.runTest {
             mSetFlagsRule.enableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
+            val blueprint by collectLastValue(underTest.blueprint)
             whenever(clockController.config)
                 .thenReturn(
                     ClockConfig(
@@ -149,15 +133,12 @@
                         description = "clock",
                     )
                 )
-            whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
-                .thenReturn(true)
-
-            reset(keyguardBlueprintRepository)
-            configurationFlow.tryEmit(Unit)
+            clockRepository.setCurrentClock(clockController)
+            overrideResource(R.bool.config_use_split_notification_shade, true)
+            configurationRepository.onConfigurationChange()
             runCurrent()
 
-            verify(keyguardBlueprintRepository)
-                .applyBlueprint(SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID)
+            assertThat(blueprint?.id).isEqualTo(SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID)
         }
     }
 
@@ -165,6 +146,7 @@
     fun testAppliesWeatherClockBlueprint() {
         testScope.runTest {
             mSetFlagsRule.enableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
+            val blueprint by collectLastValue(underTest.blueprint)
             whenever(clockController.config)
                 .thenReturn(
                     ClockConfig(
@@ -173,33 +155,12 @@
                         description = "clock",
                     )
                 )
-            whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
-                .thenReturn(false)
-
-            reset(keyguardBlueprintRepository)
-            configurationFlow.tryEmit(Unit)
+            clockRepository.setCurrentClock(clockController)
+            overrideResource(R.bool.config_use_split_notification_shade, false)
+            configurationRepository.onConfigurationChange()
             runCurrent()
 
-            verify(keyguardBlueprintRepository).applyBlueprint(WEATHER_CLOCK_BLUEPRINT_ID)
+            assertThat(blueprint?.id).isEqualTo(WEATHER_CLOCK_BLUEPRINT_ID)
         }
     }
-
-    @Test
-    fun testRefreshBlueprint() {
-        underTest.refreshBlueprint()
-        verify(keyguardBlueprintRepository).refreshBlueprint()
-    }
-
-    @Test
-    fun testTransitionToBlueprint() {
-        underTest.transitionToBlueprint("abc")
-        verify(keyguardBlueprintRepository).applyBlueprint("abc")
-    }
-
-    @Test
-    fun testRefreshBlueprintWithTransition() {
-        underTest.refreshBlueprint(Type.DefaultTransition)
-        verify(keyguardBlueprintRepository)
-            .refreshBlueprint(Config(Type.DefaultTransition, true, true))
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
new file mode 100644
index 0000000..8a77ed2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import kotlin.test.Test
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardOcclusionInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val underTest = kosmos.keyguardOcclusionInteractor
+    private val powerInteractor = kosmos.powerInteractor
+    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+    @Test
+    fun testTransitionFromPowerGesture_whileGoingToSleep_isTrue() =
+        testScope.runTest {
+            powerInteractor.setAwakeForTest()
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                testScope = testScope,
+                throughTransitionState = TransitionState.RUNNING
+            )
+
+            powerInteractor.onCameraLaunchGestureDetected()
+            runCurrent()
+
+            assertTrue(underTest.shouldTransitionFromPowerButtonGesture())
+        }
+
+    @Test
+    fun testTransitionFromPowerGesture_whileAsleep_isTrue() =
+        testScope.runTest {
+            powerInteractor.setAwakeForTest()
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                testScope = testScope,
+            )
+
+            powerInteractor.onCameraLaunchGestureDetected()
+            runCurrent()
+
+            assertTrue(underTest.shouldTransitionFromPowerButtonGesture())
+        }
+
+    @Test
+    fun testTransitionFromPowerGesture_whileWaking_isFalse() =
+        testScope.runTest {
+            powerInteractor.setAwakeForTest()
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                testScope = testScope,
+            )
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.LOCKSCREEN,
+                testScope = testScope,
+                throughTransitionState = TransitionState.RUNNING
+            )
+
+            powerInteractor.onCameraLaunchGestureDetected()
+            runCurrent()
+
+            assertFalse(underTest.shouldTransitionFromPowerButtonGesture())
+        }
+
+    @Test
+    fun testTransitionFromPowerGesture_whileAwake_isFalse() =
+        testScope.runTest {
+            powerInteractor.setAwakeForTest()
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                testScope = testScope,
+            )
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.LOCKSCREEN,
+                testScope = testScope,
+            )
+
+            powerInteractor.onCameraLaunchGestureDetected()
+            runCurrent()
+
+            assertFalse(underTest.shouldTransitionFromPowerButtonGesture())
+        }
+
+    @Test
+    fun testShowWhenLockedActivityLaunchedFromPowerGesture_notTrueSecondTime() =
+        testScope.runTest {
+            val values by collectValues(underTest.showWhenLockedActivityLaunchedFromPowerGesture)
+            powerInteractor.setAsleepForTest()
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                testScope = testScope,
+            )
+
+            powerInteractor.onCameraLaunchGestureDetected()
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+            runCurrent()
+
+            assertThat(values)
+                .containsExactly(
+                    false,
+                    true,
+                )
+
+            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false)
+            runCurrent()
+
+            assertThat(values)
+                .containsExactly(
+                    false,
+                    true,
+                    false,
+                )
+
+            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+            runCurrent()
+
+            assertThat(values)
+                .containsExactly(
+                    false,
+                    true,
+                    // Power button gesture was not triggered a second time, so this should remain
+                    // false.
+                    false,
+                )
+        }
+
+    @Test
+    fun testShowWhenLockedActivityLaunchedFromPowerGesture_falseIfReturningToGone() =
+        testScope.runTest {
+            val values by collectValues(underTest.showWhenLockedActivityLaunchedFromPowerGesture)
+            powerInteractor.setAwakeForTest()
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                testScope = testScope,
+            )
+
+            powerInteractor.setAsleepForTest()
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.AOD,
+                testScope = testScope,
+                throughTransitionState = TransitionState.RUNNING
+            )
+
+            powerInteractor.onCameraLaunchGestureDetected()
+            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.GONE,
+                testScope = testScope,
+            )
+
+            assertThat(values)
+                .containsExactly(
+                    false,
+                )
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index d2a8444..45b2a42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -46,7 +46,9 @@
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
@@ -242,6 +244,8 @@
     private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
     private lateinit var userTracker: UserTracker
 
+    private val kosmos = testKosmos()
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -311,6 +315,7 @@
                             featureFlags = featureFlags,
                         )
                         .keyguardInteractor,
+                shadeInteractor = kosmos.shadeInteractor,
                 lockPatternUtils = lockPatternUtils,
                 keyguardStateController = keyguardStateController,
                 userTracker = userTracker,
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 69cd173..95606ae 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
@@ -18,15 +18,15 @@
 
 import android.app.StatusBarManager
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
+import com.android.systemui.Flags
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
-import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
 import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.communal.shared.model.CommunalSceneKey
-import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeCommandQueue
@@ -40,7 +40,6 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
-import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
@@ -48,7 +47,6 @@
 import com.android.systemui.shade.data.repository.fakeShadeRepository
 import com.android.systemui.statusbar.commandQueue
 import com.android.systemui.testKosmos
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -92,30 +90,26 @@
     private var commandQueue = kosmos.fakeCommandQueue
     private val shadeRepository = kosmos.fakeShadeRepository
     private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
-    private val transitionInteractor = kosmos.keyguardTransitionInteractor
     private lateinit var featureFlags: FakeFeatureFlags
 
     // Used to verify transition requests for test output
     @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
-    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
 
     private val fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor
-    private lateinit var fromDreamingTransitionInteractor: FromDreamingTransitionInteractor
-    private lateinit var fromDozingTransitionInteractor: FromDozingTransitionInteractor
-    private lateinit var fromOccludedTransitionInteractor: FromOccludedTransitionInteractor
-    private lateinit var fromGoneTransitionInteractor: FromGoneTransitionInteractor
-    private lateinit var fromAodTransitionInteractor: FromAodTransitionInteractor
-    private lateinit var fromAlternateBouncerTransitionInteractor:
-        FromAlternateBouncerTransitionInteractor
+    private val fromDreamingTransitionInteractor = kosmos.fromDreamingTransitionInteractor
+    private val fromDozingTransitionInteractor = kosmos.fromDozingTransitionInteractor
+    private val fromOccludedTransitionInteractor = kosmos.fromOccludedTransitionInteractor
+    private val fromGoneTransitionInteractor = kosmos.fromGoneTransitionInteractor
+    private val fromAodTransitionInteractor = kosmos.fromAodTransitionInteractor
+    private val fromAlternateBouncerTransitionInteractor =
+        kosmos.fromAlternateBouncerTransitionInteractor
     private val fromPrimaryBouncerTransitionInteractor =
         kosmos.fromPrimaryBouncerTransitionInteractor
-    private lateinit var fromDreamingLockscreenHostedTransitionInteractor:
-        FromDreamingLockscreenHostedTransitionInteractor
-    private lateinit var fromGlanceableHubTransitionInteractor:
-        FromGlanceableHubTransitionInteractor
+    private val fromDreamingLockscreenHostedTransitionInteractor =
+        kosmos.fromDreamingLockscreenHostedTransitionInteractor
+    private val fromGlanceableHubTransitionInteractor = kosmos.fromGlanceableHubTransitionInteractor
 
     private val powerInteractor = kosmos.powerInteractor
-    private val keyguardInteractor = kosmos.keyguardInteractor
     private val communalInteractor = kosmos.communalInteractor
 
     @Before
@@ -125,121 +119,21 @@
         whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
 
         mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
+        mSetFlagsRule.disableFlags(
+            Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+        )
         featureFlags = FakeFeatureFlags()
 
-        val glanceableHubTransitions =
-            GlanceableHubTransitions(
-                bgDispatcher = kosmos.testDispatcher,
-                transitionInteractor = transitionInteractor,
-                transitionRepository = transitionRepository,
-                communalInteractor = communalInteractor
-            )
-
         fromLockscreenTransitionInteractor.start()
         fromPrimaryBouncerTransitionInteractor.start()
-
-        fromDreamingTransitionInteractor =
-            FromDreamingTransitionInteractor(
-                    scope = testScope,
-                    bgDispatcher = kosmos.testDispatcher,
-                    mainDispatcher = kosmos.testDispatcher,
-                    keyguardInteractor = keyguardInteractor,
-                    transitionRepository = transitionRepository,
-                    transitionInteractor = transitionInteractor,
-                    glanceableHubTransitions = glanceableHubTransitions,
-                )
-                .apply { start() }
-
-        fromDreamingLockscreenHostedTransitionInteractor =
-            FromDreamingLockscreenHostedTransitionInteractor(
-                    scope = testScope,
-                    bgDispatcher = kosmos.testDispatcher,
-                    mainDispatcher = kosmos.testDispatcher,
-                    keyguardInteractor = keyguardInteractor,
-                    transitionRepository = transitionRepository,
-                    transitionInteractor = transitionInteractor,
-                )
-                .apply { start() }
-
-        fromAodTransitionInteractor =
-            FromAodTransitionInteractor(
-                    scope = testScope,
-                    bgDispatcher = kosmos.testDispatcher,
-                    mainDispatcher = kosmos.testDispatcher,
-                    keyguardInteractor = keyguardInteractor,
-                    transitionRepository = transitionRepository,
-                    transitionInteractor = transitionInteractor,
-                )
-                .apply { start() }
-
-        fromGoneTransitionInteractor =
-            FromGoneTransitionInteractor(
-                    scope = testScope,
-                    bgDispatcher = kosmos.testDispatcher,
-                    mainDispatcher = kosmos.testDispatcher,
-                    keyguardInteractor = keyguardInteractor,
-                    transitionRepository = transitionRepository,
-                    transitionInteractor = transitionInteractor,
-                    powerInteractor = powerInteractor,
-                    communalInteractor = communalInteractor,
-                )
-                .apply { start() }
-
-        fromDozingTransitionInteractor =
-            FromDozingTransitionInteractor(
-                    scope = testScope,
-                    bgDispatcher = kosmos.testDispatcher,
-                    mainDispatcher = kosmos.testDispatcher,
-                    keyguardInteractor = keyguardInteractor,
-                    transitionRepository = transitionRepository,
-                    transitionInteractor = transitionInteractor,
-                    powerInteractor = powerInteractor,
-                    communalInteractor = communalInteractor,
-                )
-                .apply { start() }
-
-        fromOccludedTransitionInteractor =
-            FromOccludedTransitionInteractor(
-                    scope = testScope,
-                    bgDispatcher = kosmos.testDispatcher,
-                    mainDispatcher = kosmos.testDispatcher,
-                    keyguardInteractor = keyguardInteractor,
-                    transitionRepository = transitionRepository,
-                    transitionInteractor = transitionInteractor,
-                    powerInteractor = powerInteractor,
-                    communalInteractor = communalInteractor,
-                )
-                .apply { start() }
-
-        fromAlternateBouncerTransitionInteractor =
-            FromAlternateBouncerTransitionInteractor(
-                    scope = testScope,
-                    bgDispatcher = kosmos.testDispatcher,
-                    mainDispatcher = kosmos.testDispatcher,
-                    keyguardInteractor = keyguardInteractor,
-                    transitionRepository = transitionRepository,
-                    transitionInteractor = transitionInteractor,
-                    communalInteractor = communalInteractor,
-                    powerInteractor = powerInteractor,
-                )
-                .apply { start() }
-
-        fromGlanceableHubTransitionInteractor =
-            FromGlanceableHubTransitionInteractor(
-                    scope = testScope,
-                    bgDispatcher = kosmos.testDispatcher,
-                    mainDispatcher = kosmos.testDispatcher,
-                    glanceableHubTransitions = glanceableHubTransitions,
-                    keyguardInteractor = keyguardInteractor,
-                    transitionRepository = transitionRepository,
-                    transitionInteractor = transitionInteractor,
-                    powerInteractor = powerInteractor,
-                )
-                .apply { start() }
-
-        mSetFlagsRule.disableFlags(
-            FLAG_KEYGUARD_WM_STATE_REFACTOR,
-        )
+        fromDreamingTransitionInteractor.start()
+        fromDreamingLockscreenHostedTransitionInteractor.start()
+        fromAodTransitionInteractor.start()
+        fromGoneTransitionInteractor.start()
+        fromDozingTransitionInteractor.start()
+        fromOccludedTransitionInteractor.start()
+        fromAlternateBouncerTransitionInteractor.start()
+        fromGlanceableHubTransitionInteractor.start()
     }
 
     @Test
@@ -256,7 +150,9 @@
                 .startedTransition(
                     to = KeyguardState.PRIMARY_BOUNCER,
                     from = KeyguardState.LOCKSCREEN,
-                    ownerName = "FromLockscreenTransitionInteractor",
+                    ownerName =
+                        "FromLockscreenTransitionInteractor" +
+                            "(#listenForLockscreenToPrimaryBouncer)",
                     animatorAssertion = { it.isNotNull() }
                 )
 
@@ -281,7 +177,7 @@
                 .startedTransition(
                     to = KeyguardState.DOZING,
                     from = KeyguardState.OCCLUDED,
-                    ownerName = "FromOccludedTransitionInteractor",
+                    ownerName = "FromOccludedTransitionInteractor(Sleep transition triggered)",
                     animatorAssertion = { it.isNotNull() }
                 )
 
@@ -306,7 +202,7 @@
                 .startedTransition(
                     to = KeyguardState.AOD,
                     from = KeyguardState.OCCLUDED,
-                    ownerName = "FromOccludedTransitionInteractor",
+                    ownerName = "FromOccludedTransitionInteractor(Sleep transition triggered)",
                     animatorAssertion = { it.isNotNull() }
                 )
 
@@ -388,7 +284,7 @@
                 .startedTransition(
                     to = KeyguardState.DOZING,
                     from = KeyguardState.LOCKSCREEN,
-                    ownerName = "FromLockscreenTransitionInteractor",
+                    ownerName = "FromLockscreenTransitionInteractor(Sleep transition triggered)",
                     animatorAssertion = { it.isNotNull() }
                 )
 
@@ -413,7 +309,7 @@
                 .startedTransition(
                     to = KeyguardState.AOD,
                     from = KeyguardState.LOCKSCREEN,
-                    ownerName = "FromLockscreenTransitionInteractor",
+                    ownerName = "FromLockscreenTransitionInteractor(Sleep transition triggered)",
                     animatorAssertion = { it.isNotNull() }
                 )
 
@@ -574,8 +470,9 @@
             runCurrent()
 
             // WHEN the device begins to wake
+            keyguardRepository.setKeyguardShowing(true)
             powerInteractor.setAwakeForTest()
-            runCurrent()
+            advanceTimeBy(60L)
 
             assertThat(transitionRepository)
                 .startedTransition(
@@ -630,14 +527,16 @@
         }
 
     @Test
-    fun dozingToGone() =
+    fun dozingToGoneWithUnlock() =
         testScope.runTest {
             // GIVEN a prior transition has run to DOZING
             runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DOZING)
+            runCurrent()
 
             // WHEN biometrics succeeds with wake and unlock mode
+            powerInteractor.setAwakeForTest()
             keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
-            runCurrent()
+            advanceTimeBy(60L)
 
             assertThat(transitionRepository)
                 .startedTransition(
@@ -651,6 +550,81 @@
         }
 
     @Test
+    fun dozingToPrimaryBouncer() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to DOZING
+            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DOZING)
+            runCurrent()
+
+            // WHEN awaked by a request to show the primary bouncer, as can happen if SPFS is
+            // touched after boot
+            powerInteractor.setAwakeForTest()
+            bouncerRepository.setPrimaryShow(true)
+            advanceTimeBy(60L)
+
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    from = KeyguardState.DOZING,
+                    ownerName = "FromDozingTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
+
+            coroutineContext.cancelChildren()
+        }
+
+    /** This handles security method NONE and screen off with lock timeout */
+    @Test
+    fun dozingToGoneWithKeyguardNotShowing() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to DOZING
+            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DOZING)
+            runCurrent()
+
+            // WHEN the device wakes up without a keyguard
+            keyguardRepository.setKeyguardShowing(false)
+            keyguardRepository.setKeyguardDismissible(true)
+            powerInteractor.setAwakeForTest()
+            advanceTimeBy(60L)
+
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.GONE,
+                    from = KeyguardState.DOZING,
+                    ownerName = "FromDozingTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
+
+            coroutineContext.cancelChildren()
+        }
+
+    /** This handles security method NONE and screen off with lock timeout */
+    @Test
+    fun dreamingToGoneWithKeyguardNotShowing() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to DREAMING
+            keyguardRepository.setDreamingWithOverlay(true)
+            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
+            runCurrent()
+
+            // WHEN the device wakes up without a keyguard
+            keyguardRepository.setKeyguardShowing(false)
+            keyguardRepository.setKeyguardDismissible(true)
+            keyguardRepository.setDreamingWithOverlay(false)
+            advanceTimeBy(60L)
+
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.GONE,
+                    from = KeyguardState.DREAMING,
+                    ownerName = "FromDreamingTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun dozingToGlanceableHub() =
         testScope.runTest {
             // GIVEN a prior transition has run to DOZING
@@ -659,15 +633,16 @@
 
             // GIVEN the device is idle on the glanceable hub
             val idleTransitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(
-                    ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(CommunalScenes.Communal)
                 )
             communalInteractor.setTransitionState(idleTransitionState)
             runCurrent()
 
             // WHEN the device begins to wake
+            keyguardRepository.setKeyguardShowing(true)
             powerInteractor.setAwakeForTest()
-            runCurrent()
+            advanceTimeBy(60L)
 
             assertThat(transitionRepository)
                 .startedTransition(
@@ -698,7 +673,7 @@
                 .startedTransition(
                     to = KeyguardState.DOZING,
                     from = KeyguardState.GONE,
-                    ownerName = "FromGoneTransitionInteractor",
+                    ownerName = "FromGoneTransitionInteractor(Sleep transition triggered)",
                     animatorAssertion = { it.isNotNull() }
                 )
 
@@ -723,7 +698,7 @@
                 .startedTransition(
                     to = KeyguardState.AOD,
                     from = KeyguardState.GONE,
-                    ownerName = "FromGoneTransitionInteractor",
+                    ownerName = "FromGoneTransitionInteractor(Sleep transition triggered)",
                     animatorAssertion = { it.isNotNull() }
                 )
 
@@ -816,8 +791,8 @@
 
             // GIVEN the device is idle on the glanceable hub
             val idleTransitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(
-                    ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(CommunalScenes.Communal)
                 )
             communalInteractor.setTransitionState(idleTransitionState)
             runCurrent()
@@ -965,8 +940,8 @@
 
             // GIVEN the device is idle on the glanceable hub
             val idleTransitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(
-                    ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(CommunalScenes.Communal)
                 )
             communalInteractor.setTransitionState(idleTransitionState)
             runCurrent()
@@ -990,12 +965,14 @@
     @Test
     fun primaryBouncerToAod() =
         testScope.runTest {
+            // GIVEN aod available
+            keyguardRepository.setAodAvailable(true)
+            runCurrent()
+
             // GIVEN a prior transition has run to PRIMARY_BOUNCER
             bouncerRepository.setPrimaryShow(true)
             runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER)
 
-            // GIVEN aod available and starting to sleep
-            keyguardRepository.setAodAvailable(true)
             powerInteractor.setAsleepForTest()
 
             // WHEN the primaryBouncer stops showing
@@ -1005,7 +982,8 @@
             // THEN a transition to AOD should occur
             assertThat(transitionRepository)
                 .startedTransition(
-                    ownerName = "FromPrimaryBouncerTransitionInteractor",
+                    ownerName =
+                        "FromPrimaryBouncerTransitionInteractor" + "(Sleep transition triggered)",
                     from = KeyguardState.PRIMARY_BOUNCER,
                     to = KeyguardState.AOD,
                     animatorAssertion = { it.isNotNull() },
@@ -1032,7 +1010,8 @@
             // THEN a transition to DOZING should occur
             assertThat(transitionRepository)
                 .startedTransition(
-                    ownerName = "FromPrimaryBouncerTransitionInteractor",
+                    ownerName =
+                        "FromPrimaryBouncerTransitionInteractor" + "(Sleep transition triggered)",
                     from = KeyguardState.PRIMARY_BOUNCER,
                     to = KeyguardState.DOZING,
                     animatorAssertion = { it.isNotNull() },
@@ -1073,8 +1052,8 @@
 
             // GIVEN the device is idle on the glanceable hub
             val idleTransitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(
-                    ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(CommunalScenes.Communal)
                 )
             communalInteractor.setTransitionState(idleTransitionState)
             runCurrent()
@@ -1108,8 +1087,8 @@
 
             // GIVEN the device is idle on the glanceable hub
             val idleTransitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(
-                    ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(CommunalScenes.Communal)
                 )
             communalInteractor.setTransitionState(idleTransitionState)
             runCurrent()
@@ -1226,8 +1205,8 @@
 
             // GIVEN the device is idle on the glanceable hub
             val idleTransitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(
-                    ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(CommunalScenes.Communal)
                 )
             communalInteractor.setTransitionState(idleTransitionState)
             runCurrent()
@@ -1335,8 +1314,9 @@
 
             // WHEN the keyguard is occluded and device wakes up
             keyguardRepository.setKeyguardOccluded(true)
+            keyguardRepository.setKeyguardShowing(true)
             powerInteractor.setAwakeForTest()
-            runCurrent()
+            advanceTimeBy(60L)
 
             // THEN a transition to OCCLUDED should occur
             assertThat(transitionRepository)
@@ -1414,13 +1394,13 @@
             runCurrent()
 
             // WHEN a transition to the glanceable hub starts
-            val currentScene = CommunalSceneKey.Blank
-            val targetScene = CommunalSceneKey.Communal
+            val currentScene = CommunalScenes.Blank
+            val targetScene = CommunalScenes.Communal
 
             val progress = MutableStateFlow(0f)
             val transitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(
-                    ObservableCommunalTransitionState.Transition(
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
                         fromScene = currentScene,
                         toScene = targetScene,
                         progress = progress,
@@ -1561,7 +1541,9 @@
             // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
             assertThat(transitionRepository)
                 .startedTransition(
-                    ownerName = "FromLockscreenTransitionInteractor",
+                    ownerName =
+                        "FromLockscreenTransitionInteractor" +
+                            "(#listenForLockscreenToPrimaryBouncerDragging)",
                     from = KeyguardState.LOCKSCREEN,
                     to = KeyguardState.PRIMARY_BOUNCER,
                     animatorAssertion = { it.isNull() }, // dragging should be manually animated
@@ -1593,13 +1575,13 @@
             runCurrent()
 
             // WHEN a glanceable hub transition starts
-            val currentScene = CommunalSceneKey.Blank
-            val targetScene = CommunalSceneKey.Communal
+            val currentScene = CommunalScenes.Blank
+            val targetScene = CommunalScenes.Communal
 
             val progress = MutableStateFlow(0f)
             val transitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(
-                    ObservableCommunalTransitionState.Transition(
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
                         fromScene = currentScene,
                         toScene = targetScene,
                         progress = progress,
@@ -1624,8 +1606,8 @@
             clearInvocations(transitionRepository)
             runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
             val idleTransitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(
-                    ObservableCommunalTransitionState.Idle(currentScene)
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(currentScene)
                 )
             communalInteractor.setTransitionState(idleTransitionState)
             runCurrent()
@@ -1649,13 +1631,13 @@
             runCurrent()
 
             // WHEN a transition away from glanceable hub starts
-            val currentScene = CommunalSceneKey.Communal
-            val targetScene = CommunalSceneKey.Blank
+            val currentScene = CommunalScenes.Communal
+            val targetScene = CommunalScenes.Blank
 
             val progress = MutableStateFlow(0f)
             val transitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(
-                    ObservableCommunalTransitionState.Transition(
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
                         fromScene = currentScene,
                         toScene = targetScene,
                         progress = progress,
@@ -1679,8 +1661,8 @@
             clearInvocations(transitionRepository)
             runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.LOCKSCREEN)
             val idleTransitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(
-                    ObservableCommunalTransitionState.Idle(currentScene)
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(currentScene)
                 )
             communalInteractor.setTransitionState(idleTransitionState)
             runCurrent()
@@ -1766,8 +1748,8 @@
 
             // GIVEN the device is idle on the glanceable hub
             val idleTransitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(
-                    ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(CommunalScenes.Communal)
                 )
             communalInteractor.setTransitionState(idleTransitionState)
             runCurrent()
@@ -1823,12 +1805,12 @@
             runCurrent()
 
             // WHEN a transition away from glanceable hub starts
-            val currentScene = CommunalSceneKey.Communal
-            val targetScene = CommunalSceneKey.Blank
+            val currentScene = CommunalScenes.Communal
+            val targetScene = CommunalScenes.Blank
 
             val transitionState =
-                MutableStateFlow<ObservableCommunalTransitionState>(
-                    ObservableCommunalTransitionState.Transition(
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
                         fromScene = currentScene,
                         toScene = targetScene,
                         progress = flowOf(0f, 0.1f),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
index 87eee1a..0a29821 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
@@ -22,23 +22,25 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.doze.util.BurnInHelperWrapper
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -53,18 +55,19 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class UdfpsKeyguardInteractorTest : SysuiTestCase() {
+    val kosmos = testKosmos()
+    val testScope = kosmos.testScope
+    val configRepository = kosmos.fakeConfigurationRepository
+    val keyguardRepository = kosmos.fakeKeyguardRepository
+
     private val burnInProgress = 1f
     private val burnInYOffset = 20
     private val burnInXOffset = 10
 
-    private lateinit var testScope: TestScope
-    private lateinit var configRepository: FakeConfigurationRepository
     private lateinit var bouncerRepository: KeyguardBouncerRepository
-    private lateinit var keyguardRepository: FakeKeyguardRepository
     private lateinit var fakeCommandQueue: FakeCommandQueue
     private lateinit var burnInInteractor: BurnInInteractor
     private lateinit var shadeRepository: FakeShadeRepository
-    private lateinit var keyguardInteractor: KeyguardInteractor
     private lateinit var powerInteractor: PowerInteractor
 
     @Mock private lateinit var burnInHelper: BurnInHelperWrapper
@@ -75,12 +78,6 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        testScope = TestScope()
-        configRepository = FakeConfigurationRepository()
-        KeyguardInteractorFactory.create().let {
-            keyguardInteractor = it.keyguardInteractor
-            keyguardRepository = it.repository
-        }
         bouncerRepository = FakeKeyguardBouncerRepository()
         shadeRepository = FakeShadeRepository()
         fakeCommandQueue = FakeCommandQueue()
@@ -89,8 +86,8 @@
                 context,
                 burnInHelper,
                 testScope.backgroundScope,
-                configRepository,
-                keyguardInteractor
+                kosmos.configurationInteractor,
+                kosmos.keyguardInteractor
             )
         powerInteractor = PowerInteractorFactory.create().powerInteractor
 
@@ -98,7 +95,7 @@
             UdfpsKeyguardInteractor(
                 configRepository,
                 burnInInteractor,
-                keyguardInteractor,
+                kosmos.keyguardInteractor,
                 shadeRepository,
                 dialogManager,
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
index a4483bd..6d605a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
@@ -375,4 +375,323 @@
                 values
             )
         }
+
+    @Test
+    fun testLockscreenVisibility_usesFromState_ifCanceled() =
+        testScope.runTest {
+            val values by collectValues(underTest.lockscreenVisibility)
+
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                testScope
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    // Initially should be true, as we start in LOCKSCREEN.
+                    true,
+                    // Then, false, since we finish in GONE.
+                    false,
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                )
+            )
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    true,
+                    // Should remain false as we transition from GONE.
+                    false,
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.CANCELED,
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                )
+            )
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    true,
+                    false,
+                    // If we cancel and then go from LS -> GONE, we should immediately flip to the
+                    // visibility of the from state (LS).
+                    true,
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    true,
+                    false,
+                    true,
+                ),
+                values
+            )
+        }
+
+    /**
+     * Tests the special case for insecure camera launch. CANCELING a transition from GONE and then
+     * STARTING a transition back to GONE should never show the lockscreen, even though the current
+     * state during the AOD/isAsleep -> GONE transition is AOD (where lockscreen visibility = true).
+     */
+    @Test
+    fun testLockscreenVisibility_falseDuringTransitionToGone_fromCanceledGone() =
+        testScope.runTest {
+            val values by collectValues(underTest.lockscreenVisibility)
+
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                testScope
+            )
+
+            runCurrent()
+            assertEquals(
+                listOf(
+                    true,
+                    // Not visible since we're GONE.
+                    false,
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                )
+            )
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.CANCELED,
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                )
+            )
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+            assertEquals(
+                listOf(
+                    true,
+                    // Remains not visible from GONE -> AOD (canceled) -> AOD since we never
+                    // FINISHED in AOD, and special-case handling for the insecure camera launch
+                    // ensures that we use the lockscreen visibility for GONE (false) if we're
+                    // STARTED to GONE after a CANCELED from GONE.
+                    false,
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.LOCKSCREEN,
+                testScope,
+            )
+
+            assertEquals(
+                listOf(
+                    true,
+                    false,
+                    // Make sure there's no stuck overrides or something - we should make lockscreen
+                    // visible again once we're finished in LOCKSCREEN.
+                    true,
+                ),
+                values
+            )
+        }
+
+    /**  */
+    @Test
+    fun testLockscreenVisibility_trueDuringTransitionToGone_fromNotCanceledGone() =
+        testScope.runTest {
+            val values by collectValues(underTest.lockscreenVisibility)
+
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                testScope
+            )
+
+            runCurrent()
+            assertEquals(
+                listOf(
+                    true,
+                    // Not visible when finished in GONE.
+                    false,
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                )
+            )
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    true,
+                    // Still not visible during GONE -> AOD.
+                    false,
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    true,
+                    false,
+                    // Visible now that we're FINISHED in AOD.
+                    true
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    true,
+                    false,
+                    // Remains visible from AOD during transition.
+                    true
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+            assertEquals(
+                listOf(
+                    true,
+                    false,
+                    true,
+                    // Until we're finished in GONE again.
+                    false
+                ),
+                values
+            )
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index 699284e..09c56b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -22,7 +22,6 @@
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.LockIconViewController
 import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.SysuiTestCase
@@ -37,8 +36,10 @@
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
 import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.TestScope
 import org.junit.Before
 import org.junit.Test
@@ -53,13 +54,13 @@
 @RunWith(JUnit4::class)
 @SmallTest
 class DefaultDeviceEntrySectionTest : 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
     private lateinit var featureFlags: FakeFeatureFlags
     @Mock private lateinit var lockIconViewController: LockIconViewController
     @Mock private lateinit var falsingManager: FalsingManager
+    @Mock private lateinit var deviceEntryIconViewModel: DeviceEntryIconViewModel
     private lateinit var underTest: DefaultDeviceEntrySection
 
     @Before
@@ -73,14 +74,13 @@
         underTest =
             DefaultDeviceEntrySection(
                 TestScope().backgroundScope,
-                keyguardUpdateMonitor,
                 authController,
                 windowManager,
                 context,
                 notificationPanelView,
                 featureFlags,
                 { lockIconViewController },
-                { mock(DeviceEntryIconViewModel::class.java) },
+                { deviceEntryIconViewModel },
                 { mock(DeviceEntryForegroundViewModel::class.java) },
                 { mock(DeviceEntryBackgroundViewModel::class.java) },
                 { falsingManager },
@@ -129,6 +129,7 @@
     @Test
     fun applyConstraints_udfps_refactor_on() {
         mSetFlagsRule.enableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+        whenever(deviceEntryIconViewModel.isUdfpsSupported).thenReturn(MutableStateFlow(false))
         val cs = ConstraintSet()
         underTest.applyConstraints(cs)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
new file mode 100644
index 0000000..4bb0d47
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel.Companion.UNLOCKED_DELAY_MS
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntryIconViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+    private lateinit var fingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository
+    private lateinit var deviceEntryIconTransition: FakeDeviceEntryIconTransition
+    private lateinit var underTest: DeviceEntryIconViewModel
+
+    @Before
+    fun setUp() {
+        keyguardRepository = kosmos.fakeKeyguardRepository
+        fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+        fingerprintAuthRepository = kosmos.fakeDeviceEntryFingerprintAuthRepository
+        deviceEntryIconTransition = kosmos.fakeDeviceEntryIconViewModelTransition
+        underTest = kosmos.deviceEntryIconViewModel
+    }
+
+    @Test
+    fun isLongPressEnabled_udfpsRunning() =
+        testScope.runTest {
+            val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled)
+            fingerprintPropertyRepository.supportsUdfps()
+            fingerprintAuthRepository.setIsRunning(true)
+            keyguardRepository.setKeyguardDismissible(false)
+            assertThat(isLongPressEnabled).isFalse()
+        }
+
+    @Test
+    fun isLongPressEnabled_unlocked() =
+        testScope.runTest {
+            val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled)
+            fingerprintPropertyRepository.supportsUdfps()
+            keyguardRepository.setKeyguardDismissible(true)
+            advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay
+            runCurrent()
+            assertThat(isLongPressEnabled).isTrue()
+        }
+
+    @Test
+    fun isLongPressEnabled_lock() =
+        testScope.runTest {
+            val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled)
+            keyguardRepository.setKeyguardDismissible(false)
+
+            // udfps supported
+            fingerprintPropertyRepository.supportsUdfps()
+            assertThat(isLongPressEnabled).isTrue()
+
+            // udfps isn't supported
+            fingerprintPropertyRepository.supportsRearFps()
+            assertThat(isLongPressEnabled).isFalse()
+        }
+
+    @Test
+    fun isVisible() =
+        testScope.runTest {
+            val isVisible by collectLastValue(underTest.isVisible)
+            deviceEntryIconTransitionAlpha(1f)
+            assertThat(isVisible).isTrue()
+
+            deviceEntryIconTransitionAlpha(0f)
+            assertThat(isVisible).isFalse()
+
+            deviceEntryIconTransitionAlpha(.5f)
+            assertThat(isVisible).isTrue()
+        }
+
+    private fun deviceEntryIconTransitionAlpha(alpha: Float) {
+        deviceEntryIconTransition.setDeviceEntryParentViewAlpha(alpha)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 2ec2fe3..7290863 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -54,6 +54,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -218,6 +219,7 @@
                 quickAffordanceInteractor =
                     KeyguardQuickAffordanceInteractor(
                         keyguardInteractor = keyguardInteractor,
+                        shadeInteractor = kosmos.shadeInteractor,
                         lockPatternUtils = lockPatternUtils,
                         keyguardStateController = keyguardStateController,
                         userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 22a2e93..f252163 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -130,6 +130,24 @@
             assertThat(value()).isEqualTo(LARGE)
         }
 
+    @Test
+    fun isLargeClockVisible_whenLargeClockSize_isTrue() =
+        scope.runTest {
+            fakeSettings.putInt(LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1)
+            keyguardClockRepository.setClockSize(LARGE)
+            var value = collectLastValue(underTest.isLargeClockVisible)
+            assertThat(value()).isEqualTo(true)
+        }
+
+    @Test
+    fun isLargeClockVisible_whenSmallClockSize_isFalse() =
+        scope.runTest {
+            fakeSettings.putInt(LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1)
+            keyguardClockRepository.setClockSize(SMALL)
+            var value = collectLastValue(underTest.isLargeClockVisible)
+            assertThat(value()).isEqualTo(false)
+        }
+
     private fun setupMockClock() {
         whenever(clock.largeClock).thenReturn(largeClock)
         whenever(largeClock.config).thenReturn(clockFaceConfig)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
deleted file mode 100644
index 864acfb..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.viewmodel
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.Flags as AConfigFlags
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(JUnit4::class)
-class KeyguardIndicationAreaViewModelTest : SysuiTestCase() {
-
-    @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
-    @Mock private lateinit var shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel
-
-    private lateinit var underTest: KeyguardIndicationAreaViewModel
-    private lateinit var repository: FakeKeyguardRepository
-
-    private val startButtonFlow =
-        MutableStateFlow<KeyguardQuickAffordanceViewModel>(
-            KeyguardQuickAffordanceViewModel(
-                slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()
-            )
-        )
-    private val endButtonFlow =
-        MutableStateFlow<KeyguardQuickAffordanceViewModel>(
-            KeyguardQuickAffordanceViewModel(
-                slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId()
-            )
-        )
-    private val alphaFlow = MutableStateFlow<Float>(1f)
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
-
-        whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
-            .thenReturn(RETURNED_BURN_IN_OFFSET)
-
-        val withDeps = KeyguardInteractorFactory.create()
-        val keyguardInteractor = withDeps.keyguardInteractor
-        repository = withDeps.repository
-
-        val bottomAreaViewModel: KeyguardBottomAreaViewModel = mock()
-        whenever(bottomAreaViewModel.startButton).thenReturn(startButtonFlow)
-        whenever(bottomAreaViewModel.endButton).thenReturn(endButtonFlow)
-        whenever(bottomAreaViewModel.alpha).thenReturn(alphaFlow)
-        underTest =
-            KeyguardIndicationAreaViewModel(
-                keyguardInteractor = keyguardInteractor,
-                bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
-                keyguardBottomAreaViewModel = bottomAreaViewModel,
-                burnInHelperWrapper = burnInHelperWrapper,
-                shortcutsCombinedViewModel = shortcutsCombinedViewModel,
-                configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
-            )
-    }
-
-    @Test
-    fun alpha() = runTest {
-        val value = collectLastValue(underTest.alpha)
-
-        assertThat(value()).isEqualTo(1f)
-        alphaFlow.value = 0.1f
-        assertThat(value()).isEqualTo(0.1f)
-        alphaFlow.value = 0.5f
-        assertThat(value()).isEqualTo(0.5f)
-        alphaFlow.value = 0.2f
-        assertThat(value()).isEqualTo(0.2f)
-        alphaFlow.value = 0f
-        assertThat(value()).isEqualTo(0f)
-    }
-
-    @Test
-    fun isIndicationAreaPadded() = runTest {
-        repository.setKeyguardShowing(true)
-        val value = collectLastValue(underTest.isIndicationAreaPadded)
-
-        assertThat(value()).isFalse()
-        startButtonFlow.value = startButtonFlow.value.copy(isVisible = true)
-        assertThat(value()).isTrue()
-        endButtonFlow.value = endButtonFlow.value.copy(isVisible = true)
-        assertThat(value()).isTrue()
-        startButtonFlow.value = startButtonFlow.value.copy(isVisible = false)
-        assertThat(value()).isTrue()
-        endButtonFlow.value = endButtonFlow.value.copy(isVisible = false)
-        assertThat(value()).isFalse()
-    }
-
-    @Test
-    fun indicationAreaTranslationX() = runTest {
-        val value = collectLastValue(underTest.indicationAreaTranslationX)
-
-        assertThat(value()).isEqualTo(0f)
-        repository.setClockPosition(100, 100)
-        assertThat(value()).isEqualTo(100f)
-        repository.setClockPosition(200, 100)
-        assertThat(value()).isEqualTo(200f)
-        repository.setClockPosition(200, 200)
-        assertThat(value()).isEqualTo(200f)
-        repository.setClockPosition(300, 100)
-        assertThat(value()).isEqualTo(300f)
-    }
-
-    @Test
-    fun indicationAreaTranslationY() = runTest {
-        val value = collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET))
-
-        // Negative 0 - apparently there's a difference in floating point arithmetic - FML
-        assertThat(value()).isEqualTo(-0f)
-        val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f)
-        assertThat(value()).isEqualTo(expected1)
-        val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f)
-        assertThat(value()).isEqualTo(expected2)
-        val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f)
-        assertThat(value()).isEqualTo(expected3)
-        val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f)
-        assertThat(value()).isEqualTo(expected4)
-    }
-
-    private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
-        repository.setDozeAmount(dozeAmount)
-        return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
-    }
-
-    companion object {
-        private const val DEFAULT_BURN_IN_OFFSET = 5
-        private const val RETURNED_BURN_IN_OFFSET = 3
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 1f14afa..b80dcd4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -62,6 +62,7 @@
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth
 import kotlin.math.min
+import kotlin.test.assertEquals
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.emptyFlow
@@ -77,7 +78,6 @@
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.MockitoAnnotations
-import kotlin.test.assertEquals
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -134,7 +134,12 @@
     private lateinit var lockscreenToPrimaryBouncerTransitionViewModel:
         LockscreenToPrimaryBouncerTransitionViewModel
     @Mock
-    private lateinit var transitionInteractor: KeyguardTransitionInteractor
+    private lateinit var lockscreenToGlanceableHubTransitionViewModel:
+        LockscreenToGlanceableHubTransitionViewModel
+    @Mock
+    private lateinit var glanceableHubToLockscreenTransitionViewModel:
+        GlanceableHubToLockscreenTransitionViewModel
+    @Mock private lateinit var transitionInteractor: KeyguardTransitionInteractor
 
     private lateinit var underTest: KeyguardQuickAffordancesCombinedViewModel
 
@@ -271,6 +276,10 @@
         whenever(lockscreenToOccludedTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow())
         whenever(lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha)
             .thenReturn(emptyFlow())
+        whenever(lockscreenToGlanceableHubTransitionViewModel.shortcutsAlpha)
+            .thenReturn(emptyFlow())
+        whenever(glanceableHubToLockscreenTransitionViewModel.shortcutsAlpha)
+            .thenReturn(emptyFlow())
         whenever(shadeInteractor.anyExpansion).thenReturn(intendedShadeAlphaMutableStateFlow)
         whenever(transitionInteractor.finishedKeyguardState)
             .thenReturn(intendedFinishedKeyguardStateFlow)
@@ -280,6 +289,7 @@
                 quickAffordanceInteractor =
                     KeyguardQuickAffordanceInteractor(
                         keyguardInteractor = keyguardInteractor,
+                        shadeInteractor = shadeInteractor,
                         lockPatternUtils = lockPatternUtils,
                         keyguardStateController = keyguardStateController,
                         userTracker = userTracker,
@@ -306,6 +316,8 @@
                 offToLockscreenTransitionViewModel = offToLockscreenTransitionViewModel,
                 primaryBouncerToLockscreenTransitionViewModel =
                     primaryBouncerToLockscreenTransitionViewModel,
+                glanceableHubToLockscreenTransitionViewModel =
+                    glanceableHubToLockscreenTransitionViewModel,
                 lockscreenToAodTransitionViewModel = lockscreenToAodTransitionViewModel,
                 lockscreenToDozingTransitionViewModel = lockscreenToDozingTransitionViewModel,
                 lockscreenToDreamingHostedTransitionViewModel =
@@ -315,6 +327,8 @@
                 lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
                 lockscreenToPrimaryBouncerTransitionViewModel =
                     lockscreenToPrimaryBouncerTransitionViewModel,
+                lockscreenToGlanceableHubTransitionViewModel =
+                    lockscreenToGlanceableHubTransitionViewModel,
                 transitionInteractor = transitionInteractor,
             )
     }
@@ -643,7 +657,7 @@
 
             val testConfig =
                 TestConfig(
-                    isVisible = true,
+                    isVisible = false,
                     isClickable = false,
                     icon = mock(),
                     canShowWhileLocked = false,
@@ -673,7 +687,7 @@
 
             val testConfig =
                 TestConfig(
-                    isVisible = true,
+                    isVisible = false,
                     isClickable = false,
                     icon = mock(),
                     canShowWhileLocked = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
index 2f92afa..83e4d31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
@@ -84,7 +84,7 @@
 import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
-import com.android.systemui.media.dialog.MediaOutputDialogFactory
+import com.android.systemui.media.dialog.MediaOutputDialogManager
 import com.android.systemui.monet.ColorScheme
 import com.android.systemui.monet.Style
 import com.android.systemui.plugins.ActivityStarter
@@ -160,7 +160,7 @@
     @Mock private lateinit var mediaDataManager: MediaDataManager
     @Mock private lateinit var expandedSet: ConstraintSet
     @Mock private lateinit var collapsedSet: ConstraintSet
-    @Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory
+    @Mock private lateinit var mediaOutputDialogManager: MediaOutputDialogManager
     @Mock private lateinit var mediaCarouselController: MediaCarouselController
     @Mock private lateinit var falsingManager: FalsingManager
     @Mock private lateinit var transitionParent: ViewGroup
@@ -266,7 +266,7 @@
                     mediaViewController,
                     seekBarViewModel,
                     Lazy { mediaDataManager },
-                    mediaOutputDialogFactory,
+                    mediaOutputDialogManager,
                     mediaCarouselController,
                     falsingManager,
                     clock,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
index 45f49f0..29820f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
@@ -28,7 +28,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.domain.interactor.setCommunalAvailable
-import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
 import com.android.systemui.dreams.DreamOverlayStateController
 import com.android.systemui.keyguard.WakefulnessLifecycle
@@ -514,7 +514,7 @@
             kosmos.setCommunalAvailable(true)
             runCurrent()
 
-            communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+            communalInteractor.onSceneChanged(CommunalScenes.Communal)
             runCurrent()
             verify(mediaCarouselController)
                 .onDesiredLocationChanged(
@@ -526,7 +526,7 @@
                 )
             clearInvocations(mediaCarouselController)
 
-            communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
+            communalInteractor.onSceneChanged(CommunalScenes.Blank)
             runCurrent()
             verify(mediaCarouselController)
                 .onDesiredLocationChanged(
@@ -549,7 +549,7 @@
             whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
 
             // UMO goes to communal even over the lock screen.
-            communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+            communalInteractor.onSceneChanged(CommunalScenes.Communal)
             runCurrent()
             verify(mediaCarouselController)
                 .onDesiredLocationChanged(
@@ -571,7 +571,7 @@
             // Device is on lock screen.
             whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
 
-            communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+            communalInteractor.onSceneChanged(CommunalScenes.Communal)
             runCurrent()
             verify(mediaCarouselController)
                 .onDesiredLocationChanged(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index ca403e0..695d3b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -19,7 +19,6 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -48,7 +47,6 @@
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
-import com.android.settingslib.media.LocalMediaManager;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogTransitionAnimator;
 import com.android.systemui.broadcast.BroadcastSender;
@@ -129,12 +127,6 @@
                 mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
 
-        // Using a fake package will cause routing operations to fail, so we intercept
-        // scanning-related operations.
-        mMediaOutputController.mLocalMediaManager = mock(LocalMediaManager.class);
-        doNothing().when(mMediaOutputController.mLocalMediaManager).startScan();
-        doNothing().when(mMediaOutputController.mLocalMediaManager).stopScan();
-
         mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
                 mMediaOutputController);
         mMediaOutputBaseDialogImpl.onCreate(new Bundle());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
index 0879884..83def8e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
@@ -42,16 +42,16 @@
 
     private MediaOutputDialogReceiver mMediaOutputDialogReceiver;
 
-    private final MediaOutputDialogFactory mMockMediaOutputDialogFactory =
-            mock(MediaOutputDialogFactory.class);
+    private final MediaOutputDialogManager mMockMediaOutputDialogManager =
+            mock(MediaOutputDialogManager.class);
 
-    private final MediaOutputBroadcastDialogFactory mMockMediaOutputBroadcastDialogFactory =
-            mock(MediaOutputBroadcastDialogFactory.class);
+    private final MediaOutputBroadcastDialogManager mMockMediaOutputBroadcastDialogManager =
+            mock(MediaOutputBroadcastDialogManager.class);
 
     @Before
     public void setup() {
-        mMediaOutputDialogReceiver = new MediaOutputDialogReceiver(mMockMediaOutputDialogFactory,
-                mMockMediaOutputBroadcastDialogFactory);
+        mMediaOutputDialogReceiver = new MediaOutputDialogReceiver(mMockMediaOutputDialogManager,
+                mMockMediaOutputBroadcastDialogManager);
     }
 
     @Test
@@ -60,9 +60,10 @@
         intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogFactory, times(1))
-                .create(getContext().getPackageName(), false, null);
-        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, times(1))
+                .createAndShow(getContext().getPackageName(), false, null);
+        verify(mMockMediaOutputBroadcastDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any());
     }
 
     @Test
@@ -71,8 +72,9 @@
         intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
-        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any());
     }
 
     @Test
@@ -80,8 +82,9 @@
         Intent intent = new Intent(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG);
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
-        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any());
     }
 
     @Test
@@ -92,8 +95,9 @@
         intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
-        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any());
     }
 
     @Test
@@ -104,9 +108,9 @@
         intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
-        verify(mMockMediaOutputBroadcastDialogFactory, times(1))
-                .create(getContext().getPackageName(), true, null);
+        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogManager, times(1))
+                .createAndShow(getContext().getPackageName(), true, null);
     }
 
     @Test
@@ -117,8 +121,9 @@
         intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
-        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any());
     }
 
     @Test
@@ -128,8 +133,9 @@
                 MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
-        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any());
     }
 
     @Test
@@ -139,8 +145,9 @@
         intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
-        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any());
     }
 
     @Test
@@ -148,7 +155,8 @@
         Intent intent = new Intent("UnKnown Action");
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
-        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
new file mode 100644
index 0000000..ac41073
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.appselector.view
+
+import android.app.ActivityOptions
+import android.app.IActivityTaskManager
+import android.os.Bundle
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
+import com.android.systemui.mediaprojection.appselector.data.RecentTask
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Expect
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.any
+import org.mockito.Mockito.verify
+
+@SmallTest
+class MediaProjectionRecentsViewControllerTest : SysuiTestCase() {
+
+    @get:Rule val expect: Expect = Expect.create()
+
+    private val recentTasksAdapter = mock<RecentTasksAdapter>()
+    private val tasksAdapterFactory = RecentTasksAdapter.Factory { _, _ -> recentTasksAdapter }
+    private val taskViewSizeProvider = mock<TaskPreviewSizeProvider>()
+    private val activityTaskManager = mock<IActivityTaskManager>()
+    private val resultHandler = mock<MediaProjectionAppSelectorResultHandler>()
+    private val bundleCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+
+    private val task =
+        RecentTask(
+            taskId = 123,
+            displayId = 456,
+            userId = 789,
+            topActivityComponent = null,
+            baseIntentComponent = null,
+            colorBackground = null,
+            isForegroundTask = false
+        )
+
+    private val taskView =
+        View(context).apply {
+            layoutParams = ViewGroup.LayoutParams(/* width = */ 100, /* height = */ 200)
+        }
+
+    private val controller =
+        MediaProjectionRecentsViewController(
+            tasksAdapterFactory,
+            taskViewSizeProvider,
+            activityTaskManager,
+            resultHandler
+        )
+
+    @Test
+    fun onRecentAppClicked_taskWithSameIdIsStartedFromRecents() {
+        controller.onRecentAppClicked(task, taskView)
+
+        verify(activityTaskManager).startActivityFromRecents(eq(task.taskId), any())
+    }
+
+    @Test
+    fun onRecentAppClicked_launchDisplayIdIsSet() {
+        controller.onRecentAppClicked(task, taskView)
+
+        assertThat(getStartedTaskActivityOptions().launchDisplayId).isEqualTo(task.displayId)
+    }
+
+    @Test
+    fun onRecentAppClicked_taskNotInForeground_usesScaleUpAnimation() {
+        controller.onRecentAppClicked(task, taskView)
+
+        assertThat(getStartedTaskActivityOptions().animationType)
+            .isEqualTo(ActivityOptions.ANIM_SCALE_UP)
+    }
+
+    @Test
+    fun onRecentAppClicked_taskInForeground_flagOff_usesScaleUpAnimation() {
+        mSetFlagsRule.disableFlags(FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX)
+
+        controller.onRecentAppClicked(task, taskView)
+
+        assertThat(getStartedTaskActivityOptions().animationType)
+            .isEqualTo(ActivityOptions.ANIM_SCALE_UP)
+    }
+
+    @Test
+    fun onRecentAppClicked_taskInForeground_flagOn_usesDefaultAnimation() {
+        mSetFlagsRule.enableFlags(FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX)
+        val foregroundTask = task.copy(isForegroundTask = true)
+
+        controller.onRecentAppClicked(foregroundTask, taskView)
+
+        expect
+            .that(getStartedTaskActivityOptions().animationType)
+            .isEqualTo(ActivityOptions.ANIM_CUSTOM)
+        expect.that(getStartedTaskActivityOptions().overrideTaskTransition).isTrue()
+        expect
+            .that(getStartedTaskActivityOptions().customExitResId)
+            .isEqualTo(com.android.internal.R.anim.resolver_close_anim)
+        expect.that(getStartedTaskActivityOptions().customEnterResId).isEqualTo(0)
+    }
+
+    private fun getStartedTaskActivityOptions(): ActivityOptions {
+        verify(activityTaskManager)
+            .startActivityFromRecents(eq(task.taskId), bundleCaptor.capture())
+        return ActivityOptions.fromBundle(bundleCaptor.value)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
index 83932b0..bda0e1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
@@ -21,32 +21,38 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createToken
+import com.android.systemui.mediaprojection.taskswitcher.activityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class ActivityTaskManagerTasksRepositoryTest : SysuiTestCase() {
 
-    private val fakeActivityTaskManager = FakeActivityTaskManager()
+    private val kosmos = taskSwitcherKosmos()
+    private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+    private val testScope = kosmos.testScope
+    private val repo = kosmos.activityTaskManagerTasksRepository
 
-    private val dispatcher = UnconfinedTestDispatcher()
-    private val testScope = TestScope(dispatcher)
+    @Test
+    fun launchRecentTask_taskIsMovedToForeground() =
+        testScope.runTest {
+            val currentForegroundTask by collectLastValue(repo.foregroundTask)
+            val newForegroundTask = createTask(taskId = 1)
+            val backgroundTask = createTask(taskId = 2)
+            fakeActivityTaskManager.addRunningTasks(backgroundTask, newForegroundTask)
 
-    private val repo =
-        ActivityTaskManagerTasksRepository(
-            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
-            applicationScope = testScope.backgroundScope,
-            backgroundDispatcher = dispatcher
-        )
+            repo.launchRecentTask(newForegroundTask)
+
+            assertThat(currentForegroundTask).isEqualTo(newForegroundTask)
+        }
 
     @Test
     fun findRunningTaskFromWindowContainerToken_noMatch_returnsNull() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
index 7bd97ce..6043ede 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -17,55 +17,52 @@
 package com.android.systemui.mediaprojection.taskswitcher.data.repository
 
 import android.os.Binder
-import android.os.Handler
 import android.testing.AndroidTestingRunner
 import android.view.ContentRecordingSession
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createToken
 import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.mediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
 
-    private val dispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(dispatcher)
+    private val kosmos = taskSwitcherKosmos()
+    private val testScope = kosmos.testScope
 
-    private val fakeMediaProjectionManager = FakeMediaProjectionManager()
-    private val fakeActivityTaskManager = FakeActivityTaskManager()
+    private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+    private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
 
-    private val tasksRepo =
-        ActivityTaskManagerTasksRepository(
-            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
-            applicationScope = testScope.backgroundScope,
-            backgroundDispatcher = dispatcher
-        )
+    private val repo = kosmos.mediaProjectionManagerRepository
 
-    private val repo =
-        MediaProjectionManagerRepository(
-            mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
-            handler = Handler.getMain(),
-            applicationScope = testScope.backgroundScope,
-            tasksRepository = tasksRepo
-        )
+    @Test
+    fun switchProjectedTask_stateIsUpdatedWithNewTask() =
+        testScope.runTest {
+            val task = createTask(taskId = 1)
+            val state by collectLastValue(repo.mediaProjectionState)
+
+            fakeActivityTaskManager.addRunningTasks(task)
+            repo.switchProjectedTask(task)
+
+            assertThat(state).isEqualTo(MediaProjectionState.SingleTask(task))
+        }
 
     @Test
     fun mediaProjectionState_onStart_emitsNotProjecting() =
         testScope.runTest {
             val state by collectLastValue(repo.mediaProjectionState)
-            runCurrent()
 
             fakeMediaProjectionManager.dispatchOnStart()
 
@@ -76,7 +73,6 @@
     fun mediaProjectionState_onStop_emitsNotProjecting() =
         testScope.runTest {
             val state by collectLastValue(repo.mediaProjectionState)
-            runCurrent()
 
             fakeMediaProjectionManager.dispatchOnStop()
 
@@ -87,7 +83,6 @@
     fun mediaProjectionState_onSessionSet_sessionNull_emitsNotProjecting() =
         testScope.runTest {
             val state by collectLastValue(repo.mediaProjectionState)
-            runCurrent()
 
             fakeMediaProjectionManager.dispatchOnSessionSet(session = null)
 
@@ -98,7 +93,6 @@
     fun mediaProjectionState_onSessionSet_contentToRecordDisplay_emitsEntireScreen() =
         testScope.runTest {
             val state by collectLastValue(repo.mediaProjectionState)
-            runCurrent()
 
             fakeMediaProjectionManager.dispatchOnSessionSet(
                 session = ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123)
@@ -111,7 +105,6 @@
     fun mediaProjectionState_sessionSet_taskWithToken_noMatchingRunningTask_emitsEntireScreen() =
         testScope.runTest {
             val state by collectLastValue(repo.mediaProjectionState)
-            runCurrent()
 
             val taskWindowContainerToken = Binder()
             fakeMediaProjectionManager.dispatchOnSessionSet(
@@ -128,7 +121,6 @@
             val task = createTask(taskId = 1, token = token)
             fakeActivityTaskManager.addRunningTasks(task)
             val state by collectLastValue(repo.mediaProjectionState)
-            runCurrent()
 
             fakeMediaProjectionManager.dispatchOnSessionSet(
                 session = ContentRecordingSession.createTaskSession(token.asBinder())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
index b2ebe1bc..33e65f26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
@@ -17,53 +17,33 @@
 package com.android.systemui.mediaprojection.taskswitcher.domain.interactor
 
 import android.content.Intent
-import android.os.Handler
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createSingleTaskSession
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager.Companion.createSingleTaskSession
 import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherInteractor
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class TaskSwitchInteractorTest : SysuiTestCase() {
 
-    private val dispatcher = UnconfinedTestDispatcher()
-    private val testScope = TestScope(dispatcher)
-
-    private val fakeActivityTaskManager = FakeActivityTaskManager()
-    private val fakeMediaProjectionManager = FakeMediaProjectionManager()
-
-    private val tasksRepo =
-        ActivityTaskManagerTasksRepository(
-            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
-            applicationScope = testScope.backgroundScope,
-            backgroundDispatcher = dispatcher
-        )
-
-    private val mediaRepo =
-        MediaProjectionManagerRepository(
-            mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
-            handler = Handler.getMain(),
-            applicationScope = testScope.backgroundScope,
-            tasksRepository = tasksRepo,
-        )
-
-    private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
+    private val kosmos = taskSwitcherKosmos()
+    private val testScope = kosmos.testScope
+    private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+    private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+    private val interactor = kosmos.taskSwitcherInteractor
 
     @Test
     fun taskSwitchChanges_notProjecting_foregroundTaskChange_emitsNotProjectingTask() =
@@ -118,6 +98,40 @@
         }
 
     @Test
+    fun taskSwitchChanges_projectingTask_foregroundTaskDifferent_thenSwitched_emitsUnchanged() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 0)
+            val foregroundTask = createTask(taskId = 1)
+            val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+            fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = createSingleTaskSession(token = projectedTask.token.asBinder())
+            )
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+            interactor.switchProjectedTask(foregroundTask)
+
+            assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged)
+        }
+
+    @Test
+    fun taskSwitchChanges_projectingTask_foregroundTaskDifferent_thenWentBack_emitsUnchanged() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 0)
+            val foregroundTask = createTask(taskId = 1)
+            val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+            fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = createSingleTaskSession(token = projectedTask.token.asBinder())
+            )
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+            interactor.goBackToTask(projectedTask)
+
+            assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged)
+        }
+
+    @Test
     fun taskSwitchChanges_projectingTask_foregroundTaskLauncher_emitsTaskUnchanged() =
         testScope.runTest {
             val projectedTask = createTask(taskId = 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
index d0c6d7c..9382c58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
@@ -18,74 +18,53 @@
 
 import android.app.Notification
 import android.app.NotificationManager
-import android.os.Handler
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
-import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
-import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherViewModel
+import com.android.systemui.res.R
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertEquals
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() {
 
-    private val notificationManager: NotificationManager = mock()
+    private val notificationManager = mock<NotificationManager>()
+    private val kosmos = taskSwitcherKosmos()
+    private val testScope = kosmos.testScope
+    private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+    private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+    private val viewModel = kosmos.taskSwitcherViewModel
 
-    private val dispatcher = UnconfinedTestDispatcher()
-    private val testScope = TestScope(dispatcher)
-
-    private val fakeActivityTaskManager = FakeActivityTaskManager()
-    private val fakeMediaProjectionManager = FakeMediaProjectionManager()
-
-    private val tasksRepo =
-        ActivityTaskManagerTasksRepository(
-            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
-            applicationScope = testScope.backgroundScope,
-            backgroundDispatcher = dispatcher
-        )
-
-    private val mediaRepo =
-        MediaProjectionManagerRepository(
-            mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
-            handler = Handler.getMain(),
-            applicationScope = testScope.backgroundScope,
-            tasksRepository = tasksRepo,
-        )
-
-    private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
-    private val viewModel = TaskSwitcherNotificationViewModel(interactor)
-
-    private val coordinator =
-        TaskSwitcherNotificationCoordinator(
-            context,
-            notificationManager,
-            testScope.backgroundScope,
-            dispatcher,
-            viewModel
-        )
+    private lateinit var coordinator: TaskSwitcherNotificationCoordinator
 
     @Before
     fun setup() {
+        coordinator =
+            TaskSwitcherNotificationCoordinator(
+                context,
+                notificationManager,
+                testScope.backgroundScope,
+                viewModel,
+                fakeBroadcastDispatcher,
+            )
         coordinator.start()
     }
 
@@ -105,7 +84,7 @@
         testScope.runTest {
             fakeMediaProjectionManager.dispatchOnStop()
 
-            verify(notificationManager).cancel(any())
+            verify(notificationManager).cancel(any(), any())
         }
     }
 
@@ -114,7 +93,7 @@
         testScope.runTest {
             fakeMediaProjectionManager.dispatchOnStop()
             val idCancel = argumentCaptor<Int>()
-            verify(notificationManager).cancel(idCancel.capture())
+            verify(notificationManager).cancel(any(), idCancel.capture())
 
             switchTask()
             val idNotify = argumentCaptor<Int>()
@@ -124,9 +103,55 @@
         }
     }
 
+    @Test
+    fun switchTaskAction_hidesNotification() =
+        testScope.runTest {
+            switchTask()
+            val notification = argumentCaptor<Notification>()
+            verify(notificationManager).notify(any(), any(), notification.capture())
+            verify(notificationManager, never()).cancel(any(), any())
+
+            val action = findSwitchAction(notification.value)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                action.actionIntent.intent
+            )
+
+            verify(notificationManager).cancel(any(), any())
+        }
+
+    @Test
+    fun goBackAction_hidesNotification() =
+        testScope.runTest {
+            switchTask()
+            val notification = argumentCaptor<Notification>()
+            verify(notificationManager).notify(any(), any(), notification.capture())
+            verify(notificationManager, never()).cancel(any(), any())
+
+            val action = findGoBackAction(notification.value)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                action.actionIntent.intent
+            )
+
+            verify(notificationManager).cancel(any(), any())
+        }
+
+    private fun findSwitchAction(notification: Notification): Notification.Action {
+        return notification.actions.first {
+            it.title == context.getString(R.string.media_projection_task_switcher_action_switch)
+        }
+    }
+
+    private fun findGoBackAction(notification: Notification): Notification.Action {
+        return notification.actions.first {
+            it.title == context.getString(R.string.media_projection_task_switcher_action_back)
+        }
+    }
+
     private fun switchTask() {
-        val projectedTask = FakeActivityTaskManager.createTask(taskId = 1)
-        val foregroundTask = FakeActivityTaskManager.createTask(taskId = 2)
+        val projectedTask = createTask(taskId = 1)
+        val foregroundTask = createTask(taskId = 2)
         fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
         fakeMediaProjectionManager.dispatchOnSessionSet(
             session =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
index 7d38de4..a468953 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
@@ -17,57 +17,35 @@
 package com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel
 
 import android.content.Intent
-import android.os.Handler
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createDisplaySession
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createSingleTaskSession
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
-import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager.Companion.createDisplaySession
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager.Companion.createSingleTaskSession
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherViewModel
 import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
+import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel.Companion.NOTIFICATION_MAX_SHOW_DURATION
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class TaskSwitcherNotificationViewModelTest : SysuiTestCase() {
 
-    private val dispatcher = UnconfinedTestDispatcher()
-    private val testScope = TestScope(dispatcher)
-
-    private val fakeActivityTaskManager = FakeActivityTaskManager()
-    private val fakeMediaProjectionManager = FakeMediaProjectionManager()
-
-    private val tasksRepo =
-        ActivityTaskManagerTasksRepository(
-            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
-            applicationScope = testScope.backgroundScope,
-            backgroundDispatcher = dispatcher
-        )
-
-    private val mediaRepo =
-        MediaProjectionManagerRepository(
-            mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
-            handler = Handler.getMain(),
-            applicationScope = testScope.backgroundScope,
-            tasksRepository = tasksRepo,
-        )
-
-    private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
-
-    private val viewModel = TaskSwitcherNotificationViewModel(interactor)
+    private val kosmos = taskSwitcherKosmos()
+    private val testScope = kosmos.testScope
+    private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+    private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+    private val viewModel = kosmos.taskSwitcherViewModel
 
     @Test
     fun uiState_notProjecting_emitsNotShowing() =
@@ -135,6 +113,75 @@
         }
 
     @Test
+    fun uiState_taskChanged_beforeDelayLimit_stillEmitsShowing() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 1)
+            val foregroundTask = createTask(taskId = 2)
+            val uiState by collectLastValue(viewModel.uiState)
+
+            fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = createSingleTaskSession(projectedTask.token.asBinder())
+            )
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+            testScheduler.advanceTimeBy(NOTIFICATION_MAX_SHOW_DURATION - 1.milliseconds)
+            assertThat(uiState)
+                .isEqualTo(TaskSwitcherNotificationUiState.Showing(projectedTask, foregroundTask))
+        }
+
+    @Test
+    fun uiState_taskChanged_afterDelayLimit_emitsNotShowing() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 1)
+            val foregroundTask = createTask(taskId = 2)
+            val uiState by collectLastValue(viewModel.uiState)
+
+            fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = createSingleTaskSession(projectedTask.token.asBinder())
+            )
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+            testScheduler.advanceTimeBy(NOTIFICATION_MAX_SHOW_DURATION)
+            assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+        }
+
+    @Test
+    fun uiState_projectingTask_foregroundTaskChanged_thenTaskSwitched_emitsNotShowing() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 1)
+            val foregroundTask = createTask(taskId = 2)
+            val uiState by collectLastValue(viewModel.uiState)
+
+            fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = createSingleTaskSession(projectedTask.token.asBinder())
+            )
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+            viewModel.onSwitchTaskClicked(foregroundTask)
+
+            assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+        }
+
+    @Test
+    fun uiState_projectingTask_foregroundTaskChanged_thenGoBack_emitsNotShowing() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 1)
+            val foregroundTask = createTask(taskId = 2)
+            val uiState by collectLastValue(viewModel.uiState)
+
+            fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = createSingleTaskSession(projectedTask.token.asBinder())
+            )
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+            viewModel.onGoBackToTaskClicked(projectedTask)
+
+            assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+        }
+
+    @Test
     fun uiState_projectingTask_foregroundTaskChanged_same_emitsNotShowing() =
         testScope.runTest {
             val projectedTask = createTask(taskId = 1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
index 52859cd..d405df7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
@@ -43,13 +43,11 @@
 
 import android.content.res.Configuration;
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
 import android.util.SparseArray;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
-import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.model.SysUiState;
@@ -61,7 +59,9 @@
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.LightBarController;
 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;
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.pip.Pip;
 
@@ -76,7 +76,6 @@
 
 /** atest NavigationBarControllerTest */
 @RunWith(AndroidTestingRunner.class)
-@RunWithLooper
 @SmallTest
 public class NavigationBarControllerImplTest extends SysuiTestCase {
 
@@ -88,6 +87,8 @@
     private StaticMockitoSession mMockitoSession;
     private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
 
+    private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+
     @Mock
     private CommandQueue mCommandQueue;
     @Mock
@@ -104,7 +105,7 @@
                         mock(NavigationModeController.class),
                         mock(SysUiState.class),
                         mCommandQueue,
-                        Dependency.get(Dependency.MAIN_HANDLER),
+                        mExecutor,
                         mock(ConfigurationController.class),
                         mock(NavBarHelper.class),
                         mTaskbarDelegate,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 65ede89..0101741 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -51,6 +51,7 @@
 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.VibratorHelper;
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
 import com.android.systemui.util.animation.DisappearParameters;
 
@@ -100,6 +101,8 @@
     Configuration mConfiguration;
     @Mock
     Runnable mHorizontalLayoutListener;
+    @Mock
+    VibratorHelper mVibratorHelper;
 
     private QSPanelControllerBase<QSPanel> mController;
 
@@ -110,7 +113,8 @@
                 MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
                 DumpManager dumpManager) {
             super(view, host, qsCustomizerController, true, mediaHost, metricsLogger, uiEventLogger,
-                    qsLogger, dumpManager, new ResourcesSplitShadeStateController());
+                    qsLogger, dumpManager, new ResourcesSplitShadeStateController(),
+                    mVibratorHelper);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 85d7d98..916e8dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
 import com.android.systemui.settings.brightness.BrightnessController
 import com.android.systemui.settings.brightness.BrightnessSliderController
+import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
 import com.android.systemui.tuner.TunerService
@@ -61,6 +62,7 @@
     @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
     @Mock private lateinit var configuration: Configuration
     @Mock private lateinit var pagedTileLayout: PagedTileLayout
+    @Mock private lateinit var vibratorHelper: VibratorHelper
 
     private val sceneContainerFlags = FakeSceneContainerFlags()
 
@@ -101,6 +103,7 @@
             statusBarKeyguardViewManager,
             ResourcesSplitShadeStateController(),
             sceneContainerFlags,
+            vibratorHelper,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 2c14308..71a9a8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.qs.customize.QSCustomizerController
 import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
 import com.android.systemui.util.leak.RotationUtils
 import org.junit.After
@@ -59,6 +60,7 @@
     @Mock private lateinit var tile: QSTile
     @Mock private lateinit var tileLayout: TileLayout
     @Captor private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener>
+    @Mock private lateinit var vibratorHelper: VibratorHelper
 
     private val uiEventLogger = UiEventLoggerFake()
     private val dumpManager = DumpManager()
@@ -89,7 +91,8 @@
                 metricsLogger,
                 uiEventLogger,
                 qsLogger,
-                dumpManager
+                dumpManager,
+                vibratorHelper,
             )
 
         controller.init()
@@ -157,7 +160,8 @@
         metricsLogger: MetricsLogger,
         uiEventLogger: UiEventLoggerFake,
         qsLogger: QSLogger,
-        dumpManager: DumpManager
+        dumpManager: DumpManager,
+        vibratorHelper: VibratorHelper,
     ) :
         QuickQSPanelController(
             view,
@@ -170,7 +174,8 @@
             uiEventLogger,
             qsLogger,
             dumpManager,
-            ResourcesSplitShadeStateController()
+            ResourcesSplitShadeStateController(),
+            vibratorHelper,
         ) {
 
         private var rotation = RotationUtils.ROTATION_NONE
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt
new file mode 100644
index 0000000..8ecb953
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt
@@ -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.systemui.qs.tiles.dialog.bluetooth
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.GONE
+import android.view.View.VISIBLE
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.model.SysUiState
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
+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.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class BluetoothTileDialogDelegateTest : SysuiTestCase() {
+    companion object {
+        const val DEVICE_NAME = "device"
+        const val DEVICE_CONNECTION_SUMMARY = "active"
+        const val ENABLED = true
+        const val CONTENT_HEIGHT = WRAP_CONTENT
+    }
+
+    @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+    @Mock private lateinit var cachedBluetoothDevice: CachedBluetoothDevice
+
+    @Mock private lateinit var bluetoothTileDialogCallback: BluetoothTileDialogCallback
+
+    @Mock private lateinit var drawable: Drawable
+
+    @Mock private lateinit var uiEventLogger: UiEventLogger
+
+    @Mock private lateinit var logger: BluetoothTileDialogLogger
+
+    private val uiProperties =
+        BluetoothTileDialogViewModel.UiProperties.build(
+            isBluetoothEnabled = ENABLED,
+            isAutoOnToggleFeatureAvailable = ENABLED
+        )
+    @Mock private lateinit var sysuiDialogFactory: SystemUIDialog.Factory
+    @Mock private lateinit var dialogManager: SystemUIDialogManager
+    @Mock private lateinit var sysuiState: SysUiState
+    @Mock private lateinit var dialogTransitionAnimator: DialogTransitionAnimator
+
+    private val fakeSystemClock = FakeSystemClock()
+
+    private lateinit var scheduler: TestCoroutineScheduler
+    private lateinit var dispatcher: CoroutineDispatcher
+    private lateinit var testScope: TestScope
+    private lateinit var icon: Pair<Drawable, String>
+    private lateinit var mBluetoothTileDialogDelegate: BluetoothTileDialogDelegate
+    private lateinit var deviceItem: DeviceItem
+
+    @Before
+    fun setUp() {
+        scheduler = TestCoroutineScheduler()
+        dispatcher = UnconfinedTestDispatcher(scheduler)
+        testScope = TestScope(dispatcher)
+
+        whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
+
+        mBluetoothTileDialogDelegate =
+            BluetoothTileDialogDelegate(
+                mContext,
+                uiProperties,
+                CONTENT_HEIGHT,
+                ENABLED,
+                bluetoothTileDialogCallback,
+                {},
+                dispatcher,
+                fakeSystemClock,
+                uiEventLogger,
+                logger,
+                sysuiDialogFactory,
+                LayoutInflater.from(mContext)
+            )
+
+        whenever(
+                sysuiDialogFactory.create(
+                    any(SystemUIDialog.Delegate::class.java),
+                    any(Context::class.java)
+                )
+            )
+            .thenAnswer {
+                SystemUIDialog(
+                    mContext,
+                    0,
+                    SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
+                    dialogManager,
+                    sysuiState,
+                    fakeBroadcastDispatcher,
+                    dialogTransitionAnimator,
+                    it.getArgument(0)
+                )
+            }
+
+        icon = Pair(drawable, DEVICE_NAME)
+        deviceItem =
+            DeviceItem(
+                type = DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
+                cachedBluetoothDevice = cachedBluetoothDevice,
+                deviceName = DEVICE_NAME,
+                connectionSummary = DEVICE_CONNECTION_SUMMARY,
+                iconWithDescription = icon,
+                background = null
+            )
+        `when`(cachedBluetoothDevice.isBusy).thenReturn(false)
+    }
+
+    @Test
+    fun testShowDialog_createRecyclerViewWithAdapter() {
+        val dialog = mBluetoothTileDialogDelegate.createDialog()
+        dialog.show()
+
+        val recyclerView = dialog.requireViewById<RecyclerView>(R.id.device_list)
+
+        assertThat(recyclerView).isNotNull()
+        assertThat(recyclerView.visibility).isEqualTo(VISIBLE)
+        assertThat(recyclerView.adapter).isNotNull()
+        assertThat(recyclerView.layoutManager is LinearLayoutManager).isTrue()
+    }
+
+    @Test
+    fun testShowDialog_displayBluetoothDevice() {
+        testScope.runTest {
+            val dialog = mBluetoothTileDialogDelegate.createDialog()
+            dialog.show()
+            fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE)
+            mBluetoothTileDialogDelegate.onDeviceItemUpdated(
+                dialog,
+                listOf(deviceItem),
+                showSeeAll = false,
+                showPairNewDevice = false
+            )
+
+            val recyclerView = dialog.requireViewById<RecyclerView>(R.id.device_list)
+            val adapter = recyclerView?.adapter as BluetoothTileDialogDelegate.Adapter
+            assertThat(adapter.itemCount).isEqualTo(1)
+            assertThat(adapter.getItem(0).deviceName).isEqualTo(DEVICE_NAME)
+            assertThat(adapter.getItem(0).connectionSummary).isEqualTo(DEVICE_CONNECTION_SUMMARY)
+            assertThat(adapter.getItem(0).iconWithDescription).isEqualTo(icon)
+        }
+    }
+
+    @Test
+    fun testDeviceItemViewHolder_cachedDeviceNotBusy() {
+        deviceItem.isEnabled = true
+
+        val view =
+            LayoutInflater.from(mContext).inflate(R.layout.bluetooth_device_item, null, false)
+        val viewHolder =
+            mBluetoothTileDialogDelegate
+                .Adapter(bluetoothTileDialogCallback)
+                .DeviceItemViewHolder(view)
+        viewHolder.bind(deviceItem, bluetoothTileDialogCallback)
+        val container = view.requireViewById<View>(R.id.bluetooth_device_row)
+
+        assertThat(container).isNotNull()
+        assertThat(container.isEnabled).isTrue()
+        assertThat(container.hasOnClickListeners()).isTrue()
+    }
+
+    @Test
+    fun testDeviceItemViewHolder_cachedDeviceBusy() {
+        deviceItem.isEnabled = false
+
+        val view =
+            LayoutInflater.from(mContext).inflate(R.layout.bluetooth_device_item, null, false)
+        val viewHolder =
+            BluetoothTileDialogDelegate(
+                    mContext,
+                    uiProperties,
+                    CONTENT_HEIGHT,
+                    ENABLED,
+                    bluetoothTileDialogCallback,
+                    {},
+                    dispatcher,
+                    fakeSystemClock,
+                    uiEventLogger,
+                    logger,
+                    sysuiDialogFactory,
+                    LayoutInflater.from(mContext)
+                )
+                .Adapter(bluetoothTileDialogCallback)
+                .DeviceItemViewHolder(view)
+        viewHolder.bind(deviceItem, bluetoothTileDialogCallback)
+        val container = view.requireViewById<View>(R.id.bluetooth_device_row)
+
+        assertThat(container).isNotNull()
+        assertThat(container.isEnabled).isFalse()
+        assertThat(container.hasOnClickListeners()).isTrue()
+    }
+
+    @Test
+    fun testOnDeviceUpdated_hideSeeAll_showPairNew() {
+        testScope.runTest {
+            val dialog = mBluetoothTileDialogDelegate.createDialog()
+            dialog.show()
+            fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE)
+            mBluetoothTileDialogDelegate.onDeviceItemUpdated(
+                dialog,
+                listOf(deviceItem),
+                showSeeAll = false,
+                showPairNewDevice = true
+            )
+
+            val seeAllButton = dialog.requireViewById<View>(R.id.see_all_button)
+            val pairNewButton = dialog.requireViewById<View>(R.id.pair_new_device_button)
+            val recyclerView = dialog.requireViewById<RecyclerView>(R.id.device_list)
+            val adapter = recyclerView?.adapter as BluetoothTileDialogDelegate.Adapter
+            val scrollViewContent = dialog.requireViewById<View>(R.id.scroll_view)
+
+            assertThat(seeAllButton).isNotNull()
+            assertThat(seeAllButton.visibility).isEqualTo(GONE)
+            assertThat(pairNewButton).isNotNull()
+            assertThat(pairNewButton.visibility).isEqualTo(VISIBLE)
+            assertThat(adapter.itemCount).isEqualTo(1)
+            assertThat(scrollViewContent.layoutParams.height).isEqualTo(WRAP_CONTENT)
+        }
+    }
+
+    @Test
+    fun testShowDialog_cachedHeightLargerThanMinHeight_displayFromCachedHeight() {
+        testScope.runTest {
+            val cachedHeight = Int.MAX_VALUE
+            val dialog =
+                BluetoothTileDialogDelegate(
+                        mContext,
+                        BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
+                        cachedHeight,
+                        ENABLED,
+                        bluetoothTileDialogCallback,
+                        {},
+                        dispatcher,
+                        fakeSystemClock,
+                        uiEventLogger,
+                        logger,
+                        sysuiDialogFactory,
+                        LayoutInflater.from(mContext)
+                    )
+                    .createDialog()
+            dialog.show()
+            assertThat(dialog.requireViewById<View>(R.id.scroll_view).layoutParams.height)
+                .isEqualTo(cachedHeight)
+        }
+    }
+
+    @Test
+    fun testShowDialog_cachedHeightLessThanMinHeight_displayFromUiProperties() {
+        testScope.runTest {
+            val dialog =
+                BluetoothTileDialogDelegate(
+                        mContext,
+                        BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
+                        MATCH_PARENT,
+                        ENABLED,
+                        bluetoothTileDialogCallback,
+                        {},
+                        dispatcher,
+                        fakeSystemClock,
+                        uiEventLogger,
+                        logger,
+                        sysuiDialogFactory,
+                        LayoutInflater.from(mContext)
+                    )
+                    .createDialog()
+            dialog.show()
+            assertThat(dialog.requireViewById<View>(R.id.scroll_view).layoutParams.height)
+                .isGreaterThan(MATCH_PARENT)
+        }
+    }
+
+    @Test
+    fun testShowDialog_bluetoothEnabled_autoOnToggleGone() {
+        testScope.runTest {
+            val dialog =
+                BluetoothTileDialogDelegate(
+                        mContext,
+                        BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
+                        MATCH_PARENT,
+                        ENABLED,
+                        bluetoothTileDialogCallback,
+                        {},
+                        dispatcher,
+                        fakeSystemClock,
+                        uiEventLogger,
+                        logger,
+                        sysuiDialogFactory,
+                        LayoutInflater.from(mContext)
+                    )
+                    .createDialog()
+            dialog.show()
+            assertThat(
+                    dialog.requireViewById<View>(R.id.bluetooth_auto_on_toggle_layout).visibility
+                )
+                .isEqualTo(GONE)
+        }
+    }
+}
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
deleted file mode 100644
index 70b0417..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt
+++ /dev/null
@@ -1,330 +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.dialog.bluetooth
-
-import android.graphics.drawable.Drawable
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.LayoutInflater
-import android.view.View
-import android.view.View.GONE
-import android.view.View.VISIBLE
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import androidx.test.filters.SmallTest
-import com.android.internal.logging.UiEventLogger
-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 kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.test.TestCoroutineScheduler
-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.`when`
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoRule
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class BluetoothTileDialogTest : SysuiTestCase() {
-    companion object {
-        const val DEVICE_NAME = "device"
-        const val DEVICE_CONNECTION_SUMMARY = "active"
-        const val ENABLED = true
-        const val CONTENT_HEIGHT = WRAP_CONTENT
-    }
-
-    @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
-
-    @Mock private lateinit var cachedBluetoothDevice: CachedBluetoothDevice
-
-    @Mock private lateinit var bluetoothTileDialogCallback: BluetoothTileDialogCallback
-
-    @Mock private lateinit var drawable: Drawable
-
-    @Mock private lateinit var uiEventLogger: UiEventLogger
-
-    @Mock private lateinit var logger: BluetoothTileDialogLogger
-
-    private val uiProperties =
-        BluetoothTileDialogViewModel.UiProperties.build(
-            isBluetoothEnabled = ENABLED,
-            isAutoOnToggleFeatureAvailable = ENABLED
-        )
-
-    private val fakeSystemClock = FakeSystemClock()
-
-    private lateinit var scheduler: TestCoroutineScheduler
-    private lateinit var dispatcher: CoroutineDispatcher
-    private lateinit var testScope: TestScope
-    private lateinit var icon: Pair<Drawable, String>
-    private lateinit var bluetoothTileDialog: BluetoothTileDialog
-    private lateinit var deviceItem: DeviceItem
-
-    @Before
-    fun setUp() {
-        scheduler = TestCoroutineScheduler()
-        dispatcher = UnconfinedTestDispatcher(scheduler)
-        testScope = TestScope(dispatcher)
-        bluetoothTileDialog =
-            BluetoothTileDialog(
-                ENABLED,
-                uiProperties,
-                CONTENT_HEIGHT,
-                bluetoothTileDialogCallback,
-                dispatcher,
-                fakeSystemClock,
-                uiEventLogger,
-                logger,
-                mContext
-            )
-        icon = Pair(drawable, DEVICE_NAME)
-        deviceItem =
-            DeviceItem(
-                type = DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
-                cachedBluetoothDevice = cachedBluetoothDevice,
-                deviceName = DEVICE_NAME,
-                connectionSummary = DEVICE_CONNECTION_SUMMARY,
-                iconWithDescription = icon,
-                background = null
-            )
-        `when`(cachedBluetoothDevice.isBusy).thenReturn(false)
-    }
-
-    @Test
-    fun testShowDialog_createRecyclerViewWithAdapter() {
-        bluetoothTileDialog.show()
-
-        val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list)
-
-        assertThat(bluetoothTileDialog.isShowing).isTrue()
-        assertThat(recyclerView).isNotNull()
-        assertThat(recyclerView.visibility).isEqualTo(VISIBLE)
-        assertThat(recyclerView.adapter).isNotNull()
-        assertThat(recyclerView.layoutManager is LinearLayoutManager).isTrue()
-    }
-
-    @Test
-    fun testShowDialog_displayBluetoothDevice() {
-        testScope.runTest {
-            bluetoothTileDialog =
-                BluetoothTileDialog(
-                    ENABLED,
-                    uiProperties,
-                    CONTENT_HEIGHT,
-                    bluetoothTileDialogCallback,
-                    dispatcher,
-                    fakeSystemClock,
-                    uiEventLogger,
-                    logger,
-                    mContext
-                )
-            bluetoothTileDialog.show()
-            fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE)
-            bluetoothTileDialog.onDeviceItemUpdated(
-                listOf(deviceItem),
-                showSeeAll = false,
-                showPairNewDevice = false
-            )
-
-            val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list)
-            val adapter = recyclerView.adapter as BluetoothTileDialog.Adapter
-            assertThat(adapter.itemCount).isEqualTo(1)
-            assertThat(adapter.getItem(0).deviceName).isEqualTo(DEVICE_NAME)
-            assertThat(adapter.getItem(0).connectionSummary).isEqualTo(DEVICE_CONNECTION_SUMMARY)
-            assertThat(adapter.getItem(0).iconWithDescription).isEqualTo(icon)
-        }
-    }
-
-    @Test
-    fun testDeviceItemViewHolder_cachedDeviceNotBusy() {
-        deviceItem.isEnabled = true
-
-        val view =
-            LayoutInflater.from(mContext).inflate(R.layout.bluetooth_device_item, null, false)
-        val viewHolder =
-            BluetoothTileDialog(
-                    ENABLED,
-                    uiProperties,
-                    CONTENT_HEIGHT,
-                    bluetoothTileDialogCallback,
-                    dispatcher,
-                    fakeSystemClock,
-                    uiEventLogger,
-                    logger,
-                    mContext
-                )
-                .Adapter(bluetoothTileDialogCallback)
-                .DeviceItemViewHolder(view)
-        viewHolder.bind(deviceItem, bluetoothTileDialogCallback)
-        val container = view.requireViewById<View>(R.id.bluetooth_device_row)
-
-        assertThat(container).isNotNull()
-        assertThat(container.isEnabled).isTrue()
-        assertThat(container.hasOnClickListeners()).isTrue()
-    }
-
-    @Test
-    fun testDeviceItemViewHolder_cachedDeviceBusy() {
-        deviceItem.isEnabled = false
-
-        val view =
-            LayoutInflater.from(mContext).inflate(R.layout.bluetooth_device_item, null, false)
-        val viewHolder =
-            BluetoothTileDialog(
-                    ENABLED,
-                    uiProperties,
-                    CONTENT_HEIGHT,
-                    bluetoothTileDialogCallback,
-                    dispatcher,
-                    fakeSystemClock,
-                    uiEventLogger,
-                    logger,
-                    mContext
-                )
-                .Adapter(bluetoothTileDialogCallback)
-                .DeviceItemViewHolder(view)
-        viewHolder.bind(deviceItem, bluetoothTileDialogCallback)
-        val container = view.requireViewById<View>(R.id.bluetooth_device_row)
-
-        assertThat(container).isNotNull()
-        assertThat(container.isEnabled).isFalse()
-        assertThat(container.hasOnClickListeners()).isTrue()
-    }
-
-    @Test
-    fun testOnDeviceUpdated_hideSeeAll_showPairNew() {
-        testScope.runTest {
-            bluetoothTileDialog =
-                BluetoothTileDialog(
-                    ENABLED,
-                    uiProperties,
-                    CONTENT_HEIGHT,
-                    bluetoothTileDialogCallback,
-                    dispatcher,
-                    fakeSystemClock,
-                    uiEventLogger,
-                    logger,
-                    mContext
-                )
-            bluetoothTileDialog.show()
-            fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE)
-            bluetoothTileDialog.onDeviceItemUpdated(
-                listOf(deviceItem),
-                showSeeAll = false,
-                showPairNewDevice = true
-            )
-
-            val seeAllButton = bluetoothTileDialog.requireViewById<View>(R.id.see_all_button)
-            val pairNewButton =
-                bluetoothTileDialog.requireViewById<View>(R.id.pair_new_device_button)
-            val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list)
-            val adapter = recyclerView.adapter as BluetoothTileDialog.Adapter
-            val scrollViewContent = bluetoothTileDialog.requireViewById<View>(R.id.scroll_view)
-
-            assertThat(seeAllButton).isNotNull()
-            assertThat(seeAllButton.visibility).isEqualTo(GONE)
-            assertThat(pairNewButton).isNotNull()
-            assertThat(pairNewButton.visibility).isEqualTo(VISIBLE)
-            assertThat(adapter.itemCount).isEqualTo(1)
-            assertThat(scrollViewContent.layoutParams.height).isEqualTo(WRAP_CONTENT)
-        }
-    }
-
-    @Test
-    fun testShowDialog_cachedHeightLargerThanMinHeight_displayFromCachedHeight() {
-        testScope.runTest {
-            val cachedHeight = Int.MAX_VALUE
-            bluetoothTileDialog =
-                BluetoothTileDialog(
-                    ENABLED,
-                    uiProperties,
-                    cachedHeight,
-                    bluetoothTileDialogCallback,
-                    dispatcher,
-                    fakeSystemClock,
-                    uiEventLogger,
-                    logger,
-                    mContext
-                )
-            bluetoothTileDialog.show()
-            assertThat(
-                    bluetoothTileDialog.requireViewById<View>(R.id.scroll_view).layoutParams.height
-                )
-                .isEqualTo(cachedHeight)
-        }
-    }
-
-    @Test
-    fun testShowDialog_cachedHeightLessThanMinHeight_displayFromUiProperties() {
-        testScope.runTest {
-            bluetoothTileDialog =
-                BluetoothTileDialog(
-                    ENABLED,
-                    uiProperties,
-                    MATCH_PARENT,
-                    bluetoothTileDialogCallback,
-                    dispatcher,
-                    fakeSystemClock,
-                    uiEventLogger,
-                    logger,
-                    mContext
-                )
-            bluetoothTileDialog.show()
-            assertThat(
-                    bluetoothTileDialog.requireViewById<View>(R.id.scroll_view).layoutParams.height
-                )
-                .isGreaterThan(MATCH_PARENT)
-        }
-    }
-
-    @Test
-    fun testShowDialog_bluetoothEnabled_autoOnToggleGone() {
-        testScope.runTest {
-            bluetoothTileDialog =
-                BluetoothTileDialog(
-                    ENABLED,
-                    BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
-                    MATCH_PARENT,
-                    bluetoothTileDialogCallback,
-                    dispatcher,
-                    fakeSystemClock,
-                    uiEventLogger,
-                    logger,
-                    mContext
-                )
-            bluetoothTileDialog.show()
-            assertThat(
-                    bluetoothTileDialog
-                        .requireViewById<View>(R.id.bluetooth_auto_on_toggle_layout)
-                        .visibility
-                )
-                .isEqualTo(GONE)
-        }
-    }
-}
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 cb9f4b4..39e2413 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
@@ -16,7 +16,6 @@
 
 package com.android.systemui.qs.tiles.dialog.bluetooth
 
-import android.content.SharedPreferences
 import android.content.pm.UserInfo
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
@@ -31,10 +30,14 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.kotlin.getMutableStateFlow
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -50,12 +53,12 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
 
@@ -84,9 +87,15 @@
 
     @Mock private lateinit var uiEventLogger: UiEventLogger
 
-    @Mock private lateinit var logger: BluetoothTileDialogLogger
+    @Mock
+    private lateinit var mBluetoothTileDialogDelegateDelegateFactory:
+        BluetoothTileDialogDelegate.Factory
 
-    @Mock private lateinit var sharedPreferences: SharedPreferences
+    @Mock private lateinit var bluetoothTileDialogDelegate: BluetoothTileDialogDelegate
+
+    @Mock private lateinit var sysuiDialog: SystemUIDialog
+
+    private val sharedPreferences = FakeSharedPreferences()
 
     private lateinit var scheduler: TestCoroutineScheduler
     private lateinit var dispatcher: CoroutineDispatcher
@@ -123,20 +132,38 @@
                 ),
                 mDialogTransitionAnimator,
                 activityStarter,
-                fakeSystemClock,
                 uiEventLogger,
-                logger,
                 testScope.backgroundScope,
                 dispatcher,
                 dispatcher,
                 sharedPreferences,
+                mBluetoothTileDialogDelegateDelegateFactory
             )
-        `when`(deviceItemInteractor.deviceItemUpdate).thenReturn(MutableSharedFlow())
-        `when`(bluetoothStateInteractor.bluetoothStateUpdate)
+        whenever(deviceItemInteractor.deviceItemUpdate).thenReturn(MutableSharedFlow())
+        whenever(bluetoothStateInteractor.bluetoothStateUpdate)
             .thenReturn(MutableStateFlow(null).asStateFlow())
-        `when`(deviceItemInteractor.deviceItemUpdateRequest)
+        whenever(deviceItemInteractor.deviceItemUpdateRequest)
             .thenReturn(MutableStateFlow(Unit).asStateFlow())
-        `when`(bluetoothStateInteractor.isBluetoothEnabled).thenReturn(true)
+        whenever(bluetoothStateInteractor.isBluetoothEnabled).thenReturn(true)
+        whenever(
+                mBluetoothTileDialogDelegateDelegateFactory.create(
+                    any(),
+                    any(),
+                    anyInt(),
+                    ArgumentMatchers.anyBoolean(),
+                    any(),
+                    any()
+                )
+            )
+            .thenReturn(bluetoothTileDialogDelegate)
+        whenever(bluetoothTileDialogDelegate.createDialog()).thenReturn(sysuiDialog)
+        whenever(bluetoothTileDialogDelegate.bluetoothStateToggle)
+            .thenReturn(getMutableStateFlow(false))
+        whenever(bluetoothTileDialogDelegate.deviceItemClick)
+            .thenReturn(getMutableStateFlow(deviceItem))
+        whenever(bluetoothTileDialogDelegate.contentHeight).thenReturn(getMutableStateFlow(0))
+        whenever(bluetoothTileDialogDelegate.bluetoothAutoOnToggle)
+            .thenReturn(getMutableStateFlow(false))
     }
 
     @Test
@@ -145,7 +172,6 @@
             bluetoothTileDialogViewModel.showDialog(context, null)
 
             verify(mDialogTransitionAnimator, never()).showFromView(any(), any(), any(), any())
-            verify(uiEventLogger).log(BluetoothTileDialogUiEvent.BLUETOOTH_TILE_DIALOG_SHOWN)
         }
     }
 
@@ -191,7 +217,7 @@
     @Test
     fun testStartSettingsActivity_activityLaunched_dialogDismissed() {
         testScope.runTest {
-            `when`(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
+            whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
             bluetoothTileDialogViewModel.showDialog(context, null)
 
             val clickedView = View(context)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt
index 92c7326..a8cd8c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt
@@ -16,10 +16,18 @@
 
 package com.android.systemui.qs.tiles.dialog.bluetooth
 
+import android.bluetooth.BluetoothDevice
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.media.AudioManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.BluetoothUtils
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.flags.Flags
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -35,19 +43,26 @@
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class DeviceItemFactoryTest : SysuiTestCase() {
-
     @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
 
     @Mock private lateinit var cachedDevice: CachedBluetoothDevice
+    @Mock private lateinit var bluetoothDevice: BluetoothDevice
+    @Mock private lateinit var packageManager: PackageManager
 
     private val availableMediaDeviceItemFactory = AvailableMediaDeviceItemFactory()
     private val connectedDeviceItemFactory = ConnectedDeviceItemFactory()
     private val savedDeviceItemFactory = SavedDeviceItemFactory()
 
+    private val audioManager = context.getSystemService(AudioManager::class.java)!!
+
     @Before
     fun setup() {
         `when`(cachedDevice.name).thenReturn(DEVICE_NAME)
+        `when`(cachedDevice.address).thenReturn(DEVICE_ADDRESS)
+        `when`(cachedDevice.device).thenReturn(bluetoothDevice)
         `when`(cachedDevice.connectionSummary).thenReturn(CONNECTION_SUMMARY)
+
+        context.setMockPackageManager(packageManager)
     }
 
     @Test
@@ -72,6 +87,225 @@
         assertThat(deviceItem.background).isNotNull()
     }
 
+    @Test
+    @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+    fun testSavedFactory_isFilterMatched_bondedAndNotConnected_returnsTrue() {
+        `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+        `when`(cachedDevice.isConnected).thenReturn(false)
+
+        assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+            .isTrue()
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+    fun testSavedFactory_isFilterMatched_connected_returnsFalse() {
+        `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+        `when`(cachedDevice.isConnected).thenReturn(true)
+
+        assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+            .isFalse()
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+    fun testSavedFactory_isFilterMatched_notBonded_returnsFalse() {
+        `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
+        `when`(cachedDevice.isConnected).thenReturn(false)
+
+        assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+            .isFalse()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+    fun testSavedFactory_isFilterMatched_exclusivelyManaged_returnsFalse() {
+        val exclusiveManagerName =
+            BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
+        `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+            .thenReturn(exclusiveManagerName.toByteArray())
+        `when`(packageManager.getPackageInfo(exclusiveManagerName, 0)).thenReturn(PackageInfo())
+        `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+        `when`(cachedDevice.isConnected).thenReturn(false)
+
+        assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+            .isFalse()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+    fun testSavedFactory_isFilterMatched_noExclusiveManager_returnsTrue() {
+        `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+        `when`(cachedDevice.isConnected).thenReturn(false)
+
+        assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+            .isTrue()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+    fun testSavedFactory_isFilterMatched_notAllowedExclusiveManager_returnsTrue() {
+        `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+            .thenReturn(FAKE_EXCLUSIVE_MANAGER_NAME.toByteArray())
+        `when`(packageManager.getPackageInfo(FAKE_EXCLUSIVE_MANAGER_NAME, 0))
+            .thenReturn(PackageInfo())
+        `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+        `when`(cachedDevice.isConnected).thenReturn(false)
+
+        assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+            .isTrue()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+    fun testSavedFactory_isFilterMatched_uninstalledExclusiveManager_returnsTrue() {
+        val exclusiveManagerName =
+            BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
+        `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+            .thenReturn(exclusiveManagerName.toByteArray())
+        `when`(packageManager.getPackageInfo(exclusiveManagerName, 0))
+            .thenThrow(PackageManager.NameNotFoundException("Test!"))
+        `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+        `when`(cachedDevice.isConnected).thenReturn(false)
+
+        assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+            .isTrue()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+    fun testSavedFactory_isFilterMatched_notExclusivelyManaged_notBonded_returnsFalse() {
+        `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
+        `when`(cachedDevice.isConnected).thenReturn(false)
+
+        assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+            .isFalse()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+    fun testSavedFactory_isFilterMatched_notExclusivelyManaged_connected_returnsFalse() {
+        `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+        `when`(cachedDevice.isConnected).thenReturn(true)
+
+        assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+            .isFalse()
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+    fun testConnectedFactory_isFilterMatched_bondedAndConnected_returnsTrue() {
+        `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+        `when`(bluetoothDevice.isConnected).thenReturn(true)
+        audioManager.setMode(AudioManager.MODE_NORMAL)
+
+        assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+            .isTrue()
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+    fun testConnectedFactory_isFilterMatched_notConnected_returnsFalse() {
+        `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+        `when`(bluetoothDevice.isConnected).thenReturn(false)
+        audioManager.setMode(AudioManager.MODE_NORMAL)
+
+        assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+            .isFalse()
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+    fun testConnectedFactory_isFilterMatched_notBonded_returnsFalse() {
+        `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
+        `when`(bluetoothDevice.isConnected).thenReturn(true)
+        audioManager.setMode(AudioManager.MODE_NORMAL)
+
+        assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+            .isFalse()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+    fun testConnectedFactory_isFilterMatched_exclusivelyManaged_returnsFalse() {
+        val exclusiveManagerName =
+            BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
+        `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+            .thenReturn(exclusiveManagerName.toByteArray())
+        `when`(packageManager.getPackageInfo(exclusiveManagerName, 0)).thenReturn(PackageInfo())
+        `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+        `when`(bluetoothDevice.isConnected).thenReturn(true)
+        audioManager.setMode(AudioManager.MODE_NORMAL)
+
+        assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+            .isFalse()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+    fun testConnectedFactory_isFilterMatched_noExclusiveManager_returnsTrue() {
+        `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+        `when`(bluetoothDevice.isConnected).thenReturn(true)
+        audioManager.setMode(AudioManager.MODE_NORMAL)
+
+        assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+            .isTrue()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+    fun testConnectedFactory_isFilterMatched_notAllowedExclusiveManager_returnsTrue() {
+        `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+            .thenReturn(FAKE_EXCLUSIVE_MANAGER_NAME.toByteArray())
+        `when`(packageManager.getPackageInfo(FAKE_EXCLUSIVE_MANAGER_NAME, 0))
+            .thenReturn(PackageInfo())
+        `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+        `when`(bluetoothDevice.isConnected).thenReturn(true)
+        audioManager.setMode(AudioManager.MODE_NORMAL)
+
+        assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+            .isTrue()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+    fun testConnectedFactory_isFilterMatched_uninstalledExclusiveManager_returnsTrue() {
+        val exclusiveManagerName =
+            BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
+        `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+            .thenReturn(exclusiveManagerName.toByteArray())
+        `when`(packageManager.getPackageInfo(exclusiveManagerName, 0))
+            .thenThrow(PackageManager.NameNotFoundException("Test!"))
+        `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+        `when`(bluetoothDevice.isConnected).thenReturn(true)
+        audioManager.setMode(AudioManager.MODE_NORMAL)
+
+        assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+            .isTrue()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+    fun testConnectedFactory_isFilterMatched_notExclusivelyManaged_notBonded_returnsFalse() {
+        `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
+        `when`(bluetoothDevice.isConnected).thenReturn(true)
+        audioManager.setMode(AudioManager.MODE_NORMAL)
+
+        assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+            .isFalse()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+    fun testConnectedFactory_isFilterMatched_notExclusivelyManaged_notConnected_returnsFalse() {
+        `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+        `when`(bluetoothDevice.isConnected).thenReturn(false)
+        audioManager.setMode(AudioManager.MODE_NORMAL)
+
+        assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+            .isFalse()
+    }
+
     private fun assertDeviceItem(deviceItem: DeviceItem?, deviceItemType: DeviceItemType) {
         assertThat(deviceItem).isNotNull()
         assertThat(deviceItem!!.type).isEqualTo(deviceItemType)
@@ -83,5 +317,7 @@
     companion object {
         const val DEVICE_NAME = "DeviceName"
         const val CONNECTION_SUMMARY = "ConnectionSummary"
+        private const val FAKE_EXCLUSIVE_MANAGER_NAME = "com.fake.name"
+        private const val DEVICE_ADDRESS = "04:52:C7:0B:D8:3C"
     }
 }
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 e236f4a..ddf0b9a 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
@@ -279,6 +279,7 @@
     ): DeviceItemFactory {
         return object : DeviceItemFactory() {
             override fun isFilterMatched(
+                context: Context,
                 cachedDevice: CachedBluetoothDevice,
                 audioManager: AudioManager?
             ) = isFilterMatchFunc(cachedDevice)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index 7b285ab..2e8160b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -33,26 +33,26 @@
 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.ScreenCaptureDisabledDialogDelegate
 import com.android.systemui.model.SysUiState
 import com.android.systemui.qs.tiles.RecordIssueTile
 import com.android.systemui.res.R
-import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
+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.whenever
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.CountDownLatch
-import java.util.concurrent.Executor
 import java.util.concurrent.TimeUnit
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
@@ -70,16 +70,19 @@
     @Mock private lateinit var devicePolicyResolver: ScreenCaptureDevicePolicyResolver
     @Mock private lateinit var dprLazy: dagger.Lazy<ScreenCaptureDevicePolicyResolver>
     @Mock private lateinit var mediaProjectionMetricsLogger: MediaProjectionMetricsLogger
-    @Mock private lateinit var userContextProvider: UserContextProvider
     @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var userFileManager: UserFileManager
     @Mock private lateinit var sharedPreferences: SharedPreferences
+    @Mock
+    private lateinit var screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate
+    @Mock private lateinit var screenCaptureDisabledDialog: SystemUIDialog
 
     @Mock private lateinit var sysuiState: SysUiState
     @Mock private lateinit var systemUIDialogManager: SystemUIDialogManager
     @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
-    @Mock private lateinit var bgExecutor: Executor
-    @Mock private lateinit var mainExecutor: Executor
+    private val systemClock = FakeSystemClock()
+    private val bgExecutor = FakeExecutor(systemClock)
+    private val mainExecutor = FakeExecutor(systemClock)
     @Mock private lateinit var mDialogTransitionAnimator: DialogTransitionAnimator
 
     private lateinit var dialog: SystemUIDialog
@@ -91,7 +94,8 @@
         MockitoAnnotations.initMocks(this)
         whenever(dprLazy.get()).thenReturn(devicePolicyResolver)
         whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
-        whenever(userContextProvider.userContext).thenReturn(mContext)
+        whenever(screenCaptureDisabledDialogDelegate.createDialog())
+            .thenReturn(screenCaptureDisabledDialog)
         whenever(
                 userFileManager.getSharedPreferences(
                     eq(RecordIssueTile.TILE_SPEC),
@@ -116,7 +120,6 @@
         dialog =
             RecordIssueDialogDelegate(
                     factory,
-                    userContextProvider,
                     userTracker,
                     flags,
                     bgExecutor,
@@ -124,6 +127,7 @@
                     dprLazy,
                     mediaProjectionMetricsLogger,
                     userFileManager,
+                    screenCaptureDisabledDialogDelegate,
                 ) {
                     latch.countDown()
                 }
@@ -163,13 +167,8 @@
         val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
         screenRecordSwitch.isChecked = true
 
-        val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
-        verify(bgExecutor).execute(bgCaptor.capture())
-        bgCaptor.value.run()
-
-        val mainCaptor = ArgumentCaptor.forClass(Runnable::class.java)
-        verify(mainExecutor).execute(mainCaptor.capture())
-        mainCaptor.value.run()
+        bgExecutor.runAllReady()
+        mainExecutor.runAllReady()
 
         verify(mediaProjectionMetricsLogger, never())
             .notifyProjectionInitiated(
@@ -192,13 +191,8 @@
         val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
         screenRecordSwitch.isChecked = true
 
-        val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
-        verify(bgExecutor).execute(bgCaptor.capture())
-        bgCaptor.value.run()
-
-        val mainCaptor = ArgumentCaptor.forClass(Runnable::class.java)
-        verify(mainExecutor).execute(mainCaptor.capture())
-        mainCaptor.value.run()
+        bgExecutor.runAllReady()
+        mainExecutor.runAllReady()
 
         verify(mediaProjectionMetricsLogger)
             .notifyProjectionInitiated(
@@ -219,9 +213,7 @@
         val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
         screenRecordSwitch.isChecked = true
 
-        val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
-        verify(bgExecutor).execute(bgCaptor.capture())
-        bgCaptor.value.run()
+        bgExecutor.runAllReady()
 
         verify(mediaProjectionMetricsLogger)
             .notifyProjectionInitiated(
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 6cbe8c9..b3df12ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -17,13 +17,11 @@
 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.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -48,10 +46,9 @@
 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.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.DialogDelegate;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -84,8 +81,6 @@
     @Mock
     private BroadcastDispatcher mBroadcastDispatcher;
     @Mock
-    private UserContextProvider mUserContextProvider;
-    @Mock
     private ScreenCaptureDevicePolicyResolver mDevicePolicyResolver;
     @Mock
     private DialogTransitionAnimator mDialogTransitionAnimator;
@@ -96,6 +91,22 @@
     @Mock
     private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
 
+    @Mock
+    private ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate;
+    @Mock
+    private SystemUIDialog mScreenCaptureDisabledDialog;
+    @Mock
+    private ScreenRecordDialogDelegate.Factory mScreenRecordDialogFactory;
+    @Mock
+    private ScreenRecordDialogDelegate mScreenRecordDialogDelegate;
+    @Mock
+    private ScreenRecordPermissionDialogDelegate.Factory
+            mScreenRecordPermissionDialogDelegateFactory;
+    @Mock
+    private ScreenRecordPermissionDialogDelegate mScreenRecordPermissionDialogDelegate;
+    @Mock
+    private SystemUIDialog mScreenRecordSystemUIDialog;
+
     private FakeFeatureFlags mFeatureFlags;
     private RecordingController mController;
     private TestSystemUIDialogFactory mDialogFactory;
@@ -108,8 +119,6 @@
         Context spiedContext = spy(mContext);
         when(spiedContext.getUserId()).thenReturn(TEST_USER_ID);
 
-        when(mUserContextProvider.getUserContext()).thenReturn(spiedContext);
-
         mDialogFactory = new TestSystemUIDialogFactory(
                 mContext,
                 Dependency.get(SystemUIDialogManager.class),
@@ -119,16 +128,26 @@
         );
 
         mFeatureFlags = new FakeFeatureFlags();
+        when(mScreenCaptureDisabledDialogDelegate.createDialog())
+                .thenReturn(mScreenCaptureDisabledDialog);
+        when(mScreenRecordDialogFactory.create(any(), any()))
+                .thenReturn(mScreenRecordDialogDelegate);
+        when(mScreenRecordDialogDelegate.createDialog()).thenReturn(mScreenRecordSystemUIDialog);
+        when(mScreenRecordPermissionDialogDelegateFactory.create(any(), any(), anyInt(), any()))
+                .thenReturn(mScreenRecordPermissionDialogDelegate);
+        when(mScreenRecordPermissionDialogDelegate.createDialog())
+                .thenReturn(mScreenRecordSystemUIDialog);
         mController = new RecordingController(
                 mMainExecutor,
                 mBroadcastDispatcher,
-                mContext,
                 mFeatureFlags,
-                mUserContextProvider,
                 () -> mDevicePolicyResolver,
                 mUserTracker,
                 mMediaProjectionMetricsLogger,
-                mDialogFactory);
+                mScreenCaptureDisabledDialogDelegate,
+                mScreenRecordDialogFactory,
+                mScreenRecordPermissionDialogDelegateFactory
+        );
         mController.addCallback(mCallback);
     }
 
@@ -242,8 +261,8 @@
                         mActivityStarter,
                         /* onStartRecordingClicked= */ null);
 
-        assertThat(dialog).isSameInstanceAs(mDialogFactory.mLastCreatedDialog);
-        assertThat(mDialogFactory.mLastDelegate)
+        assertThat(dialog).isSameInstanceAs(mScreenRecordSystemUIDialog);
+        assertThat(mScreenRecordPermissionDialogDelegate)
                 .isInstanceOf(ScreenRecordPermissionDialogDelegate.class);
     }
 
@@ -255,7 +274,7 @@
         Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
                 mDialogTransitionAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
 
-        assertThat(dialog).isInstanceOf(ScreenRecordDialog.class);
+        assertThat(dialog).isEqualTo(mScreenRecordSystemUIDialog);
     }
 
     @Test
@@ -267,7 +286,7 @@
         Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
                 mDialogTransitionAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
 
-        assertThat(dialog).isInstanceOf(ScreenCaptureDisabledDialog.class);
+        assertThat(dialog).isEqualTo(mScreenCaptureDisabledDialog);
     }
 
     @Test
@@ -284,8 +303,8 @@
                         mActivityStarter,
                         /* onStartRecordingClicked= */ null);
 
-        assertThat(dialog).isSameInstanceAs(mDialogFactory.mLastCreatedDialog);
-        assertThat(mDialogFactory.mLastDelegate)
+        assertThat(dialog).isSameInstanceAs(mScreenRecordSystemUIDialog);
+        assertThat(mScreenRecordPermissionDialogDelegate)
                 .isInstanceOf(ScreenRecordPermissionDialogDelegate.class);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
index 90ced92..6e48074 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
@@ -58,6 +59,7 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() {
 
+    //@Mock private lateinit var dialogFactory: SystemUIDialog.Factory
     @Mock private lateinit var starter: ActivityStarter
     @Mock private lateinit var controller: RecordingController
     @Mock private lateinit var userContextProvider: UserContextProvider
@@ -71,14 +73,17 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
+        whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
+
         val systemUIDialogFactory =
-            SystemUIDialog.Factory(
-                context,
-                Dependency.get(SystemUIDialogManager::class.java),
-                Dependency.get(SysUiState::class.java),
-                Dependency.get(BroadcastDispatcher::class.java),
-                Dependency.get(DialogTransitionAnimator::class.java),
-            )
+                SystemUIDialog.Factory(
+                        context,
+                        Dependency.get(SystemUIDialogManager::class.java),
+                        Dependency.get(SysUiState::class.java),
+                        Dependency.get(BroadcastDispatcher::class.java),
+                        Dependency.get(DialogTransitionAnimator::class.java),
+                )
+
         val delegate =
             ScreenRecordPermissionDialogDelegate(
                 UserHandle.of(0),
@@ -88,11 +93,9 @@
                 userContextProvider,
                 onStartRecordingClicked,
                 mediaProjectionMetricsLogger,
-                systemUIDialogFactory
+                systemUIDialogFactory,
             )
         dialog = delegate.createDialog()
-        delegate.onCreate(dialog, savedInstanceState = null)
-        whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
index 6068c23..f4d7a5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.screenshot;
 
+import static com.android.systemui.screenshot.ImageExporter.createSystemFileDisplayName;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -182,6 +184,30 @@
     }
 
     @Test
+    public void testImageExport_customizedFileName()
+            throws ExecutionException, InterruptedException {
+        // This test only asserts the file name for the case when user specifies a file name,
+        // instead of using the auto-generated name by ImageExporter::createFileName. Other
+        // metadata are not affected by the specified file name.
+        final String customizedFileName = "customized_file_name";
+        ContentResolver contentResolver = mContext.getContentResolver();
+        ImageExporter exporter = new ImageExporter(contentResolver, mFeatureFlags);
+
+        UUID requestId = UUID.fromString("3c11da99-9284-4863-b1d5-6f3684976814");
+        Bitmap original = createCheckerBitmap(10, 10, 10);
+
+        ListenableFuture<ImageExporter.Result> direct =
+                exporter.export(DIRECT_EXECUTOR, requestId, original, CAPTURE_TIME,
+                        Process.myUserHandle(), customizedFileName);
+        assertTrue("future should be done", direct.isDone());
+        assertFalse("future should not be canceled", direct.isCancelled());
+        ImageExporter.Result result = direct.get();
+        assertEquals("Filename should contain the correct filename",
+                createSystemFileDisplayName(customizedFileName, CompressFormat.PNG),
+                result.fileName);
+    }
+
+    @Test
     public void testMediaStoreMetadata() {
         String name = ImageExporter.createFilename(CAPTURE_TIME, CompressFormat.PNG,
                 Display.DEFAULT_DISPLAY);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
index fbb77cd..25dd9fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
@@ -34,7 +34,6 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import java.util.concurrent.CompletableFuture
-import java.util.function.Supplier
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNull
 import org.junit.Before
@@ -46,8 +45,6 @@
     private val smartActions = mock<ScreenshotSmartActions>()
     private val smartActionsProvider = mock<ScreenshotNotificationSmartActionsProvider>()
     private val saveImageData = SaveImageInBackgroundData()
-    private val sharedTransitionSupplier =
-        mock<Supplier<ScreenshotController.SavedImageData.ActionTransition>>()
     private val testScreenshotId: String = "testScreenshotId"
     private val testBitmap = mock<Bitmap>()
     private val testUser = UserHandle.getUserHandleForUid(0)
@@ -88,7 +85,6 @@
             imageExporter,
             smartActions,
             saveImageData,
-            sharedTransitionSupplier,
             smartActionsProvider,
         )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
index 85c8ba7..2a9aca7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
@@ -20,8 +20,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.doThrow;
@@ -32,19 +30,15 @@
 import static org.mockito.Mockito.when;
 
 import android.app.Notification;
-import android.content.Intent;
 import android.graphics.Bitmap;
 import android.net.Uri;
-import android.os.Bundle;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.UserHandle;
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -84,7 +78,7 @@
         ScreenshotNotificationSmartActionsProvider smartActionsProvider = mock(
                 ScreenshotNotificationSmartActionsProvider.class);
         when(smartActionsProvider.getActions(any(), any(), any(), any(), any(), any()))
-            .thenThrow(RuntimeException.class);
+                .thenThrow(RuntimeException.class);
         CompletableFuture<List<Notification.Action>> smartActionsFuture =
                 mScreenshotSmartActions.getSmartActionsFuture(
                         "", Uri.parse("content://authority/data"), bitmap, smartActionsProvider,
@@ -166,89 +160,4 @@
         List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
         assertEquals(smartActions.size(), 0);
     }
-
-    // Tests for share action extras
-    @Test
-    public void testShareActionExtras() {
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-
-        ScreenshotController.SaveImageInBackgroundData
-                data = new ScreenshotController.SaveImageInBackgroundData();
-        data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
-        data.finisher = null;
-        data.mActionsReadyListener = null;
-        SaveImageInBackgroundTask task =
-                new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data,
-                        ActionTransition::new, mSmartActionsProvider);
-
-        Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(),
-                Uri.parse("Screenshot_123.png"), true).get().action;
-
-        Intent intent = shareAction.actionIntent.getIntent();
-        assertNotNull(intent);
-        Bundle bundle = intent.getExtras();
-        assertTrue(bundle.containsKey(ScreenshotController.EXTRA_ID));
-        assertTrue(bundle.containsKey(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED));
-        assertEquals(ScreenshotController.ACTION_TYPE_SHARE, shareAction.title);
-        assertEquals(Intent.ACTION_SEND, intent.getAction());
-    }
-
-    // Tests for edit action extras
-    @Test
-    public void testEditActionExtras() {
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-
-        ScreenshotController.SaveImageInBackgroundData
-                data = new ScreenshotController.SaveImageInBackgroundData();
-        data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
-        data.finisher = null;
-        data.mActionsReadyListener = null;
-        SaveImageInBackgroundTask task =
-                new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data,
-                        ActionTransition::new, mSmartActionsProvider);
-
-        Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(),
-                Uri.parse("Screenshot_123.png"), true).get().action;
-
-        Intent intent = editAction.actionIntent.getIntent();
-        assertNotNull(intent);
-        Bundle bundle = intent.getExtras();
-        assertTrue(bundle.containsKey(ScreenshotController.EXTRA_ID));
-        assertTrue(bundle.containsKey(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED));
-        assertEquals(ScreenshotController.ACTION_TYPE_EDIT, editAction.title);
-        assertEquals(Intent.ACTION_EDIT, intent.getAction());
-    }
-
-    // Tests for share action extras
-    @Test
-    public void testDeleteActionExtras() {
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-
-        ScreenshotController.SaveImageInBackgroundData
-                data = new ScreenshotController.SaveImageInBackgroundData();
-        data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
-        data.finisher = null;
-        data.mActionsReadyListener = null;
-        SaveImageInBackgroundTask task =
-                new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data,
-                        ActionTransition::new, mSmartActionsProvider);
-
-        Notification.Action deleteAction = task.createDeleteAction(mContext,
-                mContext.getResources(),
-                Uri.parse("Screenshot_123.png"), true);
-
-        Intent intent = deleteAction.actionIntent.getIntent();
-        assertNotNull(intent);
-        Bundle bundle = intent.getExtras();
-        assertTrue(bundle.containsKey(ScreenshotController.EXTRA_ID));
-        assertTrue(bundle.containsKey(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED));
-        assertEquals(deleteAction.title, ScreenshotController.ACTION_TYPE_DELETE);
-        assertNull(intent.getAction());
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt
index 2f911fff..92c2404 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt
@@ -22,8 +22,10 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import java.lang.IllegalStateException
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -31,12 +33,14 @@
 import org.mockito.Mockito.verify
 
 @SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
 class ScreenshotSoundControllerTest : SysuiTestCase() {
 
     private val soundProvider = mock<ScreenshotSoundProvider>()
     private val mediaPlayer = mock<MediaPlayer>()
     private val bgDispatcher = UnconfinedTestDispatcher()
     private val scope = TestScope(bgDispatcher)
+
     @Before
     fun setup() {
         whenever(soundProvider.getScreenshotSound()).thenReturn(mediaPlayer)
@@ -45,52 +49,59 @@
     @Test
     fun init_soundLoading() {
         createController()
-        bgDispatcher.scheduler.runCurrent()
+        scope.advanceUntilIdle()
 
         verify(soundProvider).getScreenshotSound()
     }
 
     @Test
-    fun init_soundLoadingException_playAndReleaseDoNotThrow() = runTest {
-        whenever(soundProvider.getScreenshotSound()).thenThrow(IllegalStateException())
+    fun init_soundLoadingException_playAndReleaseDoNotThrow() =
+        scope.runTest {
+            whenever(soundProvider.getScreenshotSound()).thenThrow(IllegalStateException())
 
-        val controller = createController()
+            val controller = createController()
 
-        controller.playCameraSound().await()
-        controller.releaseScreenshotSound().await()
+            controller.playScreenshotSound()
+            advanceUntilIdle()
 
-        verify(mediaPlayer, never()).start()
-        verify(mediaPlayer, never()).release()
-    }
+            verify(mediaPlayer, never()).start()
+            verify(mediaPlayer, never()).release()
+        }
 
     @Test
-    fun playCameraSound_soundLoadingSuccessful_mediaPlayerPlays() = runTest {
-        val controller = createController()
+    fun playCameraSound_soundLoadingSuccessful_mediaPlayerPlays() =
+        scope.runTest {
+            val controller = createController()
 
-        controller.playCameraSound().await()
+            controller.playScreenshotSound()
+            advanceUntilIdle()
 
-        verify(mediaPlayer).start()
-    }
+            verify(mediaPlayer).start()
+        }
 
     @Test
-    fun playCameraSound_illegalStateException_doesNotThrow() = runTest {
-        whenever(mediaPlayer.start()).thenThrow(IllegalStateException())
+    fun playCameraSound_illegalStateException_doesNotThrow() =
+        scope.runTest {
+            whenever(mediaPlayer.start()).thenThrow(IllegalStateException())
 
-        val controller = createController()
-        controller.playCameraSound().await()
+            val controller = createController()
+            controller.playScreenshotSound()
+            advanceUntilIdle()
 
-        verify(mediaPlayer).start()
-        verify(mediaPlayer).release()
-    }
+            verify(mediaPlayer).start()
+            verify(mediaPlayer).release()
+        }
 
     @Test
-    fun playCameraSound_soundLoadingSuccessful_mediaPlayerReleases() = runTest {
-        val controller = createController()
+    fun playCameraSound_soundLoadingSuccessful_mediaPlayerReleases() =
+        scope.runTest {
+            val controller = createController()
 
-        controller.releaseScreenshotSound().await()
+            controller.releaseScreenshotSound()
+            advanceUntilIdle()
 
-        verify(mediaPlayer).release()
-    }
+            verify(mediaPlayer).release()
+        }
 
     private fun createController() =
         ScreenshotSoundControllerImpl(soundProvider, scope, bgDispatcher)
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 3dc9037..0baee5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -274,8 +274,8 @@
             screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
 
             screenshotExecutor.onCloseSystemDialogsReceived()
-            verify(controller0).dismissScreenshot(any())
-            verify(controller1).dismissScreenshot(any())
+            verify(controller0).requestDismissal(any())
+            verify(controller1).requestDismissal(any())
 
             screenshotExecutor.onDestroy()
         }
@@ -290,8 +290,8 @@
             screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
 
             screenshotExecutor.onCloseSystemDialogsReceived()
-            verify(controller0, never()).dismissScreenshot(any())
-            verify(controller1).dismissScreenshot(any())
+            verify(controller0, never()).requestDismissal(any())
+            verify(controller1).requestDismissal(any())
 
             screenshotExecutor.onDestroy()
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 665fc75..07d9350 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -26,6 +26,7 @@
 import android.view.WindowManager
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.FakeCommunalRepository
@@ -33,15 +34,15 @@
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.domain.interactor.setCommunalAvailable
-import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
-import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
@@ -52,9 +53,7 @@
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import org.junit.After
 import org.junit.Assert.assertThrows
-import org.junit.Assume.assumeTrue
 import org.junit.Before
-import org.junit.BeforeClass
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
@@ -78,6 +77,7 @@
     @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Mock private lateinit var shadeInteractor: ShadeInteractor
     @Mock private lateinit var powerManager: PowerManager
+    @Mock private lateinit var dialogFactory: SystemUIDialogFactory
 
     private lateinit var parentView: FrameLayout
     private lateinit var containerView: View
@@ -101,6 +101,7 @@
             GlanceableHubContainerController(
                 communalInteractor,
                 communalViewModel,
+                dialogFactory,
                 keyguardTransitionInteractor,
                 shadeInteractor,
                 powerManager
@@ -140,6 +141,7 @@
             GlanceableHubContainerController(
                 communalInteractor,
                 communalViewModel,
+                dialogFactory,
                 keyguardTransitionInteractor,
                 shadeInteractor,
                 powerManager,
@@ -155,7 +157,7 @@
     @Test
     fun onTouchEvent_communalClosed_doesNotIntercept() {
         // Communal is closed.
-        goToScene(CommunalSceneKey.Blank)
+        goToScene(CommunalScenes.Blank)
 
         assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
     }
@@ -163,7 +165,7 @@
     @Test
     fun onTouchEvent_openGesture_interceptsTouches() {
         // Communal is closed.
-        goToScene(CommunalSceneKey.Blank)
+        goToScene(CommunalScenes.Blank)
 
         // Initial touch down is intercepted, and so are touches outside of the region, until an
         // up event is received.
@@ -176,7 +178,7 @@
     @Test
     fun onTouchEvent_communalOpen_interceptsTouches() {
         // Communal is open.
-        goToScene(CommunalSceneKey.Communal)
+        goToScene(CommunalScenes.Communal)
 
         // Touch events are intercepted outside of any gesture areas.
         assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
@@ -187,7 +189,7 @@
     @Test
     fun onTouchEvent_topSwipeWhenCommunalOpen_doesNotIntercept() {
         // Communal is open.
-        goToScene(CommunalSceneKey.Communal)
+        goToScene(CommunalScenes.Communal)
 
         // Touch event in the top swipe reqgion is not intercepted.
         assertThat(underTest.onTouchEvent(DOWN_IN_TOP_SWIPE_REGION_EVENT)).isFalse()
@@ -196,7 +198,7 @@
     @Test
     fun onTouchEvent_bottomSwipeWhenCommunalOpen_doesNotIntercept() {
         // Communal is open.
-        goToScene(CommunalSceneKey.Communal)
+        goToScene(CommunalScenes.Communal)
 
         // Touch event in the bottom swipe reqgion is not intercepted.
         assertThat(underTest.onTouchEvent(DOWN_IN_BOTTOM_SWIPE_REGION_EVENT)).isFalse()
@@ -205,7 +207,7 @@
     @Test
     fun onTouchEvent_communalAndBouncerShowing_doesNotIntercept() {
         // Communal is open.
-        goToScene(CommunalSceneKey.Communal)
+        goToScene(CommunalScenes.Communal)
 
         // Bouncer is visible.
         bouncerShowingFlow.value = true
@@ -220,7 +222,7 @@
     @Test
     fun onTouchEvent_communalAndShadeShowing_doesNotIntercept() {
         // Communal is open.
-        goToScene(CommunalSceneKey.Communal)
+        goToScene(CommunalScenes.Communal)
 
         shadeShowingFlow.value = true
         testableLooper.processAllMessages()
@@ -232,7 +234,7 @@
     @Test
     fun onTouchEvent_containerViewDisposed_doesNotIntercept() {
         // Communal is open.
-        goToScene(CommunalSceneKey.Communal)
+        goToScene(CommunalScenes.Communal)
 
         // Touch events are intercepted.
         assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
@@ -265,7 +267,7 @@
         wm.updateViewLayout(parentView, lp)
     }
 
-    private fun goToScene(scene: CommunalSceneKey) {
+    private fun goToScene(scene: SceneKey) {
         communalRepository.setDesiredScene(scene)
         testableLooper.processAllMessages()
     }
@@ -305,13 +307,5 @@
             MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, CONTAINER_HEIGHT.toFloat(), 0)
         private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
         private val UP_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
-
-        @BeforeClass
-        @JvmStatic
-        fun beforeClass() {
-            // Glanceable hub requires Compose, no point running any of these tests if compose isn't
-            // enabled.
-            assumeTrue(ComposeFacade.isComposeAvailable())
-        }
     }
 }
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 950a9db..43fcdf3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -38,6 +38,7 @@
 import static org.mockito.Mockito.when;
 
 import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
 
 import android.annotation.IdRes;
 import android.content.ContentResolver;
@@ -213,7 +214,6 @@
 import java.util.Optional;
 
 import kotlinx.coroutines.CoroutineDispatcher;
-import kotlinx.coroutines.flow.StateFlowKt;
 import kotlinx.coroutines.test.TestScope;
 
 public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
@@ -287,7 +287,7 @@
     @Mock protected KeyguardMediaController mKeyguardMediaController;
     @Mock protected NavigationModeController mNavigationModeController;
     @Mock protected NavigationBarController mNavigationBarController;
-    @Mock protected QuickSettingsController mQsController;
+    @Mock protected QuickSettingsControllerImpl mQsController;
     @Mock protected ShadeHeaderController mShadeHeaderController;
     @Mock protected ContentResolver mContentResolver;
     @Mock protected TapAgainViewController mTapAgainViewController;
@@ -380,7 +380,7 @@
     protected final ShadeExpansionStateManager mShadeExpansionStateManager =
             new ShadeExpansionStateManager();
 
-    protected QuickSettingsController mQuickSettingsController;
+    protected QuickSettingsControllerImpl mQuickSettingsController;
     @Mock protected Lazy<NotificationPanelViewController> mNotificationPanelViewControllerLazy;
 
     protected FragmentHostManager.FragmentListener mFragmentListener;
@@ -409,10 +409,10 @@
                 new ShadeAnimationRepository(), mShadeRepository);
         mPowerInteractor = keyguardInteractorDeps.getPowerInteractor();
         when(mKeyguardTransitionInteractor.isInTransitionToStateWhere(any())).thenReturn(
-                StateFlowKt.MutableStateFlow(false));
+                MutableStateFlow(false));
         DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
                 mock(DeviceEntryUdfpsInteractor.class);
-        when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow());
+        when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(MutableStateFlow(false));
 
         mShadeInteractor = new ShadeInteractorImpl(
                 mTestScope.getBackgroundScope(),
@@ -461,7 +461,6 @@
                 mKeyguardLogger,
                 mInteractionJankMonitor,
                 mKeyguardInteractor,
-                mKeyguardTransitionInteractor,
                 mDumpManager,
                 mPowerInteractor));
 
@@ -599,6 +598,8 @@
         // Primary Bouncer->Gone
         when(mPrimaryBouncerToGoneTransitionViewModel.getLockscreenAlpha())
                 .thenReturn(emptyFlow());
+        when(mPrimaryBouncerToGoneTransitionViewModel.getNotificationAlpha())
+                .thenReturn(emptyFlow());
 
         NotificationsKeyguardViewStateRepository notifsKeyguardViewStateRepository =
                 new NotificationsKeyguardViewStateRepository();
@@ -792,7 +793,7 @@
 
         when(mNotificationPanelViewControllerLazy.get())
                 .thenReturn(mNotificationPanelViewController);
-        mQuickSettingsController = new QuickSettingsController(
+        mQuickSettingsController = new QuickSettingsControllerImpl(
                 mNotificationPanelViewControllerLazy,
                 mView,
                 mQsFrameTranslateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index d24fe1b..6d5d5be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -854,7 +854,7 @@
         // We are interested in the last value of the stack alpha.
         ArgumentCaptor<Float> alphaCaptor = ArgumentCaptor.forClass(Float.class);
         verify(mNotificationStackScrollLayoutController, atLeastOnce())
-                .setMaxAlphaForExpansion(alphaCaptor.capture());
+                .setMaxAlphaForKeyguard(alphaCaptor.capture(), any());
         assertThat(alphaCaptor.getValue()).isEqualTo(1.0f);
     }
 
@@ -875,7 +875,7 @@
         // We are interested in the last value of the stack alpha.
         ArgumentCaptor<Float> alphaCaptor = ArgumentCaptor.forClass(Float.class);
         verify(mNotificationStackScrollLayoutController, atLeastOnce())
-                .setMaxAlphaForExpansion(alphaCaptor.capture());
+                .setMaxAlphaForKeyguard(alphaCaptor.capture(), any());
         assertThat(alphaCaptor.getValue()).isEqualTo(0.0f);
     }
 
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 3808d30..c31c625 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -36,7 +36,7 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
 
 import android.app.IActivityManager;
 import android.content.pm.ActivityInfo;
@@ -205,7 +205,7 @@
 
         DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
                 mock(DeviceEntryUdfpsInteractor.class);
-        when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow());
+        when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(MutableStateFlow(false));
 
         mShadeInteractor = new ShadeInteractorImpl(
                 mTestScope.getBackgroundScope(),
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 b426d1d..617b25d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shade
 
+import org.mockito.Mockito.`when` as whenever
 import android.content.Context
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
@@ -33,7 +34,6 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.bouncer.ui.binder.BouncerViewBinder
 import com.android.systemui.classifier.FalsingCollectorFake
-import com.android.systemui.compose.ComposeFacade.isComposeAvailable
 import com.android.systemui.dock.DockManager
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FakeFeatureFlagsClassic
@@ -88,7 +88,6 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -113,7 +112,7 @@
     @Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
     @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
     @Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController
-    @Mock private lateinit var quickSettingsController: QuickSettingsController
+    @Mock private lateinit var quickSettingsController: QuickSettingsControllerImpl
     @Mock
     private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
     @Mock private lateinit var lockIconViewController: LockIconViewController
@@ -511,10 +510,6 @@
     @Test
     @Ignore("b/321332798")
     fun setsUpCommunalHubLayout_whenFlagEnabled() {
-        if (!isComposeAvailable()) {
-            return
-        }
-
         whenever(mGlanceableHubContainerController.communalAvailable())
             .thenReturn(MutableStateFlow(true))
 
@@ -537,10 +532,6 @@
 
     @Test
     fun doesNotSetupCommunalHubLayout_whenFlagDisabled() {
-        if (!isComposeAvailable()) {
-            return
-        }
-
         whenever(mGlanceableHubContainerController.communalAvailable())
             .thenReturn(MutableStateFlow(false))
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 0b49a95..a077164 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -22,7 +22,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
 import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
 
 import android.content.res.Resources;
@@ -109,7 +109,7 @@
 
 import kotlinx.coroutines.test.TestScope;
 
-public class QuickSettingsControllerBaseTest extends SysuiTestCase {
+public class QuickSettingsControllerImplBaseTest extends SysuiTestCase {
     protected static final float QS_FRAME_START_X = 0f;
     protected static final int QS_FRAME_WIDTH = 1000;
     protected static final int QS_FRAME_TOP = 0;
@@ -119,7 +119,7 @@
     protected static final int DEFAULT_MIN_HEIGHT_SPLIT_SHADE = DEFAULT_HEIGHT;
     protected static final int DEFAULT_MIN_HEIGHT = 300;
 
-    protected QuickSettingsController mQsController;
+    protected QuickSettingsControllerImpl mQsController;
 
     protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
     protected TestScope mTestScope = mKosmos.getTestScope();
@@ -234,7 +234,8 @@
 
         DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
                 mock(DeviceEntryUdfpsInteractor.class);
-        when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow());
+        when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(
+                MutableStateFlow(false));
 
         mShadeInteractor = new ShadeInteractorImpl(
                 mTestScope.getBackgroundScope(),
@@ -304,7 +305,7 @@
 
         mMainHandler = new Handler(Looper.getMainLooper());
 
-        mQsController = new QuickSettingsController(
+        mQsController = new QuickSettingsControllerImpl(
                 mPanelViewControllerLazy,
                 mPanelView,
                 mQsFrameTranslateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
index 997e0e2..b16f412 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
@@ -41,8 +41,8 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.res.R;
 import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.res.R;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -53,7 +53,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class QuickSettingsControllerTest extends QuickSettingsControllerBaseTest {
+public class QuickSettingsControllerImplTest extends QuickSettingsControllerImplBaseTest {
 
     @Test
     public void testCloseQsSideEffects() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerWithCoroutinesTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
index cc4a063..2c453a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
@@ -27,7 +27,7 @@
 
 @SmallTest
 @OptIn(ExperimentalCoroutinesApi::class)
-class QuickSettingsControllerWithCoroutinesTest : QuickSettingsControllerBaseTest() {
+class QuickSettingsControllerImplWithCoroutinesTest : QuickSettingsControllerImplBaseTest() {
 
     @Test
     fun isExpansionEnabled_dozing_false() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
index 7737b43..0a9541a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
@@ -1,15 +1,47 @@
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.shade.transition
 
+import android.platform.test.annotations.DisableFlags
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.FakeSceneDataSource
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.shade.STATE_OPENING
 import com.android.systemui.shade.ShadeExpansionChangeEvent
 import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.domain.interactor.panelExpansionInteractor
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.policy.FakeConfigurationController
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -29,32 +61,95 @@
 
     private val configurationController = FakeConfigurationController()
     private val shadeExpansionStateManager = ShadeExpansionStateManager()
+    private val kosmos = testKosmos()
+    private lateinit var testScope: TestScope
+    private lateinit var applicationScope: CoroutineScope
+    private lateinit var panelExpansionInteractor: PanelExpansionInteractor
+    private lateinit var deviceEntryRepository: FakeDeviceEntryRepository
+    private lateinit var deviceUnlockedInteractor: DeviceUnlockedInteractor
+    private lateinit var sceneInteractor: SceneInteractor
+    private lateinit var fakeSceneDataSource: FakeSceneDataSource
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
+        testScope = kosmos.testScope
+        applicationScope = kosmos.applicationCoroutineScope
+        panelExpansionInteractor = kosmos.panelExpansionInteractor
+        deviceEntryRepository = kosmos.fakeDeviceEntryRepository
+        deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
+        sceneInteractor = kosmos.sceneInteractor
+        fakeSceneDataSource = kosmos.fakeSceneDataSource
+
         controller =
             ShadeTransitionController(
+                applicationScope,
                 configurationController,
                 shadeExpansionStateManager,
                 dumpManager,
                 context,
                 scrimShadeTransitionController,
                 statusBarStateController,
-                    ResourcesSplitShadeStateController()
-            )
+                ResourcesSplitShadeStateController(),
+            ) {
+                panelExpansionInteractor
+            }
     }
 
     @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER)
     fun onPanelStateChanged_forwardsToScrimTransitionController() {
-        startPanelExpansion()
+        startLegacyPanelExpansion()
 
         verify(scrimShadeTransitionController).onPanelStateChanged(STATE_OPENING)
         verify(scrimShadeTransitionController).onPanelExpansionChanged(DEFAULT_EXPANSION_EVENT)
     }
 
-    private fun startPanelExpansion() {
+    @Test
+    @EnableSceneContainer
+    fun sceneChanges_forwardsToScrimTransitionController() =
+        testScope.runTest {
+            var latestChangeEvent: ShadeExpansionChangeEvent? = null
+            whenever(scrimShadeTransitionController.onPanelExpansionChanged(any())).thenAnswer {
+                latestChangeEvent = it.arguments[0] as ShadeExpansionChangeEvent
+                Unit
+            }
+            setUnlocked(true)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(Scenes.Gone)
+                )
+            sceneInteractor.setTransitionState(transitionState)
+
+            changeScene(Scenes.Gone, transitionState)
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            assertThat(currentScene).isEqualTo(Scenes.Gone)
+
+            assertThat(latestChangeEvent)
+                .isEqualTo(
+                    ShadeExpansionChangeEvent(
+                        fraction = 0f,
+                        expanded = false,
+                        tracking = true,
+                        dragDownPxAmount = 0f,
+                    )
+                )
+
+            changeScene(Scenes.Shade, transitionState) { progress ->
+                assertThat(latestChangeEvent)
+                    .isEqualTo(
+                        ShadeExpansionChangeEvent(
+                            fraction = progress,
+                            expanded = progress > 0,
+                            tracking = true,
+                            dragDownPxAmount = 0f,
+                        )
+                    )
+            }
+        }
+
+    private fun startLegacyPanelExpansion() {
         shadeExpansionStateManager.onPanelExpansionChanged(
             DEFAULT_EXPANSION_EVENT.fraction,
             DEFAULT_EXPANSION_EVENT.expanded,
@@ -63,6 +158,52 @@
         )
     }
 
+    private fun TestScope.setUnlocked(isUnlocked: Boolean) {
+        val isDeviceUnlocked by collectLastValue(deviceUnlockedInteractor.isDeviceUnlocked)
+        deviceEntryRepository.setUnlocked(isUnlocked)
+        runCurrent()
+
+        assertThat(isDeviceUnlocked).isEqualTo(isUnlocked)
+    }
+
+    private fun TestScope.changeScene(
+        toScene: SceneKey,
+        transitionState: MutableStateFlow<ObservableTransitionState>,
+        assertDuringProgress: ((progress: Float) -> Unit) = {},
+    ) {
+        val currentScene by collectLastValue(sceneInteractor.currentScene)
+        val progressFlow = MutableStateFlow(0f)
+        transitionState.value =
+            ObservableTransitionState.Transition(
+                fromScene = checkNotNull(currentScene),
+                toScene = toScene,
+                progress = progressFlow,
+                isInitiatedByUserInput = true,
+                isUserInputOngoing = flowOf(true),
+            )
+        runCurrent()
+        assertDuringProgress(progressFlow.value)
+
+        progressFlow.value = 0.2f
+        runCurrent()
+        assertDuringProgress(progressFlow.value)
+
+        progressFlow.value = 0.6f
+        runCurrent()
+        assertDuringProgress(progressFlow.value)
+
+        progressFlow.value = 1f
+        runCurrent()
+        assertDuringProgress(progressFlow.value)
+
+        transitionState.value = ObservableTransitionState.Idle(toScene)
+        fakeSceneDataSource.changeScene(toScene)
+        runCurrent()
+        assertDuringProgress(progressFlow.value)
+
+        assertThat(currentScene).isEqualTo(toScene)
+    }
+
     companion object {
         private const val DEFAULT_DRAG_DOWN_AMOUNT = 123f
         private val DEFAULT_EXPANSION_EVENT =
@@ -70,6 +211,7 @@
                 fraction = 0.5f,
                 expanded = true,
                 tracking = true,
-                dragDownPxAmount = DEFAULT_DRAG_DOWN_AMOUNT)
+                dragDownPxAmount = DEFAULT_DRAG_DOWN_AMOUNT
+            )
     }
 }
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 aa53558..1504d4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -92,6 +92,21 @@
 @TestableLooper.RunWithLooper
 public class KeyguardIndicationControllerTest extends KeyguardIndicationControllerBaseTest {
     @Test
+    public void afterFaceLockout_skipShowingFaceNotRecognized() {
+        createController();
+        onFaceLockoutError("lockout");
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE, "lockout");
+        clearInvocations(mRotateTextViewController);
+
+        // WHEN face sends an onBiometricHelp BIOMETRIC_HELP_FACE_NOT_RECOGNIZED (face fail)
+        mKeyguardUpdateMonitorCallback.onBiometricHelp(
+                BIOMETRIC_HELP_FACE_NOT_RECOGNIZED,
+                "Face not recognized",
+                BiometricSourceType.FACE);
+        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE); // no updated message
+    }
+
+    @Test
     public void createController_setIndicationAreaAgain_destroysPreviousRotateTextViewController() {
         // GIVEN a controller with a mocked rotate text view controlller
         final KeyguardIndicationRotateTextViewController mockedRotateTextViewController =
@@ -593,7 +608,7 @@
         mController.getKeyguardCallback().onBiometricError(FACE_ERROR_TIMEOUT,
                 "A message", BiometricSourceType.FACE);
 
-        verify(mStatusBarKeyguardViewManager).setKeyguardMessage(eq(message), any());
+        verify(mStatusBarKeyguardViewManager).setKeyguardMessage(eq(message), any(), any());
     }
 
     @Test
@@ -608,7 +623,8 @@
         mController.getKeyguardCallback().onBiometricError(FACE_ERROR_TIMEOUT,
                 "A message", BiometricSourceType.FACE);
 
-        verify(mStatusBarKeyguardViewManager, never()).setKeyguardMessage(eq(message), any());
+        verify(mStatusBarKeyguardViewManager, never()).setKeyguardMessage(
+                eq(message), any(), any());
     }
 
     @Test
@@ -1242,7 +1258,7 @@
     public void onBiometricFailed_resetFaceHelpMessageDeferral() {
         createController();
 
-        // WHEN face sends an onBiometricHelp BIOMETRIC_HELP_FACE_NOT_RECOGNIZED
+        // WHEN face sends an onBiometricAuthFailed
         mKeyguardUpdateMonitorCallback.onBiometricAuthFailed(BiometricSourceType.FACE);
 
         // THEN face help message deferral is reset
@@ -1331,7 +1347,9 @@
         verify(mStatusBarKeyguardViewManager)
                 .setKeyguardMessage(
                         eq(mContext.getString(R.string.keyguard_face_unlock_unavailable)),
-                        any());
+                        any(),
+                        any()
+                );
     }
 
     @Test
@@ -1471,6 +1489,71 @@
                 mContext.getString(R.string.keyguard_suggest_fingerprint));
     }
 
+    @Test
+    public void faceErrorMessageDroppedBecauseFingerprintMessageShowing() {
+        createController();
+        mController.setVisible(true);
+        mController.getKeyguardCallback().onBiometricHelp(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
+                "fp not recognized", BiometricSourceType.FINGERPRINT);
+        clearInvocations(mRotateTextViewController);
+
+        onFaceLockoutError("lockout");
+        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+    }
+
+    @Test
+    public void faceUnlockedMessageShowsEvenWhenFingerprintMessageShowing() {
+        createController();
+        mController.setVisible(true);
+        mController.getKeyguardCallback().onBiometricHelp(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
+                "fp not recognized", BiometricSourceType.FINGERPRINT);
+        clearInvocations(mRotateTextViewController);
+
+        when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(true);
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
+                .thenReturn(true);
+        mController.getKeyguardCallback().onBiometricAuthenticated(0,
+                BiometricSourceType.FACE, false);
+        verifyIndicationMessage(
+                INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                mContext.getString(R.string.keyguard_face_successful_unlock));
+    }
+
+    @Test
+    public void onTrustAgentErrorMessageDroppedBecauseFingerprintMessageShowing() {
+        createController();
+        mController.setVisible(true);
+        mController.getKeyguardCallback().onBiometricHelp(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
+                "fp not recognized", BiometricSourceType.FINGERPRINT);
+        clearInvocations(mRotateTextViewController);
+
+        mKeyguardUpdateMonitorCallback.onTrustAgentErrorMessage("testMessage");
+        verifyNoMessage(INDICATION_TYPE_TRUST);
+        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+    }
+
+    @Test
+    public void trustGrantedMessageShowsEvenWhenFingerprintMessageShowing() {
+        createController();
+        mController.setVisible(true);
+        mController.getKeyguardCallback().onBiometricHelp(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
+                "fp not recognized", BiometricSourceType.FINGERPRINT);
+        clearInvocations(mRotateTextViewController);
+
+        // GIVEN trust is granted
+        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+
+        // WHEN the showTrustGranted method is called
+        final String trustGrantedMsg = "testing trust granted message after fp message";
+        mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+                false, false, new TrustGrantFlags(0), trustGrantedMsg);
+
+        // THEN verify the trust granted message shows
+        verifyIndicationMessage(
+                INDICATION_TYPE_TRUST,
+                trustGrantedMsg);
+    }
+
     private void screenIsTurningOn() {
         when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_TURNING_ON);
     }
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 86116a0..d35c7dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -9,6 +9,7 @@
 import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.TestMocksModule
+import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
 import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.dagger.SysUISingleton
@@ -35,6 +36,8 @@
 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.any
+import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
 import dagger.BindsInstance
 import dagger.Component
@@ -55,6 +58,7 @@
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyLong
 import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.isNull
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.clearInvocations
@@ -310,6 +314,17 @@
     }
 
     @Test
+    fun testGoToLockedShadeCancelDoesntLeaveShadeOpenOnKeyguardHide() {
+        whenever(lockScreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false)
+        whenever(lockScreenUserManager.isLockscreenPublicMode(any())).thenReturn(true)
+        transitionController.goToLockedShade(null)
+        val captor = argumentCaptor<Runnable>()
+        verify(centralSurfaces).showBouncerWithDimissAndCancelIfKeyguard(isNull(), captor.capture())
+        captor.value.run()
+        verify(statusbarStateController).setLeaveOpenOnKeyguardHide(false)
+    }
+
+    @Test
     fun testDragDownAmountDoesntCallOutInLockedDownShade() {
         whenever(nsslController.isInLockedDownShade).thenReturn(true)
         transitionController.dragDownAmount = 10f
@@ -611,6 +626,7 @@
             [
                 SysUITestModule::class,
                 UserDomainLayerModule::class,
+                BiometricsDomainLayerModule::class,
             ]
     )
     interface TestComponent {
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 fe16347..103dcb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -54,7 +54,7 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.LargeScreenHeaderHelper
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -70,7 +70,7 @@
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertEquals
@@ -86,8 +86,8 @@
 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)
@@ -155,7 +155,7 @@
                 { kosmos.sceneInteractor },
             )
 
-        whenever(deviceEntryUdfpsInteractor.isUdfpsSupported).thenReturn(emptyFlow())
+        whenever(deviceEntryUdfpsInteractor.isUdfpsSupported).thenReturn(MutableStateFlow(false))
         shadeInteractor =
             ShadeInteractorImpl(
                 testScope.backgroundScope,
@@ -313,48 +313,48 @@
             kosmos.fakeDeviceEntryRepository.setUnlocked(false)
             runCurrent()
             kosmos.sceneInteractor.changeScene(
-                toScene = SceneKey.Lockscreen,
+                toScene = Scenes.Lockscreen,
                 loggingReason = "reason"
             )
             runCurrent()
             assertThat(kosmos.deviceUnlockedInteractor.isDeviceUnlocked.value).isFalse()
-            assertThat(currentScene).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
 
             // Call start to begin hydrating based on the scene framework:
             underTest.start()
 
-            kosmos.sceneInteractor.changeScene(toScene = SceneKey.Bouncer, loggingReason = "reason")
+            kosmos.sceneInteractor.changeScene(toScene = Scenes.Bouncer, loggingReason = "reason")
             runCurrent()
-            assertThat(currentScene).isEqualTo(SceneKey.Bouncer)
+            assertThat(currentScene).isEqualTo(Scenes.Bouncer)
             assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
 
-            kosmos.sceneInteractor.changeScene(toScene = SceneKey.Shade, loggingReason = "reason")
+            kosmos.sceneInteractor.changeScene(toScene = Scenes.Shade, loggingReason = "reason")
             runCurrent()
-            assertThat(currentScene).isEqualTo(SceneKey.Shade)
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
             assertThat(statusBarState).isEqualTo(StatusBarState.SHADE_LOCKED)
 
             kosmos.sceneInteractor.changeScene(
-                toScene = SceneKey.QuickSettings,
+                toScene = Scenes.QuickSettings,
                 loggingReason = "reason"
             )
             runCurrent()
-            assertThat(currentScene).isEqualTo(SceneKey.QuickSettings)
+            assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
             assertThat(statusBarState).isEqualTo(StatusBarState.SHADE_LOCKED)
 
             kosmos.sceneInteractor.changeScene(
-                toScene = SceneKey.Communal,
+                toScene = Scenes.Communal,
                 loggingReason = "reason"
             )
             runCurrent()
-            assertThat(currentScene).isEqualTo(SceneKey.Communal)
+            assertThat(currentScene).isEqualTo(Scenes.Communal)
             assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
 
             kosmos.sceneInteractor.changeScene(
-                toScene = SceneKey.Lockscreen,
+                toScene = Scenes.Lockscreen,
                 loggingReason = "reason"
             )
             runCurrent()
-            assertThat(currentScene).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
             assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
         }
 
@@ -377,25 +377,25 @@
             )
             kosmos.fakeDeviceEntryRepository.setUnlocked(true)
             runCurrent()
-            kosmos.sceneInteractor.changeScene(toScene = SceneKey.Gone, loggingReason = "reason")
+            kosmos.sceneInteractor.changeScene(toScene = Scenes.Gone, loggingReason = "reason")
             runCurrent()
             assertThat(kosmos.deviceUnlockedInteractor.isDeviceUnlocked.value).isTrue()
-            assertThat(currentScene).isEqualTo(SceneKey.Gone)
+            assertThat(currentScene).isEqualTo(Scenes.Gone)
 
             // Call start to begin hydrating based on the scene framework:
             underTest.start()
 
-            kosmos.sceneInteractor.changeScene(toScene = SceneKey.Shade, loggingReason = "reason")
+            kosmos.sceneInteractor.changeScene(toScene = Scenes.Shade, loggingReason = "reason")
             runCurrent()
-            assertThat(currentScene).isEqualTo(SceneKey.Shade)
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
             assertThat(statusBarState).isEqualTo(StatusBarState.SHADE)
 
             kosmos.sceneInteractor.changeScene(
-                toScene = SceneKey.QuickSettings,
+                toScene = Scenes.QuickSettings,
                 loggingReason = "reason"
             )
             runCurrent()
-            assertThat(currentScene).isEqualTo(SceneKey.QuickSettings)
+            assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
             assertThat(statusBarState).isEqualTo(StatusBarState.SHADE)
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
index a59ba07..eb692eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
@@ -32,9 +32,9 @@
 import android.view.LayoutInflater;
 import android.view.View;
 
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -51,6 +51,7 @@
 import java.util.List;
 import java.util.Map;
 
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -65,7 +66,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mDependency.injectMockDependency(MediaOutputDialogFactory.class);
+        mDependency.injectMockDependency(MediaOutputDialogManager.class);
         allowTestableLooperAsMainThread();
         when(mBindStage.getStageParams(any())).thenReturn(new RowContentBindParams());
         mDynamicChildBindController =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index 4519ba6..419b0fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -68,6 +68,7 @@
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.collection.render.NotifViewBarn;
 import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
 import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.Before;
@@ -107,6 +108,7 @@
     @Mock private IStatusBarService mService;
     @Mock private BindEventManagerImpl mBindEventManagerImpl;
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+    @Mock private SensitiveNotificationProtectionController mSensitiveNotifProtectionController;
     @Mock private Handler mHandler;
     @Mock private SecureSettings mSecureSettings;
     @Spy private FakeNotifInflater mNotifInflater = new FakeNotifInflater();
@@ -128,6 +130,7 @@
                 mHandler,
                 mSecureSettings,
                 mLockscreenUserManager,
+                mSensitiveNotifProtectionController,
                 mSectionStyleProvider,
                 mUserTracker,
                 mGroupMembershipManager
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 7d99d05..018a571 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
@@ -34,6 +34,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.notification.collection.listbuilder.pluggable.Pluggable
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -93,6 +94,26 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun onSensitiveStateChanged_invokeInvalidationListener() {
+        coordinator.attach(pipeline)
+        val invalidator =
+            withArgCaptor<Invalidator> { verify(pipeline).addPreRenderInvalidator(capture()) }
+        val onSensitiveStateChangedListener =
+            withArgCaptor<Runnable> {
+                verify(sensitiveNotificationProtectionController)
+                    .registerSensitiveStateListener(capture())
+            }
+
+        val invalidationListener = mock<Pluggable.PluggableListener<Invalidator>>()
+        invalidator.setInvalidationListener(invalidationListener)
+
+        onSensitiveStateChangedListener.run()
+
+        verify(invalidationListener).onPluggableInvalidated(eq(invalidator), any())
+    }
+
+    @Test
     fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction() {
         coordinator.attach(pipeline)
         val onBeforeRenderListListener =
@@ -130,6 +151,7 @@
         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
 
         verify(entry.representativeEntry!!).setSensitive(false, true)
+        verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(true)
     }
 
     @Test
@@ -156,6 +178,7 @@
         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
 
         verify(entry.representativeEntry!!).setSensitive(true, false)
+        verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(false)
     }
 
     @Test
@@ -196,6 +219,7 @@
         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
 
         verify(entry.representativeEntry!!).setSensitive(false, true)
+        verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(true)
     }
 
     @Test
@@ -222,6 +246,7 @@
         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
 
         verify(entry.representativeEntry!!).setSensitive(true, false)
+        verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(false)
     }
 
     @Test
@@ -262,6 +287,7 @@
         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
 
         verify(entry.representativeEntry!!).setSensitive(false, true)
+        verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(true)
     }
 
     @Test
@@ -288,6 +314,7 @@
         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
 
         verify(entry.representativeEntry!!).setSensitive(true, false)
+        verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(false)
     }
 
     @Test
@@ -329,6 +356,7 @@
         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
 
         verify(entry.representativeEntry!!).setSensitive(false, true)
+        verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(true)
     }
 
     @Test
@@ -356,6 +384,7 @@
         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
 
         verify(entry.representativeEntry!!).setSensitive(true, true)
+        verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(false)
     }
 
     @Test
@@ -396,6 +425,7 @@
         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
 
         verify(entry.representativeEntry!!).setSensitive(true, true)
+        verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(true)
     }
 
     @Test
@@ -422,6 +452,7 @@
         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
 
         verify(entry.representativeEntry!!).setSensitive(true, true)
+        verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(false)
     }
 
     @Test
@@ -462,6 +493,7 @@
         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
 
         verify(entry.representativeEntry!!).setSensitive(false, true)
+        verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(true)
     }
 
     @Test
@@ -489,6 +521,7 @@
         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
 
         verify(entry.representativeEntry!!).setSensitive(true, true)
+        verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(false)
     }
 
     @Test
@@ -531,6 +564,7 @@
         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
 
         verify(entry.representativeEntry!!).setSensitive(true, true)
+        verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(true)
     }
 
     @Test
@@ -559,6 +593,7 @@
         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
 
         verify(entry.representativeEntry!!).setSensitive(true, true)
+        verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(false)
     }
 
     @Test
@@ -584,6 +619,7 @@
         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
 
         verify(entry.representativeEntry!!, never()).setSensitive(any(), any())
+        verify(entry.representativeEntry!!.row!!, never()).setPublicExpanderVisible(any())
     }
 
     private fun fakeNotification(notifUserId: Int, needsRedaction: Boolean): ListEntry {
@@ -591,7 +627,11 @@
             mock<UserHandle>().apply { whenever(identifier).thenReturn(notifUserId) }
         val mockSbn: StatusBarNotification =
             mock<StatusBarNotification>().apply { whenever(user).thenReturn(mockUserHandle) }
-        val mockEntry = mock<NotificationEntry>().apply { whenever(sbn).thenReturn(mockSbn) }
+        val mockRow: ExpandableNotificationRow = mock<ExpandableNotificationRow>()
+        val mockEntry = mock<NotificationEntry>().apply {
+            whenever(sbn).thenReturn(mockSbn)
+            whenever(row).thenReturn(mockRow)
+        }
         whenever(lockscreenUserManager.needsRedaction(mockEntry)).thenReturn(needsRedaction)
         whenever(mockEntry.rowExists()).thenReturn(true)
         return object : ListEntry("key", 0) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
index 73c49c0..34eeba0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
@@ -23,6 +23,7 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
+import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -31,7 +32,9 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
+import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation
 import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
@@ -46,6 +49,7 @@
 import org.junit.runner.RunWith
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.Mockito.`when` as whenever
@@ -55,6 +59,8 @@
 @RunWithLooper
 class NotifUiAdjustmentProviderTest : SysuiTestCase() {
     private val lockscreenUserManager: NotificationLockscreenUserManager = mock()
+    private val sensitiveNotifProtectionController: SensitiveNotificationProtectionController =
+        mock()
     private val sectionStyleProvider: SectionStyleProvider = mock()
     private val handler: Handler = mock()
     private val secureSettings: SecureSettings = mock()
@@ -75,6 +81,7 @@
         handler,
         secureSettings,
         lockscreenUserManager,
+        sensitiveNotifProtectionController,
         sectionStyleProvider,
         userTracker,
         groupMembershipManager,
@@ -106,6 +113,19 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun sensitiveNotifProtectionStateChangeWillNotifDirty() {
+        val dirtyListener = mock<Runnable>()
+        adjustmentProvider.addDirtyListener(dirtyListener)
+        val sensitiveStateChangedListener =
+            withArgCaptor<Runnable> {
+                verify(sensitiveNotifProtectionController).registerSensitiveStateListener(capture())
+            }
+        sensitiveStateChangedListener.run()
+        verify(dirtyListener).run()
+    }
+
+    @Test
     fun additionalAddDoesNotRegisterAgain() {
         clearInvocations(secureSettings)
         adjustmentProvider.addDirtyListener(mock())
@@ -141,12 +161,14 @@
     fun changeIsChildInGroup_asyncHybirdFlagEnabled_needReInflation() {
         // Given: an Entry that is not child in group
         // AsyncHybridViewInflation flag is enabled
-        whenever(groupMembershipManager.isChildInGroup(entry)).thenReturn(false)
+        val spySbn = spy(entry.sbn)
+        entry.sbn = spySbn
+        whenever(spySbn.isAppOrSystemGroupChild).thenReturn(false)
         val oldAdjustment = adjustmentProvider.calculateAdjustment(entry)
         assertThat(oldAdjustment.isChildInGroup).isFalse()
 
         // When: the Entry becomes a group child
-        whenever(groupMembershipManager.isChildInGroup(entry)).thenReturn(true)
+        whenever(spySbn.isAppOrSystemGroupChild).thenReturn(true)
         val newAdjustment = adjustmentProvider.calculateAdjustment(entry)
         assertThat(newAdjustment.isChildInGroup).isTrue()
         assertThat(newAdjustment).isNotEqualTo(oldAdjustment)
@@ -160,12 +182,14 @@
     fun changeIsChildInGroup_asyncHybirdFlagDisabled_noNeedForReInflation() {
         // Given: an Entry that is not child in group
         // AsyncHybridViewInflation flag is disabled
-        whenever(groupMembershipManager.isChildInGroup(entry)).thenReturn(false)
+        val spySbn = spy(entry.sbn)
+        entry.sbn = spySbn
+        whenever(spySbn.isAppOrSystemGroupChild).thenReturn(false)
         val oldAdjustment = adjustmentProvider.calculateAdjustment(entry)
         assertThat(oldAdjustment.isChildInGroup).isFalse()
 
         // When: the Entry becomes a group child
-        whenever(groupMembershipManager.isChildInGroup(entry)).thenReturn(true)
+        whenever(spySbn.isAppOrSystemGroupChild).thenReturn(true)
         val newAdjustment = adjustmentProvider.calculateAdjustment(entry)
         assertThat(newAdjustment.isChildInGroup).isTrue()
         assertThat(newAdjustment).isNotEqualTo(oldAdjustment)
@@ -173,4 +197,58 @@
         // Then: need no re-inflation
         assertFalse(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment))
     }
+
+    @Test
+    @EnableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME)
+    fun changeIsGroupSummary_needReInflation() {
+        // Given: an Entry that is not a group summary
+        val spySbn = spy(entry.sbn)
+        entry.sbn = spySbn
+        whenever(spySbn.isAppOrSystemGroupSummary).thenReturn(false)
+        val oldAdjustment = adjustmentProvider.calculateAdjustment(entry)
+        assertThat(oldAdjustment.isGroupSummary).isFalse()
+
+        // When: the Entry becomes a group summary
+        whenever(spySbn.isAppOrSystemGroupSummary).thenReturn(true)
+        val newAdjustment = adjustmentProvider.calculateAdjustment(entry)
+        assertThat(newAdjustment.isGroupSummary).isTrue()
+        assertThat(newAdjustment).isNotEqualTo(oldAdjustment)
+
+        // Then: Need re-inflation
+        assertTrue(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment))
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun changeSensitiveNotifProtection_screenshareNotificationHidingEnabled_needReinflate() {
+        whenever(sensitiveNotifProtectionController.shouldProtectNotification(entry))
+            .thenReturn(false)
+        val oldAdjustment: NotifUiAdjustment = adjustmentProvider.calculateAdjustment(entry)
+        assertFalse(oldAdjustment.needsRedaction)
+
+        whenever(sensitiveNotifProtectionController.shouldProtectNotification(entry))
+            .thenReturn(true)
+        val newAdjustment = adjustmentProvider.calculateAdjustment(entry)
+        assertTrue(newAdjustment.needsRedaction)
+
+        // Then: need re-inflation
+        assertTrue(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment))
+    }
+
+    @Test
+    @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun changeSensitiveNotifProtection_screenshareNotificationHidingDisabled_noNeedReinflate() {
+        whenever(sensitiveNotifProtectionController.shouldProtectNotification(entry))
+            .thenReturn(false)
+        val oldAdjustment = adjustmentProvider.calculateAdjustment(entry)
+        assertFalse(oldAdjustment.needsRedaction)
+
+        whenever(sensitiveNotifProtectionController.shouldProtectNotification(entry))
+            .thenReturn(true)
+        val newAdjustment = adjustmentProvider.calculateAdjustment(entry)
+        assertFalse(newAdjustment.needsRedaction)
+
+        // Then: need no re-inflation
+        assertFalse(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment))
+    }
 }
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 b3fc25c..24195fe 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
@@ -16,8 +16,10 @@
 
 package com.android.systemui.statusbar.notification.icon.ui.viewmodel
 
+import android.platform.test.annotations.DisableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
 import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
 import com.android.systemui.SysUITestComponent
 import com.android.systemui.SysUITestModule
@@ -238,6 +240,7 @@
         }
 
     @Test
+    @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun animationsEnabled_isTrue_whenKeyguardIsShowing() =
         testComponent.runTest {
             keyguardTransitionRepository.sendTransitionStep(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 49ba915..b114e13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -60,7 +60,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.SysuiTestableContext;
 import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
@@ -69,6 +68,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
+import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 
@@ -103,7 +103,6 @@
                 TestableLooper.get(this),
                 mFeatureFlags);
         mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL);
-        mFeatureFlags.setDefault(Flags.SENSITIVE_REVEAL_ANIM);
     }
 
     @Test
@@ -185,14 +184,6 @@
     }
 
     @Test
-    public void testSetSensitiveOnNotifRowNotifiesOfHeightChange_withOtherFlagValue()
-            throws Exception {
-        FakeFeatureFlags flags = mFeatureFlags;
-        flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
-        testSetSensitiveOnNotifRowNotifiesOfHeightChange();
-    }
-
-    @Test
     public void testSetSensitiveOnNotifRowNotifiesOfHeightChange() throws Exception {
         // GIVEN a sensitive notification row that's currently redacted
         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
@@ -209,19 +200,10 @@
         // WHEN the row is set to no longer be sensitive
         row.setSensitive(false, true);
 
-        boolean expectAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
         // VERIFY that the height change listener is invoked
         assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPrivateLayout());
         assertThat(row.getIntrinsicHeight()).isGreaterThan(0);
-        verify(listener).onHeightChanged(eq(row), eq(expectAnimation));
-    }
-
-    @Test
-    public void testSetSensitiveOnGroupRowNotifiesOfHeightChange_withOtherFlagValue()
-            throws Exception {
-        FakeFeatureFlags flags = mFeatureFlags;
-        flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
-        testSetSensitiveOnGroupRowNotifiesOfHeightChange();
+        verify(listener).onHeightChanged(eq(row), eq(true));
     }
 
     @Test
@@ -241,19 +223,10 @@
         // WHEN the row is set to no longer be sensitive
         group.setSensitive(false, true);
 
-        boolean expectAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
         // VERIFY that the height change listener is invoked
         assertThat(group.getShowingLayout()).isSameInstanceAs(group.getPrivateLayout());
         assertThat(group.getIntrinsicHeight()).isGreaterThan(0);
-        verify(listener).onHeightChanged(eq(group), eq(expectAnimation));
-    }
-
-    @Test
-    public void testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange_withOtherFlagValue()
-            throws Exception {
-        FakeFeatureFlags flags = mFeatureFlags;
-        flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
-        testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange();
+        verify(listener).onHeightChanged(eq(group), eq(true));
     }
 
     @Test
@@ -330,6 +303,61 @@
     }
 
     @Test
+    @EnableFlags(NotificationContentAlphaOptimization.FLAG_NAME)
+    public void setHideSensitive_shouldNotDisturbAnimation() throws Exception {
+        //Given: A row that is during alpha animation
+        ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+
+        assertEquals(row.getPrivateLayout(), row.getContentView());
+        row.setContentAlpha(0.5f);
+
+        //When: Set its hideSensitive without changing the content view to show
+        row.setHideSensitive(
+                /* hideSensitive= */ false,
+                /* animated= */ false,
+                /* delay=  */ 0L,
+                /* duration=  */ 0L
+        );
+        assertEquals(row.getPrivateLayout(), row.getContentView());
+
+        //Then: The alpha value should not be reset
+        assertEquals(0.5f, row.getPrivateLayout().getAlpha(), 0);
+    }
+
+    @Test
+    @EnableFlags(NotificationContentAlphaOptimization.FLAG_NAME)
+    public void setHideSensitive_changeContent_shouldNotDisturbAnimation() throws Exception {
+
+        // Given: A sensitive row that has public version but is not hiding sensitive,
+        // and is during an animation that sets its alpha value to be 0.5f
+        Notification publicNotif = mNotificationTestHelper.createNotification();
+        publicNotif.publicVersion = mNotificationTestHelper.createNotification();
+        ExpandableNotificationRow row = mNotificationTestHelper.createRow(publicNotif);
+        row.setSensitive(true, false);
+        row.setContentAlpha(0.5f);
+
+        assertEquals(0.5f, row.getPrivateLayout().getAlpha(), 0);
+        assertEquals(View.VISIBLE, row.getPrivateLayout().getVisibility());
+
+        // When: Change its hideSensitive and changes the content view to show the public version
+        row.setHideSensitive(
+                /* hideSensitive= */ true,
+                /* animated= */ false,
+                /* delay=  */ 0L,
+                /* duration=  */ 0L
+        );
+
+        // Then: The alpha value of private layout should be reset to 1, private layout be
+        // INVISIBLE;
+        // The alpha value of public layout should be 0.5 to preserve the animation state, public
+        // layout should be VISIBLE
+        assertEquals(View.INVISIBLE, row.getPrivateLayout().getVisibility());
+        assertEquals(1f, row.getPrivateLayout().getAlpha(), 0);
+        assertEquals(View.VISIBLE, row.getPublicLayout().getVisibility());
+        assertEquals(0.5f, row.getPublicLayout().getAlpha(), 0);
+    }
+
+    @Test
     public void testReinflatedOnDensityChange() throws Exception {
         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
         NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 1763d9b..3c1f559 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
@@ -183,8 +184,6 @@
                 .thenReturn(packageInfo);
         final ApplicationInfo applicationInfo = new ApplicationInfo();
         applicationInfo.uid = TEST_UID;  // non-zero
-        when(mMockPackageManager.getApplicationInfo(eq(TEST_PACKAGE_NAME), anyInt())).thenReturn(
-                applicationInfo);
         final PackageInfo systemPackageInfo = new PackageInfo();
         systemPackageInfo.packageName = TEST_SYSTEM_PACKAGE_NAME;
         when(mMockPackageManager.getPackageInfo(eq(TEST_SYSTEM_PACKAGE_NAME), anyInt()))
@@ -207,6 +206,7 @@
                         .addMessage(new Notification.MessagingStyle.Message(
                                 "hello!", 1000, new Person.Builder().setName("other").build())))
                 .build();
+        notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, applicationInfo);
         mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
                 notification, UserHandle.CURRENT, null, 0);
         mEntry = new NotificationEntryBuilder().setSbn(mSbn).setShortcutInfo(mShortcutInfo).build();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
index dbf7b6c..012ff2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
@@ -38,6 +38,7 @@
 import android.view.View
 import android.view.accessibility.accessibilityManager
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.logging.UiEventLogger
 import com.android.internal.logging.metricsLogger
@@ -55,8 +56,7 @@
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.shade.shadeControllerSceneImpl
 import com.android.systemui.statusbar.NotificationEntryHelper
@@ -602,9 +602,9 @@
     private fun setIsLockscreenOrShadeVisible(isVisible: Boolean) {
         val key =
             if (isVisible) {
-                SceneKey.Lockscreen
+                Scenes.Lockscreen
             } else {
-                SceneKey.Bouncer
+                Scenes.Bouncer
             }
         sceneInteractor.changeScene(key, "test")
         sceneInteractor.setTransitionState(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index b59385c..f31b1c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO;
 import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
@@ -143,8 +144,6 @@
                 .thenReturn(packageInfo);
         final ApplicationInfo applicationInfo = new ApplicationInfo();
         applicationInfo.uid = TEST_UID;  // non-zero
-        when(mMockPackageManager.getApplicationInfo(eq(TEST_PACKAGE_NAME), anyInt())).thenReturn(
-                applicationInfo);
         final PackageInfo systemPackageInfo = new PackageInfo();
         systemPackageInfo.packageName = TEST_SYSTEM_PACKAGE_NAME;
         when(mMockPackageManager.getPackageInfo(eq(TEST_SYSTEM_PACKAGE_NAME), anyInt()))
@@ -162,8 +161,10 @@
         mDefaultNotificationChannel = new NotificationChannel(
                 NotificationChannel.DEFAULT_CHANNEL_ID, TEST_CHANNEL_NAME,
                 IMPORTANCE_LOW);
+        Notification notification = new Notification();
+        notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, applicationInfo);
         mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
-                new Notification(), UserHandle.getUserHandleForUid(TEST_UID), null, 0);
+                notification, UserHandle.getUserHandleForUid(TEST_UID), null, 0);
         mEntry = new NotificationEntryBuilder().setSbn(mSbn).build();
         when(mAssistantFeedbackController.isFeedbackEnabled()).thenReturn(false);
         when(mAssistantFeedbackController.getInlineDescriptionResource(any()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index e78081f..718f998 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -58,7 +58,7 @@
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.controls.util.MediaFeatureFlag;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -89,6 +89,8 @@
 import com.android.systemui.statusbar.policy.SmartReplyConstants;
 import com.android.systemui.statusbar.policy.SmartReplyStateInflater;
 import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
+import com.android.systemui.util.time.SystemClock;
+import com.android.systemui.util.time.SystemClockImpl;
 import com.android.systemui.wmshell.BubblesManager;
 import com.android.systemui.wmshell.BubblesTestActivity;
 
@@ -136,6 +138,8 @@
     public final Runnable mFutureDismissalRunnable;
     private @InflationFlag int mDefaultInflationFlags;
     private final FakeFeatureFlags mFeatureFlags;
+    private final SystemClock mSystemClock;
+    private final RowInflaterTaskLogger mRowInflaterTaskLogger;
 
     public NotificationTestHelper(
             Context context,
@@ -155,7 +159,7 @@
         dependency.injectTestDependency(FeatureFlags.class, mFeatureFlags);
         dependency.injectMockDependency(NotificationMediaManager.class);
         dependency.injectMockDependency(NotificationShadeWindowController.class);
-        dependency.injectMockDependency(MediaOutputDialogFactory.class);
+        dependency.injectMockDependency(MediaOutputDialogManager.class);
         mMockLogger = mock(ExpandableNotificationRowLogger.class);
         mStatusBarStateController = mock(StatusBarStateController.class);
         mKeyguardBypassController = mock(KeyguardBypassController.class);
@@ -199,6 +203,9 @@
         mFutureDismissalRunnable = mock(Runnable.class);
         when(mOnUserInteractionCallback.registerFutureDismissal(any(), anyInt()))
                 .thenReturn(mFutureDismissalRunnable);
+
+        mSystemClock = new SystemClockImpl();
+        mRowInflaterTaskLogger = mock(RowInflaterTaskLogger.class);
     }
 
     public void setDefaultInflationFlags(@InflationFlag int defaultInflationFlags) {
@@ -572,7 +579,8 @@
         LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
                 Context.LAYOUT_INFLATER_SERVICE);
         if (com.android.systemui.Flags.notificationRowUserContext()) {
-            inflater.setFactory2(new RowInflaterTask.RowAsyncLayoutInflater(entry));
+            inflater.setFactory2(new RowInflaterTask.RowAsyncLayoutInflater(entry, mSystemClock,
+                    mRowInflaterTaskLogger));
         }
         mRow = (ExpandableNotificationRow) inflater.inflate(
                 R.layout.status_bar_notification_row,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt
new file mode 100644
index 0000000..f88bd7e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(NotificationViewFlipperPausing.FLAG_NAME)
+class NotificationViewFlipperViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    val underTest
+        get() = kosmos.notificationViewFlipperViewModel
+
+    @Test
+    fun testIsPaused_falseWhenViewingShade() =
+        kosmos.testScope.runTest {
+            val isPaused by collectLastValue(underTest.isPaused)
+
+            // WHEN shade is open
+            kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            runCurrent()
+
+            // THEN view flippers should NOT be paused
+            assertThat(isPaused).isFalse()
+        }
+
+    @Test
+    fun testIsPaused_trueWhenViewingKeyguard() =
+        kosmos.testScope.runTest {
+            val isPaused by collectLastValue(underTest.isPaused)
+
+            // WHEN on keyguard
+            kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+            runCurrent()
+
+            // THEN view flippers should be paused
+            assertThat(isPaused).isTrue()
+        }
+
+    @Test
+    fun testIsPaused_trueWhenStartingToSleep() =
+        kosmos.testScope.runTest {
+            val isPaused by collectLastValue(underTest.isPaused)
+
+            // WHEN shade is open
+            kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            // AND device is starting to go to sleep
+            kosmos.fakePowerRepository.updateWakefulness(WakefulnessState.STARTING_TO_SLEEP)
+            runCurrent()
+
+            // THEN view flippers should be paused
+            assertThat(isPaused).isTrue()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
index 3d75288..4715b33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
@@ -57,39 +57,6 @@
             )
     }
 
-    // region isDimmed
-    @Test
-    fun isDimmed_whenTrue_shouldReturnTrue() {
-        sut.arrangeDimmed(true)
-
-        assertThat(sut.isDimmed).isTrue()
-    }
-
-    @Test
-    fun isDimmed_whenFalse_shouldReturnFalse() {
-        sut.arrangeDimmed(false)
-
-        assertThat(sut.isDimmed).isFalse()
-    }
-
-    @Test
-    fun isDimmed_whenDozeAmountIsEmpty_shouldReturnTrue() {
-        sut.arrangeDimmed(true)
-        sut.dozeAmount = 0f
-
-        assertThat(sut.isDimmed).isTrue()
-    }
-
-    @Test
-    fun isDimmed_whenPulseExpandingIsFalse_shouldReturnTrue() {
-        sut.arrangeDimmed(true)
-        sut.arrangePulseExpanding(false)
-        sut.dozeAmount = 1f // arrangePulseExpanding changes dozeAmount
-
-        assertThat(sut.isDimmed).isTrue()
-    }
-    // endregion
-
     // region pulseHeight
     @Test
     fun pulseHeight_whenValueChanged_shouldCallListener() {
@@ -383,12 +350,6 @@
 }
 
 // region Arrange helper methods.
-private fun AmbientState.arrangeDimmed(value: Boolean) {
-    isDimmed = value
-    dozeAmount = if (value) 0f else 1f
-    arrangePulseExpanding(!value)
-}
-
 private fun AmbientState.arrangePulseExpanding(value: Boolean) {
     if (value) {
         dozeAmount = 1f
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index be976a1c..1f38a73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -16,11 +16,17 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
+import static org.junit.Assert.assertNull;
+
+import android.app.Notification;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.NotificationHeaderView;
 import android.view.View;
+import android.widget.RemoteViews;
 
 import androidx.test.filters.SmallTest;
 
@@ -28,6 +34,7 @@
 import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
 
 import org.junit.Assert;
@@ -40,6 +47,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
+//@DisableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME)
 public class NotificationChildrenContainerTest extends SysuiTestCase {
 
     private ExpandableNotificationRow mGroup;
@@ -138,6 +146,7 @@
     }
 
     @Test
+    @DisableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME)
     public void testLowPriorityHeaderCleared() {
         mGroup.setIsLowPriority(true);
         NotificationHeaderView lowPriorityHeaderView =
@@ -145,11 +154,12 @@
         Assert.assertEquals(View.VISIBLE, lowPriorityHeaderView.getVisibility());
         Assert.assertSame(mChildrenContainer, lowPriorityHeaderView.getParent());
         mGroup.setIsLowPriority(false);
-        Assert.assertNull(lowPriorityHeaderView.getParent());
-        Assert.assertNull(mChildrenContainer.getLowPriorityViewWrapper());
+        assertNull(lowPriorityHeaderView.getParent());
+        assertNull(mChildrenContainer.getLowPriorityViewWrapper());
     }
 
     @Test
+    @DisableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME)
     public void testRecreateNotificationHeader_hasHeader() {
         mChildrenContainer.recreateNotificationHeader(null, false);
         Assert.assertNotNull("Children container must have a header after recreation",
@@ -157,6 +167,76 @@
     }
 
     @Test
+    @EnableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME)
+    public void testSetLowPriorityWithAsyncInflation_noHeaderReInflation() {
+        mChildrenContainer.setIsLowPriority(true);
+        assertNull("We don't inflate header from the main thread with Async "
+                + "Inflation enabled", mChildrenContainer.getCurrentHeaderView());
+    }
+
+    @Test
+    @EnableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME)
+    public void setLowPriorityBeforeLowPriorityHeaderSet() {
+
+        //Given: the children container does not have a low-priority header, and is not low-priority
+        assertNull(mChildrenContainer.getLowPriorityViewWrapper());
+        mGroup.setIsLowPriority(false);
+
+        //When: set the children container to be low-priority and set the low-priority header
+        mGroup.setIsLowPriority(true);
+        mGroup.setLowPriorityGroupHeader(createHeaderView(/* lowPriorityHeader= */ true));
+
+        //Then: the low-priority group header should be visible
+        NotificationHeaderView lowPriorityHeaderView =
+                mChildrenContainer.getLowPriorityViewWrapper().getNotificationHeader();
+        Assert.assertEquals(View.VISIBLE, lowPriorityHeaderView.getVisibility());
+        Assert.assertSame(mChildrenContainer, lowPriorityHeaderView.getParent());
+
+        //When: set the children container to be not low-priority and set the normal header
+        mGroup.setIsLowPriority(false);
+        mGroup.setGroupHeader(createHeaderView(/* lowPriorityHeader= */ false));
+
+        //Then: the low-priority group header should not be visible , normal header should be
+        // visible
+        Assert.assertEquals(View.INVISIBLE, lowPriorityHeaderView.getVisibility());
+        Assert.assertEquals(
+                View.VISIBLE,
+                mChildrenContainer.getNotificationHeaderWrapper().getNotificationHeader()
+                        .getVisibility()
+        );
+    }
+
+    @Test
+    @EnableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME)
+    public void changeLowPriorityAfterHeaderSet() {
+
+        //Given: the children container does not have headers, and is not low-priority
+        assertNull(mChildrenContainer.getLowPriorityViewWrapper());
+        assertNull(mChildrenContainer.getNotificationHeaderWrapper());
+        mGroup.setIsLowPriority(false);
+
+        //When: set the set the normal header
+        mGroup.setGroupHeader(createHeaderView(/* lowPriorityHeader= */ false));
+
+        //Then: the group header should be visible
+        NotificationHeaderView headerView =
+                mChildrenContainer.getNotificationHeaderWrapper().getNotificationHeader();
+        Assert.assertEquals(View.VISIBLE, headerView.getVisibility());
+        Assert.assertSame(mChildrenContainer, headerView.getParent());
+
+        //When: set the set the row to be low priority, and set the low-priority header
+        mGroup.setIsLowPriority(true);
+        mGroup.setLowPriorityGroupHeader(createHeaderView(/* lowPriorityHeader= */ true));
+
+        //Then: the header view should not be visible, the low-priority group header should be
+        // visible
+        Assert.assertEquals(View.INVISIBLE, headerView.getVisibility());
+        NotificationHeaderView lowPriorityHeaderView =
+                mChildrenContainer.getLowPriorityViewWrapper().getNotificationHeader();
+        Assert.assertEquals(View.VISIBLE, lowPriorityHeaderView.getVisibility());
+    }
+
+    @Test
     public void applyRoundnessAndInvalidate_should_be_immediately_applied_on_last_child() {
         List<ExpandableNotificationRow> children = mChildrenContainer.getAttachedChildren();
         ExpandableNotificationRow notificationRow = children.get(children.size() - 1);
@@ -170,6 +250,7 @@
     }
 
     @Test
+    @DisableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME)
     public void applyRoundnessAndInvalidate_should_be_immediately_applied_on_header() {
         NotificationHeaderViewWrapper header = mChildrenContainer.getNotificationHeaderWrapper();
         Assert.assertEquals(0f, header.getTopRoundness(), 0.001f);
@@ -180,6 +261,7 @@
     }
 
     @Test
+    @DisableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME)
     public void applyRoundnessAndInvalidate_should_be_immediately_applied_on_headerLowPriority() {
         mChildrenContainer.setIsLowPriority(true);
 
@@ -190,4 +272,17 @@
 
         Assert.assertEquals(1f, header.getTopRoundness(), 0.001f);
     }
+
+    private NotificationHeaderView createHeaderView(boolean lowPriority) {
+        Notification notification = mNotificationTestHelper.createNotification();
+        final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(),
+                notification);
+        RemoteViews headerRemoteViews;
+        if (lowPriority) {
+            headerRemoteViews = builder.makeLowPriorityContentView(true);
+        } else {
+            headerRemoteViews = builder.makeNotificationGroupHeader();
+        }
+        return (NotificationHeaderView) headerRemoteViews.apply(getContext(), mChildrenContainer);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index b938029..9a7b8ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -10,7 +10,6 @@
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.res.R
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
 import com.android.systemui.statusbar.NotificationShelf
@@ -23,7 +22,6 @@
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
-import org.junit.Assume.assumeTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -38,7 +36,6 @@
 @RunWithLooper
 open class NotificationShelfTest : SysuiTestCase() {
 
-    open val useSensitiveReveal: Boolean = false
     private val flags = FakeFeatureFlags()
 
     @Mock private lateinit var largeScreenShadeInterpolator: LargeScreenShadeInterpolator
@@ -53,7 +50,6 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         mDependency.injectTestDependency(FeatureFlags::class.java, flags)
-        flags.set(Flags.SENSITIVE_REVEAL_ANIM, useSensitiveReveal)
         val root = FrameLayout(context)
         shelf =
             LayoutInflater.from(root.context)
@@ -335,7 +331,6 @@
     @Test
     fun updateState_withNullLastVisibleBackgroundChild_hideShelf() {
         // GIVEN
-        assumeTrue(useSensitiveReveal)
         whenever(ambientState.stackY).thenReturn(100f)
         whenever(ambientState.stackHeight).thenReturn(100f)
         val paddingBetweenElements =
@@ -362,7 +357,6 @@
     @Test
     fun updateState_withNullFirstViewInShelf_hideShelf() {
         // GIVEN
-        assumeTrue(useSensitiveReveal)
         whenever(ambientState.stackY).thenReturn(100f)
         whenever(ambientState.stackHeight).thenReturn(100f)
         val paddingBetweenElements =
@@ -389,7 +383,6 @@
     @Test
     fun updateState_withCollapsedShade_hideShelf() {
         // GIVEN
-        assumeTrue(useSensitiveReveal)
         whenever(ambientState.stackY).thenReturn(100f)
         whenever(ambientState.stackHeight).thenReturn(100f)
         val paddingBetweenElements =
@@ -416,7 +409,6 @@
     @Test
     fun updateState_withHiddenSectionBeforeShelf_hideShelf() {
         // GIVEN
-        assumeTrue(useSensitiveReveal)
         whenever(ambientState.stackY).thenReturn(100f)
         whenever(ambientState.stackHeight).thenReturn(100f)
         val paddingBetweenElements =
@@ -476,10 +468,3 @@
         assertEquals(expectedAlpha, shelf.viewState.alpha)
     }
 }
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@RunWithLooper
-class NotificationShelfWithSensitiveRevealTest : NotificationShelfTest() {
-    override val useSensitiveReveal: Boolean = true
-}
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 f326cea..13df091 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
@@ -38,7 +38,6 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
@@ -73,11 +72,11 @@
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.EnableSceneContainer;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.EmptyShadeView;
@@ -97,13 +96,11 @@
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
 
 import org.junit.Assert;
-import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -168,8 +165,6 @@
         // TODO: Ideally we wouldn't need to set these unless a test actually reads them,
         //  and then we would test both configurations, but currently they are all read
         //  in the constructor.
-        mFeatureFlags.setDefault(Flags.SENSITIVE_REVEAL_ANIM);
-        mFeatureFlags.setDefault(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS);
         mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION);
         mFeatureFlags.setDefault(Flags.UNCLEARED_TRANSIENT_HUN_FIX);
 
@@ -232,6 +227,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
     public void testUpdateStackHeight_qsExpansionZero() {
         final float expansionFraction = 0.2f;
         final float overExpansion = 50f;
@@ -307,14 +303,6 @@
     }
 
     @Test
-    public void testNotDimmedOnKeyguard() {
-        when(mBarState.getState()).thenReturn(StatusBarState.SHADE);
-        mStackScroller.setDimmed(true /* dimmed */, false /* animate */);
-        mStackScroller.setDimmed(true /* dimmed */, true /* animate */);
-        assertFalse(mStackScroller.isDimmed());
-    }
-
-    @Test
     public void updateEmptyView_dndSuppressing() {
         when(mEmptyShadeView.willBeGone()).thenReturn(true);
 
@@ -738,6 +726,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address lack of QS Header
     public void testInsideQSHeader_noOffset() {
         ViewGroup qsHeader = mock(ViewGroup.class);
         Rect boundsOnScreen = new Rect(0, 0, 1000, 1000);
@@ -754,6 +743,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address lack of QS Header
     public void testInsideQSHeader_Offset() {
         ViewGroup qsHeader = mock(ViewGroup.class);
         Rect boundsOnScreen = new Rect(100, 100, 1000, 1000);
@@ -773,12 +763,14 @@
     }
 
     @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
     public void setFractionToShade_recomputesStackHeight() {
         mStackScroller.setFractionToShade(1f);
         verify(mNotificationStackSizeCalculator).computeHeight(any(), anyInt(), anyFloat());
     }
 
     @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
     public void testSetOwnScrollY_shadeNotClosing_scrollYChanges() {
         // Given: shade is not closing, scrollY is 0
         mAmbientState.setScrollY(0);
@@ -877,6 +869,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
     public void testSplitShade_hasTopOverscroll() {
         mTestableResources
                 .addOverride(R.bool.config_use_split_notification_shade, /* value= */ true);
@@ -949,6 +942,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
     public void testSetMaxDisplayedNotifications_notifiesListeners() {
         ExpandableView.OnHeightChangedListener listener =
                 mock(ExpandableView.OnHeightChangedListener.class);
@@ -963,9 +957,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER)
     public void testDispatchTouchEvent_sceneContainerDisabled() {
-        Assume.assumeFalse(SceneContainerFlag.isEnabled());
-
         MotionEvent event = MotionEvent.obtain(
                 SystemClock.uptimeMillis(),
                 SystemClock.uptimeMillis(),
@@ -981,34 +974,60 @@
     }
 
     @Test
+    @EnableSceneContainer
     public void testDispatchTouchEvent_sceneContainerEnabled() {
-        Assume.assumeTrue(SceneContainerFlag.isEnabled());
         mStackScroller.setIsBeingDragged(true);
 
-        MotionEvent moveEvent = MotionEvent.obtain(
-                SystemClock.uptimeMillis(),
-                SystemClock.uptimeMillis(),
+        long downTime = SystemClock.uptimeMillis() - 100;
+        MotionEvent moveEvent1 = MotionEvent.obtain(
+                /* downTime= */ downTime,
+                /* eventTime= */ SystemClock.uptimeMillis(),
                 MotionEvent.ACTION_MOVE,
-                0,
-                0,
+                101,
+                201,
                 0
         );
-        MotionEvent syntheticDownEvent = moveEvent.copy();
+        MotionEvent syntheticDownEvent = moveEvent1.copy();
         syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN);
-        mStackScroller.dispatchTouchEvent(moveEvent);
+        mStackScroller.dispatchTouchEvent(moveEvent1);
 
-        verify(mStackScrollLayoutController).sendTouchToSceneFramework(argThat(
-                new MotionEventMatcher(syntheticDownEvent)));
+        assertThatMotionEvent(captureTouchSentToSceneFramework()).matches(syntheticDownEvent);
+        assertTrue(mStackScroller.getIsBeingDragged());
+        clearInvocations(mStackScrollLayoutController);
 
-        mStackScroller.dispatchTouchEvent(moveEvent);
+        MotionEvent moveEvent2 = MotionEvent.obtain(
+                /* downTime= */ downTime,
+                /* eventTime= */ SystemClock.uptimeMillis(),
+                MotionEvent.ACTION_MOVE,
+                102,
+                202,
+                0
+        );
 
-        verify(mStackScrollLayoutController).sendTouchToSceneFramework(moveEvent);
+        mStackScroller.dispatchTouchEvent(moveEvent2);
+
+        assertThatMotionEvent(captureTouchSentToSceneFramework()).matches(moveEvent2);
+        assertTrue(mStackScroller.getIsBeingDragged());
+        clearInvocations(mStackScrollLayoutController);
+
+        MotionEvent upEvent = MotionEvent.obtain(
+                /* downTime= */ downTime,
+                /* eventTime= */ SystemClock.uptimeMillis(),
+                MotionEvent.ACTION_UP,
+                103,
+                203,
+                0
+        );
+
+        mStackScroller.dispatchTouchEvent(upEvent);
+
+        assertThatMotionEvent(captureTouchSentToSceneFramework()).matches(upEvent);
+        assertFalse(mStackScroller.getIsBeingDragged());
     }
 
     @Test
-    @EnableFlags(FLAG_SCENE_CONTAINER)
-    public void testDispatchTouchEvent_sceneContainerEnabled_actionUp() {
-        Assume.assumeTrue(SceneContainerFlag.isEnabled());
+    @EnableSceneContainer
+    public void testDispatchTouchEvent_sceneContainerEnabled_ignoresInitialActionUp() {
         mStackScroller.setIsBeingDragged(true);
 
         MotionEvent upEvent = MotionEvent.obtain(
@@ -1019,21 +1038,18 @@
                 0,
                 0
         );
-        MotionEvent syntheticDownEvent = upEvent.copy();
-        syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN);
 
         mStackScroller.dispatchTouchEvent(upEvent);
-
-        verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat(
-                new MotionEventMatcher(syntheticDownEvent)));
-
-        mStackScroller.dispatchTouchEvent(upEvent);
-
-        verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat(
-                new MotionEventMatcher(upEvent)));
+        verify(mStackScrollLayoutController, never()).sendTouchToSceneFramework(any());
         assertFalse(mStackScroller.getIsBeingDragged());
     }
 
+    private MotionEvent captureTouchSentToSceneFramework() {
+        ArgumentCaptor<MotionEvent> captor = ArgumentCaptor.forClass(MotionEvent.class);
+        verify(mStackScrollLayoutController).sendTouchToSceneFramework(captor.capture());
+        return captor.getValue();
+    }
+
     private void setBarStateForTest(int state) {
         // Can't inject this through the listener or we end up on the actual implementation
         // rather than the mock because the spy just coppied the anonymous inner /shruggie.
@@ -1081,20 +1097,23 @@
         );
     }
 
-    private static class MotionEventMatcher implements ArgumentMatcher<MotionEvent> {
-        private final MotionEvent mLeftEvent;
+    private MotionEventSubject assertThatMotionEvent(MotionEvent actual) {
+        return new MotionEventSubject(actual);
+    }
 
-        MotionEventMatcher(MotionEvent leftEvent) {
-            mLeftEvent = leftEvent;
+    private static class MotionEventSubject {
+        private final MotionEvent mActual;
+
+        MotionEventSubject(MotionEvent actual) {
+            mActual = actual;
         }
 
-        @Override
-        public boolean matches(MotionEvent right) {
-            return mLeftEvent.getActionMasked() == right.getActionMasked()
-                    && mLeftEvent.getDownTime() == right.getDownTime()
-                    && mLeftEvent.getEventTime() == right.getEventTime()
-                    && mLeftEvent.getX() == right.getX()
-                    && mLeftEvent.getY() == right.getY();
+        public void matches(MotionEvent expected) {
+            assertThat(mActual.getActionMasked()).isEqualTo(expected.getActionMasked());
+            assertThat(mActual.getDownTime()).isEqualTo(expected.getDownTime());
+            assertThat(mActual.getEventTime()).isEqualTo(expected.getEventTime());
+            assertThat(mActual.getX()).isEqualTo(expected.getX());
+            assertThat(mActual.getY()).isEqualTo(expected.getY());
         }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractorTest.kt
new file mode 100644
index 0000000..1c6bda9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractorTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.stack.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotificationStackInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    val underTest
+        get() = kosmos.notificationStackInteractor
+
+    @Test
+    fun testIsShowingOnLockscreen_falseWhenViewingShade() =
+        kosmos.testScope.runTest {
+            val onLockscreen by collectLastValue(underTest.isShowingOnLockscreen)
+
+            // WHEN shade is open
+            kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            runCurrent()
+
+            // THEN notifications are not showing on lockscreen
+            assertThat(onLockscreen).isFalse()
+        }
+
+    @Test
+    fun testIsShowingOnLockscreen_trueWhenViewingKeyguard() =
+        kosmos.testScope.runTest {
+            val onLockscreen by collectLastValue(underTest.isShowingOnLockscreen)
+
+            // WHEN on keyguard
+            kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+            runCurrent()
+
+            // THEN notifications are showing on lockscreen
+            assertThat(onLockscreen).isTrue()
+        }
+
+    @Test
+    fun testIsShowingOnLockscreen_trueWhenStartingToSleep() =
+        kosmos.testScope.runTest {
+            val onLockscreen by collectLastValue(underTest.isShowingOnLockscreen)
+
+            // WHEN shade is open
+            kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            // AND device is starting to go to sleep
+            kosmos.fakePowerRepository.updateWakefulness(WakefulnessState.STARTING_TO_SLEEP)
+            runCurrent()
+
+            // THEN notifications are showing on lockscreen
+            assertThat(onLockscreen).isTrue()
+        }
+}
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 3a94295d..c8c54dbd 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
@@ -73,6 +73,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.compose.animation.scene.ObservableTransitionState;
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -95,8 +96,7 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.communal.data.repository.CommunalRepository;
 import com.android.systemui.communal.domain.interactor.CommunalInteractor;
-import com.android.systemui.communal.shared.model.CommunalSceneKey;
-import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState;
+import com.android.systemui.communal.shared.model.CommunalScenes;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
@@ -842,16 +842,16 @@
     @Test
     public void testEnteringGlanceableHub_updatesScrim() {
         // Transition to the glanceable hub.
-        mCommunalRepository.setTransitionState(flowOf(new ObservableCommunalTransitionState.Idle(
-                CommunalSceneKey.Communal.INSTANCE)));
+        mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle(
+                CommunalScenes.Communal)));
         mTestScope.getTestScheduler().runCurrent();
 
         // ScrimState also transitions.
         verify(mScrimController).transitionTo(ScrimState.GLANCEABLE_HUB);
 
         // Transition away from the glanceable hub.
-        mCommunalRepository.setTransitionState(flowOf(new ObservableCommunalTransitionState.Idle(
-                CommunalSceneKey.Blank.INSTANCE)));
+        mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle(
+                CommunalScenes.Blank)));
         mTestScope.getTestScheduler().runCurrent();
 
         // ScrimState goes back to UNLOCKED.
@@ -859,6 +859,29 @@
     }
 
     @Test
+    public void testEnteringGlanceableHub_whenDreaming_updatesScrim() {
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isOccluded()).thenReturn(true);
+        when(mKeyguardUpdateMonitor.isDreaming()).thenReturn(true);
+
+        // Transition to the glanceable hub.
+        mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle(
+                CommunalScenes.Communal)));
+        mTestScope.getTestScheduler().runCurrent();
+
+        // ScrimState also transitions.
+        verify(mScrimController).transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
+
+        // Transition away from the glanceable hub.
+        mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle(
+                CommunalScenes.Blank)));
+        mTestScope.getTestScheduler().runCurrent();
+
+        // ScrimState goes back to UNLOCKED.
+        verify(mScrimController).transitionTo(eq(ScrimState.DREAMING));
+    }
+
+    @Test
     public void testShowKeyguardImplementation_setsState() {
         when(mLockscreenUserManager.getCurrentProfiles()).thenReturn(new SparseArray<>());
 
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 56fc7b9..1748cff 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
@@ -22,6 +22,7 @@
 
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -38,6 +39,8 @@
 
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -226,7 +229,22 @@
     }
 
     @Test
-    public void onViewAttached_callbacksRegistered() {
+    @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    public void onViewAttached_updateUserSwitcherFlagEnabled_callbacksRegistered() {
+        mController.onViewAttached();
+
+        runAllScheduled();
+        verify(mConfigurationController).addCallback(any());
+        verify(mAnimationScheduler).addCallback(any());
+        verify(mUserInfoController).addCallback(any());
+        verify(mCommandQueue).addCallback(any());
+        verify(mStatusBarIconController).addIconGroup(any());
+        verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
+    }
+
+    @Test
+    @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    public void onViewAttached_updateUserSwitcherFlagDisabled_callbacksRegistered() {
         mController.onViewAttached();
 
         verify(mConfigurationController).addCallback(any());
@@ -238,7 +256,26 @@
     }
 
     @Test
-    public void onConfigurationChanged_updatesUserSwitcherVisibility() {
+    @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    public void
+            onConfigurationChanged_updateUserSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
+        mController.onViewAttached();
+        runAllScheduled();
+        verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture());
+        clearInvocations(mUserManager);
+        clearInvocations(mKeyguardStatusBarView);
+
+        mConfigurationListenerCaptor.getValue().onConfigChanged(null);
+
+        runAllScheduled();
+        verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
+        verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
+    }
+
+    @Test
+    @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    public void
+            onConfigurationChanged_updateUserSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
         mController.onViewAttached();
         verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture());
         clearInvocations(mUserManager);
@@ -250,7 +287,26 @@
     }
 
     @Test
-    public void onKeyguardVisibilityChanged_updatesUserSwitcherVisibility() {
+    @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    public void
+            onKeyguardVisibilityChanged_userSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
+        mController.onViewAttached();
+        runAllScheduled();
+        verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture());
+        clearInvocations(mUserManager);
+        clearInvocations(mKeyguardStatusBarView);
+
+        mKeyguardCallbackCaptor.getValue().onKeyguardVisibilityChanged(true);
+
+        runAllScheduled();
+        verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
+        verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
+    }
+
+    @Test
+    @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    public void
+            onKeyguardVisibilityChanged_userSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
         mController.onViewAttached();
         verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture());
         clearInvocations(mUserManager);
@@ -298,7 +354,7 @@
 
         verify(mStatusBarIconController).addIconGroup(any());
     }
-    
+
     @Test
     public void setBatteryListening_true_callbackAdded() {
         mController.setBatteryListening(true);
@@ -762,6 +818,11 @@
         return captor.getValue();
     }
 
+    private void runAllScheduled() {
+        mBackgroundExecutor.runAllReady();
+        mFakeExecutor.runAllReady();
+    }
+
     private static class TestShadeViewStateProvider
             implements ShadeViewStateProvider {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 1687ccb..443dd6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -20,6 +20,7 @@
 import android.app.StatusBarManager.WINDOW_STATE_HIDING
 import android.app.StatusBarManager.WINDOW_STATE_SHOWING
 import android.app.StatusBarManager.WINDOW_STATUS_BAR
+import android.view.InputDevice
 import android.view.LayoutInflater
 import android.view.MotionEvent
 import android.view.View
@@ -233,14 +234,35 @@
     }
 
     @Test
-    fun shadeIsExpandedOnStatusIconClick() {
+    fun shadeIsExpandedOnStatusIconMouseClick() {
         val view = createViewMock()
         InstrumentationRegistry.getInstrumentation().runOnMainSync {
             controller = createAndInitController(view)
         }
         val statusContainer = view.requireViewById<View>(R.id.system_icons)
-        statusContainer.performClick()
-        verify(shadeViewController).expand(any())
+        statusContainer.dispatchTouchEvent(
+            getActionUpEventFromSource(InputDevice.SOURCE_MOUSE)
+        )
+        verify(shadeControllerImpl).animateExpandShade()
+    }
+
+    @Test
+    fun statusIconContainerIsNotHandlingTouchScreenTouches() {
+        val view = createViewMock()
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            controller = createAndInitController(view)
+        }
+        val statusContainer = view.requireViewById<View>(R.id.system_icons)
+        val handled = statusContainer.dispatchTouchEvent(
+            getActionUpEventFromSource(InputDevice.SOURCE_TOUCHSCREEN)
+        )
+        assertThat(handled).isFalse()
+    }
+
+    private fun getActionUpEventFromSource(source: Int): MotionEvent {
+        val ev = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0f, 0f, 0)
+        ev.source = source
+        return ev
     }
 
     @Test
@@ -250,7 +272,7 @@
             controller = createAndInitController(view)
         }
         view.performClick()
-        verify(shadeViewController, never()).expand(any())
+        verify(shadeControllerImpl, never()).animateExpandShade()
     }
 
     private fun getCommandQueueCallback(): CommandQueue.Callbacks {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index 5e8b62e..fd2dead 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -26,6 +26,7 @@
 import android.view.MotionEvent
 import android.view.PrivacyIndicatorBounds
 import android.view.RoundedCorners
+import android.view.View
 import android.view.WindowInsets
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
@@ -50,6 +51,8 @@
 class PhoneStatusBarViewTest : SysuiTestCase() {
 
     private lateinit var view: PhoneStatusBarView
+    private val systemIconsContainer: View
+        get() = view.requireViewById(R.id.system_icons)
 
     private val contentInsetsProvider = mock<StatusBarContentInsetsProvider>()
     private val windowController = mock<StatusBarWindowController>()
@@ -62,6 +65,7 @@
         )
         mDependency.injectTestDependency(DarkIconDispatcher::class.java, mock<DarkIconDispatcher>())
         mDependency.injectTestDependency(StatusBarWindowController::class.java, windowController)
+        context.ensureTestableResources()
         view = spy(createStatusBarView())
         whenever(view.rootWindowInsets).thenReturn(emptyWindowInsets())
         whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
@@ -217,7 +221,7 @@
 
         val newInsets = Insets.NONE
         whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
-                .thenReturn(newInsets)
+            .thenReturn(newInsets)
         view.onConfigurationChanged(Configuration())
 
         assertThat(view.paddingLeft).isEqualTo(previousInsets.left)
@@ -239,7 +243,7 @@
 
         val newInsets = Insets.NONE
         whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
-                .thenReturn(newInsets)
+            .thenReturn(newInsets)
         configuration.densityDpi = 456
         view.onConfigurationChanged(configuration)
 
@@ -262,7 +266,7 @@
 
         val newInsets = Insets.NONE
         whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
-                .thenReturn(newInsets)
+            .thenReturn(newInsets)
         configuration.fontScale = 2f
         view.onConfigurationChanged(configuration)
 
@@ -273,6 +277,19 @@
     }
 
     @Test
+    fun onConfigurationChanged_systemIconsHeightChanged_containerHeightIsUpdated() {
+        val newHeight = 123456
+        context.orCreateTestableResources.addOverride(
+            R.dimen.status_bar_system_icons_height,
+            newHeight
+        )
+
+        view.onConfigurationChanged(Configuration())
+
+        assertThat(systemIconsContainer.layoutParams.height).isEqualTo(newHeight)
+    }
+
+    @Test
     fun onApplyWindowInsets_updatesLeftTopRightPaddingsBasedOnInsets() {
         val insets = Insets.of(/* left = */ 90, /* top = */ 10, /* right = */ 45, /* bottom = */ 50)
         whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
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 99c2dc7..cdbbc93 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
@@ -794,6 +794,73 @@
     }
 
     @Test
+    public void transitionToHubOverDream() {
+        mScrimController.setRawPanelExpansionFraction(0f);
+        mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
+        mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
+        finishAnimationsImmediately();
+
+        // All scrims transparent on the hub.
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mNotificationsScrim, TRANSPARENT,
+                mScrimBehind, TRANSPARENT));
+    }
+
+    @Test
+    public void openBouncerOnHubOverDream() {
+        mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
+
+        // Open the bouncer.
+        mScrimController.setRawPanelExpansionFraction(0f);
+        mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_VISIBLE);
+        finishAnimationsImmediately();
+
+        // Only behind widget is visible.
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mNotificationsScrim, TRANSPARENT,
+                mScrimBehind, OPAQUE));
+
+        // Bouncer is closed.
+        mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
+        mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
+        finishAnimationsImmediately();
+
+        // All scrims are transparent.
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mNotificationsScrim, TRANSPARENT,
+                mScrimBehind, TRANSPARENT));
+    }
+
+    @Test
+    public void openShadeOnHubOverDream() {
+        mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
+
+        // Open the shade.
+        mScrimController.transitionTo(SHADE_LOCKED);
+        mScrimController.setQsPosition(1f, 0);
+        mScrimController.setRawPanelExpansionFraction(1f);
+        finishAnimationsImmediately();
+
+        // Shade scrims are visible.
+        assertScrimAlpha(Map.of(
+                mNotificationsScrim, OPAQUE,
+                mScrimInFront, TRANSPARENT,
+                mScrimBehind, OPAQUE));
+
+        mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
+        finishAnimationsImmediately();
+
+        // All scrims are transparent.
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mNotificationsScrim, TRANSPARENT,
+                mScrimBehind, TRANSPARENT));
+    }
+
+    @Test
     public void onThemeChange_bouncerBehindTint_isUpdatedToSurfaceColor() {
         assertEquals(BOUNCER.getBehindTint(), 0x112233);
         mSurfaceColor = 0x223344;
@@ -1432,7 +1499,8 @@
                 ScrimState.UNINITIALIZED, ScrimState.KEYGUARD, BOUNCER,
                 ScrimState.DREAMING, ScrimState.BOUNCER_SCRIMMED, ScrimState.BRIGHTNESS_MIRROR,
                 ScrimState.UNLOCKED, SHADE_LOCKED, ScrimState.AUTH_SCRIMMED,
-                ScrimState.AUTH_SCRIMMED_SHADE, ScrimState.GLANCEABLE_HUB));
+                ScrimState.AUTH_SCRIMMED_SHADE, ScrimState.GLANCEABLE_HUB,
+                ScrimState.GLANCEABLE_HUB_OVER_DREAM));
 
         for (ScrimState state : ScrimState.values()) {
             if (!lowPowerModeStates.contains(state) && !regularStates.contains(state)) {
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 bd7406a..562aa6a 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
@@ -86,6 +86,7 @@
 import com.android.systemui.navigationbar.TaskbarDelegate;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
@@ -94,9 +95,11 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.domain.interactor.StatusBarKeyguardViewManagerInteractor;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.util.kotlin.JavaAdapter;
 
 import com.google.common.truth.Truth;
 
@@ -222,7 +225,10 @@
                         () -> mock(WindowManagerLockscreenVisibilityInteractor.class),
                         () -> mock(KeyguardDismissActionInteractor.class),
                         mSelectedUserInteractor,
-                        () -> mock(KeyguardSurfaceBehindInteractor.class)) {
+                        () -> mock(KeyguardSurfaceBehindInteractor.class),
+                        mock(JavaAdapter.class),
+                        () -> mock(SceneInteractor.class),
+                        mock(StatusBarKeyguardViewManagerInteractor.class)) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
@@ -730,7 +736,10 @@
                         () -> mock(WindowManagerLockscreenVisibilityInteractor.class),
                         () -> mock(KeyguardDismissActionInteractor.class),
                         mSelectedUserInteractor,
-                        () -> mock(KeyguardSurfaceBehindInteractor.class)) {
+                        () -> mock(KeyguardSurfaceBehindInteractor.class),
+                        mock(JavaAdapter.class),
+                        () -> mock(SceneInteractor.class),
+                        mock(StatusBarKeyguardViewManagerInteractor.class)) {
                     @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 41514ce..938b2f8 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
@@ -79,6 +79,7 @@
 import com.android.systemui.shade.data.repository.FakeShadeRepository;
 import com.android.systemui.shade.data.repository.ShadeAnimationRepository;
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationClickNotifier;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
@@ -130,6 +131,8 @@
     @Mock
     private ActivityStarter mActivityStarter;
     @Mock
+    private CommandQueue mCommandQueue;
+    @Mock
     private NotificationClickNotifier mClickNotifier;
     @Mock
     private StatusBarStateController mStatusBarStateController;
@@ -236,6 +239,7 @@
                         mVisibilityProvider,
                         headsUpManager,
                         mActivityStarter,
+                        mCommandQueue,
                         mClickNotifier,
                         mStatusBarKeyguardViewManager,
                         mock(KeyguardManager.class),
@@ -257,7 +261,9 @@
                         mock(NotificationShadeWindowController.class),
                         mActivityTransitionAnimator,
                         new ShadeAnimationInteractorLegacyImpl(
-                                new ShadeAnimationRepository(), new FakeShadeRepository()),
+                                new ShadeAnimationRepository(),
+                                new FakeShadeRepository()
+                        ),
                         notificationAnimationProvider,
                         mock(LaunchFullScreenIntentProvider.class),
                         mPowerInteractor,
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 1dafcc4..b0404a0 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
@@ -15,13 +15,14 @@
 package com.android.systemui.statusbar.phone;
 
 import static android.view.Display.DEFAULT_DISPLAY;
+
 import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE;
 import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK;
 import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
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 6a0375d..3bf54a3 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
@@ -31,6 +31,7 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.StatusBarStateControllerImpl
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.settings.GlobalSettings
 import junit.framework.Assert.assertFalse
@@ -80,6 +81,8 @@
     @Mock
     private lateinit var handler: Handler
 
+    val kosmos = testKosmos()
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -87,14 +90,14 @@
                 context,
                 wakefulnessLifecycle,
                 statusBarStateController,
-                dagger.Lazy<KeyguardViewMediator> { keyguardViewMediator },
+                { keyguardViewMediator },
                 keyguardStateController,
-                dagger.Lazy<DozeParameters> { dozeParameters },
+                { dozeParameters },
                 globalSettings,
-                dagger.Lazy<NotificationShadeWindowController> { notifShadeWindowController },
+                { notifShadeWindowController },
                 interactionJankMonitor,
                 powerManager,
-                handler = handler,
+                handler = handler
         )
         controller.initialize(centralSurfaces, shadeViewController, lightRevealScrim)
 
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 b958f35..a41bc0d 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
@@ -616,7 +616,7 @@
     dataType: SignalIcon.MobileIconGroup? = THREE_G,
     subId: Int? = 1,
     carrierId: Int? = UNKNOWN_CARRIER_ID,
-    inflateStrength: Boolean? = false,
+    inflateStrength: Boolean = false,
     activity: Int? = null,
     carrierNetworkChange: Boolean = false,
     roaming: Boolean = false,
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 1f27a28..47899a6 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
@@ -38,9 +38,12 @@
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.isActive
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
@@ -156,14 +159,35 @@
             val model2 = underTest.viewModelForSub(2, StatusBarLocation.QS)
 
             // Both impls are cached
-            assertThat(underTest.mobileIconSubIdCache)
-                .containsExactly(1, model1.commonImpl, 2, model2.commonImpl)
+            assertThat(underTest.reuseCache.keys).containsExactly(1, 2)
 
             // SUB_1 is removed from the list...
             interactor.filteredSubscriptions.value = listOf(SUB_2)
 
             // ... and dropped from the cache
-            assertThat(underTest.mobileIconSubIdCache).containsExactly(2, model2.commonImpl)
+            assertThat(underTest.reuseCache.keys).containsExactly(2)
+        }
+
+    @Test
+    fun caching_invalidatedViewModelsAreCanceled() =
+        testScope.runTest {
+            // Retrieve models to trigger caching
+            val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME)
+            val model2 = underTest.viewModelForSub(2, StatusBarLocation.QS)
+
+            var scope1 = underTest.reuseCache[1]?.second
+            var scope2 = underTest.reuseCache[2]?.second
+
+            // Scopes are not canceled
+            assertTrue(scope1!!.isActive)
+            assertTrue(scope2!!.isActive)
+
+            // SUB_1 is removed from the list...
+            interactor.filteredSubscriptions.value = listOf(SUB_2)
+
+            // scope1 is canceled
+            assertFalse(scope1!!.isActive)
+            assertTrue(scope2!!.isActive)
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
index d465b47..c07f289 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
@@ -239,7 +239,9 @@
 
             // WHEN all of the connections are OOS
             i1.isInService.value = false
+            i1.isEmergencyOnly.value = false
             i2.isInService.value = false
+            i2.isEmergencyOnly.value = false
 
             // THEN the value is propagated to this interactor
             assertThat(latest).isTrue()
@@ -256,6 +258,7 @@
 
             // WHEN all of the connections are OOS
             i1.isInService.value = false
+            i1.isEmergencyOnly.value = false
 
             // THEN the value is propagated to this interactor
             assertThat(latest).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index cd0652e..ec6642d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -80,6 +80,7 @@
             // GIVEN all icons are OOS
             val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
             i1.isInService.value = false
+            i1.isEmergencyOnly.value = false
 
             // GIVEN apm is disabled
             airplaneModeRepository.setIsAirplaneMode(false)
@@ -99,6 +100,7 @@
             // GIVEN all icons are not OOS
             val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
             i1.isInService.value = true
+            i1.isEmergencyOnly.value = false
 
             // GIVEN apm is disabled
             airplaneModeRepository.setIsAirplaneMode(false)
@@ -108,6 +110,35 @@
         }
 
     @Test
+    fun icon_nullWhenShouldNotShow_isEmergencyOnly() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.icon)
+
+            // GIVEN satellite is allowed
+            repo.isSatelliteAllowedForCurrentLocation.value = true
+
+            // GIVEN all icons are OOS
+            val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+            i1.isInService.value = false
+            i1.isEmergencyOnly.value = false
+
+            // GIVEN apm is disabled
+            airplaneModeRepository.setIsAirplaneMode(false)
+
+            // Wait for delay to be completed
+            advanceTimeBy(10.seconds)
+
+            // THEN icon is set because we don't have service
+            assertThat(latest).isInstanceOf(Icon::class.java)
+
+            // GIVEN the connection is emergency only
+            i1.isEmergencyOnly.value = true
+
+            // THEN icon is null because we have emergency connection
+            assertThat(latest).isNull()
+        }
+
+    @Test
     fun icon_nullWhenShouldNotShow_apmIsEnabled() =
         testScope.runTest {
             val latest by collectLastValue(underTest.icon)
@@ -118,6 +149,7 @@
             // GIVEN all icons are OOS
             val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
             i1.isInService.value = false
+            i1.isEmergencyOnly.value = false
 
             // GIVEN apm is enabled
             airplaneModeRepository.setIsAirplaneMode(true)
@@ -138,6 +170,7 @@
             // GIVEN all icons are OOS
             val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
             i1.isInService.value = false
+            i1.isEmergencyOnly.value = false
 
             // GIVEN apm is disabled
             airplaneModeRepository.setIsAirplaneMode(false)
@@ -161,6 +194,7 @@
             // GIVEN all icons are OOS
             val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
             i1.isInService.value = false
+            i1.isEmergencyOnly.value = false
 
             // GIVEN apm is disabled
             airplaneModeRepository.setIsAirplaneMode(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 13167b2..c259782 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -71,7 +71,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.AnimatorTestRule;
 import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -455,7 +454,6 @@
     private RemoteInputViewController bindController(
             RemoteInputView view,
             NotificationEntry entry) {
-        mFeatureFlags.set(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION, true);
         RemoteInputViewControllerImpl viewController = new RemoteInputViewControllerImpl(
                 view,
                 entry,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index c02583a..ab28a2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -718,7 +718,8 @@
 
     @Test
     public void onPrivateProfileAdded_ignoresUntilStartComplete() {
-        mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         reset(mDeviceProvisionedController);
         when(mUserManager.isManagedProfile(anyInt())).thenReturn(false);
         mBroadcastReceiver.getValue().onReceive(null,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt
index e499a3c..e4a1c26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt
@@ -30,16 +30,22 @@
         assumeTrue("Test should be launched on a foldable device",
             foldedDeviceStates.isNotEmpty())
 
-        val folded =
-            DeviceState(foldedDeviceStates.maxOrNull()!! /* identifier */,
-                "" /* name */,
-                emptySet() /* properties */)
-        val unfolded =
-            DeviceState(folded.identifier + 1 /* identifier */,
-                "" /* name */,
-                emptySet() /* properties */)
+        val folded = getDeviceState(
+            identifier = foldedDeviceStates.maxOrNull()!!
+        )
+        val unfolded = getDeviceState(
+            identifier = folded.identifier + 1
+        )
         return FoldableDeviceStates(folded = folded, unfolded = unfolded)
     }
+
+    private fun getDeviceState(identifier: Int): DeviceState {
+        return DeviceState(
+            DeviceState.Configuration.Builder(
+                identifier, "" /* name */
+            ).build()
+        )
+    }
 }
 
 data class FoldableDeviceStates(val folded: DeviceState, val unfolded: DeviceState)
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
index 23a9207..ed07ad2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
@@ -21,21 +21,40 @@
 
 import android.os.Build;
 import android.os.PowerManager;
+import android.platform.test.flag.junit.FlagsParameterization;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.After;
+import org.junit.Assume;
 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.List;
 
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 public class WakeLockTest extends SysuiTestCase {
 
+    @Parameterized.Parameters(name = "{0}")
+    public static List<FlagsParameterization> getFlags() {
+        return FlagsParameterization.allCombinationsOf(
+                Flags.FLAG_DELAYED_WAKELOCK_RELEASE_ON_BACKGROUND_THREAD);
+    }
+
+    @Rule public final SetFlagsRule mSetFlagsRule;
+
+    public WakeLockTest(FlagsParameterization flags) {
+        mSetFlagsRule = new SetFlagsRule(SetFlagsRule.DefaultInitValueType.NULL_DEFAULT, flags);
+    }
+
     private static final String WHY = "test";
     WakeLock mWakeLock;
     PowerManager.WakeLock mInner;
@@ -91,10 +110,7 @@
 
     @Test
     public void prodBuild_wakeLock_releaseWithoutAcquire_noThrow() {
-        if (Build.IS_ENG) {
-            return;
-        }
-
+        Assume.assumeFalse(Build.IS_ENG);
         // shouldn't throw an exception on production builds
         mWakeLock.release(WHY);
     }
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 d2e0386..5206db4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -20,6 +20,7 @@
 import static android.media.AudioManager.RINGER_MODE_SILENT;
 import static android.media.AudioManager.RINGER_MODE_VIBRATE;
 
+import static com.android.systemui.Flags.FLAG_HAPTIC_VOLUME_SLIDER;
 import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
 import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN;
 import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
@@ -46,7 +47,10 @@
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
+import android.media.AudioSystem;
 import android.os.SystemClock;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -61,7 +65,6 @@
 import android.widget.SeekBar;
 
 import androidx.test.core.view.MotionEventBuilder;
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.jank.InteractionJankMonitor;
@@ -70,7 +73,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.AnimatorTestRule;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.plugins.VolumeDialogController.State;
 import com.android.systemui.res.R;
@@ -92,6 +95,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -131,7 +135,7 @@
     @Mock
     DeviceProvisionedController mDeviceProvisionedController;
     @Mock
-    MediaOutputDialogFactory mMediaOutputDialogFactory;
+    MediaOutputDialogManager mMediaOutputDialogManager;
     @Mock
     InteractionJankMonitor mInteractionJankMonitor;
     @Mock
@@ -197,7 +201,7 @@
                 mAccessibilityMgr,
                 mDeviceProvisionedController,
                 mConfigurationController,
-                mMediaOutputDialogFactory,
+                mMediaOutputDialogManager,
                 mInteractionJankMonitor,
                 mVolumePanelNavigationInteractor,
                 mVolumeNavigator,
@@ -266,6 +270,55 @@
     }
 
     @Test
+    @DisableFlags(FLAG_HAPTIC_VOLUME_SLIDER)
+    public void testVolumeChange_noSliderHaptics_doesNotDeliverOnProgressChangedHaptics() {
+        // Initialize the dialog again with haptic sliders disabled
+        mDialog.init(0, null);
+        final State shellState = createShellState();
+        VolumeDialogController.StreamState musicStreamState =
+                shellState.states.get(AudioSystem.STREAM_MUSIC);
+
+        mDialog.show(SHOW_REASON_UNKNOWN);
+        mTestableLooper.processMessages(1); //Only the SHOW message
+
+        // Change the volume two times
+        musicStreamState.level += 10;
+        mDialog.onStateChangedH(shellState);
+        mAnimatorTestRule.advanceTimeBy(10);
+        musicStreamState.level += 10;
+        mDialog.onStateChangedH(shellState);
+
+        // expected: the type of the progress haptics for the stream should be DISABLED
+        short type = mDialog.progressHapticsForStream(AudioSystem.STREAM_MUSIC);
+        assertEquals(VolumeDialogImpl.PROGRESS_HAPTICS_DISABLED, type);
+    }
+
+    @Ignore("Causing breakages so ignoring to resolve, b/329099861")
+    @Test
+    @EnableFlags(FLAG_HAPTIC_VOLUME_SLIDER)
+    public void testVolumeChange_withSliderHaptics_deliversOnProgressChangedHapticsEagerly() {
+       // Initialize the dialog again to create haptic plugins on the rows with the flag enabled
+        mDialog.init(0, null);
+        final State shellState = createShellState();
+        VolumeDialogController.StreamState musicStreamState =
+                shellState.states.get(AudioSystem.STREAM_MUSIC);
+
+        mDialog.show(SHOW_REASON_UNKNOWN);
+        mTestableLooper.processMessages(1); //Only the SHOW message
+
+        // Change the volume two times
+        musicStreamState.level += 10;
+        mDialog.onStateChangedH(shellState);
+        mAnimatorTestRule.advanceTimeBy(10);
+        musicStreamState.level += 10;
+        mDialog.onStateChangedH(shellState);
+
+        // expected: the type of the progress haptics for the stream should be EAGER
+        short type = mDialog.progressHapticsForStream(AudioSystem.STREAM_MUSIC);
+        assertEquals(VolumeDialogImpl.PROGRESS_HAPTICS_EAGER, type);
+    }
+
+    @Test
     public void testComputeTimeout() {
         Mockito.reset(mAccessibilityMgr);
         mDialog.rescheduleTimeoutH();
@@ -502,7 +555,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 326204750)
     public void dialogDestroy_removesPostureControllerCallback() {
         verify(mPostureController, never()).removeCallback(any());
         mDialog.destroy();
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 a930860..19f31d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -50,7 +50,7 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
 
 import android.app.ActivityManager;
 import android.app.IActivityManager;
@@ -90,6 +90,7 @@
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
 
+import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.colorextraction.ColorExtractor;
@@ -196,6 +197,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
 import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.onehanded.OneHandedController;
@@ -450,7 +452,7 @@
 
         DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
                 mock(DeviceEntryUdfpsInteractor.class);
-        when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow());
+        when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(MutableStateFlow(false));
 
         mShadeInteractor =
                 new ShadeInteractorImpl(
@@ -609,6 +611,7 @@
                 mSysUiState,
                 mFeatureFlags,
                 mNotifPipelineFlags,
+                syncExecutor,
                 syncExecutor);
         mBubblesManager.addNotifCallback(mNotifCallback);
 
@@ -651,7 +654,7 @@
     }
 
     @Test
-    public void dreamingHidesBubbles() throws RemoteException {
+    public void bubblesHiddenWhileDreaming() throws RemoteException {
         mBubbleController.updateBubble(mBubbleEntry);
         assertTrue(mBubbleController.hasBubbles());
         assertThat(mBubbleController.getStackView().getVisibility()).isEqualTo(View.VISIBLE);
@@ -662,7 +665,17 @@
                 mKeyguardStateControllerCallbackCaptor.getValue();
         callback.onKeyguardShowingChanged();
 
+        // Dreaming should hide bubbles
         assertThat(mBubbleController.getStackView().getVisibility()).isEqualTo(View.INVISIBLE);
+
+        // Finish dreaming should show bubbles
+        mNotificationShadeWindowController.setDreaming(false);
+        when(mIDreamManager.isDreamingOrInPreview()).thenReturn(false); // dreaming finished
+
+        // Dreaming updates come through mNotificationShadeWindowController
+        mNotificationShadeWindowController.notifyStateChangedCallbacks();
+
+        assertThat(mBubbleController.getStackView().getVisibility()).isEqualTo(View.VISIBLE);
     }
 
     @Test
@@ -2240,6 +2253,30 @@
         verify(mBubbleController).onSensitiveNotificationProtectionStateChanged(false);
     }
 
+    @Test
+    public void setBubbleBarLocation_listenerNotified() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+        mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT);
+        assertThat(bubbleStateListener.mLastUpdate).isNotNull();
+        assertThat(bubbleStateListener.mLastUpdate.bubbleBarLocation).isEqualTo(
+                BubbleBarLocation.LEFT);
+    }
+
+    @Test
+    public void setBubbleBarLocation_barDisabled_shouldBeIgnored() {
+        mBubbleProperties.mIsBubbleBarEnabled = false;
+        mPositioner.setIsLargeScreen(true);
+
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+        mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT);
+        assertThat(bubbleStateListener.mStateChangeCalls).isEqualTo(0);
+    }
+
     /** Creates a bubble using the userId and package. */
     private Bubble createBubble(int userId, String pkg) {
         final UserHandle userHandle = new UserHandle(userId);
@@ -2425,8 +2462,15 @@
     }
 
     private static class FakeBubbleStateListener implements Bubbles.BubbleStateListener {
+
+        int mStateChangeCalls = 0;
+        @Nullable
+        BubbleBarUpdate mLastUpdate;
+
         @Override
         public void onBubbleStateChange(BubbleBarUpdate update) {
+            mStateChangeCalls++;
+            mLastUpdate = update;
         }
     }
 
diff --git a/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
index f6ddfcc..185deea 100644
--- a/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
@@ -20,5 +20,6 @@
 import com.android.systemui.kosmos.testCase
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.applicationContext: Context by Kosmos.Fixture { testCase.context }
+var Kosmos.applicationContext: Context by
+    Kosmos.Fixture { testCase.context.apply { ensureTestableResources() } }
 val Kosmos.mockedContext: Context by Kosmos.Fixture { mock<Context>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt b/packages/SystemUI/tests/utils/src/android/os/LooperKosmos.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
copy to packages/SystemUI/tests/utils/src/android/os/LooperKosmos.kt
index ad8ccb0..a8ca9bfc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/os/LooperKosmos.kt
@@ -14,12 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel
+package android.os
 
+import android.testing.TestableLooper
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.media.mediaOutputDialogFactory
-import com.android.systemui.plugins.activityStarter
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testCase
 
-val Kosmos.mediaOutputActionsInteractor by
-    Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) }
+val Kosmos.looper by Fixture {
+    checkNotNull(TestableLooper.get(testCase).looper) {
+        "TestableLooper is returning null, make sure the test class is annotated with RunWithLooper"
+    }
+}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt
similarity index 73%
copy from packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt
index 8ad0a08..ed291d1 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel.component.mediaoutput
+package com.android.systemui.animation
 
-import dagger.Module
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
 
-@Module interface MediaOutputModule
+val Kosmos.dialogTransitionAnimator by Fixture { fakeDialogTransitionAnimator() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt
index 3b5ff38..9765d53 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt
@@ -19,11 +19,17 @@
 
 import android.util.Size
 import com.android.systemui.biometrics.shared.model.DisplayRotation
+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.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
-class FakeDisplayStateRepository : DisplayStateRepository {
+@SysUISingleton
+class FakeDisplayStateRepository @Inject constructor() : DisplayStateRepository {
     private val _isInRearDisplayMode = MutableStateFlow<Boolean>(false)
     override val isInRearDisplayMode: StateFlow<Boolean> = _isInRearDisplayMode.asStateFlow()
 
@@ -33,6 +39,9 @@
     private val _currentDisplaySize = MutableStateFlow<Size>(Size(0, 0))
     override val currentDisplaySize: StateFlow<Size> = _currentDisplaySize.asStateFlow()
 
+    private val _isLargeScreen = MutableStateFlow<Boolean>(false)
+    override val isLargeScreen: Flow<Boolean> = _isLargeScreen.asStateFlow()
+
     override val isReverseDefaultRotation = false
 
     fun setIsInRearDisplayMode(isInRearDisplayMode: Boolean) {
@@ -47,3 +56,8 @@
         _currentDisplaySize.value = size
     }
 }
+
+@Module
+interface FakeDisplayStateRepositoryModule {
+    @Binds fun bindFake(fake: FakeDisplayStateRepository): DisplayStateRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
index 005cac4..bd30fb4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
@@ -28,6 +28,7 @@
 
 @SysUISingleton
 class FakeFingerprintPropertyRepository @Inject constructor() : FingerprintPropertyRepository {
+    override val propertiesInitialized: MutableStateFlow<Boolean> = MutableStateFlow(false)
 
     private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1)
     override val sensorId = _sensorId.asStateFlow()
@@ -54,6 +55,7 @@
         _strength.value = strength
         _sensorType.value = sensorType
         _sensorLocations.value = sensorLocations
+        propertiesInitialized.value = true
     }
 
     /** setProperties as if the device supports UDFPS_OPTICAL. */
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
index f192de2..c3af437 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
@@ -1,6 +1,8 @@
 package com.android.systemui.biometrics.data.repository
 
+import android.hardware.biometrics.Flags
 import android.hardware.biometrics.PromptInfo
+import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.shared.model.PromptKind
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
@@ -26,6 +28,9 @@
     private val _isConfirmationRequired = MutableStateFlow(false)
     override val isConfirmationRequired = _isConfirmationRequired.asStateFlow()
 
+    private val _showBpWithoutIconForCredential = MutableStateFlow(false)
+    override val showBpWithoutIconForCredential = _showBpWithoutIconForCredential.asStateFlow()
+
     private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null)
     override val opPackageName = _opPackageName.asStateFlow()
 
@@ -69,6 +74,16 @@
         _isConfirmationRequired.value = false
     }
 
+    override fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) {
+        val hasCredentialViewShown = kind.value !is PromptKind.Biometric
+        val showBpForCredential =
+            Flags.customBiometricPrompt() &&
+                !Utils.isBiometricAllowed(promptInfo) &&
+                Utils.isDeviceCredentialAllowed(promptInfo) &&
+                promptInfo.contentView != null
+        _showBpWithoutIconForCredential.value = showBpForCredential && !hasCredentialViewShown
+    }
+
     fun setIsShowing(showing: Boolean) {
         _isShowing.value = showing
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorKosmos.kt
index e262066..34a9c8a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorKosmos.kt
@@ -21,9 +21,11 @@
 import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
 
 val Kosmos.fingerprintPropertyInteractor by Fixture {
     FingerprintPropertyInteractor(
+        applicationScope = applicationCoroutineScope,
         context = applicationContext,
         repository = fingerprintPropertyRepository,
         configurationInteractor = configurationInteractor,
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
similarity index 62%
copy from packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
index 8ad0a08..5c3e1f4 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
@@ -14,8 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel.component.mediaoutput
+package com.android.systemui.bouncer.shared.flag
 
-import dagger.Module
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 
-@Module interface MediaOutputModule
+var Kosmos.fakeComposeBouncerFlags by
+    Kosmos.Fixture { FakeComposeBouncerFlags(fakeSceneContainerFlags) }
+val Kosmos.composeBouncerFlags by Kosmos.Fixture<ComposeBouncerFlags> { fakeComposeBouncerFlags }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt
new file mode 100644
index 0000000..c116bbd
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.shared.flag
+
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
+
+class FakeComposeBouncerFlags(
+    private val sceneContainerFlags: SceneContainerFlags,
+    var composeBouncerEnabled: Boolean = false
+) : ComposeBouncerFlags {
+    override fun isComposeBouncerOrSceneContainerEnabled(): Boolean {
+        return sceneContainerFlags.isEnabled() || composeBouncerEnabled
+    }
+
+    @Deprecated(
+        "Avoid using this, this is meant to be used only by the glue code " +
+            "that includes compose bouncer in legacy keyguard.",
+        replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()")
+    )
+    override fun isOnlyComposeBouncerEnabled(): Boolean = composeBouncerEnabled
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
index 99dfe94..6d97238 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
@@ -21,12 +21,12 @@
 import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
 import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
+import com.android.systemui.bouncer.shared.flag.composeBouncerFlags
 import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.user.domain.interactor.selectedUserInteractor
 import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel
 import com.android.systemui.util.mockito.mock
@@ -42,7 +42,7 @@
         simBouncerInteractor = simBouncerInteractor,
         authenticationInteractor = authenticationInteractor,
         selectedUserInteractor = selectedUserInteractor,
-        flags = sceneContainerFlags,
+        flags = composeBouncerFlags,
         selectedUser = userSwitcherViewModel.selectedUser,
         users = userSwitcherViewModel.users,
         userSwitcherMenu = userSwitcherViewModel.menu,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepository.kt
new file mode 100644
index 0000000..b8284ac
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepository.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.camera.data.repository
+
+import android.os.UserHandle
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class FakeCameraAutoRotateRepository : CameraAutoRotateRepository {
+    private val userMap = mutableMapOf<Int, MutableStateFlow<Boolean>>()
+
+    /** Send a Unit signal when value changes */
+    override fun isCameraAutoRotateSettingEnabled(userHandle: UserHandle): StateFlow<Boolean> =
+        getFlow(userHandle.identifier)
+
+    fun setEnabled(userHandle: UserHandle, enabled: Boolean) {
+        getFlow(userHandle.identifier).value = enabled
+    }
+
+    /** initializes the flow if already not */
+    private fun getFlow(userId: Int): MutableStateFlow<Boolean> =
+        userMap.getOrPut(userId) { MutableStateFlow(false) }
+}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryKosmos.kt
similarity index 72%
copy from packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryKosmos.kt
index 8ad0a08..615c596 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryKosmos.kt
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel.component.mediaoutput
+package com.android.systemui.camera.data.repository
 
-import dagger.Module
+import com.android.systemui.kosmos.Kosmos
 
-@Module interface MediaOutputModule
+val Kosmos.fakeCameraAutoRotateRepository: FakeCameraAutoRotateRepository by
+    Kosmos.Fixture { FakeCameraAutoRotateRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepository.kt
new file mode 100644
index 0000000..994e9b2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepository.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.camera.data.repository
+
+import android.os.UserHandle
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class FakeCameraSensorPrivacyRepository : CameraSensorPrivacyRepository {
+
+    private val userMap = mutableMapOf<Int, MutableStateFlow<Boolean>>()
+    override fun isEnabled(userHandle: UserHandle): StateFlow<Boolean> =
+        getFlow(userHandle.identifier)
+
+    fun setEnabled(userHandle: UserHandle, enabled: Boolean) {
+        getFlow(userHandle.identifier).value = enabled
+    }
+
+    /** initializes the flow if already not */
+    private fun getFlow(userId: Int): MutableStateFlow<Boolean> =
+        userMap.getOrPut(userId) { MutableStateFlow(false) }
+}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryKosmos.kt
similarity index 71%
copy from packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryKosmos.kt
index 8ad0a08..c7e704c 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryKosmos.kt
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel.component.mediaoutput
+package com.android.systemui.camera.data.repository
 
-import dagger.Module
+import com.android.systemui.kosmos.Kosmos
 
-@Module interface MediaOutputModule
+val Kosmos.fakeCameraSensorPrivacyRepository: FakeCameraSensorPrivacyRepository by
+    Kosmos.Fixture { FakeCameraSensorPrivacyRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java
index 5038285..974a11c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java
@@ -201,4 +201,13 @@
     public List<FalsingTapListener> getTapListeners() {
         return mTapListeners;
     }
+
+    /**
+     * Calls every registered {@link FalsingBeliefListener} as if false touch occurred.
+     */
+    public void sendFalsingBelief() {
+        for (FalsingBeliefListener listener : mFalsingBeliefListeners) {
+            listener.onFalse();
+        }
+    }
 }
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 050c2c9..4d74254c 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
@@ -30,7 +30,11 @@
 
 @SysUISingleton
 class FakeConfigurationRepository @Inject constructor() : ConfigurationRepository {
-    private val _onAnyConfigurationChange = MutableSharedFlow<Unit>()
+    private val _onAnyConfigurationChange =
+        MutableSharedFlow<Unit>(
+            replay = 1,
+            onBufferOverflow = BufferOverflow.DROP_OLDEST,
+        )
     override val onAnyConfigurationChange: Flow<Unit> = _onAnyConfigurationChange.asSharedFlow()
 
     private val _onConfigurationChange =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
index 9d508d2..5ff588f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
@@ -1,7 +1,8 @@
 package com.android.systemui.communal.data.repository
 
-import com.android.systemui.communal.shared.model.CommunalSceneKey
-import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.communal.shared.model.CommunalScenes
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
@@ -16,17 +17,16 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 class FakeCommunalRepository(
     applicationScope: CoroutineScope,
-    override val desiredScene: MutableStateFlow<CommunalSceneKey> =
-        MutableStateFlow(CommunalSceneKey.DEFAULT),
+    override val desiredScene: MutableStateFlow<SceneKey> =
+        MutableStateFlow(CommunalScenes.Default),
 ) : CommunalRepository {
-    override fun setDesiredScene(desiredScene: CommunalSceneKey) {
+    override fun setDesiredScene(desiredScene: SceneKey) {
         this.desiredScene.value = desiredScene
     }
 
-    private val defaultTransitionState =
-        ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT)
-    private val _transitionState = MutableStateFlow<Flow<ObservableCommunalTransitionState>?>(null)
-    override val transitionState: StateFlow<ObservableCommunalTransitionState> =
+    private val defaultTransitionState = ObservableTransitionState.Idle(CommunalScenes.Default)
+    private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null)
+    override val transitionState: StateFlow<ObservableTransitionState> =
         _transitionState
             .flatMapLatest { innerFlowOrNull -> innerFlowOrNull ?: flowOf(defaultTransitionState) }
             .stateIn(
@@ -35,7 +35,7 @@
                 initialValue = defaultTransitionState,
             )
 
-    override fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?) {
+    override fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
         _transitionState.value = transitionState
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 6ac702e..8866fd3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.communal.domain.interactor
 
+import android.os.userManager
+import com.android.systemui.broadcast.broadcastDispatcher
 import com.android.systemui.communal.data.repository.communalMediaRepository
 import com.android.systemui.communal.data.repository.communalPrefsRepository
 import com.android.systemui.communal.data.repository.communalRepository
@@ -29,6 +31,7 @@
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.plugins.activityStarter
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.settings.userTracker
@@ -39,6 +42,7 @@
 val Kosmos.communalInteractor by Fixture {
     CommunalInteractor(
         applicationScope = applicationCoroutineScope,
+        broadcastDispatcher = broadcastDispatcher,
         communalRepository = communalRepository,
         widgetRepository = communalWidgetRepository,
         mediaRepository = communalMediaRepository,
@@ -48,6 +52,8 @@
         keyguardInteractor = keyguardInteractor,
         editWidgetsActivityStarter = editWidgetsActivityStarter,
         userTracker = userTracker,
+        activityStarter = activityStarter,
+        userManager = userManager,
         logBuffer = logcatLogBuffer("CommunalInteractor"),
         tableLogBuffer = mock(),
         communalSettingsInteractor = communalSettingsInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
index 8ff04a63..1c8190e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
@@ -15,8 +15,10 @@
 
 package com.android.systemui.deviceentry.data
 
+import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepositoryModule
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepositoryModule
 import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepositoryModule
+import com.android.systemui.display.data.repository.FakeDisplayRepositoryModule
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepositoryModule
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepositoryModule
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepositoryModule
@@ -30,6 +32,8 @@
             FakeDeviceEntryRepositoryModule::class,
             FakeDeviceEntryFaceAuthRepositoryModule::class,
             FakeDeviceEntryFingerprintAuthRepositoryModule::class,
+            FakeDisplayRepositoryModule::class,
+            FakeDisplayStateRepositoryModule::class,
             FakeFingerprintPropertyRepositoryModule::class,
             FakeTrustRepositoryModule::class,
         ]
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorKosmos.kt
index b04161a..81123d0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorKosmos.kt
@@ -18,7 +18,7 @@
 
 package com.android.systemui.deviceentry.domain.interactor
 
-import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.fingerprintPropertyInteractor
 import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
 import com.android.systemui.kosmos.Kosmos
@@ -27,7 +27,7 @@
 
 val Kosmos.deviceEntryUdfpsInteractor by Fixture {
     DeviceEntryUdfpsInteractor(
-        fingerprintPropertyRepository = fingerprintPropertyRepository,
+        fingerprintPropertyInteractor = fingerprintPropertyInteractor,
         fingerprintAuthRepository = deviceEntryFingerprintAuthRepository,
         biometricSettingsRepository = biometricSettingsRepository,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt
index c3f677e..d5411ad 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
 import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.plugins.activityStarter
@@ -40,5 +41,6 @@
             context = mockedContext,
             activityStarter = activityStarter,
             powerInteractor = powerInteractor,
+            keyguardTransitionInteractor = keyguardTransitionInteractor,
         )
     }
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 d8098b7..0fc0a3c 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
@@ -16,7 +16,11 @@
 package com.android.systemui.display.data.repository
 
 import android.view.Display
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.util.mockito.mock
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import org.mockito.Mockito.`when` as whenever
@@ -42,8 +46,9 @@
 fun createPendingDisplay(id: Int = 0): DisplayRepository.PendingDisplay =
     mock<DisplayRepository.PendingDisplay> { whenever(this.id).thenReturn(id) }
 
+@SysUISingleton
 /** Fake [DisplayRepository] implementation for testing. */
-class FakeDisplayRepository : DisplayRepository {
+class FakeDisplayRepository @Inject constructor() : DisplayRepository {
     private val flow = MutableSharedFlow<Set<Display>>(replay = 1)
     private val pendingDisplayFlow =
         MutableSharedFlow<DisplayRepository.PendingDisplay?>(replay = 1)
@@ -71,3 +76,8 @@
     override val displayChangeEvent: Flow<Int> = _displayChangeEvent
     suspend fun emitDisplayChangeEvent(displayId: Int) = _displayChangeEvent.emit(displayId)
 }
+
+@Module
+interface FakeDisplayRepositoryModule {
+    @Binds fun bindFake(fake: FakeDisplayRepository): DisplayRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt
index 3faa6eb..4e05de2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.flags
 
 import android.util.Log
-import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import org.junit.Assert
 import org.junit.Assume
@@ -42,10 +41,6 @@
                         null || description?.getAnnotation(EnableSceneContainer::class.java) != null
                 if (hasAnnotation) {
                     Assume.assumeTrue(
-                        "Compose must be available for @EnableSceneContainer test",
-                        ComposeFacade.isComposeAvailable()
-                    )
-                    Assume.assumeTrue(
                         "Couldn't set Flags.SCENE_CONTAINER_ENABLED for @EnableSceneContainer test",
                         trySetSceneContainerEnabled(true)
                     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
index 534f773..5b642ea 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
@@ -42,17 +42,28 @@
     private val _currentClockId = MutableStateFlow(DEFAULT_CLOCK_ID)
     override val currentClockId: Flow<ClockId> = _currentClockId
 
-    private val _currentClock = MutableStateFlow(null)
+    private val _currentClock: MutableStateFlow<ClockController?> = MutableStateFlow(null)
     override val currentClock = _currentClock
 
-    private val _previewClock = MutableStateFlow(Mockito.mock(ClockController::class.java))
-    override val previewClock: StateFlow<ClockController> = _previewClock
+    private val _previewClockPair =
+        MutableStateFlow(
+            Pair(
+                Mockito.mock(ClockController::class.java),
+                Mockito.mock(ClockController::class.java)
+            )
+        )
+    override val previewClockPair: StateFlow<Pair<ClockController, ClockController>> =
+        _previewClockPair
     override val clockEventController: ClockEventController
         get() = mock()
 
     override fun setClockSize(@ClockSize size: Int) {
         _clockSize.value = size
     }
+
+    fun setCurrentClock(clockController: ClockController) {
+        _currentClock.value = clockController
+    }
 }
 
 @Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 793e2d7..1e305d6 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
@@ -18,7 +18,6 @@
 package com.android.systemui.keyguard.data.repository
 
 import android.graphics.Point
-import com.android.systemui.common.shared.model.Position
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
@@ -58,9 +57,6 @@
     private val _bottomAreaAlpha = MutableStateFlow(1f)
     override val bottomAreaAlpha: StateFlow<Float> = _bottomAreaAlpha
 
-    private val _clockPosition = MutableStateFlow(Position(0, 0))
-    override val clockPosition: StateFlow<Position> = _clockPosition
-
     private val _isKeyguardShowing = MutableStateFlow(false)
     override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
 
@@ -149,10 +145,6 @@
         _bottomAreaAlpha.value = alpha
     }
 
-    override fun setClockPosition(x: Int, y: Int) {
-        _clockPosition.value = Position(x, y)
-    }
-
     fun setKeyguardShowing(isShowing: Boolean) {
         _isKeyguardShowing.value = isShowing
     }
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 a9a2d91..dcbd577 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
@@ -18,6 +18,7 @@
 package com.android.systemui.keyguard.data.repository
 
 import android.annotation.FloatRange
+import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
@@ -65,55 +66,79 @@
     }
 
     /**
-     * Sends STARTED, RUNNING, and FINISHED TransitionSteps between [from] and [to], calling
-     * [runCurrent] after each step.
+     * Sends TransitionSteps between [from] and [to], calling [runCurrent] after each step.
+     *
+     * By default, sends steps through FINISHED (STARTED, RUNNING, FINISHED) but can be halted part
+     * way using [throughTransitionState].
      */
     suspend fun sendTransitionSteps(
         from: KeyguardState,
         to: KeyguardState,
         testScope: TestScope,
+        throughTransitionState: TransitionState = TransitionState.FINISHED,
     ) {
-        sendTransitionSteps(from, to, testScope.testScheduler)
+        sendTransitionSteps(from, to, testScope.testScheduler, throughTransitionState)
     }
 
     /**
-     * Sends STARTED, RUNNING, and FINISHED TransitionSteps between [from] and [to], calling
-     * [runCurrent] after each step.
+     * Sends TransitionSteps between [from] and [to], calling [runCurrent] after each step.
+     *
+     * By default, sends steps through FINISHED (STARTED, RUNNING, FINISHED) but can be halted part
+     * way using [throughTransitionState].
      */
     suspend fun sendTransitionSteps(
         from: KeyguardState,
         to: KeyguardState,
         testScheduler: TestCoroutineScheduler,
+        throughTransitionState: TransitionState = TransitionState.FINISHED,
     ) {
         sendTransitionStep(
-            TransitionStep(
-                transitionState = TransitionState.STARTED,
-                from = from,
-                to = to,
-                value = 0f,
-            )
+            step =
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = from,
+                    to = to,
+                    value = 0f,
+                )
         )
         testScheduler.runCurrent()
 
-        sendTransitionStep(
-            TransitionStep(
-                transitionState = TransitionState.RUNNING,
-                from = from,
-                to = to,
-                value = 0.5f
+        if (
+            throughTransitionState == TransitionState.RUNNING ||
+                throughTransitionState == TransitionState.FINISHED
+        ) {
+            sendTransitionStep(
+                step =
+                    TransitionStep(
+                        transitionState = TransitionState.RUNNING,
+                        from = from,
+                        to = to,
+                        value = 0.5f
+                    )
             )
-        )
-        testScheduler.runCurrent()
+            testScheduler.runCurrent()
+        }
 
-        sendTransitionStep(
-            TransitionStep(
-                transitionState = TransitionState.FINISHED,
-                from = from,
-                to = to,
-                value = 1f,
+        if (throughTransitionState == TransitionState.FINISHED) {
+            sendTransitionStep(
+                step =
+                    TransitionStep(
+                        transitionState = TransitionState.FINISHED,
+                        from = from,
+                        to = to,
+                        value = 1f,
+                    )
             )
+            testScheduler.runCurrent()
+        }
+    }
+
+    suspend fun sendTransitionStep(step: TransitionStep, validateStep: Boolean = true) {
+        this.sendTransitionStep(
+            step = step,
+            validateStep = validateStep,
+            ownerName = step.ownerName
         )
-        testScheduler.runCurrent()
     }
 
     /**
@@ -132,7 +157,22 @@
      * If you're testing something involving transitions themselves and are sure you want to send
      * only a FINISHED step, override [validateStep].
      */
-    suspend fun sendTransitionStep(step: TransitionStep, validateStep: Boolean = true) {
+    suspend fun sendTransitionStep(
+        from: KeyguardState = KeyguardState.OFF,
+        to: KeyguardState = KeyguardState.OFF,
+        value: Float = 0f,
+        transitionState: TransitionState = TransitionState.FINISHED,
+        ownerName: String = "",
+        step: TransitionStep =
+            TransitionStep(
+                from = from,
+                to = to,
+                value = value,
+                transitionState = transitionState,
+                ownerName = ownerName
+            ),
+        validateStep: Boolean = true
+    ) {
         _transitions.replayCache.last().let { lastStep ->
             if (
                 validateStep &&
@@ -159,7 +199,9 @@
         step: TransitionStep,
         validateStep: Boolean = true
     ): Job {
-        return coroutineScope.launch { sendTransitionStep(step, validateStep) }
+        return coroutineScope.launch {
+            sendTransitionStep(step = step, validateStep = validateStep)
+        }
     }
 
     suspend fun sendTransitionSteps(
@@ -168,12 +210,13 @@
         validateStep: Boolean = true
     ) {
         steps.forEach {
-            sendTransitionStep(it, validateStep = validateStep)
+            sendTransitionStep(step = it, validateStep = validateStep)
             testScope.testScheduler.runCurrent()
         }
     }
 
     override fun startTransition(info: TransitionInfo): UUID? {
+        Log.i("TEST", "Start transition: ", Exception())
         return if (info.animator == null) UUID.randomUUID() else null
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
index b24b95e..f26bb83 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
@@ -35,7 +35,10 @@
     private val _revealAmount: MutableStateFlow<Float> = MutableStateFlow(0.0f)
     override val revealAmount: Flow<Float> = _revealAmount
 
-    override fun startRevealAmountAnimator(reveal: Boolean) {
+    override val isAnimating: Boolean
+        get() = false
+
+    override fun startRevealAmountAnimator(reveal: Boolean, duration: Long) {
         if (reveal) {
             _revealAmount.value = 1.0f
         } else {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
index 8452963..75489b6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
@@ -17,10 +17,12 @@
 package com.android.systemui.keyguard.data.repository
 
 import android.os.fakeExecutorHandler
-import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.WEATHER_CLOCK_BLUEPRINT_ID
 import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT
+import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.util.ThreadAssert
 import com.android.systemui.util.mockito.mock
@@ -28,8 +30,13 @@
 val Kosmos.keyguardBlueprintRepository by
     Kosmos.Fixture {
         KeyguardBlueprintRepository(
-            configurationRepository = configurationRepository,
-            blueprints = setOf(defaultBlueprint),
+            blueprints =
+                setOf(
+                    defaultBlueprint,
+                    splitShadeBlueprint,
+                    weatherClockBlueprint,
+                    splitShadeWeatherClockBlueprint,
+                ),
             handler = fakeExecutorHandler,
             assert = mock<ThreadAssert>(),
         )
@@ -42,3 +49,27 @@
         override val sections: List<KeyguardSection>
             get() = listOf()
     }
+
+private val weatherClockBlueprint =
+    object : KeyguardBlueprint {
+        override val id: String
+            get() = WEATHER_CLOCK_BLUEPRINT_ID
+        override val sections: List<KeyguardSection>
+            get() = listOf()
+    }
+
+private val splitShadeWeatherClockBlueprint =
+    object : KeyguardBlueprint {
+        override val id: String
+            get() = SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID
+        override val sections: List<KeyguardSection>
+            get() = listOf()
+    }
+
+private val splitShadeBlueprint =
+    object : KeyguardBlueprint {
+        override val id: String
+            get() = SplitShadeKeyguardBlueprint.Companion.ID
+        override val sections: List<KeyguardSection>
+            get() = listOf()
+    }
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepositoryKosmos.kt
similarity index 76%
copy from packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepositoryKosmos.kt
index 8ad0a08..4c8bf90 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepositoryKosmos.kt
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel.component.mediaoutput
+package com.android.systemui.keyguard.data.repository
 
-import dagger.Module
+import com.android.systemui.kosmos.Kosmos
 
-@Module interface MediaOutputModule
+val Kosmos.keyguardOcclusionRepository by Kosmos.Fixture { KeyguardOcclusionRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt
index a9d89a3..40131c7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt
@@ -19,7 +19,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.content.applicationContext
-import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.doze.util.burnInHelperWrapper
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -31,7 +31,7 @@
         context = applicationContext,
         burnInHelperWrapper = burnInHelperWrapper,
         scope = applicationCoroutineScope,
-        configurationRepository = configurationRepository,
+        configurationInteractor = configurationInteractor,
         keyguardInteractor = keyguardInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..530cbed
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+val Kosmos.fromAlternateBouncerTransitionInteractor by
+    Kosmos.Fixture {
+        FromAlternateBouncerTransitionInteractor(
+            transitionRepository = keyguardTransitionRepository,
+            transitionInteractor = keyguardTransitionInteractor,
+            scope = applicationCoroutineScope,
+            bgDispatcher = testDispatcher,
+            mainDispatcher = testDispatcher,
+            keyguardInteractor = keyguardInteractor,
+            communalInteractor = communalInteractor,
+            powerInteractor = powerInteractor,
+            keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
index da5cd67..bbe37c1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
@@ -18,17 +18,21 @@
 
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
 
 val Kosmos.fromAodTransitionInteractor by
     Kosmos.Fixture {
         FromAodTransitionInteractor(
             transitionRepository = fakeKeyguardTransitionRepository,
             transitionInteractor = keyguardTransitionInteractor,
-            scope = testScope,
+            scope = applicationCoroutineScope,
             bgDispatcher = testDispatcher,
             mainDispatcher = testDispatcher,
             keyguardInteractor = keyguardInteractor,
+            powerInteractor = powerInteractor,
+            keyguardOcclusionInteractor = keyguardOcclusionInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..23dcd96
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+var Kosmos.fromDozingTransitionInteractor by
+    Kosmos.Fixture {
+        FromDozingTransitionInteractor(
+            transitionRepository = keyguardTransitionRepository,
+            transitionInteractor = keyguardTransitionInteractor,
+            scope = applicationCoroutineScope,
+            bgDispatcher = testDispatcher,
+            mainDispatcher = testDispatcher,
+            keyguardInteractor = keyguardInteractor,
+            communalInteractor = communalInteractor,
+            powerInteractor = powerInteractor,
+            keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..f7a9d59
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractorKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+var Kosmos.fromDreamingLockscreenHostedTransitionInteractor by
+    Kosmos.Fixture {
+        FromDreamingLockscreenHostedTransitionInteractor(
+            transitionRepository = keyguardTransitionRepository,
+            transitionInteractor = keyguardTransitionInteractor,
+            scope = applicationCoroutineScope,
+            bgDispatcher = testDispatcher,
+            mainDispatcher = testDispatcher,
+            keyguardInteractor = keyguardInteractor,
+            powerInteractor = powerInteractor,
+            keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..135644c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+var Kosmos.fromDreamingTransitionInteractor by
+    Kosmos.Fixture {
+        FromDreamingTransitionInteractor(
+            transitionRepository = keyguardTransitionRepository,
+            transitionInteractor = keyguardTransitionInteractor,
+            scope = applicationCoroutineScope,
+            bgDispatcher = testDispatcher,
+            mainDispatcher = testDispatcher,
+            keyguardInteractor = keyguardInteractor,
+            glanceableHubTransitions = glanceableHubTransitions,
+            powerInteractor = powerInteractor,
+            keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..1695327
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+var Kosmos.fromGlanceableHubTransitionInteractor by
+    Kosmos.Fixture {
+        FromGlanceableHubTransitionInteractor(
+            transitionRepository = keyguardTransitionRepository,
+            transitionInteractor = keyguardTransitionInteractor,
+            scope = applicationCoroutineScope,
+            bgDispatcher = testDispatcher,
+            mainDispatcher = testDispatcher,
+            keyguardInteractor = keyguardInteractor,
+            powerInteractor = powerInteractor,
+            keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+            glanceableHubTransitions = glanceableHubTransitions,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
index 25fc67a..604d9e4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
@@ -17,11 +17,13 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
 
 val Kosmos.fromGoneTransitionInteractor by
     Kosmos.Fixture {
@@ -34,5 +36,7 @@
             keyguardInteractor = keyguardInteractor,
             powerInteractor = powerInteractor,
             communalInteractor = communalInteractor,
+            keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+            biometricSettingsRepository = biometricSettingsRepository,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
index 3b52676..162fd90 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
 
 var Kosmos.fromLockscreenTransitionInteractor by
     Kosmos.Fixture {
@@ -38,5 +39,6 @@
             powerInteractor = powerInteractor,
             glanceableHubTransitions = glanceableHubTransitions,
             swipeToDismissInteractor = swipeToDismissInteractor,
+            keyguardOcclusionInteractor = keyguardOcclusionInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..fc740a1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+val Kosmos.fromOccludedTransitionInteractor by
+    Kosmos.Fixture {
+        FromOccludedTransitionInteractor(
+            transitionRepository = keyguardTransitionRepository,
+            transitionInteractor = keyguardTransitionInteractor,
+            scope = applicationCoroutineScope,
+            bgDispatcher = testDispatcher,
+            mainDispatcher = testDispatcher,
+            keyguardInteractor = keyguardInteractor,
+            powerInteractor = powerInteractor,
+            communalInteractor = communalInteractor,
+            keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
index 6b76449..98babff 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
 import com.android.systemui.user.domain.interactor.selectedUserInteractor
 
 var Kosmos.fromPrimaryBouncerTransitionInteractor by
@@ -40,5 +41,6 @@
             keyguardSecurityModel = keyguardSecurityModel,
             selectedUserInteractor = selectedUserInteractor,
             powerInteractor = powerInteractor,
+            keyguardOcclusionInteractor = keyguardOcclusionInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
index 5dd5073..a45b269 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
@@ -19,12 +19,10 @@
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testDispatcher
 
 val Kosmos.glanceableHubTransitions by
     Kosmos.Fixture {
         GlanceableHubTransitions(
-            bgDispatcher = testDispatcher,
             transitionRepository = keyguardTransitionRepository,
             transitionInteractor = keyguardTransitionInteractor,
             communalInteractor = communalInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt
index 8b0bba1..87d6c17 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.content.applicationContext
+import com.android.systemui.biometrics.domain.interactor.fingerprintPropertyInteractor
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.keyguard.data.repository.keyguardBlueprintRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -30,5 +32,7 @@
             context = applicationContext,
             splitShadeStateController = splitShadeStateController,
             clockInteractor = keyguardClockInteractor,
+            configurationInteractor = configurationInteractor,
+            fingerprintPropertyInteractor = fingerprintPropertyInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
index 6df7493..6cc1e8e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -16,19 +16,21 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import com.android.systemui.keyguard.data.repository.keyguardRepository
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
-import dagger.Lazy
 
 val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by
     Kosmos.Fixture {
         KeyguardTransitionInteractor(
             scope = applicationCoroutineScope,
             repository = keyguardTransitionRepository,
-            fromLockscreenTransitionInteractor = Lazy { fromLockscreenTransitionInteractor },
-            fromPrimaryBouncerTransitionInteractor =
-                Lazy { fromPrimaryBouncerTransitionInteractor },
-            fromAodTransitionInteractor = Lazy { fromAodTransitionInteractor },
+            keyguardRepository = keyguardRepository,
+            fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
+            fromPrimaryBouncerTransitionInteractor = { fromPrimaryBouncerTransitionInteractor },
+            fromAodTransitionInteractor = { fromAodTransitionInteractor },
+            fromAlternateBouncerTransitionInteractor = { fromAlternateBouncerTransitionInteractor },
+            fromDozingTransitionInteractor = { fromDozingTransitionInteractor },
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt
index 58e0a3b..d5bdbdb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt
@@ -29,6 +29,6 @@
             lightRevealScrimRepository,
             applicationCoroutineScope,
             scrimLogger,
-            powerInteractor,
+            { powerInteractor },
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelKosmos.kt
new file mode 100644
index 0000000..b7d9676
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.deviceentry.domain.interactor.biometricMessageInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.time.systemClock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.alternateBouncerMessageAreaViewModel by
+    Kosmos.Fixture {
+        AlternateBouncerMessageAreaViewModel(
+            biometricMessageInteractor = biometricMessageInteractor,
+            alternateBouncerInteractor = alternateBouncerInteractor,
+            systemClock = systemClock,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModelKosmos.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModelKosmos.kt
index ad8ccb0..8162520 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModelKosmos.kt
@@ -14,12 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel
+package com.android.systemui.keyguard.ui.viewmodel
 
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.media.mediaOutputDialogFactory
-import com.android.systemui.plugins.activityStarter
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 
-val Kosmos.mediaOutputActionsInteractor by
-    Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) }
+@ExperimentalCoroutinesApi
+val Kosmos.dozingToOccludedTransitionViewModel by
+    Kosmos.Fixture {
+        DozingToOccludedTransitionViewModel(
+            animationFlow = keyguardTransitionAnimationFlow,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelKosmos.kt
index 00741eb..298c70d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.keyguard.domain.interactor.fromDreamingTransitionInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 
@@ -25,5 +26,6 @@
         DreamingToGlanceableHubTransitionViewModel(
             configurationInteractor = configurationInteractor,
             animationFlow = keyguardTransitionAnimationFlow,
+            fromDreamingTransitionInteractor = fromDreamingTransitionInteractor
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt
index 5f70a2f..450dcc2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -26,7 +25,6 @@
 @ExperimentalCoroutinesApi
 val Kosmos.dreamingToLockscreenTransitionViewModel by Fixture {
     DreamingToLockscreenTransitionViewModel(
-        keyguardTransitionInteractor = keyguardTransitionInteractor,
         fromDreamingTransitionInteractor = mock(),
         animationFlow = keyguardTransitionAnimationFlow,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index ecf66a2..a863edf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 @file:OptIn(ExperimentalCoroutinesApi::class)
 
 package com.android.systemui.keyguard.ui.viewmodel
@@ -24,6 +23,7 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
 import com.android.systemui.statusbar.phone.dozeParameters
@@ -32,6 +32,7 @@
 
 val Kosmos.keyguardRootViewModel by Fixture {
     KeyguardRootViewModel(
+        scope = applicationCoroutineScope,
         deviceEntryInteractor = deviceEntryInteractor,
         dozeParameters = dozeParameters,
         keyguardInteractor = keyguardInteractor,
@@ -39,9 +40,18 @@
         keyguardTransitionInteractor = keyguardTransitionInteractor,
         notificationsKeyguardInteractor = notificationsKeyguardInteractor,
         alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
+        aodToGoneTransitionViewModel = aodToGoneTransitionViewModel,
         aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
+        aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
+        dozingToGoneTransitionViewModel = dozingToGoneTransitionViewModel,
         dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
+        dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
+        dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
         glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
+        goneToAodTransitionViewModel = goneToAodTransitionViewModel,
+        goneToDozingTransitionViewModel = goneToDozingTransitionViewModel,
+        lockscreenToAodTransitionViewModel = lockscreenToAodTransitionViewModel,
+        lockscreenToDozingTransitionViewModel = lockscreenToDozingTransitionViewModel,
         lockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel,
         lockscreenToGlanceableHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel,
         lockscreenToGoneTransitionViewModel = lockscreenToGoneTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
index 96de4ba..8da5dd4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardBlueprintInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.policy.splitShadeStateController
 
 val Kosmos.lockscreenContentViewModel by
     Kosmos.Fixture {
@@ -28,5 +29,6 @@
             interactor = keyguardBlueprintInteractor,
             authController = authController,
             longPress = keyguardLongPressViewModel,
+            splitShadeStateController = splitShadeStateController,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt
index e1b1966..e788669 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.media
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.media.dialog.MediaOutputDialogFactory
+import com.android.systemui.media.dialog.MediaOutputDialogManager
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.mediaOutputDialogFactory: MediaOutputDialogFactory by Kosmos.Fixture { mock {} }
+var Kosmos.mediaOutputDialogManager: MediaOutputDialogManager by Kosmos.Fixture { mock {} }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/SpatializerKosmos.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/media/SpatializerKosmos.kt
index ad8ccb0..7001ea8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/SpatializerKosmos.kt
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel
+package com.android.systemui.media
 
+import com.android.settingslib.media.domain.interactor.SpatializerInteractor
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.media.mediaOutputDialogFactory
-import com.android.systemui.plugins.activityStarter
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
+import com.android.systemui.media.data.repository.FakeSpatializerRepository
 
-val Kosmos.mediaOutputActionsInteractor by
-    Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) }
+val Kosmos.spatializerRepository by Kosmos.Fixture { FakeSpatializerRepository() }
+val Kosmos.spatializerInteractor by Kosmos.Fixture { SpatializerInteractor(spatializerRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/data/repository/FakeSpatializerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/data/repository/FakeSpatializerRepository.kt
new file mode 100644
index 0000000..63e2d4b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/data/repository/FakeSpatializerRepository.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.data.repository
+
+import android.media.AudioDeviceAttributes
+import com.android.settingslib.media.data.repository.SpatializerRepository
+
+class FakeSpatializerRepository : SpatializerRepository {
+
+    var defaultSpatialAudioAvailable: Boolean = false
+    var defaultHeadTrackingAvailable: Boolean = false
+
+    private val spatialAudioAvailabilityByDevice: MutableMap<AudioDeviceAttributes, Boolean> =
+        mutableMapOf()
+    private val headTrackingAvailabilityByDevice: MutableMap<AudioDeviceAttributes, Boolean> =
+        mutableMapOf()
+    private val spatialAudioCompatibleDevices: MutableList<AudioDeviceAttributes> = mutableListOf()
+
+    private val headTrackingEnabledByDevice = mutableMapOf<AudioDeviceAttributes, Boolean>()
+
+    override suspend fun isHeadTrackingAvailableForDevice(
+        audioDeviceAttributes: AudioDeviceAttributes
+    ): Boolean =
+        headTrackingAvailabilityByDevice.getOrDefault(
+            audioDeviceAttributes,
+            defaultHeadTrackingAvailable
+        )
+
+    override suspend fun isSpatialAudioAvailableForDevice(
+        audioDeviceAttributes: AudioDeviceAttributes
+    ): Boolean =
+        spatialAudioAvailabilityByDevice.getOrDefault(
+            audioDeviceAttributes,
+            defaultSpatialAudioAvailable
+        )
+
+    override suspend fun getSpatialAudioCompatibleDevices(): Collection<AudioDeviceAttributes> =
+        spatialAudioCompatibleDevices
+
+    override suspend fun addSpatialAudioCompatibleDevice(
+        audioDeviceAttributes: AudioDeviceAttributes
+    ) {
+        spatialAudioCompatibleDevices.add(audioDeviceAttributes)
+    }
+
+    override suspend fun removeSpatialAudioCompatibleDevice(
+        audioDeviceAttributes: AudioDeviceAttributes
+    ) {
+        spatialAudioCompatibleDevices.remove(audioDeviceAttributes)
+    }
+
+    override suspend fun isHeadTrackingEnabled(
+        audioDeviceAttributes: AudioDeviceAttributes
+    ): Boolean = headTrackingEnabledByDevice.getOrDefault(audioDeviceAttributes, false)
+
+    override suspend fun setHeadTrackingEnabled(
+        audioDeviceAttributes: AudioDeviceAttributes,
+        isEnabled: Boolean
+    ) {
+        headTrackingEnabledByDevice[audioDeviceAttributes] = isEnabled
+    }
+
+    fun setIsSpatialAudioAvailable(
+        audioDeviceAttributes: AudioDeviceAttributes,
+        isAvailable: Boolean,
+    ) {
+        spatialAudioAvailabilityByDevice[audioDeviceAttributes] = isAvailable
+    }
+
+    fun setIsHeadTrackingAvailable(
+        audioDeviceAttributes: AudioDeviceAttributes,
+        isAvailable: Boolean,
+    ) {
+        headTrackingAvailabilityByDevice[audioDeviceAttributes] = isAvailable
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeActivityTaskManager.kt
similarity index 76%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeActivityTaskManager.kt
index 1c4870b..41d2d60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeActivityTaskManager.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.mediaprojection.taskswitcher.data.repository
+package com.android.systemui.mediaprojection.taskswitcher
 
 import android.app.ActivityManager.RunningTaskInfo
-import android.app.ActivityTaskManager
+import android.app.IActivityTaskManager
 import android.app.TaskStackListener
 import android.content.Intent
 import android.window.IWindowContainerToken
@@ -31,7 +31,7 @@
     private val runningTasks = mutableListOf<RunningTaskInfo>()
     private val taskTaskListeners = mutableListOf<TaskStackListener>()
 
-    val activityTaskManager = mock<ActivityTaskManager>()
+    val activityTaskManager = mock<IActivityTaskManager>()
 
     init {
         whenever(activityTaskManager.registerTaskStackListener(any())).thenAnswer {
@@ -42,10 +42,20 @@
             taskTaskListeners -= it.arguments[0] as TaskStackListener
             return@thenAnswer Unit
         }
-        whenever(activityTaskManager.getTasks(any())).thenAnswer {
+        whenever(activityTaskManager.getTasks(any(), any(), any(), any())).thenAnswer {
             val maxNumTasks = it.arguments[0] as Int
             return@thenAnswer runningTasks.take(maxNumTasks)
         }
+        whenever(activityTaskManager.startActivityFromRecents(any(), any())).thenAnswer {
+            val taskId = it.arguments[0] as Int
+            val runningTask = runningTasks.find { runningTask -> runningTask.taskId == taskId }
+            if (runningTask != null) {
+                moveTaskToForeground(runningTask)
+                return@thenAnswer 0
+            } else {
+                return@thenAnswer -1
+            }
+        }
     }
 
     fun moveTaskToForeground(task: RunningTaskInfo) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
similarity index 82%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
index 44c411f..2b6032c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.mediaprojection.taskswitcher.data.repository
+package com.android.systemui.mediaprojection.taskswitcher
 
 import android.media.projection.MediaProjectionInfo
 import android.media.projection.MediaProjectionManager
@@ -22,6 +22,8 @@
 import android.os.IBinder
 import android.os.UserHandle
 import android.view.ContentRecordingSession
+import android.window.WindowContainerToken
+import com.android.systemui.mediaprojection.MediaProjectionServiceHelper
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -29,6 +31,7 @@
 class FakeMediaProjectionManager {
 
     val mediaProjectionManager = mock<MediaProjectionManager>()
+    val helper = mock<MediaProjectionServiceHelper>()
 
     private val callbacks = mutableListOf<MediaProjectionManager.Callback>()
 
@@ -41,6 +44,11 @@
             callbacks -= it.arguments[0] as MediaProjectionManager.Callback
             return@thenAnswer Unit
         }
+        whenever(helper.updateTaskRecordingSession(any())).thenAnswer {
+            val token = it.arguments[0] as WindowContainerToken
+            dispatchOnSessionSet(session = createSingleTaskSession(token.asBinder()))
+            return@thenAnswer true
+        }
     }
 
     fun dispatchOnStart(info: MediaProjectionInfo = DEFAULT_INFO) {
@@ -61,6 +69,7 @@
     companion object {
         fun createDisplaySession(): ContentRecordingSession =
             ContentRecordingSession.createDisplaySession(/* displayToMirror = */ 123)
+
         fun createSingleTaskSession(token: IBinder = Binder()): ContentRecordingSession =
             ContentRecordingSession.createTaskSession(token)
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt
new file mode 100644
index 0000000..d344b75
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher
+
+import android.os.Handler
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+
+val Kosmos.fakeActivityTaskManager by Kosmos.Fixture { FakeActivityTaskManager() }
+
+val Kosmos.fakeMediaProjectionManager by Kosmos.Fixture { FakeMediaProjectionManager() }
+
+val Kosmos.activityTaskManagerTasksRepository by
+    Kosmos.Fixture {
+        ActivityTaskManagerTasksRepository(
+            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+            applicationScope = applicationCoroutineScope,
+            backgroundDispatcher = testDispatcher
+        )
+    }
+
+val Kosmos.mediaProjectionManagerRepository by
+    Kosmos.Fixture {
+        MediaProjectionManagerRepository(
+            mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+            handler = Handler.getMain(),
+            applicationScope = applicationCoroutineScope,
+            tasksRepository = activityTaskManagerTasksRepository,
+            backgroundDispatcher = testDispatcher,
+            mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
+        )
+    }
+
+val Kosmos.taskSwitcherInteractor by
+    Kosmos.Fixture {
+        TaskSwitchInteractor(mediaProjectionManagerRepository, activityTaskManagerTasksRepository)
+    }
+
+val Kosmos.taskSwitcherViewModel by
+    Kosmos.Fixture { TaskSwitcherNotificationViewModel(taskSwitcherInteractor, testDispatcher) }
+
+@OptIn(ExperimentalCoroutinesApi::class)
+fun taskSwitcherKosmos() = Kosmos().apply { testDispatcher = UnconfinedTestDispatcher() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
index 23d657d..1ce2610 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
@@ -16,16 +16,86 @@
 
 package com.android.systemui.qs
 
+import android.app.admin.devicePolicyManager
+import android.content.applicationContext
+import android.os.fakeExecutorHandler
+import android.os.looper
+import com.android.internal.logging.metricsLogger
+import com.android.internal.logging.uiEventLogger
 import com.android.internal.logging.uiEventLoggerFake
 import com.android.systemui.InstanceIdSequenceFake
+import com.android.systemui.animation.dialogTransitionAnimator
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.classifier.falsingManager
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.plugins.activityStarter
 import com.android.systemui.plugins.qs.QSFactory
+import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl
+import com.android.systemui.qs.footer.foregroundServicesRepository
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.tiles.di.NewQSTileFactory
+import com.android.systemui.security.data.repository.securityRepository
+import com.android.systemui.settings.userTracker
+import com.android.systemui.statusbar.policy.deviceProvisionedController
+import com.android.systemui.statusbar.policy.securityController
+import com.android.systemui.user.data.repository.userSwitcherRepository
+import com.android.systemui.user.domain.interactor.userSwitcherInteractor
+import com.android.systemui.util.mockito.mock
 
-val Kosmos.instanceIdSequenceFake: InstanceIdSequenceFake by
-    Kosmos.Fixture { InstanceIdSequenceFake(0) }
-val Kosmos.qsEventLogger: QsEventLoggerFake by
-    Kosmos.Fixture { QsEventLoggerFake(uiEventLoggerFake, instanceIdSequenceFake) }
+val Kosmos.instanceIdSequenceFake: InstanceIdSequenceFake by Fixture { InstanceIdSequenceFake(0) }
+val Kosmos.qsEventLogger: QsEventLoggerFake by Fixture {
+    QsEventLoggerFake(uiEventLoggerFake, instanceIdSequenceFake)
+}
 
-var Kosmos.newQSTileFactory by Kosmos.Fixture<NewQSTileFactory>()
-var Kosmos.qsTileFactory by Kosmos.Fixture<QSFactory>()
+var Kosmos.newQSTileFactory by Fixture<NewQSTileFactory>()
+var Kosmos.qsTileFactory by Fixture<QSFactory>()
+
+val Kosmos.fgsManagerController by Fixture { FakeFgsManagerController() }
+
+val Kosmos.footerActionsController by Fixture {
+    FooterActionsController(
+        fgsManagerController = fgsManagerController,
+    )
+}
+
+val Kosmos.qsSecurityFooterUtils by Fixture {
+    QSSecurityFooterUtils(
+        applicationContext,
+        devicePolicyManager,
+        userTracker,
+        fakeExecutorHandler,
+        activityStarter,
+        securityController,
+        looper,
+        dialogTransitionAnimator,
+    )
+}
+
+val Kosmos.footerActionsInteractor by Fixture {
+    FooterActionsInteractorImpl(
+        activityStarter = activityStarter,
+        metricsLogger = metricsLogger,
+        uiEventLogger = uiEventLogger,
+        deviceProvisionedController = deviceProvisionedController,
+        qsSecurityFooterUtils = qsSecurityFooterUtils,
+        fgsManagerController = fgsManagerController,
+        userSwitcherInteractor = userSwitcherInteractor,
+        securityRepository = securityRepository,
+        foregroundServicesRepository = foregroundServicesRepository,
+        userSwitcherRepository = userSwitcherRepository,
+        broadcastDispatcher = broadcastDispatcher,
+        bgDispatcher = testDispatcher,
+    )
+}
+
+val Kosmos.footerActionsViewModelFactory by Fixture {
+    FooterActionsViewModel.Factory(
+        context = applicationContext,
+        falsingManager = falsingManager,
+        footerActionsInteractor = footerActionsInteractor,
+        globalActionsDialogLiteProvider = { mock() },
+        showPowerButton = true,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/ForegroundServicesRepositoryKosmos.kt
similarity index 62%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/ForegroundServicesRepositoryKosmos.kt
index ad8ccb0..8f81b5e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/ForegroundServicesRepositoryKosmos.kt
@@ -14,12 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel
+package com.android.systemui.qs.footer
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.media.mediaOutputDialogFactory
-import com.android.systemui.plugins.activityStarter
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.qs.fgsManagerController
+import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepositoryImpl
 
-val Kosmos.mediaOutputActionsInteractor by
-    Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) }
+val Kosmos.foregroundServicesRepository by Fixture {
+    ForegroundServicesRepositoryImpl(
+        fgsManagerController = fgsManagerController,
+    )
+}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/battery/BatterySaverTileKosmos.kt
similarity index 65%
copy from packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/battery/BatterySaverTileKosmos.kt
index 8ad0a08..a2d1d93 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/battery/BatterySaverTileKosmos.kt
@@ -14,8 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel.component.mediaoutput
+package com.android.systemui.qs.tiles.impl.battery
 
-import dagger.Module
+import com.android.systemui.battery.BatterySaverModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
 
-@Module interface MediaOutputModule
+val Kosmos.qsBatterySaverTileConfig by
+    Kosmos.Fixture { BatterySaverModule.provideBatterySaverTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/internet/InternetTileKosmos.kt
similarity index 65%
copy from packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/internet/InternetTileKosmos.kt
index 8ad0a08..6772ba3 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/internet/InternetTileKosmos.kt
@@ -14,8 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel.component.mediaoutput
+package com.android.systemui.qs.tiles.impl.internet
 
-import dagger.Module
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.statusbar.connectivity.ConnectivityModule
 
-@Module interface MediaOutputModule
+val Kosmos.qsInternetTileConfig by
+    Kosmos.Fixture { ConnectivityModule.provideInternetTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/rotation/RotationLockTileKosmos.kt
similarity index 65%
copy from packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/rotation/RotationLockTileKosmos.kt
index 8ad0a08..ecf8ce5 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/rotation/RotationLockTileKosmos.kt
@@ -14,8 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel.component.mediaoutput
+package com.android.systemui.qs.tiles.impl.rotation
 
-import dagger.Module
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.rotationlock.RotationLockNewModule
 
-@Module interface MediaOutputModule
+val Kosmos.qsRotationLockTileConfig by
+    Kosmos.Fixture { RotationLockNewModule.provideRotationTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index 8fc419c..2cdf76d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -3,18 +3,18 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.scene.shared.model.SceneContainerConfig
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
 
 var Kosmos.sceneKeys by Fixture {
     listOf(
-        SceneKey.QuickSettings,
-        SceneKey.Shade,
-        SceneKey.Lockscreen,
-        SceneKey.Bouncer,
-        SceneKey.Gone,
-        SceneKey.Communal,
+        Scenes.QuickSettings,
+        Scenes.Shade,
+        Scenes.Lockscreen,
+        Scenes.Bouncer,
+        Scenes.Gone,
+        Scenes.Communal,
     )
 }
 
-val Kosmos.initialSceneKey by Fixture { SceneKey.Lockscreen }
+val Kosmos.initialSceneKey by Fixture { Scenes.Lockscreen }
 val Kosmos.sceneContainerConfig by Fixture { SceneContainerConfig(sceneKeys, initialSceneKey) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
index c208aad..59a01cb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.scene.shared.model
 
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionKey
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/security/data/repository/SecurityRepositoryKosmos.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/security/data/repository/SecurityRepositoryKosmos.kt
index ad8ccb0..6ac5bcb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/security/data/repository/SecurityRepositoryKosmos.kt
@@ -14,12 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel
+package com.android.systemui.security.data.repository
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.media.mediaOutputDialogFactory
-import com.android.systemui.plugins.activityStarter
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.statusbar.policy.securityController
 
-val Kosmos.mediaOutputActionsInteractor by
-    Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) }
+val Kosmos.securityRepository by Fixture {
+    SecurityRepositoryImpl(
+        securityController = securityController,
+        bgDispatcher = testDispatcher,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index 4aab822..728c67a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -18,11 +18,13 @@
 package com.android.systemui.shade.data.repository
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.shared.model.ShadeMode
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 
 /** Fake implementation of [ShadeRepository] */
 @SysUISingleton
@@ -59,6 +61,9 @@
 
     override val legacyLockscreenShadeTracking = MutableStateFlow(false)
 
+    private val _shadeMode = MutableStateFlow<ShadeMode>(ShadeMode.Single)
+    override val shadeMode: StateFlow<ShadeMode> = _shadeMode.asStateFlow()
+
     @Deprecated("Use ShadeInteractor instead")
     override fun setLegacyIsQsExpanded(legacyIsQsExpanded: Boolean) {
         _legacyIsQsExpanded.value = legacyIsQsExpanded
@@ -131,6 +136,10 @@
     override fun setLegacyShadeExpansion(expandedFraction: Float) {
         _legacyShadeExpansion.value = expandedFraction
     }
+
+    override fun setShadeMode(shadeMode: ShadeMode) {
+        _shadeMode.value = shadeMode
+    }
 }
 
 @Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt
index ad8ccb0..2a4dd3a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt
@@ -14,12 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel
+package com.android.systemui.shade.domain.interactor
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.media.mediaOutputDialogFactory
-import com.android.systemui.plugins.activityStarter
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 
-val Kosmos.mediaOutputActionsInteractor by
-    Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) }
+val Kosmos.panelExpansionInteractor by Fixture { panelExpansionInteractorImpl }
+val Kosmos.panelExpansionInteractorImpl by Fixture {
+    PanelExpansionInteractorImpl(
+        sceneInteractor = sceneInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
index 2bd76be..07e2d6b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
@@ -47,6 +47,7 @@
             scope = applicationCoroutineScope,
             sceneInteractor = sceneInteractor,
             sharedNotificationContainerInteractor = sharedNotificationContainerInteractor,
+            shadeRepository = shadeRepository,
         )
     }
 val Kosmos.shadeInteractorLegacyImpl by
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt
new file mode 100644
index 0000000..b99fdb9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.startable
+
+import android.content.applicationContext
+import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.statusbar.policy.splitShadeStateController
+
+val Kosmos.shadeStartable by Fixture {
+    ShadeStartable(
+        applicationScope = applicationCoroutineScope,
+        applicationContext = applicationContext,
+        configurationRepository = configurationRepository,
+        shadeRepository = shadeRepository,
+        controller = splitShadeStateController,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt
new file mode 100644
index 0000000..d793740
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+
+val Kosmos.keyguardOcclusionInteractor by
+    Kosmos.Fixture {
+        KeyguardOcclusionInteractor(
+            scope = testScope.backgroundScope,
+            repository = keyguardOcclusionRepository,
+            powerInteractor = powerInteractor,
+            transitionInteractor = keyguardTransitionInteractor,
+            keyguardInteractor = keyguardInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt
new file mode 100644
index 0000000..9e34fe8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.domain.interactor
+
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.power.domain.interactor.powerInteractor
+
+val Kosmos.statusBarKeyguardViewManagerInteractor by
+    Kosmos.Fixture {
+        StatusBarKeyguardViewManagerInteractor(
+            keyguardTransitionInteractor = this.keyguardTransitionInteractor,
+            keyguardOcclusionInteractor = this.keyguardOcclusionInteractor,
+            powerInteractor = this.powerInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelKosmos.kt
new file mode 100644
index 0000000..7ffa262
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackInteractor
+
+val Kosmos.notificationViewFlipperViewModel by Fixture {
+    NotificationViewFlipperViewModel(
+        dumpManager = dumpManager,
+        stackInteractor = notificationStackInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractorKosmos.kt
new file mode 100644
index 0000000..db6ba62
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractorKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.domain.interactor
+
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.power.domain.interactor.powerInteractor
+
+val Kosmos.notificationStackInteractor by Fixture {
+    NotificationStackInteractor(
+        keyguardInteractor = keyguardInteractor,
+        powerInteractor = powerInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
index 25e3eac..f1767eb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
@@ -16,16 +16,15 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
-import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
 import com.android.systemui.statusbar.notification.footer.ui.viewmodel.footerViewModel
 import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.notificationShelfViewModel
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackInteractor
 import com.android.systemui.statusbar.policy.domain.interactor.userSetupInteractor
 import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
 import java.util.Optional
@@ -37,8 +36,7 @@
         Optional.of(footerViewModel),
         Optional.of(notificationListLoggerViewModel),
         activeNotificationsInteractor,
-        keyguardInteractor,
-        powerInteractor,
+        notificationStackInteractor,
         remoteInputInteractor,
         seenNotificationsInteractor,
         shadeInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt
index d79633a..bada2a6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
+import com.android.systemui.dump.dumpManager
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -24,6 +25,7 @@
 
 val Kosmos.notificationStackAppearanceViewModel by Fixture {
     NotificationStackAppearanceViewModel(
+        dumpManager = dumpManager,
         stackAppearanceInteractor = notificationStackAppearanceInteractor,
         shadeInteractor = shadeInteractor,
         sceneInteractor = sceneInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index 832344d..de0cc65 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -16,13 +16,15 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
-import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.dump.dumpManager
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.aodToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.aodToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.dozingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.dozingToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.goneToAodTransitionViewModel
@@ -48,14 +50,16 @@
 val Kosmos.sharedNotificationContainerViewModel by Fixture {
     SharedNotificationContainerViewModel(
         interactor = sharedNotificationContainerInteractor,
+        dumpManager = dumpManager,
         applicationScope = applicationCoroutineScope,
         keyguardInteractor = keyguardInteractor,
         keyguardTransitionInteractor = keyguardTransitionInteractor,
         shadeInteractor = shadeInteractor,
-        communalInteractor = communalInteractor,
         alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
         aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
+        aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
         dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
+        dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
         dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
         goneToAodTransitionViewModel = goneToAodTransitionViewModel,
         goneToDozingTransitionViewModel = goneToDozingTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
index 41c11ad6..7a86c4f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.shade.domain.interactor.shadeAnimationInteractor
 import com.android.systemui.shade.shadeController
 import com.android.systemui.shade.shadeViewController
+import com.android.systemui.statusbar.commandQueue
 import com.android.systemui.statusbar.notification.collection.provider.launchFullScreenIntentProvider
 import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider
 import com.android.systemui.statusbar.notification.notificationTransitionAnimatorControllerProvider
@@ -59,6 +60,7 @@
             notificationVisibilityProvider,
             headsUpManager,
             activityStarter,
+            commandQueue,
             notificationClickNotifier,
             statusBarKeyguardViewManager,
             keyguardManager,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index c010327..11acf1c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -60,6 +60,8 @@
 
     override val isInService = MutableStateFlow(true)
 
+    override val isEmergencyOnly = MutableStateFlow(true)
+
     override val isNonTerrestrial = MutableStateFlow(false)
 
     private val _isDataEnabled = MutableStateFlow(true)
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/DevicePostureControllerKosmos.kt
similarity index 72%
copy from packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/DevicePostureControllerKosmos.kt
index 8ad0a08..89eaf15 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/DevicePostureControllerKosmos.kt
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel.component.mediaoutput
+package com.android.systemui.statusbar.policy
 
-import dagger.Module
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
 
-@Module interface MediaOutputModule
+val Kosmos.devicePostureController by Kosmos.Fixture { mock<DevicePostureController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SecurityControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SecurityControllerKosmos.kt
new file mode 100644
index 0000000..67a5cc9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SecurityControllerKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.content.applicationContext
+import android.os.fakeExecutorHandler
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.settings.userTracker
+
+val Kosmos.securityController by Fixture {
+    SecurityControllerImpl(
+        applicationContext,
+        userTracker,
+        fakeExecutorHandler,
+        broadcastDispatcher,
+        fakeExecutor,
+        fakeExecutor,
+        dumpManager,
+    )
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/FakeUserSwitcherRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserSwitcherRepository.kt
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/user/data/repository/FakeUserSwitcherRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserSwitcherRepository.kt
index 758fe93a..90fb60b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/FakeUserSwitcherRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserSwitcherRepository.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/UserSwitcherRepositoryKosmos.kt
similarity index 73%
copy from packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/UserSwitcherRepositoryKosmos.kt
index 8ad0a08..1519f30 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/UserSwitcherRepositoryKosmos.kt
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel.component.mediaoutput
+package com.android.systemui.user.data.repository
 
-import dagger.Module
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
 
-@Module interface MediaOutputModule
+val Kosmos.userSwitcherRepository by Fixture { FakeUserSwitcherRepository() }
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt
similarity index 73%
copy from packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt
index 8ad0a08..df6fc41 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel.component.mediaoutput
+package com.android.systemui.util.settings
 
-import dagger.Module
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
 
-@Module interface MediaOutputModule
+val Kosmos.fakeGlobalSettings: FakeGlobalSettings by Fixture { FakeGlobalSettings() }
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 5ae033c..d798b3b 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
@@ -30,6 +30,8 @@
     private boolean mIsAodPowerSave = false;
     private boolean mWirelessCharging;
     private boolean mPowerSaveMode = false;
+    private boolean mIsPluggedIn = false;
+    private boolean mIsExtremePowerSave = false;
 
     private final List<BatteryStateChangeCallback> mCallbacks = new ArrayList<>();
 
@@ -64,8 +66,35 @@
     }
 
     @Override
+    public boolean isExtremeSaverOn() {
+        return mIsExtremePowerSave;
+    }
+
+    /**
+     * Note: this does not affect the regular power saver. Triggers all callbacks, only on change.
+     */
+    public void setExtremeSaverOn(Boolean extremePowerSave) {
+        if (extremePowerSave == mIsExtremePowerSave) return;
+
+        mIsExtremePowerSave = extremePowerSave;
+        for (BatteryStateChangeCallback callback: mCallbacks) {
+            callback.onExtremeBatterySaverChanged(extremePowerSave);
+        }
+    }
+
+    @Override
     public boolean isPluggedIn() {
-        return false;
+        return mIsPluggedIn;
+    }
+
+    /**
+     * Notifies all registered callbacks
+     */
+    public void setPluggedIn(boolean pluggedIn) {
+        mIsPluggedIn = pluggedIn;
+        for (BatteryStateChangeCallback cb : mCallbacks) {
+            cb.onBatteryLevelChanged(0, pluggedIn, false);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
index be57658..4aa85a7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
@@ -19,13 +19,29 @@
 import com.android.systemui.statusbar.policy.RotationLockController;
 import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public class FakeRotationLockController extends BaseLeakChecker<RotationLockControllerCallback>
         implements RotationLockController {
+    private boolean mIsLocked = false;
+    private final List<RotationLockControllerCallback> mCallbacks = new ArrayList<>();
     public FakeRotationLockController(LeakCheck test) {
         super(test, "rotation");
     }
 
     @Override
+    public void addCallback(RotationLockControllerCallback listener) {
+        mCallbacks.add(listener);
+        listener.onRotationLockStateChanged(mIsLocked, isRotationLockAffordanceVisible());
+    }
+
+    @Override
+    public void removeCallback(RotationLockControllerCallback listener) {
+        mCallbacks.remove(listener);
+    }
+
+    @Override
     public void setListening(boolean listening) {
 
     }
@@ -42,12 +58,15 @@
 
     @Override
     public boolean isRotationLocked() {
-        return false;
+        return mIsLocked;
     }
 
     @Override
     public void setRotationLocked(boolean locked, String caller) {
-
+        mIsLocked = locked;
+        for (RotationLockControllerCallback callback : mCallbacks) {
+            callback.onRotationLockStateChanged(locked, isRotationLockAffordanceVisible());
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
index 3f20df3..3938f77 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
@@ -24,8 +24,7 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testCase
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.mediaOutputDialogFactory
-import com.android.systemui.plugins.activityStarter
+import com.android.systemui.media.mediaOutputDialogManager
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -43,7 +42,7 @@
     Kosmos.Fixture { FakeLocalMediaRepositoryFactory { localMediaRepository } }
 
 val Kosmos.mediaOutputActionsInteractor by
-    Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) }
+    Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogManager) }
 val Kosmos.mediaControllerRepository by Kosmos.Fixture { FakeMediaControllerRepository() }
 val Kosmos.mediaOutputInteractor by
     Kosmos.Fixture {
diff --git a/packages/Tethering/OWNERS b/packages/Tethering/OWNERS
deleted file mode 100644
index aa87958..0000000
--- a/packages/Tethering/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/net/OWNERS
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index f31eb44..23e269a 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -39,7 +39,9 @@
 import android.content.SharedPreferences;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
+import android.graphics.Point;
 import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -49,16 +51,22 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.Xml;
+import android.view.Display;
+import android.view.DisplayInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
+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.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -102,6 +110,9 @@
     @VisibleForTesting
     static final String WALLPAPER_INFO_STAGE = "wallpaper-info-stage";
 
+    @VisibleForTesting
+    static final String WALLPAPER_BACKUP_DEVICE_INFO_STAGE = "wallpaper-backup-device-info-stage";
+
     static final String EMPTY_SENTINEL = "empty";
     static final String QUOTA_SENTINEL = "quota";
 
@@ -110,6 +121,11 @@
     static final String SYSTEM_GENERATION = "system_gen";
     static final String LOCK_GENERATION = "lock_gen";
 
+    /**
+     * An approximate area threshold to compare device dimension similarity
+     */
+    static final int AREA_THRESHOLD = 50; // TODO (b/327637867): determine appropriate threshold
+
     // If this file exists, it means we exceeded our quota last time
     private File mQuotaFile;
     private boolean mQuotaExceeded;
@@ -121,6 +137,8 @@
     private boolean mSystemHasLiveComponent;
     private boolean mLockHasLiveComponent;
 
+    private DisplayManager mDisplayManager;
+
     @Override
     public void onCreate() {
         if (DEBUG) {
@@ -137,6 +155,8 @@
 
         mBackupManager = new BackupManager(getBaseContext());
         mEventLogger = new WallpaperEventLogger(mBackupManager, /* wallpaperAgent */ this);
+
+        mDisplayManager = getSystemService(DisplayManager.class);
     }
 
     @Override
@@ -175,9 +195,11 @@
             mSystemHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM) != null;
             mLockHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_LOCK) != null;
 
+            // performing backup of each file based on order of importance
             backupWallpaperInfoFile(/* sysOrLockChanged= */ sysChanged || lockChanged, data);
             backupSystemWallpaperFile(sharedPrefs, sysChanged, sysGeneration, data);
             backupLockWallpaperFileIfItExists(sharedPrefs, lockChanged, lockGeneration, data);
+            backupDeviceInfoFile(data);
         } catch (Exception e) {
             Slog.e(TAG, "Unable to back up wallpaper", e);
             mEventLogger.onBackupException(e);
@@ -191,6 +213,54 @@
         }
     }
 
+    /**
+     * This method backs up the device dimension information. The device data will always get
+     * overwritten when triggering a backup
+     */
+    private void backupDeviceInfoFile(FullBackupDataOutput data)
+            throws IOException {
+        final File deviceInfoStage = new File(getFilesDir(), WALLPAPER_BACKUP_DEVICE_INFO_STAGE);
+
+        // save the dimensions of the device with xml formatting
+        Point dimensions = getScreenDimensions();
+        Display smallerDisplay = getSmallerDisplayIfExists();
+        Point secondaryDimensions = smallerDisplay != null ? getRealSize(smallerDisplay) :
+                new Point(0, 0);
+
+        deviceInfoStage.createNewFile();
+        FileOutputStream fstream = new FileOutputStream(deviceInfoStage, false);
+        TypedXmlSerializer out = Xml.resolveSerializer(fstream);
+        out.startDocument(null, true);
+        out.startTag(null, "dimensions");
+
+        out.startTag(null, "width");
+        out.text(String.valueOf(dimensions.x));
+        out.endTag(null, "width");
+
+        out.startTag(null, "height");
+        out.text(String.valueOf(dimensions.y));
+        out.endTag(null, "height");
+
+        if (smallerDisplay != null) {
+            out.startTag(null, "secondarywidth");
+            out.text(String.valueOf(secondaryDimensions.x));
+            out.endTag(null, "secondarywidth");
+
+            out.startTag(null, "secondaryheight");
+            out.text(String.valueOf(secondaryDimensions.y));
+            out.endTag(null, "secondaryheight");
+        }
+
+        out.endTag(null, "dimensions");
+        out.endDocument();
+        fstream.flush();
+        FileUtils.sync(fstream);
+        fstream.close();
+
+        if (DEBUG) Slog.v(TAG, "Storing device dimension data");
+        backupFile(deviceInfoStage, data);
+    }
+
     private void backupWallpaperInfoFile(boolean sysOrLockChanged, FullBackupDataOutput data)
             throws IOException {
         final ParcelFileDescriptor wallpaperInfoFd = mWallpaperManager.getWallpaperInfoFile();
@@ -364,9 +434,22 @@
         final File infoStage = new File(filesDir, WALLPAPER_INFO_STAGE);
         final File imageStage = new File(filesDir, SYSTEM_WALLPAPER_STAGE);
         final File lockImageStage = new File(filesDir, LOCK_WALLPAPER_STAGE);
+        final File deviceDimensionsStage = new File(filesDir, WALLPAPER_BACKUP_DEVICE_INFO_STAGE);
         boolean lockImageStageExists = lockImageStage.exists();
 
         try {
+            // Parse the device dimensions of the source device and compare with target to
+            // to identify whether we need to skip the remainder of the restore process
+            Pair<Point, Point> sourceDeviceDimensions = parseDeviceDimensions(
+                    deviceDimensionsStage);
+
+            Point targetDeviceDimensions = getScreenDimensions();
+            if (sourceDeviceDimensions != null && targetDeviceDimensions != null
+                    && isSourceDeviceSignificantlySmallerThanTarget(sourceDeviceDimensions.first,
+                    targetDeviceDimensions)) {
+                Slog.d(TAG, "The source device is significantly smaller than target");
+            }
+
             // First parse the live component name so that we know for logging if we care about
             // logging errors with the image restore.
             ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
@@ -400,6 +483,7 @@
             infoStage.delete();
             imageStage.delete();
             lockImageStage.delete();
+            deviceDimensionsStage.delete();
 
             SharedPreferences prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
             prefs.edit()
@@ -409,6 +493,66 @@
         }
     }
 
+    /**
+     * This method parses the given file for the backed up device dimensions
+     *
+     * @param deviceDimensions the file which holds the device dimensions
+     * @return the backed up device dimensions
+     */
+    private Pair<Point, Point> parseDeviceDimensions(File deviceDimensions) {
+        int width = 0, height = 0, secondaryHeight = 0, secondaryWidth = 0;
+        try {
+            TypedXmlPullParser parser = Xml.resolvePullParser(
+                    new FileInputStream(deviceDimensions));
+
+            while (parser.next() != XmlPullParser.END_TAG) {
+                if (parser.getEventType() != XmlPullParser.START_TAG) {
+                    continue;
+                }
+
+                String name = parser.getName();
+
+                switch (name) {
+                    case "width":
+                        String widthText = readText(parser);
+                        width = Integer.valueOf(widthText);
+                        break;
+
+                    case "height":
+                        String textHeight = readText(parser);
+                        height = Integer.valueOf(textHeight);
+                        break;
+
+                    case "secondarywidth":
+                        String secondaryWidthText = readText(parser);
+                        secondaryWidth = Integer.valueOf(secondaryWidthText);
+                        break;
+
+                    case "secondaryheight":
+                        String secondaryHeightText = readText(parser);
+                        secondaryHeight = Integer.valueOf(secondaryHeightText);
+                        break;
+                    default:
+                        break;
+                }
+            }
+            return new Pair<>(new Point(width, height), new Point(secondaryWidth, secondaryHeight));
+
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    private static String readText(TypedXmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        String result = "";
+        if (parser.next() == XmlPullParser.TEXT) {
+            result = parser.getText();
+            parser.nextTag();
+        }
+        return result;
+    }
+
     @VisibleForTesting
     void updateWallpaperComponent(ComponentName wpService, int which)
             throws IOException {
@@ -691,6 +835,94 @@
         };
     }
 
+    /**
+     * This method retrieves the dimensions of the largest display of the device
+     *
+     * @return a @{Point} object that contains the dimensions of the largest display on the device
+     */
+    private Point getScreenDimensions() {
+        Point largetDimensions = null;
+        int maxArea = 0;
+
+        for (Display display : getInternalDisplays()) {
+            Point displaySize = getRealSize(display);
+
+            int width = displaySize.x;
+            int height = displaySize.y;
+            int area = width * height;
+
+            if (area > maxArea) {
+                maxArea = area;
+                largetDimensions = displaySize;
+            }
+        }
+
+        return largetDimensions;
+    }
+
+    private Point getRealSize(Display display) {
+        DisplayInfo displayInfo = new DisplayInfo();
+        display.getDisplayInfo(displayInfo);
+        return new Point(displayInfo.logicalWidth, displayInfo.logicalHeight);
+    }
+
+    /**
+     * This method returns the smaller display on a multi-display device
+     *
+     * @return Display that corresponds to the smaller display on a device or null if ther is only
+     * one Display on a device
+     */
+    private Display getSmallerDisplayIfExists() {
+        List<Display> internalDisplays = getInternalDisplays();
+        Point largestDisplaySize = getScreenDimensions();
+
+        // Find the first non-matching internal display
+        for (Display display : internalDisplays) {
+            Point displaySize = getRealSize(display);
+            if (displaySize.x != largestDisplaySize.x || displaySize.y != largestDisplaySize.y) {
+                return display;
+            }
+        }
+
+        // If no smaller display found, return null, as there is only a single display
+        return null;
+    }
+
+    /**
+     * This method retrieves the collection of Display objects available in the device.
+     * i.e. non-external displays are ignored
+     *
+     * @return list of displays corresponding to each display in the device
+     */
+    private List<Display> getInternalDisplays() {
+        Display[] allDisplays = mDisplayManager.getDisplays(
+                DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
+
+        List<Display> internalDisplays = new ArrayList<>();
+        for (Display display : allDisplays) {
+            if (display.getType() == Display.TYPE_INTERNAL) {
+                internalDisplays.add(display);
+            }
+        }
+        return internalDisplays;
+    }
+
+    /**
+     * This method compares the source and target dimensions, and returns true if there is a
+     * significant difference in area between them and the source dimensions are smaller than the
+     * target dimensions.
+     *
+     * @param sourceDimensions is the dimensions of the source device
+     * @param targetDimensions is the dimensions of the target device
+     */
+    @VisibleForTesting
+    boolean isSourceDeviceSignificantlySmallerThanTarget(Point sourceDimensions,
+            Point targetDimensions) {
+        int rawAreaDelta = (targetDimensions.x * targetDimensions.y)
+                - (sourceDimensions.x * sourceDimensions.y);
+        return rawAreaDelta > AREA_THRESHOLD;
+    }
+
     @VisibleForTesting
     boolean isDeviceInRestore() {
         try {
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index 3ecdf3f..ec9223c 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -59,6 +59,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
@@ -840,6 +841,26 @@
         testParseCropHints(testMap);
     }
 
+    @Test
+    public void test_sourceDimensionsAreLargerThanTarget() {
+        // source device is larger than target, expecting to get false
+        Point sourceDimensions = new Point(2208, 1840);
+        Point targetDimensions = new Point(1080, 2092);
+        boolean isSourceSmaller = mWallpaperBackupAgent
+                .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions);
+        assertThat(isSourceSmaller).isEqualTo(false);
+    }
+
+    @Test
+    public void test_sourceDimensionsMuchSmallerThanTarget() {
+        // source device is smaller than target, expecting to get true
+        Point sourceDimensions = new Point(1080, 2092);
+        Point targetDimensions = new Point(2208, 1840);
+        boolean isSourceSmaller = mWallpaperBackupAgent
+                .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions);
+        assertThat(isSourceSmaller).isEqualTo(true);
+    }
+
     private void testParseCropHints(Map<Integer, Rect> testMap) throws Exception {
         assumeTrue(multiCrop());
         mockRestoredStaticWallpaperFile(testMap);
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index e0fe88a..3239175 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -59,6 +59,8 @@
 import android.hardware.camera2.extension.SizeList;
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
+import android.hardware.camera2.params.ColorSpaceProfiles;
+import android.hardware.camera2.params.DynamicRangeProfiles;
 import android.hardware.camera2.utils.SurfaceUtils;
 import android.media.Image;
 import android.media.ImageReader;
@@ -1228,7 +1230,6 @@
 
             return null;
         }
-
     }
 
     private class CaptureCallbackStub implements SessionProcessorImpl.CaptureCallback {
@@ -1566,6 +1567,10 @@
         private String mCameraId = null;
         private IBinder mToken;
 
+        OutputSurfaceImplStub mOutputPreviewSurfaceImpl;
+        OutputSurfaceImplStub mOutputImageCaptureSurfaceImpl;
+        OutputSurfaceImplStub mOutputPostviewSurfaceImpl;
+
         public SessionProcessorImplStub(SessionProcessorImpl sessionProcessor) {
             mSessionProcessor = sessionProcessor;
         }
@@ -1574,21 +1579,20 @@
         public CameraSessionConfig initSession(IBinder token, String cameraId,
                 Map<String, CameraMetadataNative> charsMapNative, OutputSurface previewSurface,
                 OutputSurface imageCaptureSurface, OutputSurface postviewSurface) {
-            OutputSurfaceImplStub outputPreviewSurfaceImpl =
-                    new OutputSurfaceImplStub(previewSurface);
-            OutputSurfaceImplStub outputImageCaptureSurfaceImpl =
-                    new OutputSurfaceImplStub(imageCaptureSurface);
-            OutputSurfaceImplStub outputPostviewSurfaceImpl =
-                    new OutputSurfaceImplStub(postviewSurface);
+            mOutputPreviewSurfaceImpl = new OutputSurfaceImplStub(previewSurface);
+            mOutputImageCaptureSurfaceImpl = new OutputSurfaceImplStub(imageCaptureSurface);
+            mOutputPostviewSurfaceImpl = new OutputSurfaceImplStub(postviewSurface);
 
             Camera2SessionConfigImpl sessionConfig;
 
             if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+                int outputsColorSpace = getColorSpaceFromOutputSurfaces(previewSurface,
+                        imageCaptureSurface, postviewSurface);
                 OutputSurfaceConfigurationImplStub outputSurfaceConfigs =
-                        new OutputSurfaceConfigurationImplStub(outputPreviewSurfaceImpl,
+                        new OutputSurfaceConfigurationImplStub(mOutputPreviewSurfaceImpl,
                         // Image Analysis Output is currently only supported in CameraX
-                        outputImageCaptureSurfaceImpl, null /*imageAnalysisSurfaceConfig*/,
-                        outputPostviewSurfaceImpl);
+                        mOutputImageCaptureSurfaceImpl, null /*imageAnalysisSurfaceConfig*/,
+                        mOutputPostviewSurfaceImpl, outputsColorSpace);
 
                 sessionConfig = mSessionProcessor.initSession(cameraId,
                         getCharacteristicsMap(charsMapNative),
@@ -1596,8 +1600,8 @@
             } else {
                 sessionConfig = mSessionProcessor.initSession(cameraId,
                         getCharacteristicsMap(charsMapNative),
-                        getApplicationContext(), outputPreviewSurfaceImpl,
-                        outputImageCaptureSurfaceImpl, null /*imageAnalysisSurfaceConfig*/);
+                        getApplicationContext(), mOutputPreviewSurfaceImpl,
+                        mOutputImageCaptureSurfaceImpl, null /*imageAnalysisSurfaceConfig*/);
             }
 
             List<Camera2OutputConfigImpl> outputConfigs = sessionConfig.getOutputConfigs();
@@ -1615,6 +1619,11 @@
                 }
                 ret.outputConfigs.add(entry);
             }
+            if (Flags.extension10Bit() && EFV_SUPPORTED) {
+                ret.colorSpace = sessionConfig.getColorSpace();
+            } else {
+                ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+            }
             ret.sessionTemplateId = sessionConfig.getSessionTemplateId();
             ret.sessionType = -1;
             if (LATENCY_IMPROVEMENTS_SUPPORTED) {
@@ -1632,6 +1641,18 @@
         public void deInitSession(IBinder token) {
             CameraExtensionsProxyService.unregisterDeathRecipient(mToken, this);
             mSessionProcessor.deInitSession();
+
+            if (Flags.surfaceLeakFix()) {
+                if (mOutputImageCaptureSurfaceImpl.mSurface != null) {
+                    mOutputImageCaptureSurfaceImpl.mSurface.release();
+                }
+                if (mOutputPreviewSurfaceImpl.mSurface != null) {
+                    mOutputPreviewSurfaceImpl.mSurface.release();
+                }
+                if (mOutputPostviewSurfaceImpl.mSurface != null) {
+                    mOutputPostviewSurfaceImpl.mSurface.release();
+                }
+            }
         }
 
         @Override
@@ -1707,6 +1728,24 @@
         public void binderDied() {
             mSessionProcessor.deInitSession();
         }
+
+        // Get the color space of the output configurations. All of the OutputSurfaces
+        // can be assumed to have the same color space so return the color space
+        // of any non-null OutputSurface
+        private int getColorSpaceFromOutputSurfaces(OutputSurface previewSurface,
+                OutputSurface imageCaptureSurface, OutputSurface postviewSurface) {
+            int colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+
+            if (previewSurface.surface != null) {
+                colorSpace = previewSurface.colorSpace;
+            } else if (imageCaptureSurface.surface != null) {
+                colorSpace = imageCaptureSurface.colorSpace;
+            } else if (postviewSurface.surface != null) {
+                colorSpace = postviewSurface.colorSpace;
+            }
+
+            return colorSpace;
+        }
     }
 
     private class OutputSurfaceConfigurationImplStub implements OutputSurfaceConfigurationImpl {
@@ -1714,6 +1753,17 @@
         private OutputSurfaceImpl mOutputImageCaptureSurfaceImpl;
         private OutputSurfaceImpl mOutputImageAnalysisSurfaceImpl;
         private OutputSurfaceImpl mOutputPostviewSurfaceImpl;
+        private int mColorSpace;
+
+        public OutputSurfaceConfigurationImplStub(OutputSurfaceImpl previewOutput,
+                OutputSurfaceImpl imageCaptureOutput, OutputSurfaceImpl imageAnalysisOutput,
+                OutputSurfaceImpl postviewOutput, int colorSpace) {
+            mOutputPreviewSurfaceImpl = previewOutput;
+            mOutputImageCaptureSurfaceImpl = imageCaptureOutput;
+            mOutputImageAnalysisSurfaceImpl = imageAnalysisOutput;
+            mOutputPostviewSurfaceImpl = postviewOutput;
+            mColorSpace = colorSpace;
+        }
 
         public OutputSurfaceConfigurationImplStub(OutputSurfaceImpl previewOutput,
                 OutputSurfaceImpl imageCaptureOutput, OutputSurfaceImpl imageAnalysisOutput,
@@ -1722,6 +1772,7 @@
             mOutputImageCaptureSurfaceImpl = imageCaptureOutput;
             mOutputImageAnalysisSurfaceImpl = imageAnalysisOutput;
             mOutputPostviewSurfaceImpl = postviewOutput;
+            mColorSpace = ColorSpaceProfiles.UNSPECIFIED;
         }
 
         @Override
@@ -1743,6 +1794,11 @@
         public OutputSurfaceImpl getPostviewOutputSurface() {
             return mOutputPostviewSurfaceImpl;
         }
+
+        @Override
+        public int getColorSpace() {
+            return mColorSpace;
+        }
     }
 
     private class OutputSurfaceImplStub implements OutputSurfaceImpl {
@@ -1751,11 +1807,10 @@
         private final int mImageFormat;
         private final int mDataspace;
         private final long mUsage;
+        private final long mDynamicRangeProfile;
 
         public OutputSurfaceImplStub(OutputSurface outputSurface) {
             mSurface = outputSurface.surface;
-            mSize = new Size(outputSurface.size.width, outputSurface.size.height);
-            mImageFormat = outputSurface.imageFormat;
             if (mSurface != null) {
                 mDataspace = SurfaceUtils.getSurfaceDataspace(mSurface);
                 mUsage = SurfaceUtils.getSurfaceUsage(mSurface);
@@ -1763,6 +1818,9 @@
                 mDataspace = -1;
                 mUsage = 0;
             }
+            mDynamicRangeProfile = outputSurface.dynamicRangeProfile;
+            mSize = new Size(outputSurface.size.width, outputSurface.size.height);
+            mImageFormat = outputSurface.imageFormat;
         }
 
         @Override
@@ -1789,6 +1847,12 @@
         public long getUsage() {
             return mUsage;
         }
+
+        @Override
+        public long getDynamicRangeProfile() {
+            return mDynamicRangeProfile;
+        }
+
     }
 
     private class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub implements
@@ -2518,6 +2582,11 @@
 
     private static CameraOutputConfig getCameraOutputConfig(Camera2OutputConfigImpl output) {
         CameraOutputConfig ret = new CameraOutputConfig();
+        if (Flags.extension10Bit() && EFV_SUPPORTED) {
+            ret.dynamicRangeProfile = output.getDynamicRangeProfile();
+        } else {
+            ret.dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+        }
         ret.outputId = new OutputConfigId();
         ret.outputId.id = output.getId();
         ret.physicalCameraId = output.getPhysicalCameraId();
diff --git a/proto/src/criticalevents/critical_event_log.proto b/proto/src/criticalevents/critical_event_log.proto
index cffcd09..71d291a 100644
--- a/proto/src/criticalevents/critical_event_log.proto
+++ b/proto/src/criticalevents/critical_event_log.proto
@@ -61,6 +61,12 @@
     NativeCrash native_crash = 6;
     SystemServerStarted system_server_started = 7;
     InstallPackages install_packages = 8;
+    ExcessiveBinderCalls excessive_binder_calls = 9;
+  }
+
+  message ExcessiveBinderCalls {
+    // The uid sending many calls.
+    optional int32 uid = 1;
   }
 
   message InstallPackages {}
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 41a4a1a..178102e 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -77,6 +77,7 @@
     libs: [
         "android.test.mock",
         "framework-minus-apex.ravenwood",
+        "ravenwood-framework",
         "services.core.ravenwood",
         "junit",
     ],
@@ -85,6 +86,21 @@
     jarjar_rules: ":ravenwood-services-jarjar-rules",
 }
 
+// Separated out from ravenwood-junit-impl since it needs to compile
+// against `module_current`
+java_library {
+    name: "ravenwood-junit-impl-flag",
+    srcs: [
+        "junit-flag-src/**/*.java",
+    ],
+    sdk_version: "module_current",
+    libs: [
+        "junit",
+        "flag-junit",
+    ],
+    visibility: ["//visibility:public"],
+}
+
 // Carefully compiles against only test_current to support tests that
 // want to verify they're unbundled.  The "impl" library above is what
 // ships inside the Ravenwood environment to actually drive any API
@@ -94,14 +110,31 @@
     srcs: [
         "junit-src/**/*.java",
         "junit-stub-src/**/*.java",
+        "junit-flag-src/**/*.java",
     ],
     sdk_version: "test_current",
     libs: [
         "junit",
+        "flag-junit",
     ],
     visibility: ["//visibility:public"],
 }
 
+// Library used to publish a handful of `android.ravenwood` APIs into
+// the Ravenwood BCP; we don't want to publish these APIs into the BCP
+// on physical devices, which is why this is a separate library
+java_library {
+    name: "ravenwood-framework",
+    srcs: [
+        "framework-src/**/*.java",
+    ],
+    libs: [
+        "framework-minus-apex.ravenwood",
+    ],
+    sdk_version: "core_current",
+    visibility: ["//visibility:public"],
+}
+
 java_host_for_device {
     name: "androidx.test.monitor-for-device",
     libs: [
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index 6b67364..371c3ac 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -55,3 +55,5 @@
     method getSystemService (Ljava/lang/Class;)Ljava/lang/Object; stub
 class android.content.pm.PackageManager stub
     method <init> ()V stub
+class android.text.ClipboardManager stub
+    method <init> ()V stub
diff --git a/ravenwood/framework-src/android/ravenwood/example/BlueManager.java b/ravenwood/framework-src/android/ravenwood/example/BlueManager.java
new file mode 100644
index 0000000..fc713b1
--- /dev/null
+++ b/ravenwood/framework-src/android/ravenwood/example/BlueManager.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.ravenwood.example;
+
+import android.annotation.SystemService;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+@SystemService(BlueManager.SERVICE_NAME)
+public class BlueManager {
+    public static final String SERVICE_NAME = "example_blue";
+
+    public String getInterfaceDescriptor() {
+        try {
+            return ServiceManager.getService(SERVICE_NAME).getInterfaceDescriptor();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/ravenwood/framework-src/android/ravenwood/example/RedManager.java b/ravenwood/framework-src/android/ravenwood/example/RedManager.java
new file mode 100644
index 0000000..381a901
--- /dev/null
+++ b/ravenwood/framework-src/android/ravenwood/example/RedManager.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.ravenwood.example;
+
+import android.annotation.SystemService;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+@SystemService(RedManager.SERVICE_NAME)
+public class RedManager {
+    public static final String SERVICE_NAME = "example_red";
+
+    public String getInterfaceDescriptor() {
+        try {
+            return ServiceManager.getService(SERVICE_NAME).getInterfaceDescriptor();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/ravenwood/junit-flag-src/android/platform/test/flag/junit/RavenwoodFlagsValueProvider.java b/ravenwood/junit-flag-src/android/platform/test/flag/junit/RavenwoodFlagsValueProvider.java
new file mode 100644
index 0000000..9d62774
--- /dev/null
+++ b/ravenwood/junit-flag-src/android/platform/test/flag/junit/RavenwoodFlagsValueProvider.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.test.flag.junit;
+
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.IFlagsValueProvider;
+
+/**
+ * Offer to create {@link CheckFlagsRule} instances that are useful on the Ravenwood deviceless
+ * testing environment.
+ *
+ * At the moment, default flag values are not available on Ravenwood, so the only options offered
+ * here are "all-on" and "all-off" options. Tests that want to exercise specific flag states should
+ * use {@link android.platform.test.flag.junit.SetFlagsRule}.
+ */
+public class RavenwoodFlagsValueProvider {
+    /**
+     * Create a {@link CheckFlagsRule} instance where flags are in an "all-on" state.
+     */
+    public static CheckFlagsRule createAllOnCheckFlagsRule() {
+        return new CheckFlagsRule(new IFlagsValueProvider() {
+            @Override
+            public boolean getBoolean(String flag) {
+                return true;
+            }
+        });
+    }
+
+    /**
+     * Create a {@link CheckFlagsRule} instance where flags are in an "all-off" state.
+     */
+    public static CheckFlagsRule createAllOffCheckFlagsRule() {
+        return new CheckFlagsRule(new IFlagsValueProvider() {
+            @Override
+            public boolean getBoolean(String flag) {
+                return false;
+            }
+        });
+    }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
index 3668b03..109ef76 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
@@ -16,18 +16,30 @@
 
 package android.platform.test.ravenwood;
 
+import android.content.ClipboardManager;
 import android.content.Context;
 import android.hardware.ISerialManager;
 import android.hardware.SerialManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
 import android.os.PermissionEnforcer;
 import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.ravenwood.example.BlueManager;
+import android.ravenwood.example.RedManager;
 import android.test.mock.MockContext;
 import android.util.ArrayMap;
 import android.util.Singleton;
 
+import java.util.Objects;
+import java.util.concurrent.Executor;
 import java.util.function.Supplier;
 
 public class RavenwoodContext extends MockContext {
+    private final String mPackageName;
+    private final HandlerThread mMainThread;
+
     private final RavenwoodPermissionEnforcer mEnforcer = new RavenwoodPermissionEnforcer();
 
     private final ArrayMap<Class<?>, String> mClassToName = new ArrayMap<>();
@@ -39,14 +51,27 @@
         mNameToFactory.put(serviceName, serviceSupplier);
     }
 
-    public RavenwoodContext() {
+    public RavenwoodContext(String packageName, HandlerThread mainThread) {
+        mPackageName = packageName;
+        mMainThread = mainThread;
+
+        // Services provided by a typical shipping device
+        registerService(ClipboardManager.class,
+                Context.CLIPBOARD_SERVICE, memoize(() ->
+                        new ClipboardManager(this, getMainThreadHandler())));
         registerService(PermissionEnforcer.class,
                 Context.PERMISSION_ENFORCER_SERVICE, () -> mEnforcer);
         registerService(SerialManager.class,
-                Context.SERIAL_SERVICE, asSingleton(() ->
+                Context.SERIAL_SERVICE, memoize(() ->
                         new SerialManager(this, ISerialManager.Stub.asInterface(
                                 ServiceManager.getService(Context.SERIAL_SERVICE)))
                 ));
+
+        // Additional services we provide for testing purposes
+        registerService(BlueManager.class,
+                BlueManager.SERVICE_NAME, memoize(() -> new BlueManager()));
+        registerService(RedManager.class,
+                RedManager.SERVICE_NAME, memoize(() -> new RedManager()));
     }
 
     @Override
@@ -73,18 +98,82 @@
         }
     }
 
+    @Override
+    public Looper getMainLooper() {
+        Objects.requireNonNull(mMainThread,
+                "Test must request setProvideMainThread() via RavenwoodRule");
+        return mMainThread.getLooper();
+    }
+
+    @Override
+    public Handler getMainThreadHandler() {
+        Objects.requireNonNull(mMainThread,
+                "Test must request setProvideMainThread() via RavenwoodRule");
+        return mMainThread.getThreadHandler();
+    }
+
+    @Override
+    public Executor getMainExecutor() {
+        Objects.requireNonNull(mMainThread,
+                "Test must request setProvideMainThread() via RavenwoodRule");
+        return mMainThread.getThreadExecutor();
+    }
+
+    @Override
+    public String getPackageName() {
+        return Objects.requireNonNull(mPackageName,
+                "Test must request setPackageName() via RavenwoodRule");
+    }
+
+    @Override
+    public String getOpPackageName() {
+        return Objects.requireNonNull(mPackageName,
+                "Test must request setPackageName() via RavenwoodRule");
+    }
+
+    @Override
+    public String getAttributionTag() {
+        return null;
+    }
+
+    @Override
+    public UserHandle getUser() {
+        return android.os.UserHandle.of(android.os.UserHandle.myUserId());
+    }
+
+    @Override
+    public int getUserId() {
+        return android.os.UserHandle.myUserId();
+    }
+
+    @Override
+    public int getDeviceId() {
+        return Context.DEVICE_ID_DEFAULT;
+    }
+
     /**
-     * Wrap the given {@link Supplier} to become a memoized singleton.
+     * Wrap the given {@link Supplier} to become memoized.
+     *
+     * The underlying {@link Supplier} will only be invoked once, and that result will be cached
+     * and returned for any future requests.
      */
-    private static <T> Supplier<T> asSingleton(Supplier<T> supplier) {
+    private static <T> Supplier<T> memoize(ThrowingSupplier<T> supplier) {
         final Singleton<T> singleton = new Singleton<>() {
             @Override
             protected T create() {
-                return supplier.get();
+                try {
+                    return supplier.get();
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
             }
         };
         return () -> {
             return singleton.get();
         };
     }
+
+    public interface ThrowingSupplier<T> {
+        T get() throws Exception;
+    }
 }
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 231cce9..56a3c64 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -110,13 +110,16 @@
 
         ActivityManager.init$ravenwood(rule.mCurrentUser);
 
+        final HandlerThread main;
         if (rule.mProvideMainThread) {
-            final HandlerThread main = new HandlerThread(MAIN_THREAD_NAME);
+            main = new HandlerThread(MAIN_THREAD_NAME);
             main.start();
             Looper.setMainLooperForTest(main.getLooper());
+        } else {
+            main = null;
         }
 
-        rule.mContext = new RavenwoodContext();
+        rule.mContext = new RavenwoodContext(rule.mPackageName, main);
         rule.mInstrumentation = new Instrumentation();
         rule.mInstrumentation.basicInit(rule.mContext);
         InstrumentationRegistry.registerInstance(rule.mInstrumentation, Bundle.EMPTY);
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
index bb280f4..cd6b61d 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
@@ -16,15 +16,22 @@
 
 package android.platform.test.ravenwood;
 
+import android.content.ClipboardManager;
 import android.hardware.SerialManager;
 import android.os.SystemClock;
+import android.ravenwood.example.BlueManager;
+import android.ravenwood.example.RedManager;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
 import com.android.server.utils.TimingsTraceAndSlog;
 
+import java.util.List;
+import java.util.Set;
+
 public class RavenwoodSystemServer {
     /**
      * Set of services that we know how to provide under Ravenwood. We keep this set distinct
@@ -36,13 +43,21 @@
      */
     private static final ArrayMap<Class<?>, String> sKnownServices = new ArrayMap<>();
 
-    // TODO: expand SystemService API to support dependency expression, so we don't need test
-    // authors to exhaustively declare all transitive services
-
     static {
-        sKnownServices.put(SerialManager.class, "com.android.server.SerialService$Lifecycle");
+        // Services provided by a typical shipping device
+        sKnownServices.put(ClipboardManager.class,
+                "com.android.server.FakeClipboardService$Lifecycle");
+        sKnownServices.put(SerialManager.class,
+                "com.android.server.SerialService$Lifecycle");
+
+        // Additional services we provide for testing purposes
+        sKnownServices.put(BlueManager.class,
+                "com.android.server.example.BlueManagerService$Lifecycle");
+        sKnownServices.put(RedManager.class,
+                "com.android.server.example.RedManagerService$Lifecycle");
     }
 
+    private static Set<Class<?>> sStartedServices;
     private static TimingsTraceAndSlog sTimings;
     private static SystemServiceManager sServiceManager;
 
@@ -50,6 +65,7 @@
         // Avoid overhead if no services required
         if (rule.mServicesRequired.isEmpty()) return;
 
+        sStartedServices = new ArraySet<>();
         sTimings = new TimingsTraceAndSlog();
         sServiceManager = new SystemServiceManager(rule.mContext);
         sServiceManager.setStartInfo(false,
@@ -57,17 +73,7 @@
                 SystemClock.uptimeMillis());
         LocalServices.addService(SystemServiceManager.class, sServiceManager);
 
-        for (Class<?> service : rule.mServicesRequired) {
-            final String target = sKnownServices.get(service);
-            if (target == null) {
-                throw new RuntimeException("The requested service " + service
-                        + " is not yet supported under the Ravenwood deviceless testing "
-                        + "environment; consider requesting support from the API owner or "
-                        + "consider using Mockito; more details at go/ravenwood-docs");
-            } else {
-                sServiceManager.startService(target);
-            }
-        }
+        startServices(rule.mServicesRequired);
         sServiceManager.sealStartedServices();
 
         // TODO: expand to include additional boot phases when relevant
@@ -81,5 +87,26 @@
         LocalServices.removeServiceForTest(SystemServiceManager.class);
         sServiceManager = null;
         sTimings = null;
+        sStartedServices = null;
+    }
+
+    private static void startServices(List<Class<?>> serviceClasses) {
+        for (Class<?> serviceClass : serviceClasses) {
+            // Quietly ignore duplicate requests if service already started
+            if (sStartedServices.contains(serviceClass)) continue;
+            sStartedServices.add(serviceClass);
+
+            final String serviceName = sKnownServices.get(serviceClass);
+            if (serviceName == null) {
+                throw new RuntimeException("The requested service " + serviceClass
+                        + " is not yet supported under the Ravenwood deviceless testing "
+                        + "environment; consider requesting support from the API owner or "
+                        + "consider using Mockito; more details at go/ravenwood-docs");
+            }
+
+            // Start service and then depth-first traversal of any dependencies
+            final SystemService instance = sServiceManager.startService(serviceName);
+            startServices(instance.getDependencies());
+        }
     }
 }
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index a8c24fc..52ea340 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -35,6 +35,8 @@
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.regex.Pattern;
@@ -121,11 +123,13 @@
     int mUid = NOBODY_UID;
     int mPid = sNextPid.getAndIncrement();
 
+    String mPackageName;
+
     boolean mProvideMainThread = false;
 
     final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
 
-    final ArraySet<Class<?>> mServicesRequired = new ArraySet<>();
+    final List<Class<?>> mServicesRequired = new ArrayList<>();
 
     volatile Context mContext;
     volatile Instrumentation mInstrumentation;
@@ -158,6 +162,15 @@
         }
 
         /**
+         * Configure the identity of this process to be the given package name for the duration
+         * of the test. Has no effect on non-Ravenwood environments.
+         */
+        public Builder setPackageName(/* @NonNull */ String packageName) {
+            mRule.mPackageName = Objects.requireNonNull(packageName);
+            return this;
+        }
+
+        /**
          * Configure a "main" thread to be available for the duration of the test, as defined
          * by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments.
          */
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index eb3c55c..9b4d378 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -186,6 +186,7 @@
 android.content.ClipData
 android.content.ClipData$Item
 android.content.ClipDescription
+android.content.ClipboardManager
 android.content.ComponentName
 android.content.ContentUris
 android.content.ContentValues
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
index cc94090..9057d16 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
@@ -42,7 +42,6 @@
     public static final String KEYBOARD_PATHS = "keyboard_paths";
     public static final String GRAPHICS_NATIVE_CLASSES = "graphics_native_classes";
 
-    public static final String VALUE_N_A = "**n/a**";
     public static final String LIBANDROID_RUNTIME_NAME = "android_runtime";
 
     private static String sInitialDir = new File("").getAbsolutePath();
@@ -130,8 +129,6 @@
         }
         setProperty(CORE_NATIVE_CLASSES, jniClasses);
         setProperty(GRAPHICS_NATIVE_CLASSES, "");
-        setProperty(ICU_DATA_PATH, VALUE_N_A);
-        setProperty(KEYBOARD_PATHS, VALUE_N_A);
 
         RavenwoodUtils.loadJniLibrary(LIBANDROID_RUNTIME_NAME);
     }
diff --git a/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesDependenciesTest.java b/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesDependenciesTest.java
new file mode 100644
index 0000000..efe468d
--- /dev/null
+++ b/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesDependenciesTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ravenwood;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.ravenwood.example.BlueManager;
+import android.ravenwood.example.RedManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodServicesDependenciesTest {
+    // NOTE: we carefully only ask for RedManager here, and rely on Ravenwood internals to spin
+    // up the implicit dependency on BlueManager
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProcessSystem()
+            .setServicesRequired(RedManager.class)
+            .build();
+
+    @Test
+    public void testDirect() {
+        final RedManager red = mRavenwood.getContext().getSystemService(
+                RedManager.class);
+        assertEquals("blue+red", red.getInterfaceDescriptor());
+    }
+
+    @Test
+    public void testIndirect() {
+        final BlueManager blue = mRavenwood.getContext().getSystemService(
+                BlueManager.class);
+        assertEquals("blue", blue.getInterfaceDescriptor());
+    }
+}
diff --git a/ravenwood/test-authors.md b/ravenwood/test-authors.md
index 9179a62..7c0cee8 100644
--- a/ravenwood/test-authors.md
+++ b/ravenwood/test-authors.md
@@ -112,6 +112,24 @@
 
 This naturally composes together well with any `RavenwoodRule` that your test may need.
 
+While `SetFlagsRule` is generally a best-practice (as it can explicitly confirm behaviors for both "on" and "off" states), you may need to write tests that use `CheckFlagsRule` (such as when writing CTS).  Ravenwood currently supports `CheckFlagsRule` by offering "all-on" and "all-off" behaviors:
+
+```
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.RavenwoodFlagsValueProvider;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+@RunWith(AndroidJUnit4.class)
+public class MyCodeTest {
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isUnderRavenwood()
+            ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule()
+            : DeviceFlagsValueProvider.createCheckFlagsRule();
+```
+
+Ravenwood currently doesn't have knowledge of the "default" value of any flags, so using `createAllOnCheckFlagsRule()` is recommended to verify the widest possible set of behaviors.  The example code above falls back to using default values from `DeviceFlagsValueProvider` when not running on Ravenwood.
+
 ## Strategies for migration/bivalent tests
 
 Ravenwood aims to support tests that are written in a “bivalent” way, where the same test code can be dual-compiled to run on both a real Android device and under a Ravenwood environment.
diff --git a/rs/java/android/renderscript/ScriptC.java b/rs/java/android/renderscript/ScriptC.java
index 4a2f3da..e9f6031 100644
--- a/rs/java/android/renderscript/ScriptC.java
+++ b/rs/java/android/renderscript/ScriptC.java
@@ -38,12 +38,12 @@
     private static final String TAG = "ScriptC";
 
     /**
-     * In targetSdkVersion 35 and above, Renderscript's ScriptC stops being supported
+     * In targetSdkVersion 36 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.
+     * In targetSdkVersion 35 and below, Renderscript's ScriptC still works.
      */
     @ChangeId
-    @EnabledAfter(targetSdkVersion = 35)
+    @EnabledAfter(targetSdkVersion = 36)
     private static final long RENDERSCRIPT_SCRIPTC_DEPRECATION_CHANGE_ID = 297019750L;
 
     /**
@@ -113,9 +113,9 @@
             throw new UnsupportedOperationException(s);
         }
 
-        // Throw an exception if the target API is 35 or above
+        // Throw an exception if the target API is 36 or above
         String message =
-                "ScriptC scripts are not supported when targeting an API Level >= 35. Please refer "
+                "ScriptC scripts are not supported when targeting an API Level >= 36. Please refer "
                     + "to https://developer.android.com/guide/topics/renderscript/migration-guide "
                     + "for proposed alternatives.";
         Slog.w(TAG, message);
diff --git a/sax/tests/saxtests/Android.bp b/sax/tests/saxtests/Android.bp
index cbd19c3..446ee93 100644
--- a/sax/tests/saxtests/Android.bp
+++ b/sax/tests/saxtests/Android.bp
@@ -15,6 +15,9 @@
         "android.test.runner",
         "android.test.base",
     ],
-    static_libs: ["junit"],
+    static_libs: [
+        "junit",
+        "androidx.test.rules",
+    ],
     platform_apis: true,
 }
diff --git a/sax/tests/saxtests/src/android/sax/SafeSaxTest.java b/sax/tests/saxtests/src/android/sax/SafeSaxTest.java
index a68fc9a..2a08f54 100644
--- a/sax/tests/saxtests/src/android/sax/SafeSaxTest.java
+++ b/sax/tests/saxtests/src/android/sax/SafeSaxTest.java
@@ -17,18 +17,16 @@
 package android.sax;
 
 import android.graphics.Bitmap;
-import android.sax.Element;
-import android.sax.ElementListener;
-import android.sax.EndTextElementListener;
-import android.sax.RootElement;
-import android.sax.StartElementListener;
-import android.sax.TextElementListener;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 import android.util.Xml;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.SmallTest;
+
+import com.android.frameworks.saxtests.R;
 import com.android.internal.util.XmlUtils;
+
 import org.xml.sax.Attributes;
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
@@ -40,8 +38,6 @@
 import java.io.InputStream;
 import java.time.Instant;
 
-import com.android.frameworks.saxtests.R;
-
 public class SafeSaxTest extends AndroidTestCase {
 
     private static final String TAG = SafeSaxTest.class.getName();
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index a754ba5..997f3af 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -59,6 +59,13 @@
 }
 
 flag {
+    name: "enable_magnification_one_finger_panning_gesture"
+    namespace: "accessibility"
+    description: "Whether to allow easy-mode (one finger panning gesture) for magnification"
+    bug: "282039824"
+}
+
+flag {
     name: "fix_drag_pointer_when_ending_drag"
     namespace: "accessibility"
     description: "Send the correct pointer id when transitioning from dragging to delegating states."
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index af47ed2..73584154 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -611,12 +611,12 @@
         if (svcConnTracingEnabled()) {
             logTraceSvcConn("getWindow", "windowId=" + windowId);
         }
+        int displayId = Display.INVALID_DISPLAY;
+        if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
+            displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowId(
+                    mSystemSupport.getCurrentUserIdLocked(), windowId);
+        }
         synchronized (mLock) {
-            int displayId = Display.INVALID_DISPLAY;
-            if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
-                displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked(
-                        mSystemSupport.getCurrentUserIdLocked(), windowId);
-            }
             ensureWindowsAvailableTimedLocked(displayId);
 
             if (!hasRightsToCurrentUserLocked()) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 16119d11..54e545d 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -16,6 +16,8 @@
 
 package com.android.server.accessibility;
 
+import static android.view.InputDevice.SOURCE_CLASS_POINTER;
+import static android.view.MotionEvent.ACTION_SCROLL;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
 
@@ -425,6 +427,12 @@
 
     boolean shouldProcessMultiDeviceEvent(InputEvent event, int policyFlags) {
         if (event instanceof MotionEvent motion) {
+            if (!motion.isFromSource(SOURCE_CLASS_POINTER) || motion.getAction() == ACTION_SCROLL) {
+                // Non-pointer events are focus-dispatched and don't require special logic.
+                // Scroll events are stand-alone and therefore can be considered to not be part of
+                // a stream.
+                return true;
+            }
             // Only allow 1 device to be sending motion events at a time
             // If the event is from an active device, let it through.
             // If the event is not from an active device, only let it through if it starts a new
@@ -481,7 +489,7 @@
     }
 
     private void processMotionEvent(EventStreamState state, MotionEvent event, int policyFlags) {
-        if (!state.shouldProcessScroll() && event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
+        if (!state.shouldProcessScroll() && event.getActionMasked() == ACTION_SCROLL) {
             super.onInputEvent(event, policyFlags);
             return;
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 43c018c..4be303a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -40,6 +40,7 @@
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
 import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
+import static com.android.internal.accessibility.common.ShortcutConstants.USER_SHORTCUT_TYPES;
 import static com.android.internal.accessibility.util.AccessibilityStatsLogUtils.logAccessibilityShortcutActivated;
 import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
@@ -111,6 +112,7 @@
 import android.safetycenter.SafetyCenterManager;
 import android.text.TextUtils;
 import android.text.TextUtils.SimpleStringSplitter;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Log;
@@ -145,9 +147,13 @@
 import com.android.internal.accessibility.AccessibilityShortcutController;
 import com.android.internal.accessibility.AccessibilityShortcutController.FrameworkFeatureInfo;
 import com.android.internal.accessibility.AccessibilityShortcutController.LaunchableFrameworkFeatureInfo;
+import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.internal.accessibility.common.ShortcutConstants.FloatingMenuSize;
+import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
 import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
 import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity;
 import com.android.internal.accessibility.util.AccessibilityUtils;
+import com.android.internal.accessibility.util.ShortcutUtils;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
@@ -168,6 +174,7 @@
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.utils.Slogf;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
@@ -191,6 +198,7 @@
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 /**
  * This class is instantiated by the system as a system level service and can be
@@ -707,16 +715,6 @@
         }
     }
 
-    private void onSomePackagesChangedLocked() {
-        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)) {
-            onUserStateChangedLocked(userState);
-        }
-    }
-
     private void onSomePackagesChangedLocked(
             @Nullable List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos,
             @Nullable List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) {
@@ -834,22 +832,16 @@
                 final int userId = getChangingUserId();
                 List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
                 List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
-                if (Flags.scanPackagesWithoutLock()) {
-                    parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
-                    parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
-                }
+                parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
+                parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
                 synchronized (mLock) {
                     // Only the profile parent can install accessibility services.
                     // Therefore we ignore packages from linked profiles.
                     if (userId != mCurrentUserId) {
                         return;
                     }
-                    if (Flags.scanPackagesWithoutLock()) {
-                        onSomePackagesChangedLocked(parsedAccessibilityServiceInfos,
-                                parsedAccessibilityShortcutInfos);
-                    } else {
-                        onSomePackagesChangedLocked();
-                    }
+                    onSomePackagesChangedLocked(parsedAccessibilityServiceInfos,
+                            parsedAccessibilityShortcutInfos);
                 }
             }
 
@@ -867,10 +859,8 @@
                 final int userId = getChangingUserId();
                 List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
                 List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
-                if (Flags.scanPackagesWithoutLock()) {
-                    parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
-                    parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
-                }
+                parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
+                parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
                 synchronized (mLock) {
                     if (userId != mCurrentUserId) {
                         return;
@@ -885,12 +875,8 @@
                     // get a new one.
                     userState.mInstalledServices.clear();
                     final boolean configurationChanged;
-                    if (Flags.scanPackagesWithoutLock()) {
-                        configurationChanged = readConfigurationForUserStateLocked(userState,
-                                parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos);
-                    } else {
-                        configurationChanged = readConfigurationForUserStateLocked(userState);
-                    }
+                    configurationChanged = readConfigurationForUserStateLocked(userState,
+                            parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos);
                     if (reboundAService || configurationChanged) {
                         onUserStateChangedLocked(userState);
                     }
@@ -985,34 +971,6 @@
         // package changes
         mPackageMonitor.register(mContext, null,  UserHandle.ALL, true);
 
-        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);
-                            }
-                        }
-                    }
-                });
-            }
-        }
-
         // user change and unlock
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
@@ -1303,15 +1261,14 @@
             // the computation for performance reasons.
             boolean shouldComputeWindows = false;
             int displayId = event.getDisplayId();
+            final int windowId = event.getWindowId();
+            if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID
+                    && displayId == Display.INVALID_DISPLAY) {
+                displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowId(
+                        resolvedUserId, windowId);
+                event.setDisplayId(displayId);
+            }
             synchronized (mLock) {
-                final int windowId = event.getWindowId();
-                if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID
-                        && displayId == Display.INVALID_DISPLAY) {
-                    displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked(
-                            resolvedUserId, windowId);
-                    event.setDisplayId(displayId);
-                }
-
                 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
                         && displayId != Display.INVALID_DISPLAY
                         && mA11yWindowManager.isTrackingWindowsLocked(displayId)) {
@@ -1986,10 +1943,8 @@
         mMagnificationController.updateUserIdIfNeeded(userId);
         List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
         List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
-        if (Flags.scanPackagesWithoutLock()) {
-            parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
-            parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
-        }
+        parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
+        parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
         synchronized (mLock) {
             if (mCurrentUserId == userId && mInitialized) {
                 return;
@@ -2014,12 +1969,8 @@
             mCurrentUserId = userId;
             AccessibilityUserState userState = getCurrentUserStateLocked();
 
-            if (Flags.scanPackagesWithoutLock()) {
-                readConfigurationForUserStateLocked(userState,
-                        parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos);
-            } else {
-                readConfigurationForUserStateLocked(userState);
-            }
+            readConfigurationForUserStateLocked(userState,
+                    parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos);
             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
@@ -2334,6 +2285,7 @@
         if (!parsedAccessibilityServiceInfos.equals(userState.mInstalledServices)) {
             userState.mInstalledServices.clear();
             userState.mInstalledServices.addAll(parsedAccessibilityServiceInfos);
+            userState.updateTileServiceMapForAccessibilityServiceLocked();
             return true;
         }
         return false;
@@ -2359,6 +2311,7 @@
         if (!parsedAccessibilityShortcutInfos.equals(userState.mInstalledShortcuts)) {
             userState.mInstalledShortcuts.clear();
             userState.mInstalledShortcuts.addAll(parsedAccessibilityShortcutInfos);
+            userState.updateTileServiceMapForAccessibilityActivityLocked();
             return true;
         }
         return false;
@@ -2621,6 +2574,12 @@
 
     private <T> void persistColonDelimitedSetToSettingLocked(String settingName, int userId,
             Set<T> set, Function<T, String> toString) {
+        persistColonDelimitedSetToSettingLocked(settingName, userId, set,
+                toString, /* defaultEmptyString= */ null);
+    }
+
+    private <T> void persistColonDelimitedSetToSettingLocked(String settingName, int userId,
+            Set<T> set, Function<T, String> toString, String defaultEmptyString) {
         final StringBuilder builder = new StringBuilder();
         for (T item : set) {
             final String str = (item != null ? toString.apply(item) : null);
@@ -2636,7 +2595,18 @@
         try {
             final String settingValue = builder.toString();
             Settings.Secure.putStringForUser(mContext.getContentResolver(),
-                    settingName, TextUtils.isEmpty(settingValue) ? null : settingValue, userId);
+                    settingName,
+                    TextUtils.isEmpty(settingValue) ? defaultEmptyString : settingValue, userId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private void persistIntToSetting(int userId, String settingName, int settingValue) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            Settings.Secure.putIntForUser(
+                    mContext.getContentResolver(), settingName, settingValue, userId);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -3005,6 +2975,7 @@
         scheduleUpdateClientsIfNeededLocked(userState, forceUpdate);
         updateAccessibilityShortcutKeyTargetsLocked(userState);
         updateAccessibilityButtonTargetsLocked(userState);
+        updateAccessibilityQsTargetsLocked(userState);
         // Update the capabilities before the mode because we will check the current mode is
         // invalid or not..
         updateMagnificationCapabilitiesSettingsChangeLocked(userState);
@@ -3107,15 +3078,6 @@
         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) {
-        return readConfigurationForUserStateLocked(userState,
-                parseAccessibilityServiceInfos(mCurrentUserId),
-                parseAccessibilityShortcutInfos(mCurrentUserId));
-    }
-
     private boolean readConfigurationForUserStateLocked(
             AccessibilityUserState userState,
             List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos,
@@ -3132,6 +3094,7 @@
         somethingChanged |= readMagnificationEnabledSettingsLocked(userState);
         somethingChanged |= readAutoclickEnabledSettingLocked(userState);
         somethingChanged |= readAccessibilityShortcutKeySettingLocked(userState);
+        somethingChanged |= readAccessibilityQsTargetsLocked(userState);
         somethingChanged |= readAccessibilityButtonTargetsLocked(userState);
         somethingChanged |= readAccessibilityButtonTargetComponentLocked(userState);
         somethingChanged |= readUserRecommendedUiTimeoutSettingsLocked(userState);
@@ -3305,6 +3268,21 @@
         return true;
     }
 
+    private boolean readAccessibilityQsTargetsLocked(AccessibilityUserState userState) {
+        final Set<String> targetsFromSetting = new ArraySet<>();
+        readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_QS_TARGETS,
+                userState.mUserId, str -> str, targetsFromSetting);
+
+        final Set<String> currentTargets =
+                userState.getShortcutTargetsLocked(UserShortcutType.QUICK_SETTINGS);
+        if (targetsFromSetting.equals(currentTargets)) {
+            return false;
+        }
+        userState.updateA11yQsTargetLocked(targetsFromSetting);
+        scheduleNotifyClientsOfServicesStateChangeLocked(userState);
+        return true;
+    }
+
     private boolean readAccessibilityButtonTargetsLocked(AccessibilityUserState userState) {
         final Set<String> targetsFromSetting = new ArraySet<>();
         readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
@@ -3636,6 +3614,8 @@
 
         final Set<String> shortcutKeyTargets =
                 userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
+        final Set<String> qsShortcutTargets =
+                userState.getShortcutTargetsLocked(UserShortcutType.QUICK_SETTINGS);
         userState.mEnabledServices.forEach(componentName -> {
             if (packageName != null && componentName != null
                     && !packageName.equals(componentName.getPackageName())) {
@@ -3657,7 +3637,8 @@
                 return;
             }
             if (doesShortcutTargetsStringContain(buttonTargets, serviceName)
-                    || doesShortcutTargetsStringContain(shortcutKeyTargets, serviceName)) {
+                    || doesShortcutTargetsStringContain(shortcutKeyTargets, serviceName)
+                    || doesShortcutTargetsStringContain(qsShortcutTargets, serviceName)) {
                 return;
             }
             // For enabled a11y services targeting sdk version > Q and requesting a11y button should
@@ -3678,6 +3659,33 @@
     }
 
     /**
+     * Update the Settings.Secure.ACCESSIBILITY_QS_TARGETS so that it only contains valid content,
+     * and a side loaded service can't spoof the package name of the default service.
+     */
+    private void updateAccessibilityQsTargetsLocked(AccessibilityUserState userState) {
+        final Set<String> targets =
+                userState.getShortcutTargetsLocked(UserShortcutType.QUICK_SETTINGS);
+        final int lastSize = targets.size();
+        if (lastSize == 0) {
+            return;
+        }
+
+        // Removes the targets that are no longer installed on the device.
+        boolean somethingChanged = targets.removeIf(
+                name -> !userState.isShortcutTargetInstalledLocked(name));
+        if (!somethingChanged) {
+            return;
+        }
+        userState.updateA11yQsTargetLocked(targets);
+
+        // Update setting key with new value.
+        persistColonDelimitedSetToSettingLocked(
+                Settings.Secure.ACCESSIBILITY_QS_TARGETS,
+                userState.mUserId, targets, str -> str);
+        scheduleNotifyClientsOfServicesStateChangeLocked(userState);
+    }
+
+    /**
      * Remove the shortcut target for the unbound service which is requesting accessibility button
      * and targeting sdk > Q from the accessibility button and shortcut.
      *
@@ -3691,19 +3699,42 @@
                 .targetSdkVersion <= Build.VERSION_CODES.Q) {
             return;
         }
+
+        final List<Pair<Integer, String>> shortcutTypeAndShortcutSetting = List.of(
+                new Pair<>(ACCESSIBILITY_SHORTCUT_KEY,
+                        Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE),
+                new Pair<>(ACCESSIBILITY_BUTTON,
+                        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
+                new Pair<>(UserShortcutType.QUICK_SETTINGS,
+                        Settings.Secure.ACCESSIBILITY_QS_TARGETS)
+        );
+
         final ComponentName serviceName = service.getComponentName();
-        if (userState.removeShortcutTargetLocked(ACCESSIBILITY_SHORTCUT_KEY, serviceName)) {
-            final Set<String> currentTargets = userState.getShortcutTargetsLocked(
-                    ACCESSIBILITY_SHORTCUT_KEY);
-            persistColonDelimitedSetToSettingLocked(
-                    Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
-                    userState.mUserId, currentTargets, str -> str);
-        }
-        if (userState.removeShortcutTargetLocked(ACCESSIBILITY_BUTTON, serviceName)) {
-            final Set<String> currentTargets = userState.getShortcutTargetsLocked(
-                    ACCESSIBILITY_BUTTON);
-            persistColonDelimitedSetToSettingLocked(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
-                    userState.mUserId, currentTargets, str -> str);
+        for (Pair<Integer, String> shortcutTypePair : shortcutTypeAndShortcutSetting) {
+            int shortcutType = shortcutTypePair.first;
+            String shortcutSettingName = shortcutTypePair.second;
+            if (userState.removeShortcutTargetLocked(shortcutType, serviceName)) {
+                final Set<String> currentTargets = userState.getShortcutTargetsLocked(shortcutType);
+                persistColonDelimitedSetToSettingLocked(
+                        shortcutSettingName,
+                        userState.mUserId, currentTargets, str -> str);
+
+                if (shortcutType != UserShortcutType.QUICK_SETTINGS) {
+                    continue;
+                }
+
+                ComponentName tileService =
+                        userState.getA11yFeatureToTileService().getOrDefault(serviceName, null);
+
+                final StatusBarManagerInternal statusBarManagerInternal =
+                        LocalServices.getService(StatusBarManagerInternal.class);
+                // In case it's not initialized yet
+                if (statusBarManagerInternal == null || tileService == null) {
+                    continue;
+                }
+                mMainHandler.sendMessage(obtainMessage(StatusBarManagerInternal::removeQsTile,
+                        statusBarManagerInternal, tileService));
+            }
         }
     }
 
@@ -3967,6 +3998,262 @@
         }
     }
 
+    /**
+     * Turns on or off a shortcut type of the accessibility features. The {@code shortcutTypes} is a
+     * flag that contains values defined in the
+     * {@link ShortcutConstants.USER_SHORTCUT_TYPES}.
+     *
+     * @hide
+     */
+    @Override
+    public void enableShortcutsForTargets(
+            boolean enable, @UserShortcutType int shortcutTypes,
+            @NonNull List<String> shortcutTargets, @UserIdInt int userId) {
+        mContext.enforceCallingPermission(
+                Manifest.permission.MANAGE_ACCESSIBILITY, "enableShortcutsForTargets");
+        for (int shortcutType : USER_SHORTCUT_TYPES) {
+            if ((shortcutTypes & shortcutType) == shortcutType) {
+                enableShortcutForTargets(enable, shortcutType, shortcutTargets, userId);
+            }
+        }
+    }
+
+    private void enableShortcutForTargets(
+            boolean enable, @UserShortcutType int shortcutType,
+            @NonNull List<String> shortcutTargets, @UserIdInt int userId) {
+        final String shortcutTypeSettingKey = ShortcutUtils.convertToKey(shortcutType);
+        if (shortcutType == UserShortcutType.TRIPLETAP
+                || shortcutType == UserShortcutType.TWOFINGER_DOUBLETAP) {
+            for (String target : shortcutTargets) {
+                if (MAGNIFICATION_CONTROLLER_NAME.equals(target)) {
+                    persistIntToSetting(
+                            userId,
+                            shortcutTypeSettingKey,
+                            enable ? AccessibilityUtils.State.ON : AccessibilityUtils.State.OFF);
+                } else {
+                    Slog.w(LOG_TAG,
+                            "Triple tap or two-fingers double-tap is not supported for " + target);
+                }
+            }
+            return;
+        }
+        Set<String> validNewTargets;
+        Set<String> currentTargets;
+
+        Map<ComponentName, ComponentName> featureToTileMap =
+                getA11yFeatureToTileMapInternal(userId);
+        synchronized (mLock) {
+            AccessibilityUserState userState = getUserStateLocked(userId);
+            currentTargets =
+                    ShortcutUtils.getShortcutTargetsFromSettings(mContext, shortcutType, userId);
+
+            Set<String> newTargets = new ArraySet<>(currentTargets);
+            if (enable) {
+                newTargets.addAll(shortcutTargets);
+            } else {
+                newTargets.removeAll(shortcutTargets);
+            }
+            validNewTargets = newTargets;
+
+            // filter out targets that doesn't have qs shortcut
+            if (shortcutType == UserShortcutType.QUICK_SETTINGS) {
+                validNewTargets = newTargets.stream().filter(target -> {
+                    ComponentName targetComponent = ComponentName.unflattenFromString(target);
+                    return featureToTileMap.containsKey(targetComponent);
+                }).collect(Collectors.toUnmodifiableSet());
+            }
+
+            if (currentTargets.equals(validNewTargets)) {
+                return;
+            }
+            persistColonDelimitedSetToSettingLocked(
+                    shortcutTypeSettingKey,
+                    userId,
+                    validNewTargets,
+                    str -> str,
+                    /* defaultEmptyString= */ ""
+            );
+
+            if (shortcutType == UserShortcutType.QUICK_SETTINGS) {
+                userState.updateA11yQsTargetLocked(validNewTargets);
+                scheduleNotifyClientsOfServicesStateChangeLocked(userState);
+                onUserStateChangedLocked(userState);
+            }
+        }
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState(
+                    mContext, new ArraySet<>(shortcutTargets), userId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+
+        // Add or Remove tile in QS Panel
+        if (shortcutType == UserShortcutType.QUICK_SETTINGS) {
+            mMainHandler.sendMessage(obtainMessage(
+                    AccessibilityManagerService::updateA11yTileServicesInQuickSettingsPanel,
+                    this, validNewTargets, currentTargets, userId));
+        }
+
+        if (!enable) {
+            return;
+        }
+        if (shortcutType == UserShortcutType.HARDWARE) {
+            skipVolumeShortcutDialogTimeoutRestriction(userId);
+        } else if (shortcutType == UserShortcutType.SOFTWARE) {
+            // Update the A11y FAB size to large when the Magnification shortcut is
+            // enabled and the user hasn't changed the floating button size
+            if (shortcutTargets.contains(MAGNIFICATION_CONTROLLER_NAME)
+                    && Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                    Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
+                    FloatingMenuSize.UNKNOWN, userId) == FloatingMenuSize.UNKNOWN) {
+                persistIntToSetting(
+                        userId,
+                        Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
+                        FloatingMenuSize.LARGE);
+            }
+        }
+    }
+
+    /**
+     * Add or remove the TileServices of the a11y features in the Quick Settings panel based on the
+     * changes in the new targets and current targets.
+     *
+     * <p>
+     * The framework tiles implemented in the SysUi are automatically added/removed via the
+     * SystemUI's AutoAddable framework. This method only handles updating the TileServices
+     * provided by AccessibilityService or Accessibility Activity.
+     * </p>
+     *
+     * @see com.android.systemui.qs.pipeline.domain.model.AutoAddable AutoAddable QS Tiles
+     */
+    private void updateA11yTileServicesInQuickSettingsPanel(
+            Set<String> newQsTargets,
+            Set<String> currentQsTargets, @UserIdInt int userId) {
+        // Call StatusBarManager to add/remove tiles
+        final StatusBarManagerInternal statusBarManagerInternal =
+                LocalServices.getService(StatusBarManagerInternal.class);
+        // In case it's not initialized yet
+        if (statusBarManagerInternal == null) {
+            return;
+        }
+
+        Map<ComponentName, ComponentName> a11yFeatureToTileMap =
+                getA11yFeatureToTileMapInternal(userId);
+        Set<String> targetWithNoTile = new ArraySet<>();
+
+        // Add TileServices to QS Panel that are added to the new targets
+        newQsTargets.stream()
+                .filter(target -> !currentQsTargets.contains(target))
+                .forEach(
+                        target -> {
+                            ComponentName targetComponent =
+                                    ComponentName.unflattenFromString(target);
+                            if (targetComponent == null
+                                    || !a11yFeatureToTileMap.containsKey(targetComponent)) {
+                                targetWithNoTile.add(target);
+                                return;
+                            }
+
+                            if (ShortcutConstants.A11Y_FEATURE_TO_FRAMEWORK_TILE.containsKey(
+                                    targetComponent)) {
+                                // a11y framework tile is handled by the SysUi autoaddable framework
+                                return;
+                            }
+                            statusBarManagerInternal.addQsTileToFrontOrEnd(
+                                    a11yFeatureToTileMap.get(targetComponent), /* end= */ true);
+                        }
+                );
+
+        // Remove TileServices from QS Panel that are no longer in the new targets.
+        currentQsTargets.stream()
+                .filter(target -> !newQsTargets.contains(target))
+                .forEach(
+                        target -> {
+                            ComponentName targetComponent =
+                                    ComponentName.unflattenFromString(target);
+                            if (targetComponent == null
+                                    || !a11yFeatureToTileMap.containsKey(targetComponent)) {
+                                targetWithNoTile.add(target);
+                                return;
+                            }
+
+                            if (ShortcutConstants.A11Y_FEATURE_TO_FRAMEWORK_TILE.containsKey(
+                                    targetComponent)) {
+                                // a11y framework tile is handled by the SysUi autoaddable framework
+                                return;
+                            }
+                            statusBarManagerInternal.removeQsTile(
+                                    a11yFeatureToTileMap.get(targetComponent));
+                        }
+                );
+
+        if (!targetWithNoTile.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "Unable to add/remove Tiles for a11y features: " + targetWithNoTile
+                            + "as the Tiles aren't provided");
+        }
+    }
+
+    @Override
+    public Bundle getA11yFeatureToTileMap(@UserIdInt int userId) {
+        mContext.enforceCallingPermission(
+                Manifest.permission.MANAGE_ACCESSIBILITY, "getA11yFeatureToTileMap");
+
+        Bundle bundle = new Bundle();
+        Map<ComponentName, ComponentName> a11yFeatureToTile =
+                getA11yFeatureToTileMapInternal(userId);
+        for (Map.Entry<ComponentName, ComponentName> entry : a11yFeatureToTile.entrySet()) {
+            bundle.putParcelable(entry.getKey().flattenToString(), entry.getValue());
+        }
+        return bundle;
+    }
+
+
+
+    /**
+     * Returns accessibility feature's component and the provided tile map. This includes the
+     * TileService provided by the AccessibilityService or Accessibility Activity and the tile
+     * component provided by the framework's feature.
+     *
+     * @return a map of a feature's component name, and its provided tile's component name. The
+     * returned map's keys and values are not null. If a feature doesn't provide a tile, it won't
+     * have an entry in this map.
+     *
+     * @see ShortcutConstants.A11Y_FEATURE_TO_FRAMEWORK_TILE
+     */
+    @NonNull
+    private Map<ComponentName, ComponentName> getA11yFeatureToTileMapInternal(
+            @UserIdInt int userId) {
+        final Map<ComponentName, ComponentName> a11yFeatureToTileService;
+        Map<ComponentName, ComponentName> a11yFeatureToTile = new ArrayMap<>();
+        final int resolvedUserId;
+
+        synchronized (mLock) {
+            resolvedUserId = mSecurityPolicy
+                    .resolveCallingUserIdEnforcingPermissionsLocked(userId);
+            AccessibilityUserState userState = getUserStateLocked(resolvedUserId);
+            a11yFeatureToTileService = userState.getA11yFeatureToTileService();
+        }
+        final boolean shouldFilterAppAccess = Binder.getCallingPid() != OWN_PROCESS_ID;
+        final int callingUid = Binder.getCallingUid();
+        final PackageManagerInternal pm = LocalServices.getService(
+                PackageManagerInternal.class);
+
+        for (Map.Entry<ComponentName, ComponentName> entry :
+                a11yFeatureToTileService.entrySet()) {
+            if (shouldFilterAppAccess
+                    && pm.filterAppAccess(
+                    entry.getKey().getPackageName(), callingUid, resolvedUserId)) {
+                continue;
+            }
+            a11yFeatureToTile.put(entry.getKey(), entry.getValue());
+        }
+
+        a11yFeatureToTile.putAll(ShortcutConstants.A11Y_FEATURE_TO_FRAMEWORK_TILE);
+        return a11yFeatureToTile;
+    }
+
     @Override
     public List<String> getAccessibilityShortcutTargets(@ShortcutType int shortcutType) {
         if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
@@ -4659,8 +4946,8 @@
     public void onShellCommand(FileDescriptor in, FileDescriptor out,
             FileDescriptor err, String[] args, ShellCallback callback,
             ResultReceiver resultReceiver) {
-        new AccessibilityShellCommand(this, mSystemActionPerformer).exec(this, in, out, err, args,
-                callback, resultReceiver);
+        new AccessibilityShellCommand(mContext, this, mSystemActionPerformer)
+                .exec(this, in, out, err, args, callback, resultReceiver);
     }
 
     private final class InteractionBridge {
@@ -5836,4 +6123,15 @@
             }
         }
     }
+
+
+    /**
+     * Bypasses the timeout restriction if volume key shortcut assigned.
+     */
+    private void skipVolumeShortcutDialogTimeoutRestriction(int userId) {
+        persistIntToSetting(
+                userId,
+                Settings.Secure.SKIP_ACCESSIBILITY_SHORTCUT_DIALOG_TIMEOUT_RESTRICTION,
+                /* true */ 1);
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
index 8cf5547..4908032 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
@@ -16,8 +16,10 @@
 
 package com.android.server.accessibility;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.app.ActivityManager;
+import android.content.Context;
 import android.os.Binder;
 import android.os.Process;
 import android.os.ShellCommand;
@@ -26,18 +28,27 @@
 import com.android.server.LocalServices;
 import com.android.server.wm.WindowManagerInternal;
 
+import java.io.File;
+import java.io.IOException;
 import java.io.PrintWriter;
 
 /**
  * Shell command implementation for the accessibility manager service
  */
 final class AccessibilityShellCommand extends ShellCommand {
-    final @NonNull AccessibilityManagerService mService;
-    final @NonNull SystemActionPerformer mSystemActionPerformer;
-    final @NonNull WindowManagerInternal mWindowManagerService;
+    @NonNull
+    final Context mContext;
+    @NonNull
+    final AccessibilityManagerService mService;
+    @NonNull
+    final SystemActionPerformer mSystemActionPerformer;
+    @NonNull
+    final WindowManagerInternal mWindowManagerService;
 
-    AccessibilityShellCommand(@NonNull AccessibilityManagerService service,
+    AccessibilityShellCommand(@NonNull Context context,
+            @NonNull AccessibilityManagerService service,
             @NonNull SystemActionPerformer systemActionPerformer) {
+        mContext = context;
         mService = service;
         mSystemActionPerformer = systemActionPerformer;
         mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
@@ -61,6 +72,8 @@
             case "start-trace":
             case "stop-trace":
                 return mService.getTraceManager().onShellCommand(cmd, this);
+            case "check-hidraw":
+                return checkHidraw();
         }
         return -1;
     }
@@ -106,6 +119,67 @@
         return -1;
     }
 
+    private int checkHidraw() {
+        mContext.enforceCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY,
+                "Missing MANAGE_ACCESSIBILITY permission");
+        String subcommand = getNextArgRequired();
+        File hidrawNode = new File(getNextArgRequired());
+        switch (subcommand) {
+            case "read" -> {
+                return checkHidrawRead(hidrawNode);
+            }
+            case "write" -> {
+                return checkHidrawWrite(hidrawNode);
+            }
+            case "descriptor" -> {
+                return checkHidrawDescriptor(hidrawNode);
+            }
+            default -> {
+                getErrPrintWriter().print("Unknown subcommand " + subcommand);
+                return -1;
+            }
+        }
+    }
+
+    private int checkHidrawRead(File hidrawNode) {
+        if (!hidrawNode.canRead()) {
+            getErrPrintWriter().println("Unable to read from " + hidrawNode);
+            return -1;
+        }
+        // Tests executing this command using UiAutomation#executeShellCommand will not receive
+        // the command's exit value, so print the path to stdout to indicate success.
+        getOutPrintWriter().print(hidrawNode.getAbsolutePath());
+        return 0;
+    }
+
+    private int checkHidrawWrite(File hidrawNode) {
+        if (!hidrawNode.canWrite()) {
+            getErrPrintWriter().println("Unable to write to " + hidrawNode);
+            return -1;
+        }
+        // Tests executing this command using UiAutomation#executeShellCommand will not receive
+        // the command's exit value, so print the path to stdout to indicate success.
+        getOutPrintWriter().print(hidrawNode.getAbsolutePath());
+        return 0;
+    }
+
+    private int checkHidrawDescriptor(File hidrawNode) {
+        BrailleDisplayConnection.BrailleDisplayScanner scanner =
+                BrailleDisplayConnection.createScannerForShell();
+        byte[] descriptor = scanner.getDeviceReportDescriptor(hidrawNode.toPath());
+        if (descriptor == null) {
+            getErrPrintWriter().println("Unable to read descriptor for " + hidrawNode);
+            return -1;
+        }
+        try {
+            // Print the descriptor bytes to stdout.
+            getRawOutputStream().write(descriptor);
+            return 0;
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     private Integer parseUserId() {
         final String option = getNextOption();
         if (option != null) {
@@ -131,6 +205,8 @@
         pw.println("    Get whether binding to services provided by instant apps is allowed.");
         pw.println("  call-system-action <ACTION_ID>");
         pw.println("    Calls the system action with the given action id.");
+        pw.println("  check-hidraw [read|write|descriptor] <HIDRAW_NODE_PATH>");
+        pw.println("    Checks if the system can perform various actions on the HIDRAW node.");
         mService.getTraceManager().onHelp(pw);
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 68ee780..063eafe 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -38,10 +38,12 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.os.Binder;
 import android.os.RemoteCallbackList;
 import android.provider.Settings;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -51,6 +53,7 @@
 
 import com.android.internal.R;
 import com.android.internal.accessibility.AccessibilityShortcutController;
+import com.android.internal.accessibility.common.ShortcutConstants;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -99,6 +102,7 @@
     final ArraySet<String> mAccessibilityShortcutKeyTargets = new ArraySet<>();
 
     final ArraySet<String> mAccessibilityButtonTargets = new ArraySet<>();
+    private final ArraySet<String> mAccessibilityQsTargets = new ArraySet<>();
 
     private final ServiceInfoChangeListener mServiceInfoChangeListener;
 
@@ -146,6 +150,8 @@
     private final int mFocusStrokeWidthDefaultValue;
     // The default value of the focus color.
     private final int mFocusColorDefaultValue;
+    private final Map<ComponentName, ComponentName> mA11yServiceToTileService = new ArrayMap<>();
+    private final Map<ComponentName, ComponentName> mA11yActivityToTileService = new ArrayMap<>();
 
     private Context mContext;
 
@@ -560,6 +566,8 @@
         pw.println("}");
         pw.append("     button target:{").append(mTargetAssignedToAccessibilityButton);
         pw.println("}");
+        pw.append("     qs shortcut targets:" + mAccessibilityQsTargets);
+        pw.println();
         pw.append("     Bound services:{");
         final int serviceCount = mBoundServices.size();
         for (int j = 0; j < serviceCount; j++) {
@@ -762,6 +770,8 @@
             return mAccessibilityShortcutKeyTargets;
         } else if (shortcutType == ACCESSIBILITY_BUTTON) {
             return mAccessibilityButtonTargets;
+        } else if (shortcutType == ShortcutConstants.UserShortcutType.QUICK_SETTINGS) {
+            return getA11yQsTargets();
         }
         return null;
     }
@@ -808,7 +818,8 @@
      */
     public boolean removeShortcutTargetLocked(@ShortcutType int shortcutType,
             ComponentName target) {
-        return getShortcutTargetsLocked(shortcutType).removeIf(name -> {
+        Set<String> targets = getShortcutTargetsLocked(shortcutType);
+        boolean result = targets.removeIf(name -> {
             ComponentName componentName;
             if (name == null
                     || (componentName = ComponentName.unflattenFromString(name)) == null) {
@@ -816,6 +827,11 @@
             }
             return componentName.equals(target);
         });
+        if (shortcutType == ShortcutConstants.UserShortcutType.QUICK_SETTINGS) {
+            updateA11yQsTargetLocked(targets);
+        }
+
+        return result;
     }
 
     /**
@@ -1034,4 +1050,60 @@
         }
         return false;
     }
+
+    public void updateTileServiceMapForAccessibilityServiceLocked() {
+        mA11yServiceToTileService.clear();
+        mInstalledServices.forEach(
+                a11yServiceInfo -> {
+                    String tileServiceName = a11yServiceInfo.getTileServiceName();
+                    if (!TextUtils.isEmpty(tileServiceName)) {
+                        ResolveInfo resolveInfo = a11yServiceInfo.getResolveInfo();
+                        ComponentName a11yFeature = new ComponentName(
+                                resolveInfo.serviceInfo.packageName,
+                                resolveInfo.serviceInfo.name
+                        );
+                        ComponentName tileService = new ComponentName(
+                                a11yFeature.getPackageName(),
+                                tileServiceName
+                        );
+                        mA11yServiceToTileService.put(a11yFeature, tileService);
+                    }
+                }
+        );
+    }
+
+    public void updateTileServiceMapForAccessibilityActivityLocked() {
+        mA11yActivityToTileService.clear();
+        mInstalledShortcuts.forEach(
+                a11yShortcutInfo -> {
+                    String tileServiceName = a11yShortcutInfo.getTileServiceName();
+                    if (!TextUtils.isEmpty(tileServiceName)) {
+                        ComponentName a11yFeature = a11yShortcutInfo.getComponentName();
+                        ComponentName tileService = new ComponentName(
+                                a11yFeature.getPackageName(),
+                                tileServiceName);
+                        mA11yActivityToTileService.put(a11yFeature, tileService);
+                    }
+                }
+        );
+    }
+
+    public void updateA11yQsTargetLocked(Set<String> targets) {
+        mAccessibilityQsTargets.clear();
+        mAccessibilityQsTargets.addAll(targets);
+    }
+
+    /**
+     * Returns a copy of the targets which has qs shortcut turned on
+     */
+    public ArraySet<String> getA11yQsTargets() {
+        return new ArraySet<>(mAccessibilityQsTargets);
+    }
+
+    public Map<ComponentName, ComponentName> getA11yFeatureToTileService() {
+        Map<ComponentName, ComponentName> featureToTileServiceMap = new ArrayMap<>();
+        featureToTileServiceMap.putAll(mA11yServiceToTileService);
+        featureToTileServiceMap.putAll(mA11yActivityToTileService);
+        return featureToTileServiceMap;
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index b818150..8c06bc8 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -2038,8 +2038,11 @@
      * @param windowId The windowId
      * @return The display ID
      */
-    public int getDisplayIdByUserIdAndWindowIdLocked(int userId, int windowId) {
-        final IBinder windowToken = getWindowTokenForUserAndWindowIdLocked(userId, windowId);
+    public int getDisplayIdByUserIdAndWindowId(int userId, int windowId) {
+        final IBinder windowToken;
+        synchronized (mLock) {
+            windowToken = getWindowTokenForUserAndWindowIdLocked(userId, windowId);
+        }
         if (traceWMEnabled()) {
             logTraceWM("getDisplayIdForWindow", "token=" + windowToken);
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java b/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java
index 40b6ff0..8b41873 100644
--- a/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java
@@ -116,6 +116,13 @@
     }
 
     /**
+     * Used for `cmd accessibility` to check hidraw access.
+     */
+    static BrailleDisplayScanner createScannerForShell() {
+        return getDefaultNativeScanner(new DefaultNativeInterface());
+    }
+
+    /**
      * Interface to scan for properties of connected Braille displays.
      *
      * <p>Helps simplify testing Braille Display APIs using test data without requiring
@@ -125,7 +132,6 @@
      * @see #getDefaultNativeScanner
      * @see #setTestData
      */
-    @VisibleForTesting
     interface BrailleDisplayScanner {
         Collection<Path> getHidrawNodePaths(@NonNull Path directory);
 
@@ -441,7 +447,7 @@
      * from HIDRAW nodes and perform ioctls using the provided {@link NativeInterface}.
      */
     @VisibleForTesting
-    BrailleDisplayScanner getDefaultNativeScanner(@NonNull NativeInterface nativeInterface) {
+    static BrailleDisplayScanner getDefaultNativeScanner(@NonNull NativeInterface nativeInterface) {
         Objects.requireNonNull(nativeInterface);
         return new BrailleDisplayScanner() {
             private static final String HIDRAW_DEVICE_GLOB = "hidraw*";
@@ -576,7 +582,7 @@
     }
 
     /** Native interface that actually calls native HIDRAW ioctls. */
-    private class DefaultNativeInterface implements NativeInterface {
+    private static class DefaultNativeInterface implements NativeInterface {
         @Override
         public int getHidrawDescSize(int fd) {
             return nativeGetHidrawDescSize(fd);
@@ -598,11 +604,11 @@
         }
     }
 
-    private native int nativeGetHidrawDescSize(int fd);
+    private static native int nativeGetHidrawDescSize(int fd);
 
-    private native byte[] nativeGetHidrawDesc(int fd, int descSize);
+    private static native byte[] nativeGetHidrawDesc(int fd, int descSize);
 
-    private native String nativeGetHidrawUniq(int fd);
+    private static native String nativeGetHidrawUniq(int fd);
 
-    private native int nativeGetHidrawBusType(int fd);
+    private static native int nativeGetHidrawBusType(int fd);
 }
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 4fc65bf..be2ad21 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -1601,6 +1601,9 @@
                                 + " pointers down.");
                 return;
             }
+            if (Flags.resetHoverEventTimerOnActionUp() && mEvents.size() == 0) {
+                return;
+            }
             // Send an accessibility event to announce the touch exploration start.
             mDispatcher.sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START);
             if (isSendMotionEventsEnabled()) {
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
index d9e25ef..e13994e 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
@@ -525,8 +525,9 @@
             mReceivedPointersDown |= pointerFlag;
             mReceivedPointers[pointerId].set(
                     event.getX(pointerIndex), event.getY(pointerIndex), event.getEventTime());
-
-            mPrimaryPointerId = pointerId;
+            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+                mPrimaryPointerId = pointerId;
+            }
         }
 
         /**
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 f4ea754..6d1ab9f 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -167,7 +167,7 @@
     })
     public @interface OverscrollState {}
 
-    @VisibleForTesting boolean mIsSinglePanningEnabled;
+    @VisibleForTesting final OneFingerPanningSettingsProvider mOneFingerPanningSettingsProvider;
 
     private final FullScreenMagnificationVibrationHelper mFullScreenMagnificationVibrationHelper;
 
@@ -201,7 +201,11 @@
                 displayId,
                 fullScreenMagnificationVibrationHelper,
                 /* magnificationLogger= */ null,
-                ViewConfiguration.get(context));
+                ViewConfiguration.get(context),
+                new OneFingerPanningSettingsProvider(
+                        context,
+                        Flags.enableMagnificationOneFingerPanningGesture()
+                ));
     }
 
     /** Constructor for tests. */
@@ -218,7 +222,9 @@
             int displayId,
             FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper,
             MagnificationLogger magnificationLogger,
-            ViewConfiguration viewConfiguration) {
+            ViewConfiguration viewConfiguration,
+            OneFingerPanningSettingsProvider oneFingerPanningSettingsProvider
+    ) {
         super(displayId, detectSingleFingerTripleTap, detectTwoFingerTripleTap,
                 detectShortcutTrigger, trace, callback);
         if (DEBUG_ALL) {
@@ -301,9 +307,7 @@
         mPanningScalingState = new PanningScalingState(context);
         mSinglePanningState = new SinglePanningState(context);
         mFullScreenMagnificationVibrationHelper = fullScreenMagnificationVibrationHelper;
-        setSinglePanningEnabled(
-                context.getResources()
-                        .getBoolean(R.bool.config_enable_a11y_magnification_single_panning));
+        mOneFingerPanningSettingsProvider = oneFingerPanningSettingsProvider;
         mOverscrollHandler = new OverscrollHandler();
         mIsWatch = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
 
@@ -317,11 +321,6 @@
         transitionTo(mDetectingState);
     }
 
-    @VisibleForTesting
-    void setSinglePanningEnabled(boolean isEnabled) {
-        mIsSinglePanningEnabled = isEnabled;
-    }
-
     @Override
     void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         if (event.getActionMasked() == ACTION_DOWN) {
@@ -361,6 +360,7 @@
             Slog.i(mLogTag, "onDestroy(); delayed = "
                     + MotionEventInfo.toString(mDetectingState.mDelayedEventQueue));
         }
+        mOneFingerPanningSettingsProvider.unregister();
 
         if (mScreenStateReceiver != null) {
             mScreenStateReceiver.unregister();
@@ -524,7 +524,7 @@
                     && event.getPointerCount() == 2 // includes the pointer currently being released
                     && mPreviousState == mViewportDraggingState) {
                 // if feature flag is enabled, currently only true on watches
-                if (mIsSinglePanningEnabled) {
+                if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()) {
                     mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded();
                     mOverscrollHandler.clearEdgeState();
                 }
@@ -532,7 +532,7 @@
             } else if (action == ACTION_UP || action == ACTION_CANCEL) {
                 onPanningFinished(event);
                 // if feature flag is enabled, currently only true on watches
-                if (mIsSinglePanningEnabled) {
+                if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()) {
                     mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded();
                     mOverscrollHandler.clearEdgeState();
                 }
@@ -611,7 +611,7 @@
             onPan(second);
             mFullScreenMagnificationController.offsetMagnifiedRegion(mDisplayId, distanceX,
                     distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
-            if (mIsSinglePanningEnabled) {
+            if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()) {
                 mOverscrollHandler.onScrollStateChanged(first, second);
             }
             return /* event consumed: */ true;
@@ -1000,21 +1000,23 @@
                                 && event.getPointerCount() == 2) {
                             transitionToViewportDraggingStateAndClear(event);
                         } else if (isActivated() && event.getPointerCount() == 2) {
-                            if (mIsSinglePanningEnabled
+                            if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()
                                     && overscrollState(event, mFirstPointerDownLocation)
                                     == OVERSCROLL_VERTICAL_EDGE) {
                                 transitionToDelegatingStateAndClear();
-                            } // TODO(b/319537921): should there be an else here?
-                            //Primary pointer is swiping, so transit to PanningScalingState
-                            transitToPanningScalingStateAndClear();
-                        } else if (mIsSinglePanningEnabled
+                            } else {
+                                //Primary pointer is swiping, so transit to PanningScalingState
+                                transitToPanningScalingStateAndClear();
+                            }
+                        } else if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()
                                 && isActivated()
                                 && event.getPointerCount() == 1) {
                             if (overscrollState(event, mFirstPointerDownLocation)
                                     == OVERSCROLL_VERTICAL_EDGE) {
                                 transitionToDelegatingStateAndClear();
-                            } // TODO(b/319537921): should there be an else here?
-                            transitToSinglePanningStateAndClear();
+                            } else {
+                                transitToSinglePanningStateAndClear();
+                            }
                         } else if (!mIsTwoFingerCountReached) {
                             // If it is a two-finger gesture, do not transition to the
                             // delegating state to ensure the reachability of
@@ -1253,21 +1255,23 @@
                         if (isMultiTapTriggered(2 /* taps */) && event.getPointerCount() == 1) {
                             transitionToViewportDraggingStateAndClear(event);
                         } else if (isActivated() && event.getPointerCount() == 2) {
-                            if (mIsSinglePanningEnabled
+                            if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()
                                     && overscrollState(event, mFirstPointerDownLocation)
                                     == OVERSCROLL_VERTICAL_EDGE) {
                                 transitionToDelegatingStateAndClear();
+                            } else {
+                                //Primary pointer is swiping, so transit to PanningScalingState
+                                transitToPanningScalingStateAndClear();
                             }
-                            //Primary pointer is swiping, so transit to PanningScalingState
-                            transitToPanningScalingStateAndClear();
-                        } else if (mIsSinglePanningEnabled
+                        } else if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()
                                 && isActivated()
                                 && event.getPointerCount() == 1) {
                             if (overscrollState(event, mFirstPointerDownLocation)
                                     == OVERSCROLL_VERTICAL_EDGE) {
                                 transitionToDelegatingStateAndClear();
+                            } else {
+                                transitToSinglePanningStateAndClear();
                             }
-                            transitToSinglePanningStateAndClear();
                         } else {
                             transitionToDelegatingStateAndClear();
                         }
@@ -1629,7 +1633,8 @@
                 + ", mPreviousState=" + State.nameOf(mPreviousState)
                 + ", mMagnificationController=" + mFullScreenMagnificationController
                 + ", mDisplayId=" + mDisplayId
-                + ", mIsSinglePanningEnabled=" + mIsSinglePanningEnabled
+                + ", mIsSinglePanningEnabled="
+                + mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()
                 + ", mOverscrollHandler=" + mOverscrollHandler
                 + '}';
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/OneFingerPanningSettingsProvider.java b/services/accessibility/java/com/android/server/accessibility/magnification/OneFingerPanningSettingsProvider.java
new file mode 100644
index 0000000..3cdaf98
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/OneFingerPanningSettingsProvider.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.accessibility.magnification;
+
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.provider.Settings;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Provider for secure settings {@link Settings.Secure.ACCESSIBILITY_SINGLE_FINGER_PANNING_ENABLED}.
+ */
+public class OneFingerPanningSettingsProvider {
+
+    @VisibleForTesting
+    static final String KEY = Settings.Secure.ACCESSIBILITY_SINGLE_FINGER_PANNING_ENABLED;
+    private static final Uri URI = Settings.Secure.getUriFor(KEY);
+    private AtomicBoolean mCached = new AtomicBoolean();
+    @VisibleForTesting
+    ContentObserver mObserver;
+    @VisibleForTesting
+    ContentResolver mContentResolver;
+
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface State {
+        int OFF = 0;
+        int ON = 1;
+    }
+
+    public OneFingerPanningSettingsProvider(
+            Context context,
+            boolean featureFlagEnabled
+    ) {
+        var defaultValue = isOneFingerPanningEnabledDefault(context);
+        if (featureFlagEnabled) {
+            mContentResolver = context.getContentResolver();
+            mObserver = new ContentObserver(context.getMainThreadHandler()) {
+                @Override
+                public void onChange(boolean selfChange) {
+                    mCached.set(isOneFingerPanningEnabledInSetting(context, defaultValue));
+                }
+            };
+            mCached.set(isOneFingerPanningEnabledInSetting(context, defaultValue));
+            mContentResolver.registerContentObserver(URI, false, mObserver);
+        } else {
+            mCached.set(defaultValue);
+        }
+    }
+
+    /** Returns whether one finger panning is enabled.. */
+    public boolean isOneFingerPanningEnabled() {
+        return mCached.get();
+    }
+
+    /** Unregister content observer for listening to secure settings. */
+    public void unregister() {
+        if (mContentResolver != null) {
+            mContentResolver.unregisterContentObserver(mObserver);
+        }
+        mContentResolver = null;
+    }
+
+    private boolean isOneFingerPanningEnabledInSetting(Context context, boolean defaultValue) {
+        return State.ON == Settings.Secure.getIntForUser(
+                mContentResolver,
+                KEY,
+                (defaultValue ? State.ON : State.OFF),
+                context.getUserId());
+    }
+
+    @VisibleForTesting
+    static boolean isOneFingerPanningEnabledDefault(Context context) {
+        boolean oneFingerPanningDefaultValue;
+        try {
+            oneFingerPanningDefaultValue = context.getResources().getBoolean(
+                    com.android.internal.R.bool.config_enable_a11y_magnification_single_panning);
+        } catch (Resources.NotFoundException e) {
+            oneFingerPanningDefaultValue = false;
+        }
+        return oneFingerPanningDefaultValue;
+    }
+}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 6f45f60..1749ee3 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -84,6 +84,7 @@
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
@@ -98,6 +99,7 @@
 import android.service.appwidget.AppWidgetServiceDumpProto;
 import android.service.appwidget.WidgetProto;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.AttributeSet;
@@ -148,6 +150,7 @@
 import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -159,6 +162,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.LongSupplier;
 
 class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBackupProvider,
         OnCrossProfileWidgetProvidersChangeListener {
@@ -187,6 +191,13 @@
     // used to verify which request has successfully been received by the host.
     private static final AtomicLong UPDATE_COUNTER = new AtomicLong();
 
+    // Default reset interval for generated preview API rate limiting.
+    private static final long DEFAULT_GENERATED_PREVIEW_RESET_INTERVAL_MS =
+            Duration.ofHours(1).toMillis();
+    // Default max API calls per reset interval for generated preview API rate limiting.
+    private static final int DEFAULT_GENERATED_PREVIEW_MAX_CALLS_PER_INTERVAL = 2;
+
+
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -266,6 +277,8 @@
     // Mark widget lifecycle broadcasts as 'interactive'
     private Bundle mInteractiveBroadcast;
 
+    private ApiCounter mGeneratedPreviewsApiCounter;
+
     AppWidgetServiceImpl(Context context) {
         mContext = context;
     }
@@ -294,6 +307,17 @@
         mIsCombinedBroadcastEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
             SystemUiDeviceConfigFlags.COMBINED_BROADCAST_ENABLED, true);
 
+        final long generatedPreviewResetInterval = DeviceConfig.getLong(NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_RESET_INTERVAL_MS,
+                DEFAULT_GENERATED_PREVIEW_RESET_INTERVAL_MS);
+        final int generatedPreviewMaxCallsPerInterval = DeviceConfig.getInt(NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_RESET_INTERVAL_MS,
+                DEFAULT_GENERATED_PREVIEW_MAX_CALLS_PER_INTERVAL);
+        mGeneratedPreviewsApiCounter = new ApiCounter(generatedPreviewResetInterval,
+                generatedPreviewMaxCallsPerInterval);
+        DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI,
+                new HandlerExecutor(mCallbackHandler), this::handleSystemUiDeviceConfigChange);
+
         BroadcastOptions opts = BroadcastOptions.makeBasic();
         opts.setBackgroundActivityStartsAllowed(false);
         opts.setInteractive(true);
@@ -476,8 +500,12 @@
             } else {
                 // If the package is being updated, we'll receive a PACKAGE_ADDED
                 // shortly, otherwise it is removed permanently.
-                final boolean packageRemovedPermanently = (extras == null
-                        || !extras.getBoolean(Intent.EXTRA_REPLACING, false));
+                boolean isReplacing = extras != null && extras.getBoolean(Intent.EXTRA_REPLACING,
+                        false);
+                boolean isArchival = extras != null && extras.getBoolean(Intent.EXTRA_ARCHIVAL,
+                        false);
+                final boolean packageRemovedPermanently =
+                        (extras == null || !isReplacing || (isReplacing && isArchival));
 
                 if (packageRemovedPermanently) {
                     for (String pkgName : pkgList) {
@@ -2074,6 +2102,7 @@
     private void handleNotifyAppWidgetViewDataChanged(Host host, IAppWidgetHost callbacks,
             int appWidgetId, int viewId, long requestId) {
         try {
+            Slog.d(TAG, "Trying to notify widget view data changed");
             callbacks.viewDataChanged(appWidgetId, viewId);
             host.lastWidgetUpdateSequenceNo = requestId;
         } catch (RemoteException re) {
@@ -2158,6 +2187,9 @@
     private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks,
             int appWidgetId, RemoteViews views, long requestId) {
         try {
+            Slog.d(TAG, "Trying to notify widget update for package "
+                    + (views == null ? "null" : views.getPackage())
+                    + " with widget id: " + appWidgetId);
             callbacks.updateAppWidget(appWidgetId, views);
             host.lastWidgetUpdateSequenceNo = requestId;
         } catch (RemoteException re) {
@@ -2196,6 +2228,7 @@
     private void handleNotifyProviderChanged(Host host, IAppWidgetHost callbacks,
             int appWidgetId, AppWidgetProviderInfo info, long requestId) {
         try {
+            Slog.d(TAG, "Trying to notify provider update");
             callbacks.providerChanged(appWidgetId, info);
             host.lastWidgetUpdateSequenceNo = requestId;
         } catch (RemoteException re) {
@@ -2239,6 +2272,7 @@
     private void handleNotifyAppWidgetRemoved(Host host, IAppWidgetHost callbacks, int appWidgetId,
             long requestId) {
         try {
+            Slog.d(TAG, "Trying to notify widget removed");
             callbacks.appWidgetRemoved(appWidgetId);
             host.lastWidgetUpdateSequenceNo = requestId;
         } catch (RemoteException re) {
@@ -2286,6 +2320,7 @@
 
     private void handleNotifyProvidersChanged(Host host, IAppWidgetHost callbacks) {
         try {
+            Slog.d(TAG, "Trying to notify widget providers changed");
             callbacks.providersChanged();
         } catch (RemoteException re) {
             synchronized (mLock) {
@@ -2415,7 +2450,8 @@
         AppWidgetProviderInfo info = createPartialProviderInfo(providerId, ri, existing);
 
         if (android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.disablePrivateSpaceItemsOnHome()) {
+                && android.multiuser.Flags.disablePrivateSpaceItemsOnHome()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
             // Do not add widget providers for profiles with items restricted on home screen.
             if (info != null && mUserManager
                     .getUserProperties(info.getProfile()).areItemsRestrictedOnHomeScreen()) {
@@ -2469,6 +2505,7 @@
     private void deleteProviderLocked(Provider provider) {
         deleteWidgetsLocked(provider, UserHandle.USER_ALL);
         mProviders.remove(provider);
+        mGeneratedPreviewsApiCounter.remove(provider.id);
 
         // no need to send the DISABLE broadcast, since the receiver is gone anyway
         cancelBroadcastsLocked(provider);
@@ -3993,7 +4030,7 @@
     }
 
     @Override
-    public void setWidgetPreview(@NonNull ComponentName providerComponent,
+    public boolean setWidgetPreview(@NonNull ComponentName providerComponent,
             @AppWidgetProviderInfo.CategoryFlags int widgetCategories,
             @NonNull RemoteViews preview) {
         final int userId = UserHandle.getCallingUserId();
@@ -4015,8 +4052,12 @@
                 throw new IllegalArgumentException(
                         providerComponent + " is not a valid AppWidget provider");
             }
-            provider.setGeneratedPreviewLocked(widgetCategories, preview);
-            scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+            if (mGeneratedPreviewsApiCounter.tryApiCall(providerId)) {
+                provider.setGeneratedPreviewLocked(widgetCategories, preview);
+                scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+                return true;
+            }
+            return false;
         }
     }
 
@@ -4057,6 +4098,26 @@
         }
     }
 
+    private void handleSystemUiDeviceConfigChange(DeviceConfig.Properties properties) {
+        Set<String> changed = properties.getKeyset();
+        synchronized (mLock) {
+            if (changed.contains(
+                    SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_RESET_INTERVAL_MS)) {
+                long resetIntervalMs = properties.getLong(
+                        SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_RESET_INTERVAL_MS,
+                        /* defaultValue= */ mGeneratedPreviewsApiCounter.getResetIntervalMs());
+                mGeneratedPreviewsApiCounter.setResetIntervalMs(resetIntervalMs);
+            }
+            if (changed.contains(
+                    SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_MAX_CALLS_PER_INTERVAL)) {
+                int maxCallsPerInterval = properties.getInt(
+                        SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_MAX_CALLS_PER_INTERVAL,
+                        /* defaultValue= */ mGeneratedPreviewsApiCounter.getMaxCallsPerInterval());
+                mGeneratedPreviewsApiCounter.setMaxCallsPerInterval(maxCallsPerInterval);
+            }
+        }
+    }
+
     private final class CallbackHandler extends Handler {
         public static final int MSG_NOTIFY_UPDATE_APP_WIDGET = 1;
         public static final int MSG_NOTIFY_PROVIDER_CHANGED = 2;
@@ -4530,11 +4591,11 @@
         }
     }
 
-    private static final class ProviderId {
+    static final class ProviderId {
         final int uid;
         final ComponentName componentName;
 
-        private ProviderId(int uid, ComponentName componentName) {
+        ProviderId(int uid, ComponentName componentName) {
             this.uid = uid;
             this.componentName = componentName;
         }
@@ -4777,6 +4838,96 @@
         }
     }
 
+    /**
+     * This class keeps track of API calls and implements rate limiting. One instance of this class
+     * tracks calls from all providers for one API, or a group of APIs that should share the same
+     * rate limit.
+     */
+    static final class ApiCounter {
+
+        private static final class ApiCallRecord {
+            // Number of times the API has been called for this provider.
+            public int apiCallCount = 0;
+            // The last time (from SystemClock.elapsedRealtime) the api call count was reset.
+            public long lastResetTimeMs = 0;
+
+            void reset(long nowMs) {
+                apiCallCount = 0;
+                lastResetTimeMs = nowMs;
+            }
+        }
+
+        private final Map<ProviderId, ApiCallRecord> mCallCount = new ArrayMap<>();
+        // The interval at which the call count is reset.
+        private long mResetIntervalMs;
+        // The max number of API calls per interval.
+        private int mMaxCallsPerInterval;
+        // Returns the current time (monotonic). By default this is SystemClock.elapsedRealtime.
+        private LongSupplier mMonotonicClock;
+
+        ApiCounter(long resetIntervalMs, int maxCallsPerInterval) {
+            this(resetIntervalMs, maxCallsPerInterval, SystemClock::elapsedRealtime);
+        }
+
+        ApiCounter(long resetIntervalMs, int maxCallsPerInterval,
+                LongSupplier monotonicClock) {
+            mResetIntervalMs = resetIntervalMs;
+            mMaxCallsPerInterval = maxCallsPerInterval;
+            mMonotonicClock = monotonicClock;
+        }
+
+        public void setResetIntervalMs(long resetIntervalMs) {
+            mResetIntervalMs = resetIntervalMs;
+        }
+
+        public long getResetIntervalMs() {
+            return mResetIntervalMs;
+        }
+
+        public void setMaxCallsPerInterval(int maxCallsPerInterval) {
+            mMaxCallsPerInterval = maxCallsPerInterval;
+        }
+
+        public int getMaxCallsPerInterval() {
+            return mMaxCallsPerInterval;
+        }
+
+        /**
+         * Returns true if the API call for the provider should be allowed, false if it should be
+         * rate-limited.
+         */
+        public boolean tryApiCall(@NonNull ProviderId provider) {
+            final ApiCallRecord record = getOrCreateRecord(provider);
+            final long now = mMonotonicClock.getAsLong();
+            final long timeSinceLastResetMs = now - record.lastResetTimeMs;
+            // If the last reset was beyond the reset interval, reset now.
+            if (timeSinceLastResetMs > mResetIntervalMs) {
+                record.reset(now);
+            }
+            if (record.apiCallCount < mMaxCallsPerInterval) {
+                record.apiCallCount++;
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * Remove the provider's call record from this counter, when the provider is no longer
+         * tracked.
+         */
+        public void remove(@NonNull ProviderId id) {
+            mCallCount.remove(id);
+        }
+
+        @NonNull
+        private ApiCallRecord getOrCreateRecord(@NonNull ProviderId provider) {
+            if (!mCallCount.containsKey(provider)) {
+                mCallCount.put(provider, new ApiCallRecord());
+            }
+            return mCallCount.get(provider);
+        }
+    }
+
     private class LoadedWidgetState {
         final Widget widget;
         final int hostTag;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index c7b844b..ca2a3dd 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -193,7 +193,6 @@
 import com.android.server.wm.ActivityTaskManagerInternal;
 
 import java.io.PrintWriter;
-import java.io.Serializable;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
@@ -1247,7 +1246,8 @@
     @GuardedBy("mLock")
     private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState,
             int flags) {
-        final FillResponse existingResponse = shouldRequestSecondaryProvider(flags)
+        boolean isSecondary = shouldRequestSecondaryProvider(flags);
+        final FillResponse existingResponse = isSecondary
                 ? viewState.getSecondaryResponse() : viewState.getResponse();
         mFillRequestEventLogger.startLogForNewRequest();
         mRequestCount++;
@@ -1284,12 +1284,7 @@
         }
 
         viewState.setState(newState);
-
-        int requestId;
-        // TODO(b/158623971): Update this to prevent possible overflow
-        do {
-            requestId = sIdCounter.getAndIncrement();
-        } while (requestId == INVALID_REQUEST_ID);
+        int requestId = getRequestId(isSecondary);
 
         // Create a metrics log for the request
         final int ordinal = mRequestLogs.size() + 1;
@@ -1368,6 +1363,25 @@
         requestAssistStructureLocked(requestId, flags);
     }
 
+    private static int getRequestId(boolean isSecondary) {
+        // For authentication flows, there needs to be a way to know whether to retrieve the Fill
+        // Response from the primary provider or the secondary provider from the requestId. A simple
+        // way to achieve this is by assigning odd number request ids to secondary provider and
+        // even numbers to primary provider.
+        int requestId;
+        // TODO(b/158623971): Update this to prevent possible overflow
+        if (isSecondary) {
+            do {
+                requestId = sIdCounter.getAndIncrement();
+            } while (!isSecondaryProviderRequestId(requestId));
+        } else {
+            do {
+                requestId = sIdCounter.getAndIncrement();
+            } while (requestId == INVALID_REQUEST_ID || isSecondaryProviderRequestId(requestId));
+        }
+        return requestId;
+    }
+
     private boolean isRequestSupportFillDialog(int flags) {
         return (flags & FLAG_SUPPORTS_FILL_DIALOG) != 0;
     }
@@ -2791,7 +2805,9 @@
             removeFromService();
             return;
         }
-        final FillResponse authenticatedResponse = mResponses.get(requestId);
+        final FillResponse authenticatedResponse = isSecondaryProviderRequestId(requestId)
+                ? mSecondaryResponses.get(requestId)
+                : mResponses.get(requestId);
         if (authenticatedResponse == null || data == null) {
             Slog.w(TAG, "no authenticated response");
             mPresentationStatsEventLogger.maybeSetAuthenticationResult(
@@ -2821,8 +2837,9 @@
         mSessionFlags.mExpiredResponse = false;
 
         final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT);
-        final Serializable exception = data.getSerializable(
-                CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION);
+        final GetCredentialException exception = data.getSerializable(
+                CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION,
+                GetCredentialException.class);
 
         final Bundle newClientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE);
         if (sDebug) {
@@ -2915,6 +2932,10 @@
         }
     }
 
+    private static boolean isSecondaryProviderRequestId(int requestId) {
+        return requestId % 2 == 1;
+    }
+
     private Dataset getDatasetFromCredentialResponse(GetCredentialResponse result) {
         if (result == null) {
             return null;
@@ -3935,6 +3956,24 @@
      */
     @GuardedBy("mLock")
     @Nullable
+    private ViewNode getViewNodeFromContextsLocked(@NonNull AutofillId autofillId) {
+        final int numContexts = mContexts.size();
+        for (int i = numContexts - 1; i >= 0; i--) {
+            final FillContext context = mContexts.get(i);
+            final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(),
+                    autofillId);
+            if (node != null) {
+                return node;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets the latest non-empty value for the given id in the autofill contexts.
+     */
+    @GuardedBy("mLock")
+    @Nullable
     private AutofillValue getValueFromContextsLocked(@NonNull AutofillId autofillId) {
         final int numContexts = mContexts.size();
         for (int i = numContexts - 1; i >= 0; i--) {
@@ -4782,7 +4821,6 @@
         }
 
         if (isCredmanIntegrationActive(response)) {
-            Slog.d(TAG, "Attempting to add Credential Manager callback to pinned entries");
             addCredentialManagerCallback(response);
         }
 
@@ -5126,6 +5164,10 @@
                 toIpcFriendlyResultReceiver(resultReceiver);
 
         Intent metadataIntent = dataset.getCredentialFillInIntent();
+        if (metadataIntent == null) {
+            metadataIntent = new Intent();
+        }
+
         metadataIntent.putExtra(
                 android.credentials.selection.Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
                 ipcFriendlyResultReceiver);
@@ -5709,7 +5751,6 @@
                 /* isPrimary= */ true);
         updateFillDialogTriggerIdsLocked();
         updateTrackedIdsLocked();
-
         if (mCurrentViewId == null) {
             return;
         }
@@ -6415,7 +6456,21 @@
                     mClient.onGetCredentialException(id, viewId, exception.getType(),
                             exception.getMessage());
                 } else if (response != null) {
-                    mClient.onGetCredentialResponse(id, viewId, response);
+                    if (viewId.isVirtualInt()) {
+                        ViewNode viewNode = getViewNodeFromContextsLocked(viewId);
+                        if (viewNode != null && viewNode.getPendingCredentialCallback() != null) {
+                            Bundle resultData = new Bundle();
+                            resultData.putParcelable(
+                                    CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
+                                    response);
+                            viewNode.getPendingCredentialCallback().send(SUCCESS_CREDMAN_SELECTOR,
+                                        resultData);
+                        } else {
+                            Slog.w(TAG, "View node not found after GetCredentialResponse");
+                        }
+                    } else {
+                        mClient.onGetCredentialResponse(id, viewId, response);
+                    }
                 } else {
                     Slog.w(TAG, "sendCredentialManagerResponseToApp called with null response"
                             + "and exception");
diff --git a/services/backup/BACKUP_OWNERS b/services/backup/BACKUP_OWNERS
index f8f4f4f..29ae202 100644
--- a/services/backup/BACKUP_OWNERS
+++ b/services/backup/BACKUP_OWNERS
@@ -2,9 +2,10 @@
 
 jstemmer@google.com
 martinoh@google.com
-millmore@google.com
 niamhfw@google.com
 piee@google.com
 philippov@google.com
 rthakohov@google.com
-sarpm@google.com
\ No newline at end of file
+sarpm@google.com
+beatricemarch@google.com
+azilio@google.com
\ No newline at end of file
diff --git a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
index 6e98e68..1f8736b 100644
--- a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
+++ b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
@@ -24,7 +24,6 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
 import android.content.pm.Signature;
 import android.content.pm.SigningInfo;
@@ -32,7 +31,7 @@
 import android.os.ParcelFileDescriptor;
 import android.util.Slog;
 
-import com.android.server.LocalServices;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.backup.utils.BackupEligibilityRules;
 
 import java.io.BufferedInputStream;
@@ -49,16 +48,14 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Objects;
 import java.util.Set;
 
 /**
- * We back up the signatures of each package so that during a system restore,
- * we can verify that the app whose data we think we have matches the app
- * actually resident on the device.
+ * We back up the signatures of each package so that during a system restore, we can verify that the
+ * app whose data we think we have matches the app actually resident on the device.
  *
- * Since the Package Manager isn't a proper "application" we just provide a
- * direct IBackupAgent implementation and hand-construct it at need.
+ * <p>Since the Package Manager isn't a proper "application" we just provide a direct IBackupAgent
+ * implementation and hand-construct it at need.
  */
 public class PackageManagerBackupAgent extends BackupAgent {
     private static final String TAG = "PMBA";
@@ -66,7 +63,7 @@
 
     // key under which we store global metadata (individual app metadata
     // is stored using the package name as a key)
-    private static final String GLOBAL_METADATA_KEY = "@meta@";
+    @VisibleForTesting static final String GLOBAL_METADATA_KEY = "@meta@";
 
     // key under which we store the identity of the user's chosen default home app
     private static final String DEFAULT_HOME_KEY = "@home@";
@@ -76,19 +73,19 @@
     // ANCESTRAL_RECORD_VERSION=1 (introduced Android P).
     // Should the ANCESTRAL_RECORD_VERSION be bumped up in the future, STATE_FILE_VERSION will also
     // need bumping up, assuming more data needs saving to the state file.
-    private static final String STATE_FILE_HEADER = "=state=";
-    private static final int STATE_FILE_VERSION = 2;
+    @VisibleForTesting static final String STATE_FILE_HEADER = "=state=";
+    @VisibleForTesting static final int STATE_FILE_VERSION = 2;
 
     // key under which we store the saved ancestral-dataset format (starting from Android P)
     // IMPORTANT: this key needs to come first in the restore data stream (to find out
     // whether this version of Android knows how to restore the incoming data set), so it needs
     // to be always the first one in alphabetical order of all the keys
-    private static final String ANCESTRAL_RECORD_KEY = "@ancestral_record@";
+    @VisibleForTesting static final String ANCESTRAL_RECORD_KEY = "@ancestral_record@";
 
     // Current version of the saved ancestral-dataset format
     // Note that this constant was not used until Android P, and started being used
     // to version @pm@ data for forwards-compatibility.
-    private static final int ANCESTRAL_RECORD_VERSION = 1;
+    @VisibleForTesting static final int ANCESTRAL_RECORD_VERSION = 1;
 
     // Undefined version of the saved ancestral-dataset file format means that the restore data
     // is coming from pre-Android P device.
@@ -134,8 +131,8 @@
         init(packageMgr, packages, userId);
     }
 
-    public PackageManagerBackupAgent(PackageManager packageMgr, int userId,
-            BackupEligibilityRules backupEligibilityRules) {
+    public PackageManagerBackupAgent(
+            PackageManager packageMgr, int userId, BackupEligibilityRules backupEligibilityRules) {
         init(packageMgr, null, userId);
 
         evaluateStorablePackages(backupEligibilityRules);
@@ -159,12 +156,12 @@
     }
 
     /** Gets all packages installed on user {@code userId} eligible for backup. */
-    public static List<PackageInfo> getStorableApplications(PackageManager pm, int userId,
-            BackupEligibilityRules backupEligibilityRules) {
+    public static List<PackageInfo> getStorableApplications(
+            PackageManager pm, int userId, BackupEligibilityRules backupEligibilityRules) {
         List<PackageInfo> pkgs =
                 pm.getInstalledPackagesAsUser(PackageManager.GET_SIGNING_CERTIFICATES, userId);
         int N = pkgs.size();
-        for (int a = N-1; a >= 0; a--) {
+        for (int a = N - 1; a >= 0; a--) {
             PackageInfo pkg = pkgs.get(a);
             if (!backupEligibilityRules.appIsEligibleForBackup(pkg.applicationInfo)) {
                 pkgs.remove(a);
@@ -204,12 +201,14 @@
         return mRestoredSignatures.keySet();
     }
 
-    // The backed up data is the signature block for each app, keyed by the package name.
-    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
-            ParcelFileDescriptor newState) {
+    @Override
+    public void onBackup(
+            ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) {
         if (DEBUG) Slog.v(TAG, "onBackup()");
 
-        ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream();  // we'll reuse these
+        // The backed up data is the signature block for each app, keyed by the package name.
+
+        ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream(); // we'll reuse these
         DataOutputStream outputBufferStream = new DataOutputStream(outputBuffer);
         parseStateFile(oldState);
 
@@ -218,8 +217,13 @@
         // "already backed up" map built by parseStateFile().
         if (mStoredIncrementalVersion == null
                 || !mStoredIncrementalVersion.equals(Build.VERSION.INCREMENTAL)) {
-            Slog.i(TAG, "Previous metadata " + mStoredIncrementalVersion + " mismatch vs "
-                    + Build.VERSION.INCREMENTAL + " - rewriting");
+            Slog.i(
+                    TAG,
+                    "Previous metadata "
+                            + mStoredIncrementalVersion
+                            + " mismatch vs "
+                            + Build.VERSION.INCREMENTAL
+                            + " - rewriting");
             mExisting.clear();
         }
 
@@ -271,8 +275,9 @@
                 } else {
                     PackageInfo info = null;
                     try {
-                        info = mPackageManager.getPackageInfoAsUser(packName,
-                                PackageManager.GET_SIGNING_CERTIFICATES, mUserId);
+                        info =
+                                mPackageManager.getPackageInfoAsUser(
+                                        packName, PackageManager.GET_SIGNING_CERTIFICATES, mUserId);
                     } catch (NameNotFoundException e) {
                         // Weird; we just found it, and now are told it doesn't exist.
                         // Treat it as having been removed from the device.
@@ -294,8 +299,11 @@
 
                     SigningInfo signingInfo = info.signingInfo;
                     if (signingInfo == null) {
-                        Slog.w(TAG, "Not backing up package " + packName
-                                + " since it appears to have no signatures.");
+                        Slog.w(
+                                TAG,
+                                "Not backing up package "
+                                        + packName
+                                        + " since it appears to have no signatures.");
                         continue;
                     }
 
@@ -317,15 +325,20 @@
                     }
                     // retrieve the newest sigs to back up
                     Signature[] infoSignatures = signingInfo.getApkContentsSigners();
-                    writeSignatureHashArray(outputBufferStream,
-                            BackupUtils.hashSignatureArray(infoSignatures));
+                    writeSignatureHashArray(
+                            outputBufferStream, BackupUtils.hashSignatureArray(infoSignatures));
 
                     if (DEBUG) {
-                        Slog.v(TAG, "+ writing metadata for " + packName
-                                + " version=" + info.getLongVersionCode()
-                                + " entityLen=" + outputBuffer.size());
+                        Slog.v(
+                                TAG,
+                                "+ writing metadata for "
+                                        + packName
+                                        + " version="
+                                        + info.getLongVersionCode()
+                                        + " entityLen="
+                                        + outputBuffer.size());
                     }
-                    
+
                     // Now we can write the backup entity for this package
                     writeEntity(data, packName, outputBuffer.toByteArray());
                 }
@@ -363,13 +376,15 @@
         data.writeEntityData(bytes, bytes.length);
     }
 
-    // "Restore" here is a misnomer.  What we're really doing is reading back the
-    // set of app signatures associated with each backed-up app in this restore
-    // image.  We'll use those later to determine what we can legitimately restore.
+    @Override
     public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
             throws IOException {
         if (DEBUG) Slog.v(TAG, "onRestore()");
 
+        // "Restore" here is a misnomer.  What we're really doing is reading back the
+        // set of app signatures associated with each backed-up app in this restore
+        // image.  We'll use those later to determine what we can legitimately restore.
+
         // we expect the ANCESTRAL_RECORD_KEY ("@ancestral_record@") to always come first in the
         // restore set - based on that value we use different mechanisms to consume the data;
         // if the ANCESTRAL_RECORD_KEY is missing in the restore set, it means that the data is
@@ -380,8 +395,10 @@
 
         RestoreDataConsumer consumer = getRestoreDataConsumer(ancestralRecordVersion);
         if (consumer == null) {
-            Slog.w(TAG, "Ancestral restore set version is unknown"
-                    + " to this Android version; not restoring");
+            Slog.w(
+                    TAG,
+                    "Ancestral restore set version is unknown"
+                            + " to this Android version; not restoring");
             return;
         } else {
             consumer.consumeRestoreData(data);
@@ -443,9 +460,9 @@
                 Slog.w(TAG, "Read empty signature block");
                 return null;
             }
-            
+
             if (DEBUG) Slog.v(TAG, " ... unflatten read " + num);
-            
+
             // Sensical?
             if (num > 20) {
                 Slog.e(TAG, "Suspiciously large sig count in restore data; aborting");
@@ -506,8 +523,11 @@
             if (pkg.equals(STATE_FILE_HEADER)) {
                 int stateVersion = in.readInt();
                 if (stateVersion > STATE_FILE_VERSION) {
-                    Slog.w(TAG, "Unsupported state file version " + stateVersion
-                            + ", redoing from start");
+                    Slog.w(
+                            TAG,
+                            "Unsupported state file version "
+                                    + stateVersion
+                                    + ", redoing from start");
                     return;
                 }
                 pkg = in.readUTF();
@@ -574,7 +594,8 @@
     }
 
     // Util: write out our new backup state file
-    private void writeStateFile(List<PackageInfo> pkgs, ParcelFileDescriptor stateFile) {
+    @VisibleForTesting
+    static void writeStateFile(List<PackageInfo> pkgs, ParcelFileDescriptor stateFile) {
         FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
         BufferedOutputStream outbuf = new BufferedOutputStream(outstream);
         DataOutputStream out = new DataOutputStream(outbuf);
@@ -640,10 +661,17 @@
                     mStoredIncrementalVersion = inputBufferStream.readUTF();
                     mHasMetadata = true;
                     if (DEBUG) {
-                        Slog.i(TAG, "Restore set version " + storedSystemVersion
-                                + " is compatible with OS version " + Build.VERSION.SDK_INT
-                                + " (" + mStoredIncrementalVersion + " vs "
-                                + Build.VERSION.INCREMENTAL + ")");
+                        Slog.i(
+                                TAG,
+                                "Restore set version "
+                                        + storedSystemVersion
+                                        + " is compatible with OS version "
+                                        + Build.VERSION.SDK_INT
+                                        + " ("
+                                        + mStoredIncrementalVersion
+                                        + " vs "
+                                        + Build.VERSION.INCREMENTAL
+                                        + ")");
                     }
                 } else if (key.equals(DEFAULT_HOME_KEY)) {
                     String cn = inputBufferStream.readUTF();
@@ -652,10 +680,16 @@
                     mRestoredHomeInstaller = inputBufferStream.readUTF();
                     mRestoredHomeSigHashes = readSignatureHashArray(inputBufferStream);
                     if (DEBUG) {
-                        Slog.i(TAG, "   read preferred home app " + mRestoredHome
-                                + " version=" + mRestoredHomeVersion
-                                + " installer=" + mRestoredHomeInstaller
-                                + " sig=" + mRestoredHomeSigHashes);
+                        Slog.i(
+                                TAG,
+                                "   read preferred home app "
+                                        + mRestoredHome
+                                        + " version="
+                                        + mRestoredHomeVersion
+                                        + " installer="
+                                        + mRestoredHomeInstaller
+                                        + " sig="
+                                        + mRestoredHomeSigHashes);
                     }
                 } else {
                     // it's a file metadata record
@@ -668,14 +702,24 @@
                     }
                     ArrayList<byte[]> sigs = readSignatureHashArray(inputBufferStream);
                     if (DEBUG) {
-                        Slog.i(TAG, "   read metadata for " + key
-                                + " dataSize=" + dataSize
-                                + " versionCode=" + versionCode + " sigs=" + sigs);
+                        Slog.i(
+                                TAG,
+                                "   read metadata for "
+                                        + key
+                                        + " dataSize="
+                                        + dataSize
+                                        + " versionCode="
+                                        + versionCode
+                                        + " sigs="
+                                        + sigs);
                     }
 
                     if (sigs == null || sigs.size() == 0) {
-                        Slog.w(TAG, "Not restoring package " + key
-                                + " since it appears to have no signatures.");
+                        Slog.w(
+                                TAG,
+                                "Not restoring package "
+                                        + key
+                                        + " since it appears to have no signatures.");
                         continue;
                     }
 
@@ -687,8 +731,12 @@
 
                 boolean readNextHeader = data.readNextHeader();
                 if (!readNextHeader) {
-                    if (DEBUG) Slog.v(TAG, "LegacyRestoreDataConsumer:"
-                            + " we're done reading all the headers");
+                    if (DEBUG) {
+                        Slog.v(
+                                TAG,
+                                "LegacyRestoreDataConsumer:"
+                                        + " we're done reading all the headers");
+                    }
                     break;
                 }
             }
@@ -725,10 +773,17 @@
                     mStoredIncrementalVersion = inputBufferStream.readUTF();
                     mHasMetadata = true;
                     if (DEBUG) {
-                        Slog.i(TAG, "Restore set version " + storedSystemVersion
-                                + " is compatible with OS version " + Build.VERSION.SDK_INT
-                                + " (" + mStoredIncrementalVersion + " vs "
-                                + Build.VERSION.INCREMENTAL + ")");
+                        Slog.i(
+                                TAG,
+                                "Restore set version "
+                                        + storedSystemVersion
+                                        + " is compatible with OS version "
+                                        + Build.VERSION.SDK_INT
+                                        + " ("
+                                        + mStoredIncrementalVersion
+                                        + " vs "
+                                        + Build.VERSION.INCREMENTAL
+                                        + ")");
                     }
                 } else if (key.equals(DEFAULT_HOME_KEY)) {
                     // Default home app data is no longer backed up by this agent. This code is
@@ -739,10 +794,16 @@
                     mRestoredHomeInstaller = inputBufferStream.readUTF();
                     mRestoredHomeSigHashes = readSignatureHashArray(inputBufferStream);
                     if (DEBUG) {
-                        Slog.i(TAG, "   read preferred home app " + mRestoredHome
-                                + " version=" + mRestoredHomeVersion
-                                + " installer=" + mRestoredHomeInstaller
-                                + " sig=" + mRestoredHomeSigHashes);
+                        Slog.i(
+                                TAG,
+                                "   read preferred home app "
+                                        + mRestoredHome
+                                        + " version="
+                                        + mRestoredHomeVersion
+                                        + " installer="
+                                        + mRestoredHomeInstaller
+                                        + " sig="
+                                        + mRestoredHomeSigHashes);
                     }
                 } else {
                     // it's a file metadata record
@@ -755,14 +816,24 @@
                     }
                     ArrayList<byte[]> sigs = readSignatureHashArray(inputBufferStream);
                     if (DEBUG) {
-                        Slog.i(TAG, "   read metadata for " + key
-                                + " dataSize=" + dataSize
-                                + " versionCode=" + versionCode + " sigs=" + sigs);
+                        Slog.i(
+                                TAG,
+                                "   read metadata for "
+                                        + key
+                                        + " dataSize="
+                                        + dataSize
+                                        + " versionCode="
+                                        + versionCode
+                                        + " sigs="
+                                        + sigs);
                     }
 
                     if (sigs == null || sigs.size() == 0) {
-                        Slog.w(TAG, "Not restoring package " + key
-                                + " since it appears to have no signatures.");
+                        Slog.w(
+                                TAG,
+                                "Not restoring package "
+                                        + key
+                                        + " since it appears to have no signatures.");
                         continue;
                     }
 
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index e666442..8fece82 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -167,6 +167,9 @@
     // List of packages that have RestoreAnyVersion set to true but do not support  V-> U downgrade.
     private List<String> mVToUDenylist;
 
+    // Whether we have already initialised the V to U allowlist/denylist
+    private Boolean mAreVToUListsSet = false;
+
     // Key/value: bookkeeping about staged data and files for agent access
     private File mBackupDataName;
     private File mStageName;
@@ -235,18 +238,6 @@
                         backupManagerService.getAgentTimeoutParameters(),
                         "Timeout parameters cannot be null");
         mBackupEligibilityRules = backupEligibilityRules;
-        mVToUAllowlist =
-            createVToUList(
-                Settings.Secure.getStringForUser(
-                    backupManagerService.getContext().getContentResolver(),
-                    Settings.Secure.V_TO_U_RESTORE_ALLOWLIST,
-                    mUserId));
-        mVToUDenylist =
-            createVToUList(
-                Settings.Secure.getStringForUser(
-                    backupManagerService.getContext().getContentResolver(),
-                    Settings.Secure.V_TO_U_RESTORE_DENYLIST,
-                    mUserId));
 
         if (targetPackage != null) {
             // Single package restore
@@ -661,11 +652,38 @@
                 // installed.  If the app has not declared that it is prepared to
                 // handle this case, we do not attempt the restore.
                 if (mIsSystemRestore
-                    && isVToUDowngrade(mPmAgent.getSourceSdk(), android.os.Build.VERSION.SDK_INT)) {
+                        && isVToUDowngrade(mPmAgent.getSourceSdk(),
+                        android.os.Build.VERSION.SDK_INT)) {
+                    if (!mAreVToUListsSet) {
+                        mVToUAllowlist =
+                                createVToUList(
+                                        Settings.Secure.getStringForUser(
+                                            backupManagerService.getContext().getContentResolver(),
+                                            Settings.Secure.V_TO_U_RESTORE_ALLOWLIST,
+                                            mUserId));
+                        mVToUDenylist =
+                                createVToUList(
+                                        Settings.Secure.getStringForUser(
+                                            backupManagerService.getContext().getContentResolver(),
+                                            Settings.Secure.V_TO_U_RESTORE_DENYLIST,
+                                            mUserId));
+                        logVToUListsToBMM();
+                        mAreVToUListsSet = true;
+                    }
                     if (isPackageEligibleForVToURestore(mCurrentPackage)) {
+                        mBackupManagerMonitorEventSender.monitorEvent(
+                                BackupManagerMonitor.LOG_EVENT_ID_V_TO_U_RESTORE_PKG_ELIGIBLE,
+                                mCurrentPackage,
+                                BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+                                addRestoreOperationTypeToEvent(/* extras= */null));
                         Slog.i(TAG, "Package " + pkgName
                                 + " is eligible for V to U downgrade scenario");
                     } else {
+                        mBackupManagerMonitorEventSender.monitorEvent(
+                                BackupManagerMonitor.LOG_EVENT_ID_V_TO_U_RESTORE_PKG_NOT_ELIGIBLE,
+                                mCurrentPackage,
+                                BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+                                addRestoreOperationTypeToEvent(/* extras= */null));
                         String message = "Package not eligible for V to U downgrade scenario";
                         Slog.i(TAG, pkgName + " : " + message);
                         EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, pkgName, message);
@@ -1696,14 +1714,25 @@
         //      (and not in the denylist)
         //    - The package has restoreAnyVersion set to true and is not part of the denylist
         if (mVToUDenylist.contains(mCurrentPackage.packageName)){
+            if (DEBUG) {
+                Slog.i(TAG, mCurrentPackage.packageName + " : Package is in V to U denylist");
+            }
             return false;
         } else if ((mCurrentPackage.applicationInfo.flags
                 & ApplicationInfo.FLAG_RESTORE_ANY_VERSION)
                 == 0) {
             // package has restoreAnyVersion set to false
+            if (DEBUG) {
+                Slog.i(TAG, mCurrentPackage.packageName
+                        + " : Package has restoreAnyVersion=false and is in V to U allowlist");
+            }
             return mVToUAllowlist.contains(mCurrentPackage.packageName);
         } else {
             // package has restoreAnyVersion set to true and is nor in denylist
+            if (DEBUG) {
+                Slog.i(TAG, mCurrentPackage.packageName
+                        + " : Package has restoreAnyVersion=true and is not in V to U denylist");
+            }
             return true;
         }
     }
@@ -1748,4 +1777,31 @@
                 monitoringExtras);
     }
 
+    private void logVToUListsToBMM() {
+        // send a BMM event with the allowlist
+        Bundle monitoringExtrasAllowlist =
+                mBackupManagerMonitorEventSender.putMonitoringExtra(
+                        null,
+                        BackupManagerMonitor.EXTRA_LOG_V_TO_U_ALLOWLIST,
+                        mVToUAllowlist.toString());
+        monitoringExtrasAllowlist = addRestoreOperationTypeToEvent(monitoringExtrasAllowlist);
+        mBackupManagerMonitorEventSender.monitorEvent(
+                BackupManagerMonitor.LOG_EVENT_ID_V_TO_U_RESTORE_SET_LIST,
+                null,
+                BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+                monitoringExtrasAllowlist);
+        // send a BMM event with the denylist
+        Bundle monitoringExtrasDenylist =
+                mBackupManagerMonitorEventSender.putMonitoringExtra(
+                        null,
+                        BackupManagerMonitor.EXTRA_LOG_V_TO_U_DENYLIST,
+                        mVToUDenylist.toString());
+        monitoringExtrasDenylist = addRestoreOperationTypeToEvent(monitoringExtrasDenylist);
+        mBackupManagerMonitorEventSender.monitorEvent(
+                BackupManagerMonitor.LOG_EVENT_ID_V_TO_U_RESTORE_SET_LIST,
+                null,
+                BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+                monitoringExtrasDenylist);
+    }
+
 }
diff --git a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java
index 797aed9..6d315ba 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java
@@ -146,7 +146,6 @@
                         + eventBundle.getString(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME));
             }
 
-            // TODO(b/296818666): add extras to the events
             addAgentLogsIfAvailable(eventBundle, pw);
             addExtrasIfAvailable(eventBundle, pw);
         } catch (java.io.IOException e) {
@@ -203,6 +202,11 @@
      * EXTRA_LOG_RESTORE_VERSION [int]: the version of the package on the source
      * EXTRA_LOG_RESTORE_ANYWAY [bool]: if the package allows restore any version
      * EXTRA_LOG_RESTORE_VERSION_TARGET [int]: an extra to record the package version on the target
+     *
+     * When we are performing a V to U downgrade (event with id V_TO_U_RESTORE_SET_LIST) we record
+     * the value of the V to U allowlist and denylist:
+     * EXTRA_LOG_V_TO_U_ALLOWLIST[string]
+     * EXTRA_LOG_V_TO_U_DENYLIST[string]
      */
     private void addExtrasIfAvailable(Bundle eventBundle, PrintWriter pw) {
         if (eventBundle.getInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID) ==
@@ -216,12 +220,29 @@
                         + eventBundle.getLong(BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION));
             }
             if (eventBundle.containsKey(
-                      BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_LONG_VERSION)) {
+                    BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_LONG_VERSION)) {
                 pw.println("\t\tPackage version on target: "
                         + eventBundle.getLong(
                         BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_LONG_VERSION));
             }
         }
+
+        if (eventBundle.getInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID)
+                == BackupManagerMonitor.LOG_EVENT_ID_V_TO_U_RESTORE_SET_LIST) {
+            if (eventBundle.containsKey(
+                    BackupManagerMonitor.EXTRA_LOG_V_TO_U_DENYLIST)) {
+                pw.println("\t\tV to U Denylist : "
+                        + eventBundle.getString(
+                        BackupManagerMonitor.EXTRA_LOG_V_TO_U_DENYLIST));
+            }
+
+            if (eventBundle.containsKey(
+                    BackupManagerMonitor.EXTRA_LOG_V_TO_U_ALLOWLIST)) {
+                pw.println("\t\tV to U Allowllist : "
+                        + eventBundle.getString(
+                        BackupManagerMonitor.EXTRA_LOG_V_TO_U_ALLOWLIST));
+            }
+        }
     }
 
     /*
@@ -342,10 +363,14 @@
             case BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_ERROR_FULL_RESTORE ->
                     "Transport error full restore";
             case BackupManagerMonitor.LOG_EVENT_ID_RESTORE_COMPLETE -> "Restore complete";
-            case BackupManagerMonitor.LOG_EVENT_ID_START_PACKAGE_RESTORE ->
-                    "Start package restore";
-            case BackupManagerMonitor.LOG_EVENT_ID_AGENT_FAILURE ->
-                    "Agent failure";
+            case BackupManagerMonitor.LOG_EVENT_ID_START_PACKAGE_RESTORE -> "Start package restore";
+            case BackupManagerMonitor.LOG_EVENT_ID_AGENT_FAILURE -> "Agent failure";
+            case BackupManagerMonitor.LOG_EVENT_ID_V_TO_U_RESTORE_PKG_ELIGIBLE ->
+                    "V to U restore pkg eligible";
+            case BackupManagerMonitor.LOG_EVENT_ID_V_TO_U_RESTORE_PKG_NOT_ELIGIBLE ->
+                    "V to U restore pkg not eligible";
+            case BackupManagerMonitor.LOG_EVENT_ID_V_TO_U_RESTORE_SET_LIST ->
+                    "V to U restore lists";
             default -> "Unknown log event ID: " + code;
         };
         return id;
diff --git a/services/companion/java/com/android/server/companion/AssociationStore.java b/services/companion/java/com/android/server/companion/AssociationStore.java
deleted file mode 100644
index 01905bb..0000000
--- a/services/companion/java/com/android/server/companion/AssociationStore.java
+++ /dev/null
@@ -1,144 +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.companion;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.companion.AssociationInfo;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Interface for a store of {@link AssociationInfo}-s.
- */
-public interface AssociationStore {
-
-    @IntDef(prefix = { "CHANGE_TYPE_" }, value = {
-            CHANGE_TYPE_ADDED,
-            CHANGE_TYPE_REMOVED,
-            CHANGE_TYPE_UPDATED_ADDRESS_CHANGED,
-            CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface ChangeType {}
-
-    int CHANGE_TYPE_ADDED = 0;
-    int CHANGE_TYPE_REMOVED = 1;
-    int CHANGE_TYPE_UPDATED_ADDRESS_CHANGED = 2;
-    int CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED = 3;
-
-    /**  Listener for any changes to {@link AssociationInfo}-s. */
-    interface OnChangeListener {
-        default void onAssociationChanged(
-                @ChangeType int changeType, AssociationInfo association) {
-            switch (changeType) {
-                case CHANGE_TYPE_ADDED:
-                    onAssociationAdded(association);
-                    break;
-
-                case CHANGE_TYPE_REMOVED:
-                    onAssociationRemoved(association);
-                    break;
-
-                case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED:
-                    onAssociationUpdated(association, true);
-                    break;
-
-                case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED:
-                    onAssociationUpdated(association, false);
-                    break;
-            }
-        }
-
-        default void onAssociationAdded(AssociationInfo association) {}
-
-        default void onAssociationRemoved(AssociationInfo association) {}
-
-        default void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {}
-    }
-
-    /**
-     * @return all CDM associations.
-     */
-    @NonNull
-    Collection<AssociationInfo> getAssociations();
-
-    /**
-     * @return a {@link List} of associations that belong to the user.
-     */
-    @NonNull
-    List<AssociationInfo> getAssociationsForUser(@UserIdInt int userId);
-
-    /**
-     * @return a {@link List} of association that belong to the package.
-     */
-    @NonNull
-    List<AssociationInfo> getAssociationsForPackage(
-            @UserIdInt int userId, @NonNull String packageName);
-
-    /**
-     * @return an association with the given address that belong to the given package if such an
-     * association exists, otherwise {@code null}.
-     */
-    @Nullable
-    AssociationInfo getAssociationsForPackageWithAddress(
-            @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress);
-
-    /**
-     * @return an association with the given id if such an association exists, otherwise
-     * {@code null}.
-     */
-    @Nullable
-    AssociationInfo getAssociationById(int id);
-
-    /**
-     * @return all associations with the given MAc address.
-     */
-    @NonNull
-    List<AssociationInfo> getAssociationsByAddress(@NonNull String macAddress);
-
-    /** Register a {@link OnChangeListener} */
-    void registerListener(@NonNull OnChangeListener listener);
-
-    /** Un-register a previously registered {@link OnChangeListener} */
-    void unregisterListener(@NonNull OnChangeListener listener);
-
-    /** @hide */
-    static String changeTypeToString(@ChangeType int changeType) {
-        switch (changeType) {
-            case CHANGE_TYPE_ADDED:
-                return "ASSOCIATION_ADDED";
-
-            case CHANGE_TYPE_REMOVED:
-                return "ASSOCIATION_REMOVED";
-
-            case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED:
-                return "ASSOCIATION_UPDATED";
-
-            case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED:
-                return "ASSOCIATION_UPDATED_ADDRESS_UNCHANGED";
-
-            default:
-                return "Unknown (" + changeType + ")";
-        }
-    }
-}
diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
index e4cc1f8..f2409fb 100644
--- a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
+++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
@@ -34,6 +34,9 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.CollectionUtils;
+import com.android.server.companion.association.AssociationDiskStore;
+import com.android.server.companion.association.AssociationRequestsProcessor;
+import com.android.server.companion.association.AssociationStore;
 import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
 
 import java.nio.ByteBuffer;
@@ -54,9 +57,9 @@
     @NonNull
     private final PackageManagerInternal mPackageManager;
     @NonNull
-    private final AssociationStoreImpl mAssociationStore;
+    private final AssociationStore mAssociationStore;
     @NonNull
-    private final PersistentDataStore mPersistentStore;
+    private final AssociationDiskStore mPersistentStore;
     @NonNull
     private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
     @NonNull
@@ -71,8 +74,8 @@
             new PerUserAssociationSet();
 
     BackupRestoreProcessor(@NonNull CompanionDeviceManagerService service,
-                           @NonNull AssociationStoreImpl associationStore,
-                           @NonNull PersistentDataStore persistentStore,
+                           @NonNull AssociationStore associationStore,
+                           @NonNull AssociationDiskStore persistentStore,
                            @NonNull SystemDataTransferRequestStore systemDataTransferRequestStore,
                            @NonNull AssociationRequestsProcessor associationRequestsProcessor) {
         mService = service;
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
index af0777c..c801489 100644
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -37,7 +37,11 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.infra.PerUser;
+import com.android.server.companion.association.AssociationStore;
 import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
+import com.android.server.companion.presence.ObservableUuid;
+import com.android.server.companion.presence.ObservableUuidStore;
+import com.android.server.companion.utils.PackageUtils;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -61,7 +65,7 @@
  * <ul>
  * <li> {@link #bindCompanionApplication(int, String, boolean)}
  * <li> {@link #unbindCompanionApplication(int, String)}
- * <li> {@link #notifyCompanionApplicationDevicePresenceEvent(AssociationInfo, int)}
+ * <li> {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)}
  * <li> {@link #isCompanionApplicationBound(int, String)}
  * <li> {@link #isRebindingCompanionApplicationScheduled(int, String)}
  * </ul>
@@ -251,7 +255,13 @@
         serviceConnector.connect();
     }
 
-    void notifyCompanionApplicationDeviceAppeared(AssociationInfo association) {
+    /**
+     * Notify the app that the device appeared.
+     *
+     * @deprecated use {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)} instead
+     */
+    @Deprecated
+    public void notifyCompanionApplicationDeviceAppeared(AssociationInfo association) {
         final int userId = association.getUserId();
         final String packageName = association.getPackageName();
 
@@ -273,7 +283,13 @@
         primaryServiceConnector.postOnDeviceAppeared(association);
     }
 
-    void notifyCompanionApplicationDeviceDisappeared(AssociationInfo association) {
+    /**
+     * Notify the app that the device disappeared.
+     *
+     * @deprecated use {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)} instead
+     */
+    @Deprecated
+    public void notifyCompanionApplicationDeviceDisappeared(AssociationInfo association) {
         final int userId = association.getUserId();
         final String packageName = association.getPackageName();
 
@@ -295,7 +311,10 @@
         primaryServiceConnector.postOnDeviceDisappeared(association);
     }
 
-    void notifyCompanionApplicationDevicePresenceEvent(AssociationInfo association, int event) {
+    /**
+     * Notify the app that the device appeared.
+     */
+    public void notifyCompanionDevicePresenceEvent(AssociationInfo association, int event) {
         final int userId = association.getUserId();
         final String packageName = association.getPackageName();
         final CompanionDeviceServiceConnector primaryServiceConnector =
@@ -318,7 +337,10 @@
         primaryServiceConnector.postOnDevicePresenceEvent(devicePresenceEvent);
     }
 
-    void notifyApplicationDevicePresenceEvent(ObservableUuid uuid, int event) {
+    /**
+     * Notify the app that the device disappeared.
+     */
+    public void notifyUuidDevicePresenceEvent(ObservableUuid uuid, int event) {
         final int userId = uuid.getUserId();
         final ParcelUuid parcelUuid = uuid.getUuid();
         final String packageName = uuid.getPackageName();
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 09c7793..3846e98 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -37,16 +37,18 @@
 import static com.android.internal.util.CollectionUtils.any;
 import static com.android.internal.util.Preconditions.checkState;
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-import static com.android.server.companion.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED;
-import static com.android.server.companion.PackageUtils.isRestrictedSettingsAllowed;
-import static com.android.server.companion.PackageUtils.enforceUsesCompanionDeviceFeature;
-import static com.android.server.companion.PackageUtils.getPackageInfo;
-import static com.android.server.companion.PermissionsUtils.checkCallerCanManageCompanionDevice;
-import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
-import static com.android.server.companion.PermissionsUtils.enforceCallerCanObservingDevicePresenceByUuid;
-import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr;
-import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
-import static com.android.server.companion.PermissionsUtils.sanitizeWithCallerChecks;
+import static com.android.server.companion.association.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED;
+import static com.android.server.companion.utils.AssociationUtils.getFirstAssociationIdForUser;
+import static com.android.server.companion.utils.AssociationUtils.getLastAssociationIdForUser;
+import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
+import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
+import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
+import static com.android.server.companion.utils.PermissionsUtils.checkCallerCanManageCompanionDevice;
+import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
+import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanObservingDevicePresenceByUuid;
+import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOr;
+import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
+import static com.android.server.companion.utils.PermissionsUtils.sanitizeWithCallerChecks;
 
 import static java.util.Objects.requireNonNull;
 import static java.util.concurrent.TimeUnit.DAYS;
@@ -117,12 +119,19 @@
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.companion.association.AssociationDiskStore;
+import com.android.server.companion.association.AssociationRequestsProcessor;
+import com.android.server.companion.association.AssociationRevokeProcessor;
+import com.android.server.companion.association.AssociationStore;
+import com.android.server.companion.association.InactiveAssociationsRemovalService;
 import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
 import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
 import com.android.server.companion.datatransfer.contextsync.CrossDeviceCall;
 import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
 import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncControllerCallback;
 import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
+import com.android.server.companion.presence.ObservableUuid;
+import com.android.server.companion.presence.ObservableUuidStore;
 import com.android.server.companion.transport.CompanionTransportManager;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -145,8 +154,6 @@
     static final String TAG = "CDM_CompanionDeviceManagerService";
     static final boolean DEBUG = false;
 
-    /** Range of Association IDs allocated for a user. */
-    private static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000;
     private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min
 
     private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
@@ -158,10 +165,10 @@
     private static final int MAX_CN_LENGTH = 500;
 
     private final ActivityManager mActivityManager;
-    private PersistentDataStore mPersistentStore;
+    private AssociationDiskStore mAssociationDiskStore;
     private final PersistUserStateHandler mUserPersistenceHandler;
 
-    private final AssociationStoreImpl mAssociationStore;
+    private final AssociationStore mAssociationStore;
     private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
     private AssociationRequestsProcessor mAssociationRequestsProcessor;
     private SystemDataTransferProcessor mSystemDataTransferProcessor;
@@ -176,7 +183,7 @@
     private final IAppOpsService mAppOpsManager;
     private final PowerWhitelistManager mPowerWhitelistManager;
     private final UserManager mUserManager;
-    final PackageManagerInternal mPackageManagerInternal;
+    public final PackageManagerInternal mPackageManagerInternal;
     private final PowerManagerInternal mPowerManagerInternal;
 
     /**
@@ -208,7 +215,7 @@
         mUserManager = context.getSystemService(UserManager.class);
 
         mUserPersistenceHandler = new PersistUserStateHandler();
-        mAssociationStore = new AssociationStoreImpl();
+        mAssociationStore = new AssociationStore();
         mSystemDataTransferRequestStore = new SystemDataTransferRequestStore();
 
         mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
@@ -219,15 +226,13 @@
     public void onStart() {
         final Context context = getContext();
 
-        mPersistentStore = new PersistentDataStore();
+        mAssociationDiskStore = new AssociationDiskStore();
         mAssociationRequestsProcessor = new AssociationRequestsProcessor(
                 /* cdmService */ this, mAssociationStore);
         mBackupRestoreProcessor = new BackupRestoreProcessor(
-                /* cdmService */ this, mAssociationStore, mPersistentStore,
+                /* cdmService */ this, mAssociationStore, mAssociationDiskStore,
                 mSystemDataTransferRequestStore, mAssociationRequestsProcessor);
 
-        loadAssociationsFromDisk();
-
         mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId());
 
         mAssociationStore.registerListener(mAssociationStoreChangeListener);
@@ -238,13 +243,19 @@
         mCompanionAppController = new CompanionApplicationController(
                 context, mAssociationStore, mObservableUuidStore, mDevicePresenceMonitor,
                 mPowerManagerInternal);
+
         mTransportManager = new CompanionTransportManager(context, mAssociationStore);
+
+        mAssociationRevokeProcessor = new AssociationRevokeProcessor(this, mAssociationStore,
+                mPackageManagerInternal, mDevicePresenceMonitor, mCompanionAppController,
+                mSystemDataTransferRequestStore, mTransportManager);
+
+        loadAssociationsFromDisk();
+
         mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
                 mPackageManagerInternal, mAssociationStore,
                 mSystemDataTransferRequestStore, mTransportManager);
-        mAssociationRevokeProcessor = new AssociationRevokeProcessor(this, mAssociationStore,
-                mPackageManagerInternal, mDevicePresenceMonitor, mCompanionAppController,
-                mSystemDataTransferRequestStore);
+
         // TODO(b/279663946): move context sync to a dedicated system service
         mCrossDeviceSyncController = new CrossDeviceSyncController(getContext(), mTransportManager);
 
@@ -259,10 +270,13 @@
     void loadAssociationsFromDisk() {
         final Set<AssociationInfo> allAssociations = new ArraySet<>();
         synchronized (mPreviouslyUsedIds) {
+            List<Integer> userIds = new ArrayList<>();
+            for (UserInfo user : mUserManager.getAliveUsers()) {
+                userIds.add(user.id);
+            }
             // The data is stored in DE directories, so we can read the data for all users now
             // (which would not be possible if the data was stored to CE directories).
-            mPersistentStore.readStateForUsers(
-                    mUserManager.getAliveUsers(), allAssociations, mPreviouslyUsedIds);
+            mAssociationDiskStore.readStateForUsers(userIds, allAssociations, mPreviouslyUsedIds);
         }
 
         final Set<AssociationInfo> activeAssociations =
@@ -286,7 +300,7 @@
             }
         }
 
-        mAssociationStore.setAssociations(activeAssociations);
+        mAssociationStore.setAssociationsToCache(activeAssociations);
 
         // IMPORTANT: only do this AFTER mAssociationStore.setAssociations(), because
         // persistStateForUser() queries AssociationStore.
@@ -435,7 +449,7 @@
 
                 bindApplicationIfNeeded(association);
 
-                mCompanionAppController.notifyCompanionApplicationDevicePresenceEvent(
+                mCompanionAppController.notifyCompanionDevicePresenceEvent(
                         association, event);
                 break;
             case EVENT_BLE_DISAPPEARED:
@@ -446,7 +460,7 @@
                     return;
                 }
                 if (association.shouldBindWhenPresent()) {
-                    mCompanionAppController.notifyCompanionApplicationDevicePresenceEvent(
+                    mCompanionAppController.notifyCompanionDevicePresenceEvent(
                             association, event);
                 }
                 // Check if there are other devices associated to the app that are present.
@@ -475,7 +489,7 @@
                     Log.i(TAG, "u" + userId + "\\" + packageName + " is already bound");
                 }
 
-                mCompanionAppController.notifyApplicationDevicePresenceEvent(uuid, event);
+                mCompanionAppController.notifyUuidDevicePresenceEvent(uuid, event);
 
                 break;
             case EVENT_BT_DISCONNECTED:
@@ -484,7 +498,7 @@
                     return;
                 }
 
-                mCompanionAppController.notifyApplicationDevicePresenceEvent(uuid, event);
+                mCompanionAppController.notifyUuidDevicePresenceEvent(uuid, event);
                 // Check if there are other devices associated to the app or the UUID to be
                 // observed are present.
                 if (shouldBindPackage(userId, packageName)) return;
@@ -577,7 +591,7 @@
 
         final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId);
 
-        mPersistentStore.persistStateForUser(userId, allAssociations, usedIdsForUser);
+        mAssociationDiskStore.persistStateForUser(userId, allAssociations, usedIdsForUser);
     }
 
     private void notifyListeners(
@@ -641,7 +655,8 @@
         final List<AssociationInfo> associationsForPackage =
                 mAssociationStore.getAssociationsForPackage(userId, packageName);
         for (AssociationInfo association : associationsForPackage) {
-            updateSpecialAccessPermissionForAssociatedPackage(association);
+            updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
+                    association.getPackageName());
         }
 
         mCompanionAppController.onPackagesChanged(userId);
@@ -687,7 +702,7 @@
         }
     }
 
-    class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
+    public class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
         @Override
         public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                 throws RemoteException {
@@ -1333,7 +1348,10 @@
         return usedIdsForPackage;
     }
 
-    int getNewAssociationIdForPackage(@UserIdInt int userId, @NonNull String packageName) {
+    /**
+     * Get a new association id for the package.
+     */
+    public int getNewAssociationIdForPackage(@UserIdInt int userId, @NonNull String packageName) {
         synchronized (mPreviouslyUsedIds) {
             // First: collect all IDs currently in use for this user's Associations.
             final SparseBooleanArray usedIds = new SparseBooleanArray();
@@ -1378,9 +1396,12 @@
         }
     }
 
-    void updateSpecialAccessPermissionForAssociatedPackage(AssociationInfo association) {
+    /**
+     * Update special access for the association's package
+     */
+    public void updateSpecialAccessPermissionForAssociatedPackage(int userId, String packageName) {
         final PackageInfo packageInfo =
-                getPackageInfo(getContext(), association.getUserId(), association.getPackageName());
+                getPackageInfo(getContext(), userId, packageName);
 
         Binder.withCleanCallingIdentity(() -> updateSpecialAccessPermissionAsSystem(packageInfo));
     }
@@ -1534,15 +1555,6 @@
         }
     };
 
-    static int getFirstAssociationIdForUser(@UserIdInt int userId) {
-        // We want the IDs to start from 1, not 0.
-        return userId * ASSOCIATIONS_IDS_PER_USER_RANGE + 1;
-    }
-
-    static int getLastAssociationIdForUser(@UserIdInt int userId) {
-        return (userId + 1) * ASSOCIATIONS_IDS_PER_USER_RANGE;
-    }
-
     private static Map<String, Set<Integer>> deepUnmodifiableCopy(Map<String, Set<Integer>> orig) {
         final Map<String, Set<Integer>> copy = new HashMap<>();
 
@@ -1666,11 +1678,17 @@
         }
     }
 
-    void postPersistUserState(@UserIdInt int userId) {
+    /**
+     * Persist associations
+     */
+    public void postPersistUserState(@UserIdInt int userId) {
         mUserPersistenceHandler.postPersistUserState(userId);
     }
 
-    static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> {
+    /**
+     * Set to store associations
+     */
+    public static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> {
         @Override
         protected @NonNull Set<AssociationInfo> create(int userId) {
             return new ArraySet<>();
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index de4f2b6..16877dc 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -18,7 +18,7 @@
 
 import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC;
 
-import static com.android.server.companion.PermissionsUtils.sanitizeWithCallerChecks;
+import static com.android.server.companion.utils.PermissionsUtils.sanitizeWithCallerChecks;
 
 import android.companion.AssociationInfo;
 import android.companion.ContextSyncMessage;
@@ -32,10 +32,14 @@
 import android.util.Base64;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.server.companion.association.AssociationRequestsProcessor;
+import com.android.server.companion.association.AssociationRevokeProcessor;
+import com.android.server.companion.association.AssociationStore;
 import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
 import com.android.server.companion.datatransfer.contextsync.BitmapUtils;
 import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
 import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
+import com.android.server.companion.presence.ObservableUuid;
 import com.android.server.companion.transport.CompanionTransportManager;
 
 import java.io.PrintWriter;
@@ -46,7 +50,7 @@
 
     private final CompanionDeviceManagerService mService;
     private final AssociationRevokeProcessor mRevokeProcessor;
-    private final AssociationStoreImpl mAssociationStore;
+    private final AssociationStore mAssociationStore;
     private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
     private final CompanionTransportManager mTransportManager;
 
@@ -55,7 +59,7 @@
     private final BackupRestoreProcessor mBackupRestoreProcessor;
 
     CompanionDeviceShellCommand(CompanionDeviceManagerService service,
-            AssociationStoreImpl associationStore,
+            AssociationStore associationStore,
             CompanionDevicePresenceMonitor devicePresenceMonitor,
             CompanionTransportManager transportManager,
             SystemDataTransferProcessor systemDataTransferProcessor,
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
similarity index 91%
rename from services/companion/java/com/android/server/companion/PersistentDataStore.java
rename to services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
index 1ebe65c..75cb120 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.companion;
+package com.android.server.companion.association;
 
 import static com.android.internal.util.CollectionUtils.forEach;
 import static com.android.internal.util.XmlUtils.readBooleanAttribute;
@@ -25,25 +25,23 @@
 import static com.android.internal.util.XmlUtils.writeIntAttribute;
 import static com.android.internal.util.XmlUtils.writeLongAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
-import static com.android.server.companion.CompanionDeviceManagerService.getFirstAssociationIdForUser;
-import static com.android.server.companion.CompanionDeviceManagerService.getLastAssociationIdForUser;
-import static com.android.server.companion.DataStoreUtils.createStorageFileForUser;
-import static com.android.server.companion.DataStoreUtils.fileToByteArray;
-import static com.android.server.companion.DataStoreUtils.isEndOfTag;
-import static com.android.server.companion.DataStoreUtils.isStartOfTag;
-import static com.android.server.companion.DataStoreUtils.writeToFileSafely;
+import static com.android.server.companion.utils.AssociationUtils.getFirstAssociationIdForUser;
+import static com.android.server.companion.utils.AssociationUtils.getLastAssociationIdForUser;
+import static com.android.server.companion.utils.DataStoreUtils.createStorageFileForUser;
+import static com.android.server.companion.utils.DataStoreUtils.fileToByteArray;
+import static com.android.server.companion.utils.DataStoreUtils.isEndOfTag;
+import static com.android.server.companion.utils.DataStoreUtils.isStartOfTag;
+import static com.android.server.companion.utils.DataStoreUtils.writeToFileSafely;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.UserIdInt;
 import android.companion.AssociationInfo;
-import android.content.pm.UserInfo;
 import android.net.MacAddress;
 import android.os.Environment;
 import android.util.ArrayMap;
 import android.util.AtomicFile;
-import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.Xml;
@@ -70,6 +68,8 @@
 import java.util.concurrent.ConcurrentMap;
 
 /**
+ * IMPORTANT: This class should NOT be directly used except {@link AssociationStore}
+ *
  * The class responsible for persisting Association records and other related information (such as
  * previously used IDs) to a disk, and reading the data back from the disk.
  *
@@ -106,8 +106,6 @@
  * Since Android T the data is stored to "companion_device_manager.xml" file in
  * {@link Environment#getDataSystemDeDirectory(int) /data/system_de/}.
  *
- * See {@link DataStoreUtils#getBaseStorageFileForUser(int, String)}
- *
  * <p>
  * Since Android T the data is stored using the v1 schema.
  *
@@ -160,9 +158,8 @@
  * }</pre>
  */
 @SuppressLint("LongLogTag")
-final class PersistentDataStore {
-    private static final String TAG = "CompanionDevice_PersistentDataStore";
-    private static final boolean DEBUG = CompanionDeviceManagerService.DEBUG;
+public final class AssociationDiskStore {
+    private static final String TAG = "CompanionDevice_AssociationDiskStore";
 
     private static final int CURRENT_PERSISTENCE_VERSION = 1;
 
@@ -199,11 +196,13 @@
     private final @NonNull ConcurrentMap<Integer, AtomicFile> mUserIdToStorageFile =
             new ConcurrentHashMap<>();
 
-    void readStateForUsers(@NonNull List<UserInfo> users,
+    /**
+     * Read all associations for given users
+     */
+    public void readStateForUsers(@NonNull List<Integer> userIds,
             @NonNull Set<AssociationInfo> allAssociationsOut,
             @NonNull SparseArray<Map<String, Set<Integer>>> previouslyUsedIdsPerUserOut) {
-        for (UserInfo user : users) {
-            final int userId = user.id;
+        for (int userId : userIds) {
             // Previously used IDs are stored in the "out" collection per-user.
             final Map<String, Set<Integer>> previouslyUsedIds = new ArrayMap<>();
 
@@ -246,12 +245,11 @@
      * @param associationsOut a container to read the {@link AssociationInfo}s "into".
      * @param previouslyUsedIdsPerPackageOut a container to read the used IDs "into".
      */
-    void readStateForUser(@UserIdInt int userId,
+    private void readStateForUser(@UserIdInt int userId,
             @NonNull Collection<AssociationInfo> associationsOut,
             @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
         Slog.i(TAG, "Reading associations for user " + userId + " from disk");
         final AtomicFile file = getStorageFileForUser(userId);
-        if (DEBUG) Log.d(TAG, "  > File=" + file.getBaseFile().getPath());
 
         // getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
         // accesses to the file on the file system using this AtomicFile object.
@@ -260,12 +258,8 @@
             final AtomicFile readFrom;
             final String rootTag;
             if (!file.getBaseFile().exists()) {
-                if (DEBUG) Log.d(TAG, "  > File does not exist -> Try to read legacy file");
-
                 legacyBaseFile = getBaseLegacyStorageFileForUser(userId);
-                if (DEBUG) Log.d(TAG, "  > Legacy file=" + legacyBaseFile.getPath());
                 if (!legacyBaseFile.exists()) {
-                    if (DEBUG) Log.d(TAG, "  > Legacy file does not exist -> Abort");
                     return;
                 }
 
@@ -276,27 +270,16 @@
                 rootTag = XML_TAG_STATE;
             }
 
-            if (DEBUG) Log.d(TAG, "  > Reading associations...");
             final int version = readStateFromFileLocked(userId, readFrom, rootTag,
                     associationsOut, previouslyUsedIdsPerPackageOut);
-            if (DEBUG) {
-                Log.d(TAG, "  > Done reading: " + associationsOut);
-                if (version < CURRENT_PERSISTENCE_VERSION) {
-                    Log.d(TAG, "  > File used old format: v." + version + " -> Re-write");
-                }
-            }
 
             if (legacyBaseFile != null || version < CURRENT_PERSISTENCE_VERSION) {
                 // The data is either in the legacy file or in the legacy format, or both.
                 // Save the data to right file in using the current format.
-                if (DEBUG) {
-                    Log.d(TAG, "  > Writing the data to " + file.getBaseFile().getPath());
-                }
                 persistStateToFileLocked(file, associationsOut, previouslyUsedIdsPerPackageOut);
 
                 if (legacyBaseFile != null) {
                     // We saved the data to the right file, can delete the old file now.
-                    if (DEBUG) Log.d(TAG, "  > Deleting legacy file");
                     legacyBaseFile.delete();
                 }
             }
@@ -313,14 +296,12 @@
      * @param associations a set of user's associations.
      * @param previouslyUsedIdsPerPackage a set previously used Association IDs for the user.
      */
-    void persistStateForUser(@UserIdInt int userId,
+    public void persistStateForUser(@UserIdInt int userId,
             @NonNull Collection<AssociationInfo> associations,
             @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) {
         Slog.i(TAG, "Writing associations for user " + userId + " to disk");
-        if (DEBUG) Slog.d(TAG, "  > " + associations);
 
         final AtomicFile file = getStorageFileForUser(userId);
-        if (DEBUG) Log.d(TAG, "  > File=" + file.getBaseFile().getPath());
         // getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
         // accesses to the file on the file system using this AtomicFile object.
         synchronized (file) {
@@ -403,7 +384,10 @@
                 u -> createStorageFileForUser(userId, FILE_NAME));
     }
 
-    byte[] getBackupPayload(@UserIdInt int userId) {
+    /**
+     * Get associations backup payload from disk
+     */
+    public byte[] getBackupPayload(@UserIdInt int userId) {
         Slog.i(TAG, "Fetching stored state data for user " + userId + " from disk");
         final AtomicFile file = getStorageFileForUser(userId);
 
@@ -412,7 +396,10 @@
         }
     }
 
-    void readStateFromPayload(byte[] payload, @UserIdInt int userId,
+    /**
+     * Convert payload to a set of associations
+     */
+    public void readStateFromPayload(byte[] payload, @UserIdInt int userId,
                               @NonNull Set<AssociationInfo> associationsOut,
                               @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
         try (ByteArrayInputStream in = new ByteArrayInputStream(payload)) {
@@ -614,7 +601,7 @@
                     macAddress, displayName, profile, null, selfManaged, notify,
                     revoked, pending, timeApproved, lastTimeConnected, systemDataSyncFlags);
         } catch (Exception e) {
-            if (DEBUG) Log.w(TAG, "Could not create AssociationInfo", e);
+            Slog.e(TAG, "Could not create AssociationInfo", e);
         }
         return associationInfo;
     }
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
similarity index 85%
rename from services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
rename to services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index d0eb59d..29ec7c2 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.companion;
+package com.android.server.companion.association;
 
 import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
@@ -24,13 +24,12 @@
 import static android.content.ComponentName.createRelative;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 
-import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
-import static com.android.server.companion.MetricUtils.logCreateAssociation;
-import static com.android.server.companion.PackageUtils.enforceUsesCompanionDeviceFeature;
-import static com.android.server.companion.PermissionsUtils.enforcePermissionsForAssociation;
-import static com.android.server.companion.RolesUtils.addRoleHolderForAssociation;
-import static com.android.server.companion.RolesUtils.isRoleHolder;
-import static com.android.server.companion.Utils.prepareForIpc;
+import static com.android.server.companion.utils.MetricUtils.logCreateAssociation;
+import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
+import static com.android.server.companion.utils.PermissionsUtils.enforcePermissionForCreatingAssociation;
+import static com.android.server.companion.utils.RolesUtils.addRoleHolderForAssociation;
+import static com.android.server.companion.utils.RolesUtils.isRoleHolder;
+import static com.android.server.companion.utils.Utils.prepareForIpc;
 
 import static java.util.Objects.requireNonNull;
 
@@ -59,6 +58,8 @@
 import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.server.companion.CompanionDeviceManagerService;
+import com.android.server.companion.utils.PackageUtils;
 
 import java.util.List;
 
@@ -106,7 +107,7 @@
  * ResultReceiver, MacAddress)
  */
 @SuppressLint("LongLogTag")
-class AssociationRequestsProcessor {
+public class AssociationRequestsProcessor {
     private static final String TAG = "CDM_AssociationRequestsProcessor";
 
     // AssociationRequestsProcessor <-> UI
@@ -129,12 +130,12 @@
     private final @NonNull Context mContext;
     private final @NonNull CompanionDeviceManagerService mService;
     private final @NonNull PackageManagerInternal mPackageManager;
-    private final @NonNull AssociationStoreImpl mAssociationStore;
+    private final @NonNull AssociationStore mAssociationStore;
     @NonNull
     private final ComponentName mCompanionDeviceActivity;
 
-    AssociationRequestsProcessor(@NonNull CompanionDeviceManagerService service,
-            @NonNull AssociationStoreImpl associationStore) {
+    public AssociationRequestsProcessor(@NonNull CompanionDeviceManagerService service,
+            @NonNull AssociationStore associationStore) {
         mContext = service.getContext();
         mService = service;
         mPackageManager = service.mPackageManagerInternal;
@@ -148,7 +149,7 @@
      * Handle incoming {@link AssociationRequest}s, sent via
      * {@link android.companion.ICompanionDeviceManager#associate(AssociationRequest, IAssociationRequestCallback, String, int)}
      */
-    void processNewAssociationRequest(@NonNull AssociationRequest request,
+    public void processNewAssociationRequest(@NonNull AssociationRequest request,
             @NonNull String packageName, @UserIdInt int userId,
             @NonNull IAssociationRequestCallback callback) {
         requireNonNull(request, "Request MUST NOT be null");
@@ -160,14 +161,11 @@
         requireNonNull(callback, "Callback MUST NOT be null");
 
         final int packageUid = mPackageManager.getPackageUid(packageName, 0, userId);
-        if (DEBUG) {
-            Slog.d(TAG, "processNewAssociationRequest() "
-                    + "request=" + request + ", "
-                    + "package=u" + userId + "/" + packageName + " (uid=" + packageUid + ")");
-        }
+        Slog.d(TAG, "processNewAssociationRequest() " + "request=" + request + ", " + "package=u"
+                + userId + "/" + packageName + " (uid=" + packageUid + ")");
 
         // 1. Enforce permissions and other requirements.
-        enforcePermissionsForAssociation(mContext, request, packageUid);
+        enforcePermissionForCreatingAssociation(mContext, request, packageUid);
         enforceUsesCompanionDeviceFeature(mContext, userId, packageName);
 
         // 2a. Check if association can be created without launching UI (i.e. CDM needs NEITHER
@@ -222,7 +220,7 @@
     /**
      * Process another AssociationRequest in CompanionDeviceActivity to cancel current dialog.
      */
-    PendingIntent buildAssociationCancellationIntent(@NonNull String packageName,
+    public PendingIntent buildAssociationCancellationIntent(@NonNull String packageName,
             @UserIdInt int userId) {
         requireNonNull(packageName, "Package name MUST NOT be null");
 
@@ -247,17 +245,10 @@
         final int userId = request.getUserId();
         final int packageUid = mPackageManager.getPackageUid(packageName, 0, userId);
 
-        if (DEBUG) {
-            Slog.d(TAG, "processAssociationRequestApproval()\n"
-                    + "   package=u" + userId + "/" + packageName + " (uid=" + packageUid + ")\n"
-                    + "   request=" + request + "\n"
-                    + "   macAddress=" + macAddress + "\n");
-        }
-
         // 1. Need to check permissions again in case something changed, since we first received
         // this request.
         try {
-            enforcePermissionsForAssociation(mContext, request, packageUid);
+            enforcePermissionForCreatingAssociation(mContext, request, packageUid);
         } catch (SecurityException e) {
             // Since, at this point the caller is our own UI, we need to catch the exception on
             // forward it back to the application via the callback.
@@ -287,6 +278,9 @@
         }
     }
 
+    /**
+     * Create an association.
+     */
     public void createAssociation(@UserIdInt int userId, @NonNull String packageName,
             @Nullable MacAddress macAddress, @Nullable CharSequence displayName,
             @Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
@@ -308,6 +302,9 @@
         // that there are other devices with the same profile, so the role holder won't be removed.
     }
 
+    /**
+     * Grant a role if specified and add an association to store.
+     */
     public void maybeGrantRoleAndStoreAssociation(@NonNull AssociationInfo association,
             @Nullable IAssociationRequestCallback callback,
             @Nullable ResultReceiver resultReceiver) {
@@ -316,6 +313,9 @@
         // If it is null, then the operation will succeed without granting any role.
         addRoleHolderForAssociation(mService.getContext(), association, success -> {
             if (success) {
+                Slog.i(TAG, "Added " + association.getDeviceProfile() + " role to userId="
+                        + association.getUserId() + ", packageName="
+                        + association.getPackageName());
                 addAssociationToStore(association);
                 sendCallbackAndFinish(association, callback, resultReceiver);
             } else {
@@ -327,6 +327,9 @@
         });
     }
 
+    /**
+     * Enable system data sync.
+     */
     public void enableSystemDataSync(int associationId, int flags) {
         AssociationInfo association = mAssociationStore.getAssociationById(associationId);
         AssociationInfo updated = (new AssociationInfo.Builder(association))
@@ -334,6 +337,9 @@
         mAssociationStore.updateAssociation(updated);
     }
 
+    /**
+     * Disable system data sync.
+     */
     public void disableSystemDataSync(int associationId, int flags) {
         AssociationInfo association = mAssociationStore.getAssociationById(associationId);
         AssociationInfo updated = (new AssociationInfo.Builder(association))
@@ -346,7 +352,8 @@
 
         mAssociationStore.addAssociation(association);
 
-        mService.updateSpecialAccessPermissionForAssociatedPackage(association);
+        mService.updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
+                association.getPackageName());
 
         logCreateAssociation(association.getDeviceProfile());
     }
@@ -427,38 +434,37 @@
 
     private final ResultReceiver mOnRequestConfirmationReceiver =
             new ResultReceiver(Handler.getMain()) {
-        @Override
-        protected void onReceiveResult(int resultCode, Bundle data) {
-            if (DEBUG) {
-                Slog.d(TAG, "mOnRequestConfirmationReceiver.onReceiveResult() "
-                        + "code=" + resultCode + ", " + "data=" + data);
-            }
+                @Override
+                protected void onReceiveResult(int resultCode, Bundle data) {
+                    if (resultCode != RESULT_CODE_ASSOCIATION_APPROVED) {
+                        Slog.w(TAG, "Unknown result code:" + resultCode);
+                        return;
+                    }
 
-            if (resultCode != RESULT_CODE_ASSOCIATION_APPROVED) {
-                Slog.w(TAG, "Unknown result code:" + resultCode);
-                return;
-            }
+                    final AssociationRequest request = data.getParcelable(EXTRA_ASSOCIATION_REQUEST,
+                            android.companion.AssociationRequest.class);
+                    final IAssociationRequestCallback callback = IAssociationRequestCallback.Stub
+                            .asInterface(data.getBinder(EXTRA_APPLICATION_CALLBACK));
+                    final ResultReceiver resultReceiver = data.getParcelable(EXTRA_RESULT_RECEIVER,
+                            android.os.ResultReceiver.class);
 
-            final AssociationRequest request = data.getParcelable(EXTRA_ASSOCIATION_REQUEST, android.companion.AssociationRequest.class);
-            final IAssociationRequestCallback callback = IAssociationRequestCallback.Stub
-                    .asInterface(data.getBinder(EXTRA_APPLICATION_CALLBACK));
-            final ResultReceiver resultReceiver = data.getParcelable(EXTRA_RESULT_RECEIVER, android.os.ResultReceiver.class);
+                    requireNonNull(request);
+                    requireNonNull(callback);
+                    requireNonNull(resultReceiver);
 
-            requireNonNull(request);
-            requireNonNull(callback);
-            requireNonNull(resultReceiver);
+                    final MacAddress macAddress;
+                    if (request.isSelfManaged()) {
+                        macAddress = null;
+                    } else {
+                        macAddress = data.getParcelable(EXTRA_MAC_ADDRESS,
+                                android.net.MacAddress.class);
+                        requireNonNull(macAddress);
+                    }
 
-            final MacAddress macAddress;
-            if (request.isSelfManaged()) {
-                macAddress = null;
-            } else {
-                macAddress = data.getParcelable(EXTRA_MAC_ADDRESS, android.net.MacAddress.class);
-                requireNonNull(macAddress);
-            }
-
-            processAssociationRequestApproval(request, callback, resultReceiver, macAddress);
-        }
-    };
+                    processAssociationRequestApproval(request, callback, resultReceiver,
+                            macAddress);
+                }
+            };
 
     private boolean mayAssociateWithoutPrompt(@NonNull String packageName, @UserIdInt int userId) {
         // Throttle frequent associations
diff --git a/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java
similarity index 91%
rename from services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java
rename to services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java
index de6382e..d1efbbc 100644
--- a/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.server.companion;
+package com.android.server.companion.association;
 
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
 import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
 
 import static com.android.internal.util.CollectionUtils.any;
-import static com.android.server.companion.MetricUtils.logRemoveAssociation;
-import static com.android.server.companion.RolesUtils.removeRoleHolderForAssociation;
+import static com.android.server.companion.utils.MetricUtils.logRemoveAssociation;
+import static com.android.server.companion.utils.RolesUtils.removeRoleHolderForAssociation;
 import static com.android.server.companion.CompanionDeviceManagerService.PerUserAssociationSet;
 
 import android.annotation.NonNull;
@@ -38,8 +38,11 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.server.companion.CompanionApplicationController;
+import com.android.server.companion.CompanionDeviceManagerService;
 import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
 import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
+import com.android.server.companion.transport.CompanionTransportManager;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -55,11 +58,12 @@
     private static final boolean DEBUG = false;
     private final @NonNull Context mContext;
     private final @NonNull CompanionDeviceManagerService mService;
-    private final @NonNull AssociationStoreImpl mAssociationStore;
+    private final @NonNull AssociationStore mAssociationStore;
     private final @NonNull PackageManagerInternal mPackageManagerInternal;
     private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor;
     private final @NonNull SystemDataTransferRequestStore mSystemDataTransferRequestStore;
     private final @NonNull CompanionApplicationController mCompanionAppController;
+    private final @NonNull CompanionTransportManager mTransportManager;
     private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener;
     private final ActivityManager mActivityManager;
 
@@ -90,12 +94,13 @@
     @GuardedBy("mRevokedAssociationsPendingRoleHolderRemoval")
     private final Map<Integer, String> mUidsPendingRoleHolderRemoval = new HashMap<>();
 
-    AssociationRevokeProcessor(@NonNull CompanionDeviceManagerService service,
-            @NonNull AssociationStoreImpl associationStore,
+    public AssociationRevokeProcessor(@NonNull CompanionDeviceManagerService service,
+            @NonNull AssociationStore associationStore,
             @NonNull PackageManagerInternal packageManager,
             @NonNull CompanionDevicePresenceMonitor devicePresenceMonitor,
             @NonNull CompanionApplicationController applicationController,
-            @NonNull SystemDataTransferRequestStore systemDataTransferRequestStore) {
+            @NonNull SystemDataTransferRequestStore systemDataTransferRequestStore,
+            @NonNull CompanionTransportManager companionTransportManager) {
         mService = service;
         mContext = service.getContext();
         mActivityManager = mContext.getSystemService(ActivityManager.class);
@@ -106,15 +111,22 @@
         mDevicePresenceMonitor = devicePresenceMonitor;
         mCompanionAppController = applicationController;
         mSystemDataTransferRequestStore = systemDataTransferRequestStore;
+        mTransportManager = companionTransportManager;
     }
 
+    /**
+     * Disassociate an association
+     */
     // TODO: also revoke notification access
-    void disassociateInternal(int associationId) {
+    public void disassociateInternal(int associationId) {
         final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
         final int userId = association.getUserId();
         final String packageName = association.getPackageName();
         final String deviceProfile = association.getDeviceProfile();
 
+        // Detach transport if exists
+        mTransportManager.detachSystemDataTransport(packageName, userId, associationId);
+
         if (!maybeRemoveRoleHolderForAssociation(association)) {
             // Need to remove the app from list of the role holders, but will have to do it later
             // (the app is in foreground at the moment).
@@ -168,7 +180,7 @@
      *         {@code RoleManager.removeRoleHolderAsUser()} will kill the application's process,
      *         which would lead to the poor UX, hence need to try later.
      */
-    boolean maybeRemoveRoleHolderForAssociation(@NonNull AssociationInfo association) {
+    public boolean maybeRemoveRoleHolderForAssociation(@NonNull AssociationInfo association) {
         if (DEBUG) Log.d(TAG, "maybeRemoveRoleHolderForAssociation() association=" + association);
         final String deviceProfile = association.getDeviceProfile();
 
@@ -203,19 +215,11 @@
             return false;
         }
 
-        removeRoleHolderForAssociation(mContext, association);
+        removeRoleHolderForAssociation(mContext, association.getUserId(),
+                association.getPackageName(), association.getDeviceProfile());
         return true;
     }
 
-    @SuppressLint("MissingPermission")
-    private int  getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) {
-        return Binder.withCleanCallingIdentity(() -> {
-            final int uid =
-                    mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
-            return mActivityManager.getUidImportance(uid);
-        });
-    }
-
     /**
      * Set revoked flag for active association and add the revoked association and the uid into
      * the caches.
@@ -224,7 +228,7 @@
      * @see #mUidsPendingRoleHolderRemoval
      * @see OnPackageVisibilityChangeListener
      */
-    void addToPendingRoleHolderRemoval(@NonNull AssociationInfo association) {
+    public void addToPendingRoleHolderRemoval(@NonNull AssociationInfo association) {
         // First: set revoked flag
         association = (new AssociationInfo.Builder(association)).setRevoked(true).build();
         final String packageName = association.getPackageName();
@@ -246,6 +250,28 @@
     }
 
     /**
+     * @return a copy of the revoked associations set (safeguarding against
+     *         {@code ConcurrentModificationException}-s).
+     */
+    @NonNull
+    public Set<AssociationInfo> getPendingRoleHolderRemovalAssociationsForUser(
+            @UserIdInt int userId) {
+        synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+            // Return a copy.
+            return new ArraySet<>(mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId));
+        }
+    }
+
+    @SuppressLint("MissingPermission")
+    private int  getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) {
+        return Binder.withCleanCallingIdentity(() -> {
+            final int uid =
+                    mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
+            return mActivityManager.getUidImportance(uid);
+        });
+    }
+
+    /**
      * Remove the revoked association from the cache and also remove the uid from the map if
      * there are other associations with the same package still pending for role holder removal.
      *
@@ -278,18 +304,6 @@
         }
     }
 
-    /**
-     * @return a copy of the revoked associations set (safeguarding against
-     *         {@code ConcurrentModificationException}-s).
-     */
-    @NonNull Set<AssociationInfo> getPendingRoleHolderRemovalAssociationsForUser(
-            @UserIdInt int userId) {
-        synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
-            // Return a copy.
-            return new ArraySet<>(mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId));
-        }
-    }
-
     private String getPackageNameByUid(int uid) {
         synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
             return mUidsPendingRoleHolderRemoval.get(uid);
diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java
similarity index 68%
rename from services/companion/java/com/android/server/companion/AssociationStoreImpl.java
rename to services/companion/java/com/android/server/companion/association/AssociationStore.java
index 8c6ad3b..2f94bde 100644
--- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.server.companion;
+package com.android.server.companion.association;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.UserIdInt;
 import android.companion.AssociationInfo;
 import android.net.MacAddress;
-import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -30,6 +30,8 @@
 import com.android.internal.util.CollectionUtils;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -40,24 +42,69 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.StringJoiner;
 
 /**
- * Implementation of the {@link AssociationStore}, with addition of the methods for modification.
- * <ul>
- * <li> {@link #addAssociation(AssociationInfo)}
- * <li> {@link #removeAssociation(int)}
- * <li> {@link #updateAssociation(AssociationInfo)}
- * </ul>
- *
- * The class has package-private access level, and instances of the class should only be created by
- * the {@link CompanionDeviceManagerService}.
- * Other system component (both inside and outside if the com.android.server.companion package)
- * should use public {@link AssociationStore} interface.
+ * Association store for CRUD.
  */
 @SuppressLint("LongLogTag")
-class AssociationStoreImpl implements AssociationStore {
-    private static final boolean DEBUG = false;
+public class AssociationStore {
+
+    @IntDef(prefix = { "CHANGE_TYPE_" }, value = {
+            CHANGE_TYPE_ADDED,
+            CHANGE_TYPE_REMOVED,
+            CHANGE_TYPE_UPDATED_ADDRESS_CHANGED,
+            CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ChangeType {}
+
+    public static final int CHANGE_TYPE_ADDED = 0;
+    public static final int CHANGE_TYPE_REMOVED = 1;
+    public static final int CHANGE_TYPE_UPDATED_ADDRESS_CHANGED = 2;
+    public static final int CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED = 3;
+
+    /**  Listener for any changes to associations. */
+    public interface OnChangeListener {
+        /**
+         * Called when there are association changes.
+         */
+        default void onAssociationChanged(
+                @AssociationStore.ChangeType int changeType, AssociationInfo association) {
+            switch (changeType) {
+                case CHANGE_TYPE_ADDED:
+                    onAssociationAdded(association);
+                    break;
+
+                case CHANGE_TYPE_REMOVED:
+                    onAssociationRemoved(association);
+                    break;
+
+                case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED:
+                    onAssociationUpdated(association, true);
+                    break;
+
+                case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED:
+                    onAssociationUpdated(association, false);
+                    break;
+            }
+        }
+
+        /**
+         * Called when an association is added.
+         */
+        default void onAssociationAdded(AssociationInfo association) {}
+
+        /**
+         * Called when an association is removed.
+         */
+        default void onAssociationRemoved(AssociationInfo association) {}
+
+        /**
+         * Called when an association is updated.
+         */
+        default void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {}
+    }
+
     private static final String TAG = "CDM_AssociationStore";
 
     private final Object mLock = new Object();
@@ -72,17 +119,17 @@
     @GuardedBy("mListeners")
     private final Set<OnChangeListener> mListeners = new LinkedHashSet<>();
 
-    void addAssociation(@NonNull AssociationInfo association) {
+    /**
+     * Add an association.
+     */
+    public void addAssociation(@NonNull AssociationInfo association) {
+        Slog.i(TAG, "Adding new association=" + association);
+
         // Validity check first.
         checkNotRevoked(association);
 
         final int id = association.getId();
 
-        if (DEBUG) {
-            Log.i(TAG, "addAssociation() " + association.toShortString());
-            Log.d(TAG, "  association=" + association);
-        }
-
         synchronized (mLock) {
             if (mIdMap.containsKey(id)) {
                 Slog.e(TAG, "Association with id " + id + " already exists.");
@@ -96,34 +143,34 @@
             }
 
             invalidateCacheForUserLocked(association.getUserId());
+
+            Slog.i(TAG, "Done adding new association.");
         }
 
         broadcastChange(CHANGE_TYPE_ADDED, association);
     }
 
-    void updateAssociation(@NonNull AssociationInfo updated) {
+    /**
+     * Update an association.
+     */
+    public void updateAssociation(@NonNull AssociationInfo updated) {
+        Slog.i(TAG, "Updating new association=" + updated);
         // Validity check first.
         checkNotRevoked(updated);
 
         final int id = updated.getId();
 
-        if (DEBUG) {
-            Log.i(TAG, "updateAssociation() " + updated.toShortString());
-            Log.d(TAG, "  updated=" + updated);
-        }
-
         final AssociationInfo current;
         final boolean macAddressChanged;
         synchronized (mLock) {
             current = mIdMap.get(id);
             if (current == null) {
-                if (DEBUG) Log.w(TAG, "Association with id " + id + " does not exist.");
+                Slog.w(TAG, "Can't update association. It does not exist.");
                 return;
             }
-            if (DEBUG) Log.d(TAG, "  current=" + current);
 
             if (current.equals(updated)) {
-                if (DEBUG) Log.w(TAG, "  No changes.");
+                Slog.w(TAG, "Association is the same.");
                 return;
             }
 
@@ -144,6 +191,7 @@
                     mAddressMap.computeIfAbsent(updatedAddress, it -> new HashSet<>()).add(id);
                 }
             }
+            Slog.i(TAG, "Done updating association.");
         }
 
         final int changeType = macAddressChanged ? CHANGE_TYPE_UPDATED_ADDRESS_CHANGED
@@ -151,21 +199,19 @@
         broadcastChange(changeType, updated);
     }
 
-    void removeAssociation(int id) {
-        if (DEBUG) Log.i(TAG, "removeAssociation() id=" + id);
+    /**
+     * Remove an association
+     */
+    public void removeAssociation(int id) {
+        Slog.i(TAG, "Removing association id=" + id);
 
         final AssociationInfo association;
         synchronized (mLock) {
             association = mIdMap.remove(id);
 
             if (association == null) {
-                if (DEBUG) Log.w(TAG, "Association with id " + id + " is not stored.");
+                Slog.w(TAG, "Can't remove association. It does not exist.");
                 return;
-            } else {
-                if (DEBUG) {
-                    Log.i(TAG, "removed " + association.toShortString());
-                    Log.d(TAG, "  association=" + association);
-                }
             }
 
             final MacAddress macAddress = association.getDeviceMacAddress();
@@ -174,6 +220,8 @@
             }
 
             invalidateCacheForUserLocked(association.getUserId());
+
+            Slog.i(TAG, "Done removing association.");
         }
 
         broadcastChange(CHANGE_TYPE_REMOVED, association);
@@ -195,12 +243,18 @@
         }
     }
 
+    /**
+     * Get associations for the user.
+     */
     public @NonNull List<AssociationInfo> getAssociationsForUser(@UserIdInt int userId) {
         synchronized (mLock) {
             return getAssociationsForUserLocked(userId);
         }
     }
 
+    /**
+     * Get associations for the package
+     */
     public @NonNull List<AssociationInfo> getAssociationsForPackage(
             @UserIdInt int userId, @NonNull String packageName) {
         final List<AssociationInfo> associationsForUser = getAssociationsForUser(userId);
@@ -210,6 +264,9 @@
         return Collections.unmodifiableList(associationsForPackage);
     }
 
+    /**
+     * Get associations by mac address for the package.
+     */
     public @Nullable AssociationInfo getAssociationsForPackageWithAddress(
             @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
         final List<AssociationInfo> associations = getAssociationsByAddress(macAddress);
@@ -217,13 +274,20 @@
                 it -> it.belongsToPackage(userId, packageName));
     }
 
+    /**
+     * Get association by id.
+     */
     public @Nullable AssociationInfo getAssociationById(int id) {
         synchronized (mLock) {
             return mIdMap.get(id);
         }
     }
 
-    public @NonNull List<AssociationInfo> getAssociationsByAddress(@NonNull String macAddress) {
+    /**
+     * Get associations by mac address.
+     */
+    @NonNull
+    public List<AssociationInfo> getAssociationsByAddress(@NonNull String macAddress) {
         final MacAddress address = MacAddress.fromString(macAddress);
 
         synchronized (mLock) {
@@ -240,7 +304,8 @@
     }
 
     @GuardedBy("mLock")
-    private @NonNull List<AssociationInfo> getAssociationsForUserLocked(@UserIdInt int userId) {
+    @NonNull
+    private List<AssociationInfo> getAssociationsForUserLocked(@UserIdInt int userId) {
         final List<AssociationInfo> cached = mCachedPerUser.get(userId);
         if (cached != null) {
             return cached;
@@ -262,12 +327,18 @@
         mCachedPerUser.delete(userId);
     }
 
+    /**
+     * Register a listener for association changes.
+     */
     public void registerListener(@NonNull OnChangeListener listener) {
         synchronized (mListeners) {
             mListeners.add(listener);
         }
     }
 
+    /**
+     * Unregister a listener previously registered for association changes.
+     */
     public void unregisterListener(@NonNull OnChangeListener listener) {
         synchronized (mListeners) {
             mListeners.remove(listener);
@@ -297,43 +368,30 @@
         }
     }
 
-    void setAssociations(Collection<AssociationInfo> allAssociations) {
+    /**
+     * Set associations to cache. It will clear the existing cache.
+     */
+    public void setAssociationsToCache(Collection<AssociationInfo> associations) {
         // Validity check first.
-        allAssociations.forEach(AssociationStoreImpl::checkNotRevoked);
+        associations.forEach(AssociationStore::checkNotRevoked);
 
-        if (DEBUG) {
-            Log.i(TAG, "setAssociations() n=" + allAssociations.size());
-            final StringJoiner stringJoiner = new StringJoiner(", ");
-            allAssociations.forEach(assoc -> stringJoiner.add(assoc.toShortString()));
-            Log.v(TAG, "  associations=" + stringJoiner);
-        }
         synchronized (mLock) {
-            setAssociationsLocked(allAssociations);
-        }
-    }
+            mIdMap.clear();
+            mAddressMap.clear();
+            mCachedPerUser.clear();
 
-    @GuardedBy("mLock")
-    private void setAssociationsLocked(Collection<AssociationInfo> associations) {
-        clearLocked();
+            for (AssociationInfo association : associations) {
+                final int id = association.getId();
+                mIdMap.put(id, association);
 
-        for (AssociationInfo association : associations) {
-            final int id = association.getId();
-            mIdMap.put(id, association);
-
-            final MacAddress address = association.getDeviceMacAddress();
-            if (address != null) {
-                mAddressMap.computeIfAbsent(address, it -> new HashSet<>()).add(id);
+                final MacAddress address = association.getDeviceMacAddress();
+                if (address != null) {
+                    mAddressMap.computeIfAbsent(address, it -> new HashSet<>()).add(id);
+                }
             }
         }
     }
 
-    @GuardedBy("mLock")
-    private void clearLocked() {
-        mIdMap.clear();
-        mAddressMap.clear();
-        mCachedPerUser.clear();
-    }
-
     private static void checkNotRevoked(@NonNull AssociationInfo association) {
         if (association.isRevoked()) {
             throw new IllegalArgumentException(
diff --git a/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
similarity index 84%
rename from services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java
rename to services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
index aac628c..894c49a 100644
--- a/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java
+++ b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
@@ -14,9 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.companion;
-
-import static com.android.server.companion.CompanionDeviceManagerService.TAG;
+package com.android.server.companion.association;
 
 import static java.util.concurrent.TimeUnit.DAYS;
 
@@ -29,13 +27,17 @@
 import android.util.Slog;
 
 import com.android.server.LocalServices;
+import com.android.server.companion.CompanionDeviceManagerServiceInternal;
 
 /**
- * A Job Service responsible for clean up the Association.
+ * A Job Service responsible for clean up idle self-managed associations.
+ *
  * The job will be executed only if the device is charging and in idle mode due to the application
- * will be killed if association/role are revoked.
+ * will be killed if association/role are revoked. See {@link AssociationRevokeProcessor}
  */
 public class InactiveAssociationsRemovalService extends JobService {
+
+    private static final String TAG = "CDM_InactiveAssociationsRemovalService";
     private static final String JOB_NAMESPACE = "companion";
     private static final int JOB_ID = 1;
     private static final long ONE_DAY_INTERVAL = DAYS.toMillis(1);
@@ -60,7 +62,10 @@
         return false;
     }
 
-    static void schedule(Context context) {
+    /**
+     * Schedule this job.
+     */
+    public static void schedule(Context context) {
         Slog.i(TAG, "Scheduling the Association Removal job");
         final JobScheduler jobScheduler =
                 context.getSystemService(JobScheduler.class).forNamespace(JOB_NAMESPACE);
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 260b21f..a08e0da 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -23,7 +23,7 @@
 import static android.content.ComponentName.createRelative;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 
-import static com.android.server.companion.Utils.prepareForIpc;
+import static com.android.server.companion.utils.Utils.prepareForIpc;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -52,11 +52,11 @@
 import android.util.Slog;
 
 import com.android.internal.R;
-import com.android.server.companion.AssociationStore;
 import com.android.server.companion.CompanionDeviceManagerService;
-import com.android.server.companion.PackageUtils;
-import com.android.server.companion.PermissionsUtils;
+import com.android.server.companion.association.AssociationStore;
 import com.android.server.companion.transport.CompanionTransportManager;
+import com.android.server.companion.utils.PackageUtils;
+import com.android.server.companion.utils.PermissionsUtils;
 
 import java.util.List;
 import java.util.concurrent.ExecutorService;
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
index c4c80f9..ee816a0 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
@@ -22,11 +22,11 @@
 import static com.android.internal.util.XmlUtils.readIntAttribute;
 import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
 import static com.android.internal.util.XmlUtils.writeIntAttribute;
-import static com.android.server.companion.DataStoreUtils.createStorageFileForUser;
-import static com.android.server.companion.DataStoreUtils.fileToByteArray;
-import static com.android.server.companion.DataStoreUtils.isEndOfTag;
-import static com.android.server.companion.DataStoreUtils.isStartOfTag;
-import static com.android.server.companion.DataStoreUtils.writeToFileSafely;
+import static com.android.server.companion.utils.DataStoreUtils.createStorageFileForUser;
+import static com.android.server.companion.utils.DataStoreUtils.fileToByteArray;
+import static com.android.server.companion.utils.DataStoreUtils.isEndOfTag;
+import static com.android.server.companion.utils.DataStoreUtils.isStartOfTag;
+import static com.android.server.companion.utils.DataStoreUtils.writeToFileSafely;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
index 7e30790..99466a9 100644
--- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
+++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
@@ -34,7 +34,7 @@
 import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER;
 
 import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG;
-import static com.android.server.companion.presence.Utils.btDeviceToString;
+import static com.android.server.companion.utils.Utils.btDeviceToString;
 
 import static java.util.Objects.requireNonNull;
 
@@ -59,8 +59,8 @@
 import android.util.Log;
 import android.util.Slog;
 
-import com.android.server.companion.AssociationStore;
-import com.android.server.companion.AssociationStore.ChangeType;
+import com.android.server.companion.association.AssociationStore;
+import com.android.server.companion.association.AssociationStore.ChangeType;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -168,7 +168,7 @@
     void startScan() {
         enforceInitialized();
 
-        if (DEBUG) Log.i(TAG, "startScan()");
+        Slog.i(TAG, "startBleScan()");
         // This method should not be called if scan is already in progress.
         if (mScanning) {
             Slog.w(TAG, "Scan is already in progress.");
@@ -228,7 +228,7 @@
     void stopScanIfNeeded() {
         enforceInitialized();
 
-        if (DEBUG) Log.i(TAG, "stopScan()");
+        Slog.i(TAG, "stopBleScan()");
         if (!mScanning) {
             if (DEBUG) Log.d(TAG, "  > not scanning.");
             return;
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
index c514f3e..4da3f9b 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -20,7 +20,7 @@
 import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
 
 import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG;
-import static com.android.server.companion.presence.Utils.btDeviceToString;
+import static com.android.server.companion.utils.Utils.btDeviceToString;
 
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
@@ -39,9 +39,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
-import com.android.server.companion.AssociationStore;
-import com.android.server.companion.ObservableUuid;
-import com.android.server.companion.ObservableUuidStore;
+import com.android.server.companion.association.AssociationStore;
 
 import java.util.Arrays;
 import java.util.Collections;
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index 54a4692..37bbb93 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -41,10 +41,10 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 
-import com.android.server.companion.AssociationStore;
-import com.android.server.companion.ObservableUuid;
-import com.android.server.companion.ObservableUuidStore;
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.companion.association.AssociationStore;
 
 import java.io.PrintWriter;
 import java.util.HashSet;
@@ -102,12 +102,24 @@
     private final @NonNull Set<Integer> mNearbyBleDevices = new HashSet<>();
     private final @NonNull Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
     private final @NonNull Set<ParcelUuid> mConnectedUuidDevices = new HashSet<>();
+    @GuardedBy("mBtDisconnectedDevices")
+    private final @NonNull Set<Integer> mBtDisconnectedDevices = new HashSet<>();
+
+    // A map to track device presence within 10 seconds of Bluetooth disconnection.
+    // The key is the association ID, and the boolean value indicates if the device
+    // was detected again within that time frame.
+    @GuardedBy("mBtDisconnectedDevices")
+    private final @NonNull SparseBooleanArray mBtDisconnectedDevicesBlePresence =
+            new SparseBooleanArray();
 
     // Tracking "simulated" presence. Used for debugging and testing only.
     private final @NonNull Set<Integer> mSimulated = new HashSet<>();
     private final SimulatedDevicePresenceSchedulerHelper mSchedulerHelper =
             new SimulatedDevicePresenceSchedulerHelper();
 
+    private final BleDeviceDisappearedScheduler mBleDeviceDisappearedScheduler =
+            new BleDeviceDisappearedScheduler();
+
     public CompanionDevicePresenceMonitor(UserManager userManager,
             @NonNull AssociationStore associationStore,
             @NonNull ObservableUuidStore observableUuidStore, @NonNull Callback callback) {
@@ -229,13 +241,24 @@
 
     @Override
     public void onBluetoothCompanionDeviceConnected(int associationId) {
-        Slog.i(TAG, "onBluetoothCompanionDeviceConnected: "
-                + "associationId( " + associationId + " )");
-        onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_CONNECTED);
-        // Stop scanning for BLE devices when this device is connected
-        // and there are no other devices to connect to.
-        if (canStopBleScan()) {
-            mBleScanner.stopScanIfNeeded();
+        synchronized (mBtDisconnectedDevices) {
+            // A device is considered reconnected within 10 seconds if a pending BLE lost report is
+            // followed by a detected Bluetooth connection.
+            boolean isReconnected = mBtDisconnectedDevices.contains(associationId);
+            if (isReconnected) {
+                Slog.i(TAG, "Device ( " + associationId + " ) is reconnected within 10s.");
+                mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId);
+            }
+
+            Slog.i(TAG, "onBluetoothCompanionDeviceConnected: "
+                    + "associationId( " + associationId + " )");
+            onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_CONNECTED);
+
+            // Stop the BLE scan if all devices report BT connected status and BLE was present.
+            if (canStopBleScan()) {
+                mBleScanner.stopScanIfNeeded();
+            }
+
         }
     }
 
@@ -247,6 +270,14 @@
         mBleScanner.startScan();
 
         onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_DISCONNECTED);
+        // If current device is BLE present but BT is disconnected , means it will be
+        // potentially out of range later. Schedule BLE disappeared callback.
+        if (isBlePresent(associationId)) {
+            synchronized (mBtDisconnectedDevices) {
+                mBtDisconnectedDevices.add(associationId);
+            }
+            mBleDeviceDisappearedScheduler.scheduleBleDeviceDisappeared(associationId);
+        }
     }
 
     @Override
@@ -283,6 +314,12 @@
     @Override
     public void onBleCompanionDeviceFound(int associationId) {
         onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_APPEARED);
+        synchronized (mBtDisconnectedDevices) {
+            final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get(associationId);
+            if (mBtDisconnectedDevices.contains(associationId) && isCurrentPresent) {
+                mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId);
+            }
+        }
     }
 
     @Override
@@ -353,6 +390,16 @@
 
         switch (event) {
             case EVENT_BLE_APPEARED:
+                synchronized (mBtDisconnectedDevices) {
+                    // If a BLE device is detected within 10 seconds after BT is disconnected,
+                    // flag it as BLE is present.
+                    if (mBtDisconnectedDevices.contains(associationId)) {
+                        Slog.i(TAG, "Device ( " + associationId + " ) is present,"
+                                + " do not need to send the callback with event ( "
+                                + EVENT_BLE_APPEARED + " ).");
+                        mBtDisconnectedDevicesBlePresence.append(associationId, true);
+                    }
+                }
             case EVENT_BT_CONNECTED:
             case EVENT_SELF_MANAGED_APPEARED:
                 final boolean added = presentDevicesForSource.add(associationId);
@@ -405,6 +452,8 @@
         mNearbyBleDevices.remove(id);
         mReportedSelfManagedDevices.remove(id);
         mSimulated.remove(id);
+        mBtDisconnectedDevices.remove(id);
+        mBtDisconnectedDevicesBlePresence.delete(id);
 
         // Do NOT call mCallback.onDeviceDisappeared()!
         // CompanionDeviceManagerService will know that the association is removed, and will do
@@ -427,14 +476,21 @@
         throw new SecurityException("Caller is neither Shell nor Root");
     }
 
+    /**
+     * The BLE scan can be only stopped if all the devices have been reported
+     * BT connected and BLE presence and are not pending to report BLE lost.
+     */
     private boolean canStopBleScan() {
         for (AssociationInfo ai : mAssociationStore.getAssociations()) {
             int id = ai.getId();
-            // The BLE scan cannot be stopped if there's a device is not yet connected.
-            if (ai.isNotifyOnDeviceNearby() && !isBtConnected(id)) {
-                Slog.i(TAG, "The BLE scan cannot be stopped, "
-                        + "device( " + id + " ) is not yet connected");
-                return false;
+            synchronized (mBtDisconnectedDevices) {
+                if (ai.isNotifyOnDeviceNearby() && !(isBtConnected(id)
+                        && isBlePresent(id) && mBtDisconnectedDevices.isEmpty())) {
+                    Slog.i(TAG, "The BLE scan cannot be stopped, "
+                            + "device( " + id + " ) is not yet connected "
+                            + "OR the BLE is not current present Or is pending to report BLE lost");
+                    return false;
+                }
             }
         }
         return true;
@@ -514,4 +570,51 @@
             }
         }
     }
+
+    private class BleDeviceDisappearedScheduler extends Handler {
+        BleDeviceDisappearedScheduler() {
+            super(Looper.getMainLooper());
+        }
+
+        void scheduleBleDeviceDisappeared(int associationId) {
+            if (hasMessages(associationId)) {
+                removeMessages(associationId);
+            }
+            Slog.i(TAG, "scheduleBleDeviceDisappeared for Device: ( " + associationId + " ).");
+            sendEmptyMessageDelayed(associationId,  10 * 1000 /* 10 seconds */);
+        }
+
+        void unScheduleDeviceDisappeared(int associationId) {
+            if (hasMessages(associationId)) {
+                Slog.i(TAG, "unScheduleDeviceDisappeared for Device( " + associationId + " )");
+                synchronized (mBtDisconnectedDevices) {
+                    mBtDisconnectedDevices.remove(associationId);
+                    mBtDisconnectedDevicesBlePresence.delete(associationId);
+                }
+
+                removeMessages(associationId);
+            }
+        }
+
+        @Override
+        public void handleMessage(@NonNull Message msg) {
+            final int associationId = msg.what;
+            synchronized (mBtDisconnectedDevices) {
+                final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get(
+                        associationId);
+                // If a device hasn't reported after 10 seconds and is not currently present,
+                // assume BLE is lost and trigger the onDeviceEvent callback with the
+                // EVENT_BLE_DISAPPEARED event.
+                if (mBtDisconnectedDevices.contains(associationId)
+                        && !isCurrentPresent) {
+                    Slog.i(TAG, "Device ( " + associationId + " ) is likely BLE out of range, "
+                            + "sending callback with event ( " + EVENT_BLE_DISAPPEARED + " )");
+                    onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED);
+                }
+
+                mBtDisconnectedDevices.remove(associationId);
+                mBtDisconnectedDevicesBlePresence.delete(associationId);
+            }
+        }
+    }
 }
diff --git a/services/companion/java/com/android/server/companion/ObservableUuid.java b/services/companion/java/com/android/server/companion/presence/ObservableUuid.java
similarity index 96%
rename from services/companion/java/com/android/server/companion/ObservableUuid.java
rename to services/companion/java/com/android/server/companion/presence/ObservableUuid.java
index 6ab3188..9cfa270 100644
--- a/services/companion/java/com/android/server/companion/ObservableUuid.java
+++ b/services/companion/java/com/android/server/companion/presence/ObservableUuid.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.companion;
+package com.android.server.companion.presence;
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
diff --git a/services/companion/java/com/android/server/companion/ObservableUuidStore.java b/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
similarity index 93%
rename from services/companion/java/com/android/server/companion/ObservableUuidStore.java
rename to services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
index 94be22a..ee8b106 100644
--- a/services/companion/java/com/android/server/companion/ObservableUuidStore.java
+++ b/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.companion;
+package com.android.server.companion.presence;
 
 import static com.android.internal.util.XmlUtils.readIntAttribute;
 import static com.android.internal.util.XmlUtils.readLongAttribute;
@@ -22,10 +22,10 @@
 import static com.android.internal.util.XmlUtils.writeIntAttribute;
 import static com.android.internal.util.XmlUtils.writeLongAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
-import static com.android.server.companion.DataStoreUtils.createStorageFileForUser;
-import static com.android.server.companion.DataStoreUtils.isEndOfTag;
-import static com.android.server.companion.DataStoreUtils.isStartOfTag;
-import static com.android.server.companion.DataStoreUtils.writeToFileSafely;
+import static com.android.server.companion.utils.DataStoreUtils.createStorageFileForUser;
+import static com.android.server.companion.utils.DataStoreUtils.isEndOfTag;
+import static com.android.server.companion.utils.DataStoreUtils.isStartOfTag;
+import static com.android.server.companion.utils.DataStoreUtils.writeToFileSafely;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -57,6 +57,9 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
+/**
+ * This store manages the cache and disk data for observable uuids.
+ */
 public class ObservableUuidStore {
     private static final String TAG = "CDM_ObservableUuidStore";
     private static final String FILE_NAME = "observing_uuids_presence.xml";
@@ -84,9 +87,9 @@
     }
 
     /**
-     * Remove the observable uuid from the disk.
+     * Remove the observable uuid.
      */
-    void removeObservableUuid(@UserIdInt int userId, ParcelUuid uuid, String packageName) {
+    public void removeObservableUuid(@UserIdInt int userId, ParcelUuid uuid, String packageName) {
         List<ObservableUuid> cachedObservableUuids;
 
         synchronized (mLock) {
@@ -101,7 +104,10 @@
         mExecutor.execute(() -> writeObservableUuidToStore(userId, cachedObservableUuids));
     }
 
-    void writeObservableUuid(@UserIdInt int userId, ObservableUuid uuid) {
+    /**
+     * Write the observable uuid.
+     */
+    public void writeObservableUuid(@UserIdInt int userId, ObservableUuid uuid) {
         Slog.i(TAG, "Writing uuid=" + uuid.getUuid() + " to store.");
 
         List<ObservableUuid> cachedObservableUuids;
diff --git a/services/companion/java/com/android/server/companion/presence/Utils.java b/services/companion/java/com/android/server/companion/presence/Utils.java
deleted file mode 100644
index 583b443..0000000
--- a/services/companion/java/com/android/server/companion/presence/Utils.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion.presence;
-
-import android.annotation.NonNull;
-import android.bluetooth.BluetoothDevice;
-
-/** Utilities for working with Bluetooth and BLE devices. */
-class Utils {
-
-    /**
-     * @return short String representation of {@link BluetoothDevice}.
-     */
-    static String btDeviceToString(@NonNull BluetoothDevice btDevice) {
-        final StringBuilder sb = new StringBuilder(btDevice.getAddress());
-
-        sb.append(" [name=");
-        final String name = btDevice.getName();
-        if (name != null) {
-            sb.append('\'').append(name).append('\'');
-        } else {
-            sb.append("null");
-        }
-
-        final String alias = btDevice.getAlias();
-        if (alias != null) {
-            sb.append(", alias='").append(alias).append("'");
-        }
-
-        return sb.append(']').toString();
-    }
-
-    private Utils() {
-    }
-}
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 3861f99..793fb7f 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -32,7 +32,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.server.companion.AssociationStore;
+import com.android.server.companion.association.AssociationStore;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -153,12 +153,12 @@
 
     public void detachSystemDataTransport(String packageName, int userId, int associationId) {
         synchronized (mTransports) {
-            final Transport transport = mTransports.get(associationId);
-            if (transport != null) {
-                mTransports.delete(associationId);
-                transport.stop();
+            final Transport transport = mTransports.removeReturnOld(associationId);
+            if (transport == null) {
+                return;
             }
 
+            transport.stop();
             notifyOnTransportsChanged();
         }
     }
diff --git a/services/companion/java/com/android/server/companion/utils/AssociationUtils.java b/services/companion/java/com/android/server/companion/utils/AssociationUtils.java
new file mode 100644
index 0000000..e4d9641
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/utils/AssociationUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.utils;
+
+import android.annotation.UserIdInt;
+
+public final class AssociationUtils {
+
+    /** Range of Association IDs allocated for a user. */
+    private static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000;
+
+    /**
+     * Get the left boundary of the association id range for the user.
+     */
+    public static int getFirstAssociationIdForUser(@UserIdInt int userId) {
+        // We want the IDs to start from 1, not 0.
+        return userId * ASSOCIATIONS_IDS_PER_USER_RANGE + 1;
+    }
+
+    /**
+     * Get the right boundary of the association id range for the user.
+     */
+    public static int getLastAssociationIdForUser(@UserIdInt int userId) {
+        return (userId + 1) * ASSOCIATIONS_IDS_PER_USER_RANGE;
+    }
+
+    private AssociationUtils() {}
+}
diff --git a/services/companion/java/com/android/server/companion/DataStoreUtils.java b/services/companion/java/com/android/server/companion/utils/DataStoreUtils.java
similarity index 97%
rename from services/companion/java/com/android/server/companion/DataStoreUtils.java
rename to services/companion/java/com/android/server/companion/utils/DataStoreUtils.java
index 04ce1f6..c75b1a5 100644
--- a/services/companion/java/com/android/server/companion/DataStoreUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/DataStoreUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.companion;
+package com.android.server.companion.utils;
 
 import static org.xmlpull.v1.XmlPullParser.END_TAG;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
@@ -80,7 +80,7 @@
 
     /**
      * Writing to file could fail, for example, if the user has been recently removed and so was
-     * their DE (/data/system_de/<user-id>/) directory.
+     * their DE (/data/system_de/[user-id]/) directory.
      */
     public static void writeToFileSafely(
             @NonNull AtomicFile file, @NonNull ThrowingConsumer<FileOutputStream> consumer) {
diff --git a/services/companion/java/com/android/server/companion/MetricUtils.java b/services/companion/java/com/android/server/companion/utils/MetricUtils.java
similarity index 92%
rename from services/companion/java/com/android/server/companion/MetricUtils.java
rename to services/companion/java/com/android/server/companion/utils/MetricUtils.java
index cf867b6..8ea5c89 100644
--- a/services/companion/java/com/android/server/companion/MetricUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/MetricUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.companion;
+package com.android.server.companion.utils;
 
 import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
 import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
@@ -41,7 +41,7 @@
 
 import java.util.Map;
 
-final class MetricUtils {
+public final class MetricUtils {
 
     private static final Map<String, Integer> METRIC_DEVICE_PROFILE;
     static {
@@ -75,13 +75,19 @@
         METRIC_DEVICE_PROFILE = unmodifiableMap(map);
     }
 
-    static void logCreateAssociation(String profile) {
+    /**
+     * Log association creation
+     */
+    public static void logCreateAssociation(String profile) {
         write(CDM_ASSOCIATION_ACTION,
                 CDM_ASSOCIATION_ACTION__ACTION__CREATED,
                 METRIC_DEVICE_PROFILE.get(profile));
     }
 
-    static void logRemoveAssociation(String profile) {
+    /**
+     * Log association removal
+     */
+    public static void logRemoveAssociation(String profile) {
         write(CDM_ASSOCIATION_ACTION,
                 CDM_ASSOCIATION_ACTION__ACTION__REMOVED,
                 METRIC_DEVICE_PROFILE.get(profile));
diff --git a/services/companion/java/com/android/server/companion/PackageUtils.java b/services/companion/java/com/android/server/companion/utils/PackageUtils.java
similarity index 86%
rename from services/companion/java/com/android/server/companion/PackageUtils.java
rename to services/companion/java/com/android/server/companion/utils/PackageUtils.java
index 3aae1ec..d38590e 100644
--- a/services/companion/java/com/android/server/companion/PackageUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/PackageUtils.java
@@ -14,16 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.server.companion;
+package com.android.server.companion.utils;
 
 import static android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP;
 import static android.content.pm.PackageManager.GET_CONFIGURATIONS;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
 import static android.os.Binder.getCallingUid;
 
-import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
-import static com.android.server.companion.CompanionDeviceManagerService.TAG;
-
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -44,13 +41,11 @@
 import android.content.pm.Signature;
 import android.os.Binder;
 import android.os.Process;
-import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.util.ArrayUtils;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -58,16 +53,22 @@
 import java.util.Set;
 
 /**
- * Utility methods for working with {@link PackageInfo}-s.
+ * Utility methods for working with {@link PackageInfo}.
  */
 public final class PackageUtils {
+
+    private static final String TAG = "CDM_PackageUtils";
+
     private static final Intent COMPANION_SERVICE_INTENT =
             new Intent(CompanionDeviceService.SERVICE_INTERFACE);
     private static final String PROPERTY_PRIMARY_TAG =
             "android.companion.PROPERTY_PRIMARY_COMPANION_DEVICE_SERVICE";
 
+    /**
+     * Get package info
+     */
     @Nullable
-    static PackageInfo getPackageInfo(@NonNull Context context,
+    public static PackageInfo getPackageInfo(@NonNull Context context,
             @UserIdInt int userId, @NonNull String packageName) {
         final PackageManager pm = context.getPackageManager();
         final PackageInfoFlags flags = PackageInfoFlags.of(GET_PERMISSIONS | GET_CONFIGURATIONS);
@@ -81,26 +82,32 @@
         });
     }
 
-    static void enforceUsesCompanionDeviceFeature(@NonNull Context context,
+    /**
+     * Require the app to declare the companion device feature.
+     */
+    public static void enforceUsesCompanionDeviceFeature(@NonNull Context context,
             @UserIdInt int userId, @NonNull String packageName) {
         // Allow system server to create CDM associations without FEATURE_COMPANION_DEVICE_SETUP
         if (getCallingUid() == Process.SYSTEM_UID) {
             return;
         }
 
-        String requiredFeature = FEATURE_COMPANION_DEVICE_SETUP;
+        PackageInfo packageInfo = getPackageInfo(context, userId, packageName);
+        if (packageInfo == null) {
+            throw new IllegalArgumentException("Package " + packageName + " doesn't exist.");
+        }
 
-        FeatureInfo[] requestedFeatures = getPackageInfo(context, userId, packageName).reqFeatures;
+        FeatureInfo[] requestedFeatures = packageInfo.reqFeatures;
         if (requestedFeatures != null) {
-            for (int i = 0; i < requestedFeatures.length; i++) {
-                if (requiredFeature.equals(requestedFeatures[i].name)) {
+            for (FeatureInfo requestedFeature : requestedFeatures) {
+                if (FEATURE_COMPANION_DEVICE_SETUP.equals(requestedFeature.name)) {
                     return;
                 }
             }
         }
 
         throw new IllegalStateException("Must declare uses-feature "
-                + requiredFeature
+                + FEATURE_COMPANION_DEVICE_SETUP
                 + " in manifest to use this API");
     }
 
@@ -109,7 +116,7 @@
      *         Services marked as "primary" would always appear at the head of the lists, *before*
      *         all non-primary services.
      */
-    static @NonNull Map<String, List<ComponentName>> getCompanionServicesForUser(
+    public static @NonNull Map<String, List<ComponentName>> getCompanionServicesForUser(
             @NonNull Context context, @UserIdInt int userId) {
         final PackageManager pm = context.getPackageManager();
         final List<ResolveInfo> companionServices = pm.queryIntentServicesAsUser(
@@ -179,9 +186,7 @@
         final String[] allowlistedPackages = context.getResources()
                 .getStringArray(com.android.internal.R.array.config_companionDevicePackages);
         if (!ArrayUtils.contains(allowlistedPackages, packageName)) {
-            if (DEBUG) {
-                Log.d(TAG, packageName + " is not allowlisted.");
-            }
+            Slog.d(TAG, packageName + " is not allowlisted.");
             return false;
         }
 
@@ -212,13 +217,6 @@
 
         if (!requestingPackageSignatureAllowlisted) {
             Slog.w(TAG, "Certificate mismatch for allowlisted package " + packageName);
-            if (DEBUG) {
-                Log.d(TAG, "  > allowlisted signatures for " + packageName + ": ["
-                        + String.join(", ", allowlistedSignatureDigestsForRequestingPackage)
-                        + "]");
-                Log.d(TAG, "  > actual signatures for " + packageName + ": "
-                        + Arrays.toString(requestingPackageSignatureDigests));
-            }
         }
 
         return requestingPackageSignatureAllowlisted;
diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
similarity index 80%
rename from services/companion/java/com/android/server/companion/PermissionsUtils.java
rename to services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
index 15bebba..2cf1f46 100644
--- a/services/companion/java/com/android/server/companion/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.companion;
+package com.android.server.companion.utils;
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
@@ -75,16 +75,22 @@
         DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map);
     }
 
-    static void enforcePermissionsForAssociation(@NonNull Context context,
+    /**
+     * Require the app to declare necessary permission for creating association.
+     */
+    public static void enforcePermissionForCreatingAssociation(@NonNull Context context,
             @NonNull AssociationRequest request, int packageUid) {
-        enforceRequestDeviceProfilePermissions(context, request.getDeviceProfile(), packageUid);
+        enforcePermissionForRequestingProfile(context, request.getDeviceProfile(), packageUid);
 
         if (request.isSelfManaged()) {
-            enforceRequestSelfManagedPermission(context, packageUid);
+            enforcePermissionForRequestingSelfManaged(context, packageUid);
         }
     }
 
-    static void enforceRequestDeviceProfilePermissions(
+    /**
+     * Require the app to declare necessary permission for creating association with profile.
+     */
+    public static void enforcePermissionForRequestingProfile(
             @NonNull Context context, @Nullable String deviceProfile, int packageUid) {
         // Device profile can be null.
         if (deviceProfile == null) return;
@@ -101,7 +107,11 @@
         }
     }
 
-    static void enforceRequestSelfManagedPermission(@NonNull Context context, int packageUid) {
+    /**
+     * Require the app to declare necessary permission for creating self-managed association.
+     */
+    public static void enforcePermissionForRequestingSelfManaged(@NonNull Context context,
+            int packageUid) {
         if (context.checkPermission(REQUEST_COMPANION_SELF_MANAGED, getCallingPid(), packageUid)
                 != PERMISSION_GRANTED) {
             throw new SecurityException("Application does not hold "
@@ -109,25 +119,39 @@
         }
     }
 
-    static boolean checkCallerCanInteractWithUserId(@NonNull Context context, int userId) {
+    /**
+     * Check if the caller can interact with the user.
+     */
+    public static boolean checkCallerCanInteractWithUserId(@NonNull Context context, int userId) {
         if (getCallingUserId() == userId) return true;
 
         return context.checkCallingPermission(INTERACT_ACROSS_USERS) == PERMISSION_GRANTED;
     }
 
-    static void enforceCallerCanInteractWithUserId(@NonNull Context context, int userId) {
+    /**
+     * Require the caller to be able to interact with the user.
+     */
+    public static void enforceCallerCanInteractWithUserId(@NonNull Context context, int userId) {
         if (getCallingUserId() == userId) return;
 
         context.enforceCallingPermission(INTERACT_ACROSS_USERS, null);
     }
 
-    static void enforceCallerIsSystemOrCanInteractWithUserId(@NonNull Context context, int userId) {
+    /**
+     * Require the caller to be system UID or to be able to interact with the user.
+     */
+    public static void enforceCallerIsSystemOrCanInteractWithUserId(@NonNull Context context,
+            int userId) {
         if (getCallingUid() == SYSTEM_UID) return;
 
         enforceCallerCanInteractWithUserId(context, userId);
     }
 
-    static boolean checkCallerIsSystemOr(@UserIdInt int userId, @NonNull String packageName) {
+    /**
+     * Check if the caller is system UID or the provided user.
+     */
+    public static boolean checkCallerIsSystemOr(@UserIdInt int userId,
+            @NonNull String packageName) {
         final int callingUid = getCallingUid();
         if (callingUid == SYSTEM_UID) return true;
 
@@ -158,13 +182,19 @@
         }
     }
 
-    static boolean checkCallerCanManageCompanionDevice(@NonNull Context context) {
+    /**
+     * Check if the caller holds the necessary permission to manage companion devices.
+     */
+    public static boolean checkCallerCanManageCompanionDevice(@NonNull Context context) {
         if (getCallingUid() == SYSTEM_UID) return true;
 
         return context.checkCallingPermission(MANAGE_COMPANION_DEVICES) == PERMISSION_GRANTED;
     }
 
-    static void enforceCallerCanManageAssociationsForPackage(@NonNull Context context,
+    /**
+     * Require the caller to be able to manage the associations for the package.
+     */
+    public static void enforceCallerCanManageAssociationsForPackage(@NonNull Context context,
             @UserIdInt int userId, @NonNull String packageName,
             @Nullable String actionDescription) {
         if (checkCallerCanManageAssociationsForPackage(context, userId, packageName)) return;
@@ -175,7 +205,10 @@
                 + " for u" + userId + "/" + packageName);
     }
 
-    static void enforceCallerCanObservingDevicePresenceByUuid(@NonNull Context context) {
+    /**
+     * Require the caller to hold necessary permission to observe device presence by UUID.
+     */
+    public static void enforceCallerCanObservingDevicePresenceByUuid(@NonNull Context context) {
         if (context.checkCallingPermission(REQUEST_OBSERVE_DEVICE_UUID_PRESENCE)
                 != PERMISSION_GRANTED) {
             throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
@@ -193,7 +226,7 @@
      * </ul>
      * @return whether the caller is one of the above.
      */
-    static boolean checkCallerCanManageAssociationsForPackage(@NonNull Context context,
+    public static boolean checkCallerCanManageAssociationsForPackage(@NonNull Context context,
             @UserIdInt int userId, @NonNull String packageName) {
         if (checkCallerIsSystemOr(userId, packageName)) return true;
 
diff --git a/services/companion/java/com/android/server/companion/RolesUtils.java b/services/companion/java/com/android/server/companion/utils/RolesUtils.java
similarity index 70%
rename from services/companion/java/com/android/server/companion/RolesUtils.java
rename to services/companion/java/com/android/server/companion/utils/RolesUtils.java
index af9d2d7..f798e21 100644
--- a/services/companion/java/com/android/server/companion/RolesUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/RolesUtils.java
@@ -14,13 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.server.companion;
+package com.android.server.companion.utils;
 
 import static android.app.role.RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP;
 
-import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
-import static com.android.server.companion.CompanionDeviceManagerService.TAG;
-
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
 import android.annotation.UserIdInt;
@@ -29,7 +26,6 @@
 import android.content.Context;
 import android.os.Binder;
 import android.os.UserHandle;
-import android.util.Log;
 import android.util.Slog;
 
 import java.util.List;
@@ -37,9 +33,14 @@
 
 /** Utility methods for accessing {@link RoleManager} APIs. */
 @SuppressLint("LongLogTag")
-final class RolesUtils {
+public final class RolesUtils {
 
-    static boolean isRoleHolder(@NonNull Context context, @UserIdInt int userId,
+    private static final String TAG = "CDM_RolesUtils";
+
+    /**
+     * Check if the package holds the role.
+     */
+    public static boolean isRoleHolder(@NonNull Context context, @UserIdInt int userId,
             @NonNull String packageName, @NonNull String role) {
         final RoleManager roleManager = context.getSystemService(RoleManager.class);
         final List<String> roleHolders = roleManager.getRoleHoldersAsUser(
@@ -58,13 +59,9 @@
      *                        false if failed. If the association does not have any device profile
      *                        specified, then the operation will always be successful as a no-op.
      */
-    static void addRoleHolderForAssociation(
+    public static void addRoleHolderForAssociation(
             @NonNull Context context, @NonNull AssociationInfo associationInfo,
             @NonNull Consumer<Boolean> roleGrantResult) {
-        if (DEBUG) {
-            Log.d(TAG, "addRoleHolderForAssociation() associationInfo=" + associationInfo);
-        }
-
         final String deviceProfile = associationInfo.getDeviceProfile();
         if (deviceProfile == null) {
             // If no device profile is specified, then no-op and resolve callback with success.
@@ -83,33 +80,30 @@
                 roleGrantResult);
     }
 
-    static void removeRoleHolderForAssociation(
-            @NonNull Context context, @NonNull AssociationInfo associationInfo) {
-        if (DEBUG) {
-            Log.d(TAG, "removeRoleHolderForAssociation() associationInfo=" + associationInfo);
-        }
-
-        final String deviceProfile = associationInfo.getDeviceProfile();
+    /**
+     * Remove the role for the package association.
+     */
+    public static void removeRoleHolderForAssociation(
+            @NonNull Context context, int userId, String packageName, String deviceProfile) {
         if (deviceProfile == null) return;
 
         final RoleManager roleManager = context.getSystemService(RoleManager.class);
 
-        final String packageName = associationInfo.getPackageName();
-        final int userId = associationInfo.getUserId();
         final UserHandle userHandle = UserHandle.of(userId);
 
-        Slog.i(TAG, "Removing CDM role holder, role=" + deviceProfile
-                + ", package=u" + userId + "\\" + packageName);
+        Slog.i(TAG, "Removing CDM role=" + deviceProfile
+                + " for userId=" + userId + ", packageName=" + packageName);
         final long identity = Binder.clearCallingIdentity();
         try {
             roleManager.removeRoleHolderAsUser(deviceProfile, packageName,
-                MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(),
-                success -> {
-                    if (!success) {
-                        Slog.e(TAG, "Failed to remove u" + userId + "\\" + packageName
-                                + " from the list of " + deviceProfile + " holders.");
-                    }
-                });
+                    MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(),
+                    success -> {
+                        if (!success) {
+                            Slog.e(TAG, "Failed to remove userId=" + userId + ", packageName="
+                                    + packageName + " from the list of " + deviceProfile
+                                    + " holders.");
+                        }
+                    });
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/services/companion/java/com/android/server/companion/Utils.java b/services/companion/java/com/android/server/companion/utils/Utils.java
similarity index 64%
rename from services/companion/java/com/android/server/companion/Utils.java
rename to services/companion/java/com/android/server/companion/utils/Utils.java
index b9f61ec..8302997 100644
--- a/services/companion/java/com/android/server/companion/Utils.java
+++ b/services/companion/java/com/android/server/companion/utils/Utils.java
@@ -14,8 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.server.companion;
+package com.android.server.companion.utils;
 
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothDevice;
 import android.os.Parcel;
 import android.os.ResultReceiver;
 
@@ -43,5 +45,27 @@
         return ipcFriendly;
     }
 
+    /**
+     * Return a human-readable string for the BluetoothDevice.
+     */
+    public static String btDeviceToString(@NonNull BluetoothDevice btDevice) {
+        final StringBuilder sb = new StringBuilder(btDevice.getAddress());
+
+        sb.append(" [name=");
+        final String name = btDevice.getName();
+        if (name != null) {
+            sb.append('\'').append(name).append('\'');
+        } else {
+            sb.append("null");
+        }
+
+        final String alias = btDevice.getAlias();
+        if (alias != null) {
+            sb.append(", alias='").append(alias).append("'");
+        }
+
+        return sb.append(']').toString();
+    }
+
     private Utils() {}
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
index 82ab098..340bc32 100644
--- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
+++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
@@ -38,7 +38,8 @@
         {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
-      ]
+      ],
+      "keywords": ["primary-device"]
     },
     {
       "name": "CtsHardwareTestCases",
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 6d731b2..8244d20 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -29,6 +29,7 @@
 import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static android.companion.virtualdevice.flags.Flags.virtualCameraServiceDiscovery;
 
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
@@ -111,6 +112,8 @@
 import com.android.server.companion.virtual.camera.VirtualCameraController;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 
+import dalvik.annotation.optimization.FastNative;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.List;
@@ -210,7 +213,7 @@
                     mActivityListener.onTopActivityChanged(displayId, topActivity,
                             UserHandle.USER_NULL);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "Unable to call mActivityListener", e);
+                    Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
                 }
             }
 
@@ -220,7 +223,7 @@
                 try {
                     mActivityListener.onTopActivityChanged(displayId, topActivity, userId);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "Unable to call mActivityListener", e);
+                    Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
                 }
             }
 
@@ -229,7 +232,7 @@
                 try {
                     mActivityListener.onDisplayEmpty(displayId);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "Unable to call mActivityListener", e);
+                    Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
                 }
             }
         };
@@ -265,7 +268,7 @@
                 runningAppsChangedCallback,
                 params,
                 DisplayManagerGlobal.getInstance(),
-                Flags.virtualCamera()
+                isVirtualCameraEnabled()
                         ? new VirtualCameraController(params.getDevicePolicy(POLICY_TYPE_CAMERA))
                         : null);
     }
@@ -1213,7 +1216,7 @@
         mContext.startActivityAsUser(
                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK),
                 ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle(),
-                mContext.getUser());
+                UserHandle.SYSTEM);
     }
 
     private void onSecureWindowShown(int displayId, int uid) {
@@ -1535,4 +1538,13 @@
             return mToken;
         }
     }
+
+    private static boolean isVirtualCameraEnabled() {
+        return Flags.virtualCamera() && virtualCameraServiceDiscovery()
+                && nativeVirtualCameraServiceBuildFlagEnabled();
+    }
+
+    // Returns true if virtual_camera service is enabled in this build.
+    @FastNative
+    private static native boolean nativeVirtualCameraServiceBuildFlagEnabled();
 }
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index d4ff699..6b5ba96 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -50,7 +50,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityThread;
-import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.DevicePolicyCache;
 import android.app.assist.ActivityId;
 import android.content.ComponentName;
 import android.content.ContentCaptureOptions;
@@ -940,7 +940,7 @@
         return new ContentProtectionConsentManager(
                 BackgroundThread.getHandler(),
                 getContext().getContentResolver(),
-                LocalServices.getService(DevicePolicyManagerInternal.class));
+                DevicePolicyCache.getInstance());
     }
 
     @Nullable
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionConsentManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionConsentManager.java
index 488a51a..9aa5d2f 100644
--- a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionConsentManager.java
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionConsentManager.java
@@ -16,8 +16,13 @@
 
 package com.android.server.contentprotection;
 
+import static android.view.contentprotection.flags.Flags.manageDevicePolicyEnabled;
+
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyCache;
+import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.ContentResolver;
 import android.database.ContentObserver;
@@ -28,6 +33,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
 
 /**
  * Manages consent for content protection.
@@ -45,6 +51,8 @@
 
     @NonNull private final ContentResolver mContentResolver;
 
+    @NonNull private final DevicePolicyCache mDevicePolicyCache;
+
     @NonNull private final DevicePolicyManagerInternal mDevicePolicyManagerInternal;
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@@ -53,54 +61,98 @@
 
     private volatile boolean mCachedPackageVerifierConsent;
 
-    private volatile boolean mCachedContentProtectionConsent;
+    private volatile boolean mCachedContentProtectionUserConsent;
 
     public ContentProtectionConsentManager(
             @NonNull Handler handler,
             @NonNull ContentResolver contentResolver,
-            @NonNull DevicePolicyManagerInternal devicePolicyManagerInternal) {
+            @NonNull DevicePolicyCache devicePolicyCache) {
         mContentResolver = contentResolver;
-        mDevicePolicyManagerInternal = devicePolicyManagerInternal;
+        mDevicePolicyCache = devicePolicyCache;
+        mDevicePolicyManagerInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
         mContentObserver = new SettingsObserver(handler);
 
-        contentResolver.registerContentObserver(
-                Settings.Global.getUriFor(KEY_PACKAGE_VERIFIER_USER_CONSENT),
-                /* notifyForDescendants= */ false,
-                mContentObserver,
-                UserHandle.USER_ALL);
+        registerSettingsGlobalObserver(KEY_PACKAGE_VERIFIER_USER_CONSENT);
+        registerSettingsGlobalObserver(KEY_CONTENT_PROTECTION_USER_CONSENT);
+        readPackageVerifierConsentGranted();
+        readContentProtectionUserConsentGranted();
+    }
 
-        mCachedPackageVerifierConsent = isPackageVerifierConsentGranted();
-        mCachedContentProtectionConsent = isContentProtectionConsentGranted();
+    /** Returns true if the consent is ultimately granted. */
+    public boolean isConsentGranted(@UserIdInt int userId) {
+        return mCachedPackageVerifierConsent && isContentProtectionConsentGranted(userId);
     }
 
     /**
-     * Returns true if all the consents are granted
+     * Not always cached internally and can be expensive, when possible prefer to use {@link
+     * #mCachedPackageVerifierConsent} instead.
      */
-    public boolean isConsentGranted(@UserIdInt int userId) {
-        return mCachedPackageVerifierConsent
-                && mCachedContentProtectionConsent
-                && !isUserOrganizationManaged(userId);
-    }
-
     private boolean isPackageVerifierConsentGranted() {
-        // Not always cached internally
         return Settings.Global.getInt(
                         mContentResolver, KEY_PACKAGE_VERIFIER_USER_CONSENT, /* def= */ 0)
                 >= 1;
     }
 
-    private boolean isContentProtectionConsentGranted() {
-        // Not always cached internally
+    /**
+     * Not always cached internally and can be expensive, when possible prefer to use {@link
+     * #mCachedContentProtectionUserConsent} instead.
+     */
+    private boolean isContentProtectionUserConsentGranted() {
         return Settings.Global.getInt(
                         mContentResolver, KEY_CONTENT_PROTECTION_USER_CONSENT, /* def= */ 0)
                 >= 0;
     }
 
+    private void readPackageVerifierConsentGranted() {
+        mCachedPackageVerifierConsent = isPackageVerifierConsentGranted();
+    }
+
+    private void readContentProtectionUserConsentGranted() {
+        mCachedContentProtectionUserConsent = isContentProtectionUserConsentGranted();
+    }
+
+    /** Always cached internally, cheap and safe to use. */
     private boolean isUserOrganizationManaged(@UserIdInt int userId) {
-        // Cached internally
         return mDevicePolicyManagerInternal.isUserOrganizationManaged(userId);
     }
 
+    /** Always cached internally, cheap and safe to use. */
+    private boolean isContentProtectionPolicyGranted(@UserIdInt int userId) {
+        if (!manageDevicePolicyEnabled()) {
+            return false;
+        }
+
+        @DevicePolicyManager.ContentProtectionPolicy
+        int policy = mDevicePolicyCache.getContentProtectionPolicy(userId);
+
+        return switch (policy) {
+            case DevicePolicyManager.CONTENT_PROTECTION_ENABLED -> true;
+            case DevicePolicyManager.CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY ->
+                    mCachedContentProtectionUserConsent;
+            default -> false;
+        };
+    }
+
+    /** Always cached internally, cheap and safe to use. */
+    private boolean isContentProtectionConsentGranted(@UserIdInt int userId) {
+        if (!manageDevicePolicyEnabled()) {
+            return mCachedContentProtectionUserConsent && !isUserOrganizationManaged(userId);
+        }
+
+        return isUserOrganizationManaged(userId)
+                ? isContentProtectionPolicyGranted(userId)
+                : mCachedContentProtectionUserConsent;
+    }
+
+    private void registerSettingsGlobalObserver(@NonNull String key) {
+        registerSettingsObserver(Settings.Global.getUriFor(key));
+    }
+
+    private void registerSettingsObserver(@NonNull Uri uri) {
+        mContentResolver.registerContentObserver(
+                uri, /* notifyForDescendants= */ false, mContentObserver, UserHandle.USER_ALL);
+    }
+
     private final class SettingsObserver extends ContentObserver {
 
         SettingsObserver(Handler handler) {
@@ -108,17 +160,20 @@
         }
 
         @Override
-        public void onChange(boolean selfChange, Uri uri, @UserIdInt int userId) {
+        public void onChange(boolean selfChange, @Nullable Uri uri, @UserIdInt int userId) {
+            if (uri == null) {
+                return;
+            }
             final String property = uri.getLastPathSegment();
             if (property == null) {
                 return;
             }
             switch (property) {
                 case KEY_PACKAGE_VERIFIER_USER_CONSENT:
-                    mCachedPackageVerifierConsent = isPackageVerifierConsentGranted();
+                    readPackageVerifierConsentGranted();
                     return;
                 case KEY_CONTENT_PROTECTION_USER_CONSENT:
-                    mCachedContentProtectionConsent = isContentProtectionConsentGranted();
+                    readContentProtectionUserConsentGranted();
                     return;
                 default:
                     Slog.w(TAG, "Ignoring unexpected property: " + property);
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index f82a6aa..748253f 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -106,7 +106,7 @@
     private static final int DEFAULT_AGE_SECONDS = 3 * 86400;
     private static final int DEFAULT_MAX_FILES = 1000;
     private static final int DEFAULT_MAX_FILES_LOWRAM = 300;
-    private static final int DEFAULT_QUOTA_KB = 10 * 1024;
+    private static final int DEFAULT_QUOTA_KB = Build.IS_USERDEBUG ? 20 * 1024 : 10 * 1024;
     private static final int DEFAULT_QUOTA_PERCENT = 10;
     private static final int DEFAULT_RESERVE_PERCENT = 0;
     private static final int QUOTA_RESCAN_MILLIS = 5000;
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
index c5c2b0b..c7a8369 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -29,7 +29,6 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.IActivityManager;
-import android.app.SearchManager;
 import android.app.UidObserver;
 import android.app.pinner.IPinnerService;
 import android.app.pinner.PinnedFileStat;
@@ -53,7 +52,6 @@
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.SystemProperties;
-import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.DeviceConfig;
@@ -139,7 +137,6 @@
     private final ActivityManagerInternal mAmInternal;
     private final IActivityManager mAm;
     private final UserManager mUserManager;
-    private SearchManager mSearchManager;
 
     /** The list of the statically pinned files. */
     @GuardedBy("this") private final ArrayMap<String, PinnedFile> mPinnedFiles = new ArrayMap<>();
@@ -283,15 +280,6 @@
         sendPinAppsMessage(UserHandle.USER_SYSTEM);
     }
 
-    @Override
-    public void onBootPhase(int phase) {
-        // SearchManagerService is started after PinnerService, wait for PHASE_SYSTEM_SERVICES_READY
-        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
-            mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
-            sendPinAppsMessage(UserHandle.USER_SYSTEM);
-        }
-    }
-
     /**
      * Repin apps on user switch.
      * <p>
@@ -308,8 +296,9 @@
 
     @Override
     public void onUserUnlocking(@NonNull TargetUser user) {
-        int userId = user.getUserIdentifier();
-        if (!mUserManager.isManagedProfile(userId)) {
+        final int userId = user.getUserIdentifier();
+        if (userId != UserHandle.USER_SYSTEM && !mUserManager.isManagedProfile(userId)) {
+            // App pinning for the system should have already been triggered from onStart().
             sendPinAppsMessage(userId);
         }
     }
@@ -532,11 +521,8 @@
     }
 
     private ApplicationInfo getAssistantInfo(int userHandle) {
-        if (mSearchManager != null) {
-            Intent intent = mSearchManager.getAssistIntent(false);
-            return getApplicationInfoForIntent(intent, userHandle, true);
-        }
-        return null;
+        Intent intent = new Intent(Intent.ACTION_ASSIST);
+        return getApplicationInfoForIntent(intent, userHandle, true);
     }
 
     private ApplicationInfo getApplicationInfoForIntent(Intent intent, int userHandle,
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 9189ea7..1a3ef73 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -264,8 +264,8 @@
     final ArrayMap<String, ArraySet<String>> mAllowIgnoreLocationSettings = new ArrayMap<>();
 
     // These are the packages that are allow-listed to be able to access camera when
-    // the camera privacy state is for driver assistance apps only.
-    final ArrayMap<String, Boolean> mAllowlistCameraPrivacy = new ArrayMap<>();
+    // the camera privacy state is enabled.
+    final ArraySet<String> mAllowlistCameraPrivacy = new ArraySet<>();
 
     // These are the action strings of broadcasts which are whitelisted to
     // be delivered anonymously even to apps which target O+.
@@ -348,6 +348,9 @@
     // marked as stopped by the system
     @NonNull private final Set<String> mInitialNonStoppedSystemPackages = new ArraySet<>();
 
+    // Which packages (key) are allowed to join particular SharedUid (value).
+    @NonNull private final Map<String, String> mPackageToSharedUidAllowList = new ArrayMap<>();
+
     // A map of preloaded package names and the path to its app metadata file path.
     private final ArrayMap<String, String> mAppMetadataFilePaths = new ArrayMap<>();
 
@@ -486,7 +489,7 @@
         return mAllowedAssociations;
     }
 
-    public ArrayMap<String, Boolean> getCameraPrivacyAllowlist() {
+    public ArraySet<String> getCameraPrivacyAllowlist() {
         return mAllowlistCameraPrivacy;
     }
 
@@ -567,6 +570,11 @@
         return mInitialNonStoppedSystemPackages;
     }
 
+    @NonNull
+    public Map<String, String> getPackageToSharedUidAllowList() {
+        return mPackageToSharedUidAllowList;
+    }
+
     public ArrayMap<String, String> getAppMetadataFilePaths() {
         return mAppMetadataFilePaths;
     }
@@ -1068,13 +1076,11 @@
                     case "camera-privacy-allowlisted-app" : {
                         if (allowOverrideAppRestrictions) {
                             String pkgname = parser.getAttributeValue(null, "package");
-                            boolean isMandatory = XmlUtils.readBooleanAttribute(
-                                    parser, "mandatory", false);
                             if (pkgname == null) {
                                 Slog.w(TAG, "<" + name + "> without package in "
                                         + permFile + " at " + parser.getPositionDescription());
                             } else {
-                                mAllowlistCameraPrivacy.put(pkgname, isMandatory);
+                                mAllowlistCameraPrivacy.add(pkgname);
                             }
                         } else {
                             logNotAllowedInPartition(name, permFile, parser);
@@ -1563,6 +1569,19 @@
                             mInitialNonStoppedSystemPackages.add(pkgName);
                         }
                     } break;
+                    case "allow-package-shareduid": {
+                        String pkgName = parser.getAttributeValue(null, "package");
+                        String sharedUid = parser.getAttributeValue(null, "shareduid");
+                        if (TextUtils.isEmpty(pkgName)) {
+                            Slog.w(TAG, "<" + name + "> without package in " + permFile
+                                    + " at " + parser.getPositionDescription());
+                        } else if (TextUtils.isEmpty(sharedUid)) {
+                            Slog.w(TAG, "<" + name + "> without shareduid in " + permFile
+                                    + " at " + parser.getPositionDescription());
+                        } else {
+                            mPackageToSharedUidAllowList.put(pkgName, sharedUid);
+                        }
+                    }
                     case "asl-file": {
                         String packageName = parser.getAttributeValue(null, "package");
                         String path = parser.getAttributeValue(null, "path");
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index 7dc9f10..4de85fe 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -39,7 +39,9 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * The base class for services running in the system process. Override and implement
@@ -135,6 +137,7 @@
     public @interface BootPhase {}
 
     private final Context mContext;
+    private final List<Class<?>> mDependencies;
 
     /**
      * Class representing user in question in the lifecycle callbacks.
@@ -332,7 +335,28 @@
      * @param context The system server context.
      */
     public SystemService(@NonNull Context context) {
+        this(context, Collections.emptyList());
+    }
+
+    /**
+     * Initializes the system service.
+     * <p>
+     * Subclasses must define a single argument constructor that accepts the context
+     * and passes it to super.
+     * </p>
+     *
+     * @param context The system server context.
+     * @param dependencies The list of dependencies that this service requires to operate.
+     *                     Currently only used by the Ravenwood deviceless testing environment to
+     *                     understand transitive dependencies needed to support a specific test.
+     *                     For example, including {@code PowerManager.class} here indicates that
+     *                     this service requires the {@code PowerManager} and/or {@code
+     *                     PowerManagerInternal} APIs to function.
+     * @hide
+     */
+    public SystemService(@NonNull Context context, @NonNull List<Class<?>> dependencies) {
         mContext = context;
+        mDependencies = Objects.requireNonNull(dependencies);
     }
 
     /**
@@ -356,6 +380,22 @@
     }
 
     /**
+     * Get the list of dependencies that this service requires to operate.
+     *
+     * Currently only used by the Ravenwood deviceless testing environment to understand transitive
+     * dependencies needed to support a specific test.
+     *
+     * For example, including {@code PowerManager.class} here indicates that this service
+     * requires the {@code PowerManager} and/or {@code PowerManagerInternal} APIs to function.
+     *
+     * @hide
+     */
+    @NonNull
+    public final List<Class<?>> getDependencies() {
+        return mDependencies;
+    }
+
+    /**
      * Returns true if the system is running in safe mode.
      * TODO: we should define in which phase this becomes valid
      *
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 5e9d1cb..25337a4 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -100,7 +100,7 @@
             "file_patterns": ["VcnManagementService\\.java"]
         },
         {
-            "name": "FrameworksNetTests",
+            "name": "FrameworksVpnTests",
             "options": [
                 {
                     "exclude-annotation": "com.android.testutils.SkipPresubmit"
diff --git a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java b/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java
index 3312be2..0e0bf81 100644
--- a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java
+++ b/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java
@@ -20,6 +20,7 @@
 
 import android.app.KeyguardManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.hardware.biometrics.AuthenticationStateListener;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricSourceType;
@@ -32,8 +33,10 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseIntArray;
+import android.util.SparseLongArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
 import com.android.internal.widget.LockSettingsStateListener;
@@ -57,6 +60,8 @@
     private static final int MSG_REPORT_BIOMETRIC_AUTH_ATTEMPT = 2;
     private static final int AUTH_SUCCESS = 1;
     private static final int AUTH_FAILURE = 0;
+    private static final int TYPE_PRIMARY_AUTH = 0;
+    private static final int TYPE_BIOMETRIC_AUTH = 1;
 
     private final LockPatternUtils mLockPatternUtils;
     private final LockSettingsInternal mLockSettings;
@@ -67,6 +72,7 @@
     private final UserManagerInternal mUserManager;
     @VisibleForTesting
     final SparseIntArray mFailedAttemptsForUser = new SparseIntArray();
+    private final SparseLongArray mLastLockedTimestamp = new SparseLongArray();
 
     public AdaptiveAuthService(Context context) {
         this(context, new LockPatternUtils(context));
@@ -170,7 +176,7 @@
             Slog.d(TAG, "handleReportPrimaryAuthAttempt: success=" + success
                     + ", userId=" + userId);
         }
-        reportAuthAttempt(success, userId);
+        reportAuthAttempt(TYPE_PRIMARY_AUTH, success, userId);
     }
 
     private void handleReportBiometricAuthAttempt(boolean success, int userId) {
@@ -178,13 +184,29 @@
             Slog.d(TAG, "handleReportBiometricAuthAttempt: success=" + success
                     + ", userId=" + userId);
         }
-        reportAuthAttempt(success, userId);
+        reportAuthAttempt(TYPE_BIOMETRIC_AUTH, success, userId);
     }
 
-    private void reportAuthAttempt(boolean success, int userId) {
+    private void reportAuthAttempt(int authType, boolean success, int userId) {
+        // Disable adaptive auth for automotive devices by default
+        if (getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+            return;
+        }
+
         if (success) {
             // Deleting the entry effectively resets the counter of failed attempts for the user
             mFailedAttemptsForUser.delete(userId);
+
+            // Collect metrics if the device was locked by adaptive auth before
+            if (mLastLockedTimestamp.indexOfKey(userId) >= 0) {
+                final long lastLockedTime = mLastLockedTimestamp.get(userId);
+                collectTimeElapsedSinceLastLocked(
+                        lastLockedTime, SystemClock.elapsedRealtime(), authType);
+
+                // Remove the entry for the last locked time because a successful auth just happened
+                // and metrics have been collected
+                mLastLockedTimestamp.delete(userId);
+            }
             return;
         }
 
@@ -210,6 +232,34 @@
         lockDevice(userId);
     }
 
+    private static void collectTimeElapsedSinceLastLocked(long lastLockedTime, long authTime,
+            int authType) {
+        final int unlockType =  switch (authType) {
+            case TYPE_PRIMARY_AUTH -> FrameworkStatsLog
+                    .ADAPTIVE_AUTH_UNLOCK_AFTER_LOCK_REPORTED__UNLOCK_TYPE__PRIMARY_AUTH;
+            case TYPE_BIOMETRIC_AUTH -> FrameworkStatsLog
+                    .ADAPTIVE_AUTH_UNLOCK_AFTER_LOCK_REPORTED__UNLOCK_TYPE__BIOMETRIC_AUTH;
+            default -> FrameworkStatsLog
+                    .ADAPTIVE_AUTH_UNLOCK_AFTER_LOCK_REPORTED__UNLOCK_TYPE__UNKNOWN;
+        };
+
+        if (DEBUG) {
+            Slog.d(TAG, "collectTimeElapsedSinceLastLockedForUser: "
+                    + "lastLockedTime=" + lastLockedTime
+                    + ", authTime=" + authTime
+                    + ", unlockType=" + unlockType);
+        }
+
+        // This usually shouldn't happen, and just check out of an abundance of caution
+        if (lastLockedTime > authTime) {
+            return;
+        }
+
+        // Log to statsd
+        FrameworkStatsLog.write(FrameworkStatsLog.ADAPTIVE_AUTH_UNLOCK_AFTER_LOCK_REPORTED,
+                lastLockedTime, authTime, unlockType);
+    }
+
     /**
      * Locks the device and requires primary auth or biometric auth for unlocking
      */
@@ -234,5 +284,9 @@
 
         // Lock the device
         mWindowManager.lockNow();
+
+        // Record the time that the device is locked by adaptive auth to collect metrics when the
+        // next successful primary or biometric auth happens
+        mLastLockedTimestamp.put(userId, SystemClock.elapsedRealtime());
     }
 }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index b8f6b3f..5298846 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -119,6 +119,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOREGROUND_SERVICE;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE_EXECUTING;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
@@ -372,6 +373,15 @@
     @Overridable
     public static final long FGS_BOOT_COMPLETED_RESTRICTIONS = 296558535L;
 
+    /**
+     * Disables foreground service background starts in System Alert Window for all types
+     * unless it already has a System Overlay Window.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM)
+    @Overridable
+    public static final long FGS_SAW_RESTRICTIONS = 319471980L;
+
     final ActivityManagerService mAm;
 
     // Maximum number of services that we allow to start in the background
@@ -1488,6 +1498,11 @@
         FrameworkStatsLog.write(FrameworkStatsLog.SERVICE_STATE_CHANGED, uid, packageName,
                 serviceName, FrameworkStatsLog.SERVICE_STATE_CHANGED__STATE__START);
         mAm.mBatteryStatsService.noteServiceStartRunning(uid, packageName, serviceName);
+        final ProcessRecord hostApp = r.app;
+        final boolean wasStopped = hostApp == null ? wasStopped(r) : false;
+        final boolean firstLaunch =
+                hostApp == null ? !mAm.wasPackageEverLaunched(r.packageName, r.userId) : false;
+
         String error = bringUpServiceLocked(r, service.getFlags(), callerFg,
                 false /* whileRestarting */,
                 false /* permissionsReviewRequired */,
@@ -1500,10 +1515,14 @@
             return new ComponentName("!!", error);
         }
 
-        final boolean wasStopped = (r.appInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0;
         final int packageState = wasStopped
                 ? SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED
                 : SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
+        if (DEBUG_PROCESSES) {
+            Slog.d(TAG, "Logging startService for " + packageName + ", stopped="
+                    + wasStopped + ", firstLaunch=" + firstLaunch + ", intent=" + service
+                    + ", r.app=" + r.app);
+        }
         FrameworkStatsLog.write(SERVICE_REQUEST_EVENT_REPORTED, uid, callingUid,
                 service.getAction(),
                 SERVICE_REQUEST_EVENT_REPORTED__REQUEST_TYPE__START, false,
@@ -1518,7 +1537,9 @@
                 packageName,
                 callingPackage,
                 callingProcessState,
-                r.mProcessStateOnRequest);
+                r.mProcessStateOnRequest,
+                firstLaunch,
+                0L /* TODO: stoppedDuration */);
 
         if (r.startRequested && addToStarting) {
             boolean first = smap.mStartingBackground.size() == 0;
@@ -4029,7 +4050,6 @@
                 mAm.requireAllowedAssociationsLocked(s.appInfo.packageName);
             }
 
-            final boolean wasStopped = (s.appInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0;
             final boolean wasStartRequested = s.startRequested;
             final boolean hadConnections = !s.getConnections().isEmpty();
             mAm.startAssociationLocked(callerApp.uid, callerApp.processName,
@@ -4104,6 +4124,10 @@
                         true);
             }
 
+            final boolean wasStopped = hostApp == null ? wasStopped(s) : false;
+            final boolean firstLaunch =
+                    hostApp == null ? !mAm.wasPackageEverLaunched(s.packageName, s.userId) : false;
+
             boolean needOomAdj = false;
             if (c.hasFlag(Context.BIND_AUTO_CREATE)) {
                 s.lastActivity = SystemClock.uptimeMillis();
@@ -4133,7 +4157,7 @@
                         || (callerApp.mState.getCurProcState() <= PROCESS_STATE_TOP
                             && c.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)),
                         b.client);
-                if ((serviceBindingOomAdjPolicy
+                if (!s.mOomAdjBumpedInExec && (serviceBindingOomAdjPolicy
                         & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT) == 0) {
                     needOomAdj = true;
                     mAm.enqueueOomAdjTargetLocked(s.app);
@@ -4146,6 +4170,10 @@
             final int packageState = wasStopped
                     ? SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED
                     : SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
+            if (DEBUG_PROCESSES) {
+                Slog.d(TAG, "Logging bindService for " + s.packageName
+                        + ", stopped=" + wasStopped + ", firstLaunch=" + firstLaunch);
+            }
             FrameworkStatsLog.write(SERVICE_REQUEST_EVENT_REPORTED, s.appInfo.uid, callingUid,
                     ActivityManagerService.getShortAction(service.getAction()),
                     SERVICE_REQUEST_EVENT_REPORTED__REQUEST_TYPE__BIND, false,
@@ -4160,7 +4188,9 @@
                     s.packageName,
                     callerApp.info.packageName,
                     callerApp.mState.getCurProcState(),
-                    s.mProcessStateOnRequest);
+                    s.mProcessStateOnRequest,
+                    firstLaunch,
+                    0L /* TODO */);
 
             if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bind " + s + " with " + b
                     + ": received=" + b.intent.received
@@ -4277,7 +4307,7 @@
                 }
 
                 serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false,
-                        !Flags.serviceBindingOomAdjPolicy() || b == null || !b.mSkippedOomAdj
+                        !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec
                         ? OOM_ADJ_REASON_EXECUTING_SERVICE : OOM_ADJ_REASON_NONE);
             }
         } finally {
@@ -4398,7 +4428,6 @@
                         + (b != null ? b.apps.size() : 0));
 
                 boolean inDestroying = mDestroyingServices.contains(r);
-                boolean skipOomAdj = false;
                 if (b != null) {
                     if (b.apps.size() > 0 && !inDestroying) {
                         // Applications have already bound since the last
@@ -4423,11 +4452,11 @@
                         // a new client.
                         b.doRebind = true;
                     }
-                    skipOomAdj = Flags.serviceBindingOomAdjPolicy() && b.mSkippedOomAdj;
                 }
 
                 serviceDoneExecutingLocked(r, inDestroying, false, false,
-                        skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE);
+                        !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec
+                        ? OOM_ADJ_REASON_UNBIND_SERVICE : OOM_ADJ_REASON_NONE);
             }
         } finally {
             mAm.mInjector.restoreCallingIdentity(origId);
@@ -4905,9 +4934,8 @@
      * Bump the given service record into executing state.
      * @param oomAdjReason The caller requests it to perform the oomAdjUpdate not {@link
      *         ActivityManagerInternal#OOM_ADJ_REASON_NONE}.
-     * @return {@code true} if it performed oomAdjUpdate.
      */
-    private boolean bumpServiceExecutingLocked(
+    private void bumpServiceExecutingLocked(
             ServiceRecord r, boolean fg, String why, @OomAdjReason int oomAdjReason,
             boolean skipTimeoutIfPossible) {
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING "
@@ -4972,19 +5000,17 @@
                 }
             }
         }
-        boolean oomAdjusted = false;
         if (oomAdjReason != OOM_ADJ_REASON_NONE && r.app != null
                 && r.app.mState.getCurProcState() > ActivityManager.PROCESS_STATE_SERVICE) {
             // Force an immediate oomAdjUpdate, so the client app could be in the correct process
             // state before doing any service related transactions
             mAm.enqueueOomAdjTargetLocked(r.app);
             mAm.updateOomAdjPendingTargetsLocked(oomAdjReason);
-            oomAdjusted = true;
+            r.mOomAdjBumpedInExec = true;
         }
         r.executeFg |= fg;
         r.executeNesting++;
         r.executingStart = SystemClock.uptimeMillis();
-        return oomAdjusted;
     }
 
     private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i,
@@ -5001,7 +5027,7 @@
                 & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_BIND) != 0;
         if ((!i.requested || rebind) && i.apps.size() > 0) {
             try {
-                i.mSkippedOomAdj = !bumpServiceExecutingLocked(r, execInFg, "bind",
+                bumpServiceExecutingLocked(r, execInFg, "bind",
                         skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_BIND_SERVICE,
                         skipOomAdj /* skipTimeoutIfPossible */);
                 if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
@@ -5020,14 +5046,16 @@
                 if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r, e);
                 final boolean inDestroying = mDestroyingServices.contains(r);
                 serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
-                        skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE);
+                        !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec
+                        ? OOM_ADJ_REASON_UNBIND_SERVICE : OOM_ADJ_REASON_NONE);
                 throw e;
             } catch (RemoteException e) {
                 if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r);
                 // Keep the executeNesting count accurate.
                 final boolean inDestroying = mDestroyingServices.contains(r);
                 serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
-                        skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE);
+                        !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec
+                        ? OOM_ADJ_REASON_UNBIND_SERVICE : OOM_ADJ_REASON_NONE);
                 return false;
             }
         }
@@ -5823,6 +5851,7 @@
             // process state before doing any service related transactions
             mAm.enqueueOomAdjTargetLocked(app);
             mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
+            r.mOomAdjBumpedInExec = true;
         } else {
             // Since we skipped the oom adj update, the Service#onCreate() might be running in
             // the cached state, if the service process drops into the cached state after the call.
@@ -5863,7 +5892,8 @@
                 // Keep the executeNesting count accurate.
                 final boolean inDestroying = mDestroyingServices.contains(r);
                 serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
-                        skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_STOP_SERVICE);
+                        !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec
+                        ? OOM_ADJ_REASON_STOP_SERVICE : OOM_ADJ_REASON_NONE);
 
                 // Cleanup.
                 if (newService) {
@@ -5898,7 +5928,7 @@
                     null, null, 0, null, null, ActivityManager.PROCESS_STATE_UNKNOWN));
         }
 
-        sendServiceArgsLocked(r, execInFg, true);
+        sendServiceArgsLocked(r, execInFg, r.mOomAdjBumpedInExec);
 
         if (r.delayed) {
             if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "REM FR DELAY LIST (new proc): " + r);
@@ -6085,7 +6115,8 @@
             }
         }
 
-        boolean oomAdjusted = false;
+        boolean oomAdjusted = Flags.serviceBindingOomAdjPolicy() && r.mOomAdjBumpedInExec;
+
         // Tell the service that it has been unbound.
         if (r.app != null && r.app.isThreadReady()) {
             for (int i = r.bindings.size() - 1; i >= 0; i--) {
@@ -6094,9 +6125,10 @@
                         + ": hasBound=" + ibr.hasBound);
                 if (ibr.hasBound) {
                     try {
-                        oomAdjusted |= bumpServiceExecutingLocked(r, false, "bring down unbind",
-                                OOM_ADJ_REASON_UNBIND_SERVICE,
-                                false /* skipTimeoutIfPossible */);
+                        bumpServiceExecutingLocked(r, false, "bring down unbind",
+                                oomAdjusted ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE,
+                                oomAdjusted /* skipTimeoutIfPossible */);
+                        oomAdjusted |= r.mOomAdjBumpedInExec;
                         ibr.hasBound = false;
                         ibr.requested = false;
                         r.app.getThread().scheduleUnbindService(r,
@@ -6252,10 +6284,11 @@
                     }
                 } else {
                     try {
-                        oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy",
-                                oomAdjusted ? 0 : OOM_ADJ_REASON_STOP_SERVICE,
-                                false /* skipTimeoutIfPossible */);
+                        bumpServiceExecutingLocked(r, false, "destroy",
+                                oomAdjusted ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE,
+                                oomAdjusted /* skipTimeoutIfPossible */);
                         mDestroyingServices.add(r);
+                        oomAdjusted |= r.mOomAdjBumpedInExec;
                         r.destroying = true;
                         r.app.getThread().scheduleStopService(r);
                     } catch (Exception e) {
@@ -6411,7 +6444,7 @@
                 final boolean skipOomAdj = (serviceBindingOomAdjPolicy
                         & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT) != 0;
                 try {
-                    b.intent.mSkippedOomAdj = !bumpServiceExecutingLocked(s, false, "unbind",
+                    bumpServiceExecutingLocked(s, false, "unbind",
                             skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE,
                             skipOomAdj /* skipTimeoutIfPossible */);
                     if (b.client != s.app && c.notHasFlag(Context.BIND_WAIVE_PRIORITY)
@@ -6461,6 +6494,7 @@
         boolean inDestroying = mDestroyingServices.contains(r);
         if (r != null) {
             boolean skipOomAdj = false;
+            boolean needOomAdj = false;
             if (type == ActivityThread.SERVICE_DONE_EXECUTING_START) {
                 // This is a call from a service start...  take care of
                 // book-keeping.
@@ -6536,15 +6570,13 @@
                     // Fake it to keep from ANR due to orphaned entry.
                     r.executeNesting = 1;
                 }
-            } else if (type == ActivityThread.SERVICE_DONE_EXECUTING_REBIND
-                    || type == ActivityThread.SERVICE_DONE_EXECUTING_UNBIND) {
-                final Intent.FilterComparison filter = new Intent.FilterComparison(intent);
-                final IntentBindRecord b = r.bindings.get(filter);
-                skipOomAdj = Flags.serviceBindingOomAdjPolicy() && b != null && b.mSkippedOomAdj;
+                // The service is done, force an oom adj update.
+                needOomAdj = true;
             }
             final long origId = mAm.mInjector.clearCallingIdentity();
             serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj,
-                    skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_EXECUTING_SERVICE);
+                    !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec || needOomAdj
+                    ? OOM_ADJ_REASON_EXECUTING_SERVICE : OOM_ADJ_REASON_NONE);
             mAm.mInjector.restoreCallingIdentity(origId);
         } else {
             Slog.w(TAG, "Done executing unknown service from pid "
@@ -6600,18 +6632,16 @@
                     mDestroyingServices.remove(r);
                     r.bindings.clear();
                 }
-                boolean oomAdjusted = false;
                 if (oomAdjReason != OOM_ADJ_REASON_NONE) {
                     if (enqueueOomAdj) {
                         mAm.enqueueOomAdjTargetLocked(r.app);
                     } else {
                         mAm.updateOomAdjLocked(r.app, oomAdjReason);
                     }
-                    oomAdjusted = true;
                 } else {
                     // Skip oom adj if it wasn't bumped during the bumpServiceExecutingLocked()
-                    oomAdjusted = false;
                 }
+                r.mOomAdjBumpedInExec = false;
             }
             r.executeFg = false;
             if (r.tracker != null) {
@@ -6995,6 +7025,7 @@
             sr.setProcess(null, null, 0, null);
             sr.isolationHostProc = null;
             sr.executeNesting = 0;
+            sr.mOomAdjBumpedInExec = false;
             synchronized (mAm.mProcessStats.mLock) {
                 sr.forceClearTracker();
             }
@@ -8525,10 +8556,31 @@
             }
         }
 
+        // The flag being enabled isn't enough to deny background start: we need to also check
+        // if there is a system alert UI present.
         if (ret == REASON_DENIED) {
-            if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,
-                    callingPackage)) {
-                ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
+            // Flag check: are we disabling SAW FGS background starts?
+            final boolean shouldDisableSaw = Flags.fgsDisableSaw()
+                    && CompatChanges.isChangeEnabled(FGS_BOOT_COMPLETED_RESTRICTIONS, callingUid);
+            if (shouldDisableSaw) {
+                final ProcessRecord processRecord = mAm
+                        .getProcessRecordLocked(targetService.processName,
+                                targetService.appInfo.uid);
+                if (processRecord != null) {
+                    if (processRecord.mState.hasOverlayUi()) {
+                        if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,
+                                callingPackage)) {
+                            ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
+                        }
+                    }
+                } else {
+                    Slog.e(TAG, "Could not find process record for SAW check");
+                }
+            } else {
+                if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,
+                        callingPackage)) {
+                    ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
+                }
             }
         }
 
@@ -9081,4 +9133,8 @@
         return mCachedDeviceProvisioningPackage != null
                 && mCachedDeviceProvisioningPackage.equals(packageName);
     }
+
+    private boolean wasStopped(ServiceRecord serviceRecord) {
+        return (serviceRecord.appInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0;
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e222878..5e6ff55 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -141,7 +141,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_BACKGROUND;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
@@ -726,29 +725,13 @@
     // Whether we should use SCHED_FIFO for UI and RenderThreads.
     final boolean mUseFifoUiScheduling;
 
-    // Use an offload queue for long broadcasts, e.g. BOOT_COMPLETED.
-    // For simplicity, since we statically declare the size of the array of BroadcastQueues,
-    // we still create this new offload queue, but never ever put anything on it.
-    final boolean mEnableOffloadQueue;
-
-    /**
-     * Flag indicating if we should use {@link BroadcastQueueModernImpl} instead
-     * of the default {@link BroadcastQueueImpl}.
-     */
-    final boolean mEnableModernQueue;
-
-    static final int BROADCAST_QUEUE_FG = 0;
-    static final int BROADCAST_QUEUE_BG = 1;
-    static final int BROADCAST_QUEUE_BG_OFFLOAD = 2;
-    static final int BROADCAST_QUEUE_FG_OFFLOAD = 3;
-
     @GuardedBy("this")
     private final SparseArray<IUnsafeIntentStrictModeCallback>
             mStrictModeCallbacks = new SparseArray<>();
 
     // Convenient for easy iteration over the queues. Foreground is first
     // so that dispatch of foreground broadcasts gets precedence.
-    final BroadcastQueue[] mBroadcastQueues;
+    private BroadcastQueue mBroadcastQueue;
 
     @GuardedBy("this")
     BroadcastStats mLastBroadcastStats;
@@ -758,43 +741,6 @@
 
     TraceErrorLogger mTraceErrorLogger;
 
-    BroadcastQueue broadcastQueueForIntent(Intent intent) {
-        return broadcastQueueForFlags(intent.getFlags(), intent);
-    }
-
-    BroadcastQueue broadcastQueueForFlags(int flags) {
-        return broadcastQueueForFlags(flags, null);
-    }
-
-    BroadcastQueue broadcastQueueForFlags(int flags, Object cookie) {
-        if (mEnableModernQueue) {
-            return mBroadcastQueues[0];
-        }
-
-        if (isOnFgOffloadQueue(flags)) {
-            if (DEBUG_BROADCAST_BACKGROUND) {
-                Slog.i(TAG_BROADCAST,
-                        "Broadcast intent " + cookie + " on foreground offload queue");
-            }
-            return mBroadcastQueues[BROADCAST_QUEUE_FG_OFFLOAD];
-        }
-
-        if (isOnBgOffloadQueue(flags)) {
-            if (DEBUG_BROADCAST_BACKGROUND) {
-                Slog.i(TAG_BROADCAST,
-                        "Broadcast intent " + cookie + " on background offload queue");
-            }
-            return mBroadcastQueues[BROADCAST_QUEUE_BG_OFFLOAD];
-        }
-
-        final boolean isFg = (flags & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
-        if (DEBUG_BROADCAST_BACKGROUND) Slog.i(TAG_BROADCAST,
-                "Broadcast intent " + cookie + " on "
-                + (isFg ? "foreground" : "background") + " queue");
-        return (isFg) ? mBroadcastQueues[BROADCAST_QUEUE_FG]
-                : mBroadcastQueues[BROADCAST_QUEUE_BG];
-    }
-
     private volatile int mDeviceOwnerUid = INVALID_UID;
 
     /**
@@ -2556,9 +2502,7 @@
         mInternal = new LocalService();
         mPendingStartActivityUids = new PendingStartActivityUids();
         mUseFifoUiScheduling = false;
-        mEnableOffloadQueue = false;
-        mEnableModernQueue = false;
-        mBroadcastQueues = injector.getBroadcastQueues(this);
+        mBroadcastQueue = injector.getBroadcastQueue(this);
         mComponentAliasResolver = new ComponentAliasResolver(this);
     }
 
@@ -2599,12 +2543,7 @@
                 ? new OomAdjusterModernImpl(this, mProcessList, activeUids)
                 : new OomAdjuster(this, mProcessList, activeUids);
 
-        mEnableOffloadQueue = SystemProperties.getBoolean(
-                "persist.device_config.activity_manager_native_boot.offload_queue_enabled", true);
-        mEnableModernQueue = new BroadcastConstants(
-                Settings.Global.BROADCAST_FG_CONSTANTS).MODERN_QUEUE_ENABLED;
-
-        mBroadcastQueues = mInjector.getBroadcastQueues(this);
+        mBroadcastQueue = mInjector.getBroadcastQueue(this);
 
         mServices = new ActiveServices(this);
         mCpHelper = new ContentProviderHelper(this, true);
@@ -2671,6 +2610,14 @@
         mComponentAliasResolver = new ComponentAliasResolver(this);
     }
 
+    void setBroadcastQueueForTest(BroadcastQueue broadcastQueue) {
+        mBroadcastQueue = broadcastQueue;
+    }
+
+    BroadcastQueue getBroadcastQueue() {
+        return mBroadcastQueue;
+    }
+
     public void setSystemServiceManager(SystemServiceManager mgr) {
         mSystemServiceManager = mgr;
     }
@@ -4280,19 +4227,14 @@
         }
 
         // Clean-up disabled broadcast receivers.
-        for (int i = mBroadcastQueues.length - 1; i >= 0; i--) {
-            mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
-                    packageName, disabledClasses, userId);
-        }
+        mBroadcastQueue.cleanupDisabledPackageReceiversLocked(
+                packageName, disabledClasses, userId);
 
     }
 
     final boolean clearBroadcastQueueForUserLocked(int userId) {
-        boolean didSomething = false;
-        for (int i = mBroadcastQueues.length - 1; i >= 0; i--) {
-            didSomething |= mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
-                    null, null, userId);
-        }
+        boolean didSomething = mBroadcastQueue.cleanupDisabledPackageReceiversLocked(
+                null, null, userId);
         return didSomething;
     }
 
@@ -4445,10 +4387,8 @@
         mUgmInternal.removeUriPermissionsForPackage(packageName, userId, false, false);
 
         if (doit) {
-            for (i = mBroadcastQueues.length - 1; i >= 0; i--) {
-                didSomething |= mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
+            didSomething |= mBroadcastQueue.cleanupDisabledPackageReceiversLocked(
                         packageName, null, userId);
-            }
         }
 
         if (packageName == null || uninstalling || packageStateStopped) {
@@ -4515,9 +4455,7 @@
                 // Take care of any services that are waiting for the process.
                 mServices.processStartTimedOutLocked(app);
                 // Take care of any broadcasts waiting for the process.
-                for (BroadcastQueue queue : mBroadcastQueues) {
-                    queue.onApplicationTimeoutLocked(app);
-                }
+                mBroadcastQueue.onApplicationTimeoutLocked(app);
                 if (!isKillTimeout) {
                     mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
                     app.killLocked("start timeout",
@@ -4779,36 +4717,48 @@
                 // being bound to an application.
                 thread.runIsolatedEntryPoint(
                         app.getIsolatedEntryPoint(), app.getIsolatedEntryPointArgs());
-            } else if (instr2 != null) {
-                thread.bindApplication(processName, appInfo,
-                        app.sdkSandboxClientAppVolumeUuid, app.sdkSandboxClientAppPackage,
-                        instr2.mIsSdkInSandbox,
-                        providerList,
-                        instr2.mClass,
-                        profilerInfo, instr2.mArguments,
-                        instr2.mWatcher,
-                        instr2.mUiAutomationConnection, testMode,
-                        mBinderTransactionTrackingEnabled, enableTrackAllocation,
-                        isRestrictedBackupMode || !normalMode, app.isPersistent(),
-                        new Configuration(app.getWindowProcessController().getConfiguration()),
-                        app.getCompat(), getCommonServicesLocked(app.isolated),
-                        mCoreSettingsObserver.getCoreSettingsLocked(),
-                        buildSerial, autofillOptions, contentCaptureOptions,
-                        app.getDisabledCompatChanges(), serializedSystemFontMap,
-                        app.getStartElapsedTime(), app.getStartUptime());
             } else {
-                thread.bindApplication(processName, appInfo,
-                        app.sdkSandboxClientAppVolumeUuid, app.sdkSandboxClientAppPackage,
-                        /* isSdkInSandbox= */ false,
-                        providerList, null, profilerInfo, null, null, null, testMode,
-                        mBinderTransactionTrackingEnabled, enableTrackAllocation,
-                        isRestrictedBackupMode || !normalMode, app.isPersistent(),
+                boolean isSdkInSandbox = false;
+                ComponentName instrumentationName = null;
+                Bundle instrumentationArgs = null;
+                IInstrumentationWatcher instrumentationWatcher = null;
+                IUiAutomationConnection instrumentationUiConnection = null;
+                if (instr2 != null) {
+                    isSdkInSandbox = instr2.mIsSdkInSandbox;
+                    instrumentationName = instr2.mClass;
+                    instrumentationArgs = instr2.mArguments;
+                    instrumentationWatcher = instr2.mWatcher;
+                    instrumentationUiConnection = instr2.mUiAutomationConnection;
+                }
+                thread.bindApplication(
+                        processName,
+                        appInfo,
+                        app.sdkSandboxClientAppVolumeUuid,
+                        app.sdkSandboxClientAppPackage,
+                        isSdkInSandbox,
+                        providerList,
+                        instrumentationName,
+                        profilerInfo,
+                        instrumentationArgs,
+                        instrumentationWatcher,
+                        instrumentationUiConnection,
+                        testMode,
+                        mBinderTransactionTrackingEnabled,
+                        enableTrackAllocation,
+                        isRestrictedBackupMode || !normalMode,
+                        app.isPersistent(),
                         new Configuration(app.getWindowProcessController().getConfiguration()),
-                        app.getCompat(), getCommonServicesLocked(app.isolated),
+                        app.getCompat(),
+                        getCommonServicesLocked(app.isolated),
                         mCoreSettingsObserver.getCoreSettingsLocked(),
-                        buildSerial, autofillOptions, contentCaptureOptions,
-                        app.getDisabledCompatChanges(), serializedSystemFontMap,
-                        app.getStartElapsedTime(), app.getStartUptime());
+                        buildSerial,
+                        autofillOptions,
+                        contentCaptureOptions,
+                        app.getDisabledCompatChanges(),
+                        app.getLoggableCompatChanges(),
+                        serializedSystemFontMap,
+                        app.getStartElapsedTime(),
+                        app.getStartUptime());
             }
 
             Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_SOFT_MSG);
@@ -4948,9 +4898,7 @@
             // Check if a next-broadcast receiver is in this process...
             if (!badApp) {
                 try {
-                    for (BroadcastQueue queue : mBroadcastQueues) {
-                        didSomething |= queue.onApplicationAttachedLocked(app);
-                    }
+                    didSomething |= mBroadcastQueue.onApplicationAttachedLocked(app);
                     checkTime(startTime, "finishAttachApplicationInner: "
                             + "after dispatching broadcasts");
                 } catch (BroadcastDeliveryFailedException e) {
@@ -5089,8 +5037,11 @@
      * Send LOCKED_BOOT_COMPLETED and BOOT_COMPLETED to the package explicitly when unstopped
      */
     private void maybeSendBootCompletedLocked(ProcessRecord app) {
+        if (!android.content.pm.Flags.stayStopped()) return;
         // Nothing to do if it wasn't previously stopped
-        if (!android.content.pm.Flags.stayStopped() || !app.wasForceStopped()) return;
+        if (!app.wasForceStopped() && !app.getWindowProcessController().wasForceStopped()) {
+            return;
+        }
 
         // Send LOCKED_BOOT_COMPLETED, if necessary
         if (app.getApplicationInfo().isEncryptionAware()) {
@@ -5102,7 +5053,8 @@
             sendBootBroadcastToAppLocked(app, new Intent(Intent.ACTION_BOOT_COMPLETED),
                     REASON_BOOT_COMPLETED);
         }
-        app.setWasForceStopped(false);
+        // The stopped state is reset in ProcessRecord when the pid changes, to deal with
+        // any re-use of the ProcessRecord.
     }
 
     /** Send a boot_completed broadcast to app */
@@ -6887,6 +6839,17 @@
         return mPermissionManagerInt;
     }
 
+    /** Returns whether the given package was ever launched since install */
+    boolean wasPackageEverLaunched(String packageName, @UserIdInt int userId) {
+        boolean wasLaunched = false;
+        try {
+            wasLaunched = getPackageManagerInternal().wasPackageEverLaunched(packageName, userId);
+        } catch (Exception e) {
+            // If the package state record doesn't exist yet, assume it was never launched
+        }
+        return wasLaunched;
+    }
+
     private TestUtilityService getTestUtilityServiceLocked() {
         if (mTestUtilityService == null) {
             mTestUtilityService =
@@ -9028,6 +8991,7 @@
                         Slog.wtf(TAG, "Uid " + uid + " sent too many Binders to uid "
                                 + Process.myUid());
                         BinderProxy.dumpProxyDebugInfo();
+                        CriticalEventLog.getInstance().logExcessiveBinderCalls(uid);
                         if (uid == Process.SYSTEM_UID) {
                             Slog.i(TAG, "Skipping kill (uid is SYSTEM)");
                         } else {
@@ -9089,9 +9053,7 @@
     }
 
     private void startBroadcastObservers() {
-        for (BroadcastQueue queue : mBroadcastQueues) {
-            queue.start(mContext.getContentResolver());
-        }
+        mBroadcastQueue.start(mContext.getContentResolver());
     }
 
     private void updateForceBackgroundCheck(boolean enabled) {
@@ -9362,7 +9324,9 @@
                 sb.append("Animations-Running: ").append(info.numAnimationsRunning).append("\n");
             }
             if (info.broadcastIntentAction != null) {
-                sb.append("Broadcast-Intent-Action: ").append(info.broadcastIntentAction).append("\n");
+                sb.append("Broadcast-Intent-Action: ")
+                        .append(info.broadcastIntentAction)
+                        .append("\n");
             }
             if (info.durationMillis != -1) {
                 sb.append("Duration-Millis: ").append(info.durationMillis).append("\n");
@@ -9722,82 +9686,126 @@
         // If process is null, we are being called from some internal code
         // and may be about to die -- run this synchronously.
         final boolean runSynchronously = process == null;
-        Thread worker = new Thread("Error dump: " + dropboxTag) {
-            @Override
-            public void run() {
-                if (report != null) {
-                    sb.append(report);
-                }
-
-                String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
-                String maxBytesSetting = Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag;
-                int lines = Build.IS_USER
-                        ? 0
-                        : Settings.Global.getInt(mContext.getContentResolver(), logcatSetting, 0);
-                int dropboxMaxSize = Settings.Global.getInt(
-                        mContext.getContentResolver(), maxBytesSetting, DROPBOX_DEFAULT_MAX_SIZE);
-
-                if (dataFile != null) {
-                    // Attach the stack traces file to the report so collectors can load them
-                    // by file if they have access.
-                    sb.append(DATA_FILE_PATH_HEADER)
-                            .append(dataFile.getAbsolutePath()).append('\n');
-
-                    int maxDataFileSize = dropboxMaxSize
-                            - sb.length()
-                            - lines * RESERVED_BYTES_PER_LOGCAT_LINE
-                            - DATA_FILE_PATH_FOOTER.length();
-
-                    if (maxDataFileSize > 0) {
-                        // Inline dataFile contents if there is room.
-                        try {
-                            sb.append(FileUtils.readTextFile(dataFile, maxDataFileSize,
-                                    "\n\n[[TRUNCATED]]\n"));
-                        } catch (IOException e) {
-                            Slog.e(TAG, "Error reading " + dataFile, e);
+        Thread worker =
+                new Thread("Error dump: " + dropboxTag) {
+                    @Override
+                    public void run() {
+                        if (report != null) {
+                            sb.append(report);
                         }
+
+                        String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
+                        String maxBytesSetting =
+                                Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag;
+                        int lines =
+                                Build.IS_USER
+                                        ? 0
+                                        : Settings.Global.getInt(
+                                                mContext.getContentResolver(), logcatSetting, 0);
+                        int dropboxMaxSize =
+                                Settings.Global.getInt(
+                                        mContext.getContentResolver(),
+                                        maxBytesSetting,
+                                        DROPBOX_DEFAULT_MAX_SIZE);
+
+                        if (dataFile != null) {
+                            // Attach the stack traces file to the report so collectors can load
+                            // them
+                            // by file if they have access.
+                            sb.append(DATA_FILE_PATH_HEADER)
+                                    .append(dataFile.getAbsolutePath())
+                                    .append('\n');
+
+                            int maxDataFileSize =
+                                    dropboxMaxSize
+                                            - sb.length()
+                                            - lines * RESERVED_BYTES_PER_LOGCAT_LINE
+                                            - DATA_FILE_PATH_FOOTER.length();
+
+                            if (maxDataFileSize > 0) {
+                                // Inline dataFile contents if there is room.
+                                try {
+                                    sb.append(
+                                            FileUtils.readTextFile(
+                                                    dataFile,
+                                                    maxDataFileSize,
+                                                    "\n\n[[TRUNCATED]]\n"));
+                                } catch (IOException e) {
+                                    Slog.e(TAG, "Error reading " + dataFile, e);
+                                }
+                            }
+
+                            // Always append the footer, even there wasn't enough space to inline
+                            // the
+                            // dataFile contents.
+                            sb.append(DATA_FILE_PATH_FOOTER);
+                        }
+
+                        if (crashInfo != null && crashInfo.stackTrace != null) {
+                            sb.append(crashInfo.stackTrace);
+                        }
+
+                        if (lines > 0 && !runSynchronously) {
+                            sb.append("\n");
+
+                            InputStreamReader input = null;
+                            try {
+                                java.lang.Process logcat =
+                                        new ProcessBuilder(
+                                                        // Time out after 10s of inactivity, but
+                                                        // kill logcat with SEGV
+                                                        // so we can investigate why it didn't
+                                                        // finish.
+                                                        "/system/bin/timeout",
+                                                        "-i",
+                                                        "-s",
+                                                        "SEGV",
+                                                        "10s",
+                                                        // Merge several logcat streams, and take
+                                                        // the last N lines.
+                                                        "/system/bin/logcat",
+                                                        "-v",
+                                                        "threadtime",
+                                                        "-b",
+                                                        "events",
+                                                        "-b",
+                                                        "system",
+                                                        "-b",
+                                                        "main",
+                                                        "-b",
+                                                        "crash",
+                                                        "-t",
+                                                        String.valueOf(lines))
+                                                .redirectErrorStream(true)
+                                                .start();
+
+                                try {
+                                    logcat.getOutputStream().close();
+                                } catch (IOException e) {
+                                }
+                                try {
+                                    logcat.getErrorStream().close();
+                                } catch (IOException e) {
+                                }
+                                input = new InputStreamReader(logcat.getInputStream());
+
+                                int num;
+                                char[] buf = new char[8192];
+                                while ((num = input.read(buf)) > 0) sb.append(buf, 0, num);
+                            } catch (IOException e) {
+                                Slog.e(TAG, "Error running logcat", e);
+                            } finally {
+                                if (input != null)
+                                    try {
+                                        input.close();
+                                    } catch (IOException e) {
+                                    }
+                            }
+                        }
+
+                        dbox.addText(dropboxTag, sb.toString());
                     }
-
-                    // Always append the footer, even there wasn't enough space to inline the
-                    // dataFile contents.
-                    sb.append(DATA_FILE_PATH_FOOTER);
-                }
-
-                if (crashInfo != null && crashInfo.stackTrace != null) {
-                    sb.append(crashInfo.stackTrace);
-                }
-
-                if (lines > 0 && !runSynchronously) {
-                    sb.append("\n");
-
-                    InputStreamReader input = null;
-                    try {
-                        java.lang.Process logcat = new ProcessBuilder(
-                                // Time out after 10s of inactivity, but kill logcat with SEGV
-                                // so we can investigate why it didn't finish.
-                                "/system/bin/timeout", "-i", "-s", "SEGV", "10s",
-                                // Merge several logcat streams, and take the last N lines.
-                                "/system/bin/logcat", "-v", "threadtime", "-b", "events", "-b", "system",
-                                "-b", "main", "-b", "crash", "-t", String.valueOf(lines))
-                                        .redirectErrorStream(true).start();
-
-                        try { logcat.getOutputStream().close(); } catch (IOException e) {}
-                        try { logcat.getErrorStream().close(); } catch (IOException e) {}
-                        input = new InputStreamReader(logcat.getInputStream());
-
-                        int num;
-                        char[] buf = new char[8192];
-                        while ((num = input.read(buf)) > 0) sb.append(buf, 0, num);
-                    } catch (IOException e) {
-                        Slog.e(TAG, "Error running logcat", e);
-                    } finally {
-                        if (input != null) try { input.close(); } catch (IOException e) {}
-                    }
-                }
-
-                dbox.addText(dropboxTag, sb.toString());
-            }
-        };
+                };
 
         if (runSynchronously) {
             final int oldMask = StrictMode.allowThreadDiskWritesMask();
@@ -10165,43 +10173,48 @@
             mOomAdjuster.dumpCacheOomRankerSettings(pw);
             pw.println();
             if (dumpAll) {
-                pw.println("-------------------------------------------------------------------------------");
-
+                pw.println(
+                        "-------------------------------------------------------------------------------");
             }
             dumpAllowedAssociationsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
             pw.println();
             if (dumpAll) {
-                pw.println("-------------------------------------------------------------------------------");
-
+                pw.println(
+                        "-------------------------------------------------------------------------------");
             }
             mPendingIntentController.dumpPendingIntents(pw, dumpAll, dumpPackage);
             pw.println();
             if (dumpAll) {
-                pw.println("-------------------------------------------------------------------------------");
+                pw.println(
+                        "-------------------------------------------------------------------------------");
             }
             dumpBroadcastsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
             pw.println();
             if (dumpAll) {
-                pw.println("-------------------------------------------------------------------------------");
+                pw.println(
+                        "-------------------------------------------------------------------------------");
             }
             if (dumpAll || dumpPackage != null) {
                 dumpBroadcastStatsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
                 pw.println();
                 if (dumpAll) {
-                    pw.println("-------------------------------------------------------------------------------");
+                    pw.println(
+                            "-------------------------------------------------------------------------------");
                 }
             }
             mCpHelper.dumpProvidersLocked(fd, pw, args, opti, dumpAll, dumpPackage);
             pw.println();
             if (dumpAll) {
-                pw.println("-------------------------------------------------------------------------------");
+                pw.println(
+                        "-------------------------------------------------------------------------------");
             }
             dumpPermissions(fd, pw, args, opti, dumpAll, dumpPackage);
             pw.println();
             sdumper = mServices.newServiceDumperLocked(fd, pw, args, opti, dumpAll, dumpPackage);
             if (!dumpClient) {
                 if (dumpAll) {
-                    pw.println("-------------------------------------------------------------------------------");
+                    pw.println(
+                            "-------------------------------------------------------------------------------");
                 }
                 sdumper.dumpLocked();
             }
@@ -10216,7 +10229,8 @@
         // method with the lock held.
         if (dumpClient) {
             if (dumpAll) {
-                pw.println("-------------------------------------------------------------------------------");
+                pw.println(
+                        "-------------------------------------------------------------------------------");
             }
             sdumper.dumpWithClient();
         }
@@ -10229,33 +10243,38 @@
             // proxies in the first place.
             pw.println();
             if (dumpAll) {
-                pw.println("-------------------------------------------------------------------------------");
+                pw.println(
+                        "-------------------------------------------------------------------------------");
             }
             dumpBinderProxies(pw, BINDER_PROXY_HIGH_WATERMARK /* minToDump */);
         }
         synchronized(this) {
             pw.println();
             if (dumpAll) {
-                pw.println("-------------------------------------------------------------------------------");
+                pw.println(
+                        "-------------------------------------------------------------------------------");
             }
             mAtmInternal.dump(DUMP_RECENTS_CMD, fd, pw, args, opti, dumpAll, dumpClient,
                     dumpPackage, displayIdFilter);
             pw.println();
             if (dumpAll) {
-                pw.println("-------------------------------------------------------------------------------");
+                pw.println(
+                        "-------------------------------------------------------------------------------");
             }
             mAtmInternal.dump(DUMP_LASTANR_CMD, fd, pw, args, opti, dumpAll, dumpClient,
                     dumpPackage, displayIdFilter);
             pw.println();
             if (dumpAll) {
-                pw.println("-------------------------------------------------------------------------------");
+                pw.println(
+                        "-------------------------------------------------------------------------------");
             }
             mAtmInternal.dump(DUMP_STARTER_CMD, fd, pw, args, opti, dumpAll, dumpClient,
                     dumpPackage, displayIdFilter);
             if (dumpPackage == null) {
                 pw.println();
                 if (dumpAll) {
-                    pw.println("-------------------------------------------------------------------------------");
+                    pw.println(
+                            "-------------------------------------------------------------------------------");
                 }
                 mAtmInternal.dump(DUMP_CONTAINERS_CMD, fd, pw, args, opti, dumpAll, dumpClient,
                         dumpPackage, displayIdFilter);
@@ -10265,7 +10284,8 @@
             if (!dumpNormalPriority) {
                 pw.println();
                 if (dumpAll) {
-                    pw.println("-------------------------------------------------------------------------------");
+                    pw.println(
+                            "-------------------------------------------------------------------------------");
                 }
                 mAtmInternal.dump(DUMP_ACTIVITIES_CMD, fd, pw, args, opti, dumpAll, dumpClient,
                         dumpPackage, displayIdFilter);
@@ -10273,45 +10293,53 @@
             if (mAssociations.size() > 0) {
                 pw.println();
                 if (dumpAll) {
-                    pw.println("-------------------------------------------------------------------------------");
+                    pw.println(
+                            "-------------------------------------------------------------------------------");
                 }
                 dumpAssociationsLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
             }
             pw.println();
             if (dumpAll) {
-                pw.println("-------------------------------------------------------------------------------");
+                pw.println(
+                        "-------------------------------------------------------------------------------");
                 mProcessList.getAppStartInfoTracker().dumpHistoryProcessStartInfo(pw, dumpPackage);
-                pw.println("-------------------------------------------------------------------------------");
+                pw.println(
+                        "-------------------------------------------------------------------------------");
                 mProcessList.mAppExitInfoTracker.dumpHistoryProcessExitInfo(pw, dumpPackage);
             }
             if (dumpPackage == null) {
                 pw.println();
                 if (dumpAll) {
-                    pw.println("-------------------------------------------------------------------------------");
+                    pw.println(
+                            "-------------------------------------------------------------------------------");
                 }
                 mOomAdjProfiler.dump(pw);
                 pw.println();
                 if (dumpAll) {
-                    pw.println("-------------------------------------------------------------------------------");
+                    pw.println(
+                            "-------------------------------------------------------------------------------");
                 }
                 dumpLmkLocked(pw);
             }
             pw.println();
             if (dumpAll) {
-                pw.println("-------------------------------------------------------------------------------");
+                pw.println(
+                        "-------------------------------------------------------------------------------");
             }
             synchronized (mProcLock) {
                 mProcessList.dumpProcessesLSP(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId);
             }
             pw.println();
             if (dumpAll) {
-                pw.println("-------------------------------------------------------------------------------");
+                pw.println(
+                        "-------------------------------------------------------------------------------");
             }
             dumpUsers(pw);
 
             pw.println();
             if (dumpAll) {
-                pw.println("-------------------------------------------------------------------------------");
+                pw.println(
+                        "-------------------------------------------------------------------------------");
             }
             mComponentAliasResolver.dump(pw);
         }
@@ -10321,7 +10349,8 @@
      * Dump the app restriction controller, it's required not to hold the global lock here.
      */
     private void dumpAppRestrictionController(PrintWriter pw) {
-        pw.println("-------------------------------------------------------------------------------");
+        pw.println(
+                "-------------------------------------------------------------------------------");
         mAppRestrictionController.dump(pw, "");
     }
 
@@ -11430,9 +11459,7 @@
             }
         }
         mReceiverResolver.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.RECEIVER_RESOLVER);
-        for (BroadcastQueue q : mBroadcastQueues) {
-            q.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
-        }
+        mBroadcastQueue.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
         synchronized (mStickyBroadcasts) {
             for (int user = 0; user < mStickyBroadcasts.size(); user++) {
                 long token = proto.start(
@@ -11461,7 +11488,9 @@
 
     void dumpAllowedAssociationsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage) {
-        pw.println("ACTIVITY MANAGER ALLOWED ASSOCIATION STATE (dumpsys activity allowed-associations)");
+        pw.println(
+                "ACTIVITY MANAGER ALLOWED ASSOCIATION STATE (dumpsys activity"
+                    + " allowed-associations)");
         boolean printed = false;
         if (mAllowedAssociations != null) {
             for (int i = 0; i < mAllowedAssociations.size(); i++) {
@@ -11580,11 +11609,9 @@
         }
 
         if (!onlyReceivers) {
-            for (BroadcastQueue q : mBroadcastQueues) {
-                needSep = q.dumpLocked(fd, pw, args, opti,
-                        dumpConstants, dumpHistory, dumpAll, dumpPackage, needSep);
-                printedAnything |= needSep;
-            }
+            needSep = mBroadcastQueue.dumpLocked(fd, pw, args, opti,
+                    dumpConstants, dumpHistory, dumpAll, dumpPackage, needSep);
+            printedAnything |= needSep;
         }
 
         needSep = true;
@@ -11640,9 +11667,8 @@
 
         if (!onlyHistory && !onlyReceivers && dumpAll) {
             pw.println();
-            for (BroadcastQueue queue : mBroadcastQueues) {
-                pw.println("  Queue " + queue.toString() + ": " + queue.describeStateLocked());
-            }
+            pw.println("  Queue " + mBroadcastQueue.toString() + ": "
+                    + mBroadcastQueue.describeStateLocked());
             pw.println("  mHandler:");
             mHandler.dump(new PrintWriterPrinter(pw), "    ");
             needSep = true;
@@ -12815,7 +12841,8 @@
             if (!opts.isCompact) {
                 pw.print(" Used RAM: "); pw.print(stringifyKBSize(ss[INDEX_TOTAL_PSS] - cachedPss
                         + kernelUsed)); pw.print(" (");
-                pw.print(stringifyKBSize(ss[INDEX_TOTAL_PSS] - cachedPss)); pw.print(" used pss + ");
+                pw.print(stringifyKBSize(ss[INDEX_TOTAL_PSS] - cachedPss));
+                pw.print(" used pss + ");
                 pw.print(stringifyKBSize(kernelUsed)); pw.print(" kernel)\n");
                 pw.print(" Lost RAM: "); pw.println(stringifyKBSize(lostRAM));
             } else {
@@ -13489,9 +13516,7 @@
             mOomAdjuster.mCachedAppOptimizer.onCleanupApplicationRecordLocked(app);
         }
         mAppProfiler.onCleanupApplicationRecordLocked(app);
-        for (BroadcastQueue queue : mBroadcastQueues) {
-            queue.onApplicationCleanupLocked(app);
-        }
+        mBroadcastQueue.onApplicationCleanupLocked(app);
         clearProcessForegroundLocked(app);
         mServices.killServicesLocked(app, allowRestart);
         mPhantomProcessList.onAppDied(pid);
@@ -13685,8 +13710,15 @@
         }
         validateServiceInstanceName(instanceName);
 
-        if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
-                "*** startService: " + service + " type=" + resolvedType + " fg=" + requireForeground);
+        if (DEBUG_SERVICE)
+            Slog.v(
+                    TAG_SERVICE,
+                    "*** startService: "
+                            + service
+                            + " type="
+                            + resolvedType
+                            + " fg="
+                            + requireForeground);
         final int callingPid = Binder.getCallingPid();
         final int callingUid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
@@ -14598,7 +14630,7 @@
                             originalStickyCallingUid))) {
                         sticky = broadcast.intent;
                     }
-                    BroadcastQueue queue = broadcastQueueForIntent(broadcast.intent);
+                    BroadcastQueue queue = mBroadcastQueue;
                     BroadcastRecord r = new BroadcastRecord(queue, broadcast.intent, null,
                             null, null, -1, -1, false, null, null, null, null, OP_NONE,
                             BroadcastOptions.makeWithDeferUntilActive(broadcast.deferUntilActive),
@@ -14984,9 +15016,6 @@
                         + " ordered=" + ordered + " userid=" + userId
                         + " options=" + (brOptions == null ? "null" : brOptions.toBundle()));
         if ((resultTo != null) && !ordered) {
-            if (!mEnableModernQueue) {
-                Slog.w(TAG, "Broadcast " + intent + " not ordered but result callback requested!");
-            }
             if (!UserHandle.isCore(callingUid)) {
                 String msg = "Unauthorized unordered resultTo broadcast "
                              + intent + " sent from uid " + callingUid;
@@ -15447,9 +15476,14 @@
             if (checkPermission(android.Manifest.permission.BROADCAST_STICKY,
                     callingPid, callingUid)
                     != PackageManager.PERMISSION_GRANTED) {
-                String msg = "Permission Denial: broadcastIntent() requesting a sticky broadcast from pid="
-                        + callingPid + ", uid=" + callingUid
-                        + " requires " + android.Manifest.permission.BROADCAST_STICKY;
+                String msg =
+                        "Permission Denial: broadcastIntent() requesting a sticky broadcast from"
+                            + " pid="
+                                + callingPid
+                                + ", uid="
+                                + callingUid
+                                + " requires "
+                                + android.Manifest.permission.BROADCAST_STICKY;
                 Slog.w(TAG, msg);
                 throw new SecurityException(msg);
             }
@@ -15582,29 +15616,6 @@
         filterNonExportedComponents(intent, callingUid, callingPid, registeredReceivers,
                 mPlatformCompat, callerPackage, resolvedType);
         int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
-        if (!ordered && NR > 0 && !mEnableModernQueue) {
-            // If we are not serializing this broadcast, then send the
-            // registered receivers separately so they don't wait for the
-            // components to be launched. We don't do this split for the modern
-            // queue because delivery to registered receivers isn't blocked
-            // behind manifest receivers.
-            if (isCallerSystem) {
-                checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
-                        isProtectedBroadcast, registeredReceivers);
-            }
-            final BroadcastQueue queue = broadcastQueueForIntent(intent);
-            BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
-                    callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
-                    requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
-                    registeredReceivers, resultToApp, resultTo, resultCode, resultData,
-                    resultExtras, ordered, sticky, false, userId,
-                    backgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver,
-                    callerAppProcessState);
-            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast " + r);
-            queue.enqueueBroadcastLocked(r);
-            registeredReceivers = null;
-            NR = 0;
-        }
 
         // Merge into one list.
         int ir = 0;
@@ -15685,7 +15696,7 @@
 
         if ((receivers != null && receivers.size() > 0)
                 || resultTo != null) {
-            BroadcastQueue queue = broadcastQueueForIntent(intent);
+            BroadcastQueue queue = mBroadcastQueue;
             filterNonExportedComponents(intent, callingUid, callingPid, receivers,
                     mPlatformCompat, callerPackage, resolvedType);
             BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
@@ -15973,9 +15984,7 @@
     }
 
     void backgroundServicesFinishedLocked(int userId) {
-        for (BroadcastQueue queue : mBroadcastQueues) {
-            queue.backgroundServicesFinishedLocked(userId);
-        }
+        mBroadcastQueue.backgroundServicesFinishedLocked(userId);
     }
 
     public void finishReceiver(IBinder caller, int resultCode, String resultData,
@@ -15996,8 +16005,7 @@
                     return;
                 }
 
-                final BroadcastQueue queue = broadcastQueueForFlags(flags);
-                queue.finishReceiverLocked(callerApp, resultCode,
+                mBroadcastQueue.finishReceiverLocked(callerApp, resultCode,
                         resultData, resultExtras, resultAbort, true);
                 // updateOomAdjLocked() will be done here
                 trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
@@ -16588,10 +16596,7 @@
     // =========================================================
 
     boolean isReceivingBroadcastLocked(ProcessRecord app, int[] outSchedGroup) {
-        int res = ProcessList.SCHED_GROUP_UNDEFINED;
-        for (BroadcastQueue queue : mBroadcastQueues) {
-            res = Math.max(res, queue.getPreferredSchedulingGroupLocked(app));
-        }
+        final int res = mBroadcastQueue.getPreferredSchedulingGroupLocked(app);
         outSchedGroup[0] = res;
         return res != ProcessList.SCHED_GROUP_UNDEFINED;
     }
@@ -16715,10 +16720,8 @@
      */
     @GuardedBy("this")
     final boolean canGcNowLocked() {
-        for (BroadcastQueue q : mBroadcastQueues) {
-            if (!q.isIdleLocked()) {
-                return false;
-            }
+        if (!mBroadcastQueue.isIdleLocked()) {
+            return false;
         }
         return mAtmInternal.canGcNow();
     }
@@ -17928,9 +17931,7 @@
     }
 
     void onProcessFreezableChangedLocked(ProcessRecord app) {
-        if (mEnableModernQueue) {
-            mBroadcastQueues[0].onProcessFreezableChangedLocked(app);
-        }
+        mBroadcastQueue.onProcessFreezableChangedLocked(app);
     }
 
     @VisibleForTesting
@@ -18266,11 +18267,6 @@
         }
 
         @Override
-        public boolean isModernQueueEnabled() {
-            return mEnableModernQueue;
-        }
-
-        @Override
         public void enforceBroadcastOptionsPermissions(Bundle options, int callingUid) {
             enforceBroadcastOptionPermissionsInternal(options, callingUid);
         }
@@ -18699,7 +18695,6 @@
                     Binder.restoreCallingIdentity(origId);
                 }
             }
-
         }
 
         @Override
@@ -18709,12 +18704,8 @@
                 int userId, int[] appIdAllowList,
                 @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
                 @Nullable Bundle bOptions) {
-            // Sending broadcasts with a finish callback without the need for the broadcasts
-            // delivery to be serialized is only supported by modern queue. So, when modern
-            // queue is disabled, we continue to send broadcasts in a serialized fashion.
-            final boolean serialized = !isModernQueueEnabled();
-            return broadcastIntent(intent, resultTo, requiredPermissions, serialized, userId,
-                    appIdAllowList, filterExtrasForReceiver, bOptions);
+            return broadcastIntent(intent, resultTo, requiredPermissions, false /* serialized */,
+                    userId, appIdAllowList, filterExtrasForReceiver, bOptions);
         }
 
         @Override
@@ -19621,9 +19612,7 @@
         if (flushBroadcastLoopers) {
             BroadcastLoopers.waitForIdle(pw);
         }
-        for (BroadcastQueue queue : mBroadcastQueues) {
-            queue.waitForIdle(pw);
-        }
+        mBroadcastQueue.waitForIdle(pw);
         pw.println("All broadcast queues are idle!");
         pw.flush();
     }
@@ -19639,9 +19628,7 @@
         if (flushBroadcastLoopers) {
             BroadcastLoopers.waitForBarrier(pw);
         }
-        for (BroadcastQueue queue : mBroadcastQueues) {
-            queue.waitForBarrier(pw);
-        }
+        mBroadcastQueue.waitForBarrier(pw);
         if (flushApplicationThreads) {
             waitForApplicationBarrier(pw);
         }
@@ -19717,9 +19704,7 @@
 
     void waitForBroadcastDispatch(@NonNull PrintWriter pw, @NonNull Intent intent) {
         enforceCallingPermission(permission.DUMP, "waitForBroadcastDispatch");
-        for (BroadcastQueue queue : mBroadcastQueues) {
-            queue.waitForDispatched(intent, pw);
-        }
+        mBroadcastQueue.waitForDispatched(intent, pw);
     }
 
     void setIgnoreDeliveryGroupPolicy(@NonNull String broadcastAction) {
@@ -19759,20 +19744,8 @@
         Objects.requireNonNull(targetPackage);
         Preconditions.checkArgumentNonnegative(delayedDurationMs);
         enforceCallingPermission(permission.DUMP, "forceDelayBroadcastDelivery()");
-        // Ignore request if modern queue is not enabled
-        if (!mEnableModernQueue) {
-            return;
-        }
 
-        for (BroadcastQueue queue : mBroadcastQueues) {
-            queue.forceDelayBroadcastDelivery(targetPackage, delayedDurationMs);
-        }
-    }
-
-    @Override
-    public boolean isModernBroadcastQueueEnabled() {
-        enforceCallingPermission(permission.DUMP, "isModernBroadcastQueueEnabled()");
-        return mEnableModernQueue;
+        mBroadcastQueue.forceDelayBroadcastDelivery(targetPackage, delayedDurationMs);
     }
 
     @Override
@@ -20320,7 +20293,7 @@
             return mNmi != null;
         }
 
-        public BroadcastQueue[] getBroadcastQueues(ActivityManagerService service) {
+        public BroadcastQueue getBroadcastQueue(ActivityManagerService service) {
             // Broadcast policy parameters
             final BroadcastConstants foreConstants = new BroadcastConstants(
                     Settings.Global.BROADCAST_FG_CONSTANTS);
@@ -20336,26 +20309,8 @@
             // by default, no "slow" policy in this queue
             offloadConstants.SLOW_TIME = Integer.MAX_VALUE;
 
-            final BroadcastQueue[] broadcastQueues;
-            final Handler handler = service.mHandler;
-            if (service.mEnableModernQueue) {
-                broadcastQueues = new BroadcastQueue[1];
-                broadcastQueues[0] = new BroadcastQueueModernImpl(service, handler,
+            return new BroadcastQueueModernImpl(service, service.mHandler,
                         foreConstants, backConstants);
-            } else {
-                broadcastQueues = new BroadcastQueue[4];
-                broadcastQueues[BROADCAST_QUEUE_FG] = new BroadcastQueueImpl(service, handler,
-                        "foreground", foreConstants, false, ProcessList.SCHED_GROUP_DEFAULT);
-                broadcastQueues[BROADCAST_QUEUE_BG] = new BroadcastQueueImpl(service, handler,
-                        "background", backConstants, true, ProcessList.SCHED_GROUP_BACKGROUND);
-                broadcastQueues[BROADCAST_QUEUE_BG_OFFLOAD] = new BroadcastQueueImpl(service,
-                        handler, "offload_bg", offloadConstants, true,
-                        ProcessList.SCHED_GROUP_BACKGROUND);
-                broadcastQueues[BROADCAST_QUEUE_FG_OFFLOAD] = new BroadcastQueueImpl(service,
-                        handler, "offload_fg", foreConstants, true,
-                        ProcessList.SCHED_GROUP_BACKGROUND);
-            }
-            return broadcastQueues;
         }
 
         /** @see Binder#getCallingUid */
@@ -20677,14 +20632,6 @@
         }
     }
 
-    private boolean isOnFgOffloadQueue(int flags) {
-        return ((flags & Intent.FLAG_RECEIVER_OFFLOAD_FOREGROUND) != 0);
-    }
-
-    private boolean isOnBgOffloadQueue(int flags) {
-        return (mEnableOffloadQueue && ((flags & Intent.FLAG_RECEIVER_OFFLOAD) != 0));
-    }
-
     @Override
     public ParcelFileDescriptor getLifeMonitor() {
         if (!isCallerShell()) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 45f657d..4ebabdc 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -1164,7 +1164,8 @@
         synchronized (mInternal) {
             synchronized (mInternal.mProcLock) {
                 app.mOptRecord.setFreezeSticky(isSticky);
-                mInternal.mOomAdjuster.mCachedAppOptimizer.freezeAppAsyncInternalLSP(app, 0, true);
+                mInternal.mOomAdjuster.mCachedAppOptimizer.freezeAppAsyncInternalLSP(
+                        app, 0 /* delayMillis */, true /* force */, false /* immediate */);
             }
         }
         return 0;
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 0ce1407..48daef8 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -711,7 +711,7 @@
                 }
             }
             if (profile != null) {
-                long startTime = SystemClock.currentThreadTimeMillis();
+                long startTime = SystemClock.uptimeMillis();
                 // skip background PSS calculation under the following situations:
                 //  - app is capturing camera imagery
                 //  - app is frozen and we have already collected PSS once.
@@ -721,7 +721,7 @@
                         || mService.isCameraActiveForUid(profile.mApp.uid)
                         || mService.mConstants.APP_PROFILER_PSS_PROFILING_DISABLED;
                 long pss = skipPSSCollection ? 0 : Debug.getPss(pid, tmp, null);
-                long endTime = SystemClock.currentThreadTimeMillis();
+                long endTime = SystemClock.uptimeMillis();
                 synchronized (mProfilerLock) {
                     if (pss != 0 && profile.getThread() != null
                             && profile.getSetProcState() == procState
@@ -852,7 +852,7 @@
                 }
             }
             if (profile != null) {
-                long startTime = SystemClock.currentThreadTimeMillis();
+                long startTime = SystemClock.uptimeMillis();
                 // skip background RSS calculation under the following situations:
                 //  - app is capturing camera imagery
                 //  - app is frozen and we have already collected RSS once.
@@ -862,7 +862,7 @@
                         || mService.isCameraActiveForUid(profile.mApp.uid)
                         || mService.mConstants.APP_PROFILER_PSS_PROFILING_DISABLED;
                 long rss = skipRSSCollection ? 0 : Debug.getRss(pid, null);
-                long endTime = SystemClock.currentThreadTimeMillis();
+                long endTime = SystemClock.uptimeMillis();
                 synchronized (mProfilerLock) {
                     if (rss != 0 && profile.getThread() != null
                             && profile.getSetProcState() == procState
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 1dc384d..3e633cc 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -54,6 +54,7 @@
 import com.android.server.IoThread;
 import com.android.server.ServiceThread;
 import com.android.server.SystemServiceManager;
+import com.android.server.wm.WindowProcessController;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -385,8 +386,10 @@
         start.setPackageName(app.info.packageName);
         if (android.content.pm.Flags.stayStopped()) {
             // TODO: Verify this is created at the right time to have the correct force-stopped
-            // state in the ProcessRecord. Also use the WindowProcessRecord if activity.
-            start.setForceStopped(app.wasForceStopped());
+            // state in the ProcessRecord.
+            final WindowProcessController wpc = app.getWindowProcessController();
+            start.setForceStopped(app.wasForceStopped()
+                    || (wpc != null ? wpc.wasForceStopped() : false));
         }
     }
 
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 2fff79b..57080f8 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -128,14 +128,6 @@
     public long ALLOW_BG_ACTIVITY_START_TIMEOUT = DEFAULT_ALLOW_BG_ACTIVITY_START_TIMEOUT;
 
     /**
-     * Flag indicating if we should use {@link BroadcastQueueModernImpl} instead
-     * of the default {@link BroadcastQueueImpl}.
-     */
-    public boolean MODERN_QUEUE_ENABLED = DEFAULT_MODERN_QUEUE_ENABLED;
-    private static final String KEY_MODERN_QUEUE_ENABLED = "modern_queue_enabled";
-    private static final boolean DEFAULT_MODERN_QUEUE_ENABLED = true;
-
-    /**
      * For {@link BroadcastQueueModernImpl}: Maximum dispatch parallelism
      * that we'll tolerate for ordinary broadcast dispatch.
      */
@@ -296,11 +288,21 @@
      * For {@link BroadcastQueueModernImpl}: How frequently we should check for the pending
      * cold start validity.
      */
-    public long PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 30 * 1000;
+    public long PENDING_COLD_START_CHECK_INTERVAL_MILLIS =
+            DEFAULT_PENDING_COLD_START_CHECK_INTERVAL_MILLIS;
     private static final String KEY_PENDING_COLD_START_CHECK_INTERVAL_MILLIS =
             "pending_cold_start_check_interval_millis";
     private static final long DEFAULT_PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 30_000;
 
+    /**
+     * For {@link BroadcastQueueModernImpl}: Maximum number of outgoing broadcasts from a
+     * freezable process that will be allowed before killing the process.
+     */
+    public long MAX_FROZEN_OUTGOING_BROADCASTS = DEFAULT_MAX_FROZEN_OUTGOING_BROADCASTS;
+    private static final String KEY_MAX_FROZEN_OUTGOING_BROADCASTS =
+            "max_frozen_outgoing_broadcasts";
+    private static final int DEFAULT_MAX_FROZEN_OUTGOING_BROADCASTS = 32;
+
     // Settings override tracking for this instance
     private String mSettingsKey;
     private SettingsObserver mSettingsObserver;
@@ -411,8 +413,6 @@
      */
     private void updateDeviceConfigConstants() {
         synchronized (this) {
-            MODERN_QUEUE_ENABLED = getDeviceConfigBoolean(KEY_MODERN_QUEUE_ENABLED,
-                    DEFAULT_MODERN_QUEUE_ENABLED);
             MAX_RUNNING_PROCESS_QUEUES = getDeviceConfigInt(KEY_MAX_RUNNING_PROCESS_QUEUES,
                     DEFAULT_MAX_RUNNING_PROCESS_QUEUES);
             EXTRA_RUNNING_URGENT_PROCESS_QUEUES = getDeviceConfigInt(
@@ -453,6 +453,9 @@
             PENDING_COLD_START_CHECK_INTERVAL_MILLIS = getDeviceConfigLong(
                     KEY_PENDING_COLD_START_CHECK_INTERVAL_MILLIS,
                     DEFAULT_PENDING_COLD_START_CHECK_INTERVAL_MILLIS);
+            MAX_FROZEN_OUTGOING_BROADCASTS = getDeviceConfigInt(
+                    KEY_MAX_FROZEN_OUTGOING_BROADCASTS,
+                    DEFAULT_MAX_FROZEN_OUTGOING_BROADCASTS);
         }
 
         // TODO: migrate BroadcastRecord to accept a BroadcastConstants
@@ -485,7 +488,6 @@
             pw.print(NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT);
             pw.println("):");
             pw.increaseIndent();
-            pw.print(KEY_MODERN_QUEUE_ENABLED, MODERN_QUEUE_ENABLED).println();
             pw.print(KEY_MAX_RUNNING_PROCESS_QUEUES, MAX_RUNNING_PROCESS_QUEUES).println();
             pw.print(KEY_MAX_RUNNING_ACTIVE_BROADCASTS, MAX_RUNNING_ACTIVE_BROADCASTS).println();
             pw.print(KEY_CORE_MAX_RUNNING_BLOCKING_BROADCASTS,
@@ -513,6 +515,8 @@
                     CORE_DEFER_UNTIL_ACTIVE).println();
             pw.print(KEY_PENDING_COLD_START_CHECK_INTERVAL_MILLIS,
                     PENDING_COLD_START_CHECK_INTERVAL_MILLIS).println();
+            pw.print(KEY_MAX_FROZEN_OUTGOING_BROADCASTS,
+                    MAX_FROZEN_OUTGOING_BROADCASTS).println();
             pw.decreaseIndent();
             pw.println();
         }
diff --git a/services/core/java/com/android/server/am/BroadcastDispatcher.java b/services/core/java/com/android/server/am/BroadcastDispatcher.java
deleted file mode 100644
index 8aa3921..0000000
--- a/services/core/java/com/android/server/am/BroadcastDispatcher.java
+++ /dev/null
@@ -1,1266 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.am;
-
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL;
-import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UptimeMillisLong;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.AlarmManagerInternal;
-import com.android.server.LocalServices;
-
-import dalvik.annotation.optimization.NeverCompile;
-
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Set;
-
-/**
- * Manages ordered broadcast delivery, applying policy to mitigate the effects of
- * slow receivers.
- */
-public class BroadcastDispatcher {
-    private static final String TAG = "BroadcastDispatcher";
-
-    // Deferred broadcasts to one app; times are all uptime time base like
-    // other broadcast-related timekeeping
-    static class Deferrals {
-        final int uid;
-        long deferredAt;    // when we started deferring
-        long deferredBy;    // how long did we defer by last time?
-        long deferUntil;    // when does the next element become deliverable?
-        int alarmCount;
-
-        final ArrayList<BroadcastRecord> broadcasts;
-
-        Deferrals(int uid, long now, long backoff, int count) {
-            this.uid = uid;
-            this.deferredAt = now;
-            this.deferredBy = backoff;
-            this.deferUntil = now + backoff;
-            this.alarmCount = count;
-            broadcasts = new ArrayList<>();
-        }
-
-        void add(BroadcastRecord br) {
-            broadcasts.add(br);
-        }
-
-        int size() {
-            return broadcasts.size();
-        }
-
-        boolean isEmpty() {
-            return broadcasts.isEmpty();
-        }
-
-        @NeverCompile
-        void dumpDebug(ProtoOutputStream proto, long fieldId) {
-            for (BroadcastRecord br : broadcasts) {
-                br.dumpDebug(proto, fieldId);
-            }
-        }
-
-        @NeverCompile
-        void dumpLocked(Dumper d) {
-            for (BroadcastRecord br : broadcasts) {
-                d.dump(br);
-            }
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("Deferrals{uid=");
-            sb.append(uid);
-            sb.append(", deferUntil=");
-            sb.append(deferUntil);
-            sb.append(", #broadcasts=");
-            sb.append(broadcasts.size());
-            sb.append("}");
-            return sb.toString();
-        }
-    }
-
-    // Carrying dump formatting state across multiple concatenated datasets
-    class Dumper {
-        final PrintWriter mPw;
-        final String mQueueName;
-        final String mDumpPackage;
-        final SimpleDateFormat mSdf;
-        boolean mPrinted;
-        boolean mNeedSep;
-        String mHeading;
-        String mLabel;
-        int mOrdinal;
-
-        Dumper(PrintWriter pw, String queueName, String dumpPackage, SimpleDateFormat sdf) {
-            mPw = pw;
-            mQueueName = queueName;
-            mDumpPackage = dumpPackage;
-            mSdf = sdf;
-
-            mPrinted = false;
-            mNeedSep = true;
-        }
-
-        void setHeading(String heading) {
-            mHeading = heading;
-            mPrinted = false;
-        }
-
-        void setLabel(String label) {
-            //"  Active Ordered Broadcast " + mQueueName + " #" + i + ":"
-            mLabel = "  " + label + " " + mQueueName + " #";
-            mOrdinal = 0;
-        }
-
-        boolean didPrint() {
-            return mPrinted;
-        }
-
-        @NeverCompile
-        void dump(BroadcastRecord br) {
-            if (mDumpPackage == null || mDumpPackage.equals(br.callerPackage)) {
-                if (!mPrinted) {
-                    if (mNeedSep) {
-                        mPw.println();
-                    }
-                    mPrinted = true;
-                    mNeedSep = true;
-                    mPw.println("  " + mHeading + " [" + mQueueName + "]:");
-                }
-                mPw.println(mLabel + mOrdinal + ":");
-                mOrdinal++;
-
-                br.dump(mPw, "    ", mSdf);
-            }
-        }
-    }
-
-    private final Object mLock;
-    private final BroadcastQueueImpl mQueue;
-    private final BroadcastConstants mConstants;
-    private final Handler mHandler;
-    private AlarmManagerInternal mAlarm;
-
-    // Current alarm targets; mapping uid -> in-flight alarm count
-    final SparseIntArray mAlarmUids = new SparseIntArray();
-    final AlarmManagerInternal.InFlightListener mAlarmListener =
-            new AlarmManagerInternal.InFlightListener() {
-        @Override
-        public void broadcastAlarmPending(final int recipientUid) {
-            synchronized (mLock) {
-                final int newCount = mAlarmUids.get(recipientUid, 0) + 1;
-                mAlarmUids.put(recipientUid, newCount);
-                // any deferred broadcasts to this app now get fast-tracked
-                final int numEntries = mDeferredBroadcasts.size();
-                for (int i = 0; i < numEntries; i++) {
-                    if (recipientUid == mDeferredBroadcasts.get(i).uid) {
-                        Deferrals d = mDeferredBroadcasts.remove(i);
-                        mAlarmDeferrals.add(d);
-                        break;
-                    }
-                }
-            }
-        }
-
-        @Override
-        public void broadcastAlarmComplete(final int recipientUid) {
-            synchronized (mLock) {
-                final int newCount = mAlarmUids.get(recipientUid, 0) - 1;
-                if (newCount >= 0) {
-                    mAlarmUids.put(recipientUid, newCount);
-                } else {
-                    Slog.wtf(TAG, "Undercount of broadcast alarms in flight for " + recipientUid);
-                    mAlarmUids.put(recipientUid, 0);
-                }
-
-                // No longer an alarm target, so resume ordinary deferral policy
-                if (newCount <= 0) {
-                    final int numEntries = mAlarmDeferrals.size();
-                    for (int i = 0; i < numEntries; i++) {
-                        if (recipientUid == mAlarmDeferrals.get(i).uid) {
-                            Deferrals d = mAlarmDeferrals.remove(i);
-                            insertLocked(mDeferredBroadcasts, d);
-                            break;
-                        }
-                    }
-                }
-            }
-        }
-    };
-
-    // Queue recheck operation used to tickle broadcast delivery when appropriate
-    final Runnable mScheduleRunnable = new Runnable() {
-        @Override
-        public void run() {
-            synchronized (mLock) {
-                if (DEBUG_BROADCAST_DEFERRAL) {
-                    Slog.v(TAG, "Deferral recheck of pending broadcasts");
-                }
-                mQueue.scheduleBroadcastsLocked();
-                mRecheckScheduled = false;
-            }
-        }
-    };
-    private boolean mRecheckScheduled = false;
-
-    // Usual issuance-order outbound queue
-    private final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<>();
-    // General deferrals not holding up alarms
-    private final ArrayList<Deferrals> mDeferredBroadcasts = new ArrayList<>();
-    // Deferrals that *are* holding up alarms; ordered by alarm dispatch time
-    private final ArrayList<Deferrals> mAlarmDeferrals = new ArrayList<>();
-    // Under the "deliver alarm broadcasts immediately" policy, the queue of
-    // upcoming alarm broadcasts.  These are always delivered first - if the
-    // policy is changed on the fly from immediate-alarm-delivery to the previous
-    // in-order-queueing behavior, pending immediate alarm deliveries will drain
-    // and then the behavior settle into the pre-U semantics.
-    private final ArrayList<BroadcastRecord> mAlarmQueue = new ArrayList<>();
-
-    // Next outbound broadcast, established by getNextBroadcastLocked()
-    private BroadcastRecord mCurrentBroadcast;
-
-    // Map userId to its deferred boot completed broadcasts.
-    private SparseArray<DeferredBootCompletedBroadcastPerUser> mUser2Deferred = new SparseArray<>();
-
-    /**
-     * Deferred LOCKED_BOOT_COMPLETED and BOOT_COMPLETED broadcasts that is sent to a user.
-     */
-    static class DeferredBootCompletedBroadcastPerUser {
-        private int mUserId;
-        // UID that has process started at least once, ready to execute LOCKED_BOOT_COMPLETED
-        // receivers.
-        @VisibleForTesting
-        SparseBooleanArray mUidReadyForLockedBootCompletedBroadcast = new SparseBooleanArray();
-        // UID that has process started at least once, ready to execute BOOT_COMPLETED receivers.
-        @VisibleForTesting
-        SparseBooleanArray mUidReadyForBootCompletedBroadcast = new SparseBooleanArray();
-        // Map UID to deferred LOCKED_BOOT_COMPLETED broadcasts.
-        // LOCKED_BOOT_COMPLETED broadcast receivers are deferred until the first time the uid has
-        // any process started.
-        @VisibleForTesting
-        SparseArray<BroadcastRecord> mDeferredLockedBootCompletedBroadcasts = new SparseArray<>();
-        // is the LOCKED_BOOT_COMPLETED broadcast received by the user.
-        @VisibleForTesting
-        boolean mLockedBootCompletedBroadcastReceived;
-        // Map UID to deferred BOOT_COMPLETED broadcasts.
-        // BOOT_COMPLETED broadcast receivers are deferred until the first time the uid has any
-        // process started.
-        @VisibleForTesting
-        SparseArray<BroadcastRecord> mDeferredBootCompletedBroadcasts = new SparseArray<>();
-        // is the BOOT_COMPLETED broadcast received by the user.
-        @VisibleForTesting
-        boolean mBootCompletedBroadcastReceived;
-
-        DeferredBootCompletedBroadcastPerUser(int userId) {
-            this.mUserId = userId;
-        }
-
-        public void updateUidReady(int uid) {
-            if (!mLockedBootCompletedBroadcastReceived
-                    || mDeferredLockedBootCompletedBroadcasts.size() != 0) {
-                mUidReadyForLockedBootCompletedBroadcast.put(uid, true);
-            }
-            if (!mBootCompletedBroadcastReceived
-                    || mDeferredBootCompletedBroadcasts.size() != 0) {
-                mUidReadyForBootCompletedBroadcast.put(uid, true);
-            }
-        }
-
-        public void enqueueBootCompletedBroadcasts(String action,
-                SparseArray<BroadcastRecord> deferred) {
-            if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) {
-                enqueueBootCompletedBroadcasts(deferred, mDeferredLockedBootCompletedBroadcasts,
-                        mUidReadyForLockedBootCompletedBroadcast);
-                mLockedBootCompletedBroadcastReceived = true;
-                if (DEBUG_BROADCAST_DEFERRAL) {
-                    dumpBootCompletedBroadcastRecord(mDeferredLockedBootCompletedBroadcasts);
-                }
-            } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
-                enqueueBootCompletedBroadcasts(deferred, mDeferredBootCompletedBroadcasts,
-                        mUidReadyForBootCompletedBroadcast);
-                mBootCompletedBroadcastReceived = true;
-                if (DEBUG_BROADCAST_DEFERRAL) {
-                    dumpBootCompletedBroadcastRecord(mDeferredBootCompletedBroadcasts);
-                }
-            }
-        }
-
-        /**
-         * Merge UID to BroadcastRecord map into {@link #mDeferredBootCompletedBroadcasts} or
-         * {@link #mDeferredLockedBootCompletedBroadcasts}
-         * @param from the UID to BroadcastRecord map.
-         * @param into The UID to list of BroadcastRecord map.
-         */
-        private void enqueueBootCompletedBroadcasts(SparseArray<BroadcastRecord> from,
-                SparseArray<BroadcastRecord> into, SparseBooleanArray uidReadyForReceiver) {
-            // remove unwanted uids from uidReadyForReceiver.
-            for (int i = uidReadyForReceiver.size() - 1; i >= 0; i--) {
-                if (from.indexOfKey(uidReadyForReceiver.keyAt(i)) < 0) {
-                    uidReadyForReceiver.removeAt(i);
-                }
-            }
-            for (int i = 0, size = from.size(); i < size; i++) {
-                final int uid = from.keyAt(i);
-                into.put(uid, from.valueAt(i));
-                if (uidReadyForReceiver.indexOfKey(uid) < 0) {
-                    // uid is wanted but not ready.
-                    uidReadyForReceiver.put(uid, false);
-                }
-            }
-        }
-
-        public @Nullable BroadcastRecord dequeueDeferredBootCompletedBroadcast(
-                boolean isAllUidReady) {
-            BroadcastRecord next = dequeueDeferredBootCompletedBroadcast(
-                    mDeferredLockedBootCompletedBroadcasts,
-                    mUidReadyForLockedBootCompletedBroadcast, isAllUidReady);
-            if (next == null) {
-                next = dequeueDeferredBootCompletedBroadcast(mDeferredBootCompletedBroadcasts,
-                        mUidReadyForBootCompletedBroadcast, isAllUidReady);
-            }
-            return next;
-        }
-
-        private @Nullable BroadcastRecord dequeueDeferredBootCompletedBroadcast(
-                SparseArray<BroadcastRecord> uid2br, SparseBooleanArray uidReadyForReceiver,
-                boolean isAllUidReady) {
-            for (int i = 0, size = uid2br.size(); i < size; i++) {
-                final int uid = uid2br.keyAt(i);
-                if (isAllUidReady || uidReadyForReceiver.get(uid)) {
-                    final BroadcastRecord br = uid2br.valueAt(i);
-                    if (DEBUG_BROADCAST_DEFERRAL) {
-                        final Object receiver = br.receivers.get(0);
-                        if (receiver instanceof BroadcastFilter) {
-                            if (DEBUG_BROADCAST_DEFERRAL) {
-                                Slog.i(TAG, "getDeferredBootCompletedBroadcast uid:" + uid
-                                        + " BroadcastFilter:" + (BroadcastFilter) receiver
-                                        + " broadcast:" + br.intent.getAction());
-                            }
-                        } else /* if (receiver instanceof ResolveInfo) */ {
-                            ResolveInfo info = (ResolveInfo) receiver;
-                            String packageName = info.activityInfo.applicationInfo.packageName;
-                            if (DEBUG_BROADCAST_DEFERRAL) {
-                                Slog.i(TAG, "getDeferredBootCompletedBroadcast uid:" + uid
-                                        + " packageName:" + packageName
-                                        + " broadcast:" + br.intent.getAction());
-                            }
-                        }
-                    }
-                    // remove the BroadcastRecord.
-                    uid2br.removeAt(i);
-                    if (uid2br.size() == 0) {
-                        // All deferred receivers are executed, do not need uidReadyForReceiver
-                        // any more.
-                        uidReadyForReceiver.clear();
-                    }
-                    return br;
-                }
-            }
-            return null;
-        }
-
-        private @Nullable SparseArray<BroadcastRecord> getDeferredList(String action) {
-            SparseArray<BroadcastRecord> brs = null;
-            if (action.equals(Intent.ACTION_LOCKED_BOOT_COMPLETED)) {
-                brs = mDeferredLockedBootCompletedBroadcasts;
-            } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
-                brs = mDeferredBootCompletedBroadcasts;
-            }
-            return brs;
-        }
-
-        /**
-         * Return the total number of UIDs in all BroadcastRecord in
-         * {@link #mDeferredBootCompletedBroadcasts} or
-         * {@link #mDeferredLockedBootCompletedBroadcasts}
-         */
-        private int getBootCompletedBroadcastsUidsSize(String action) {
-            SparseArray<BroadcastRecord> brs = getDeferredList(action);
-            return brs != null ? brs.size() : 0;
-        }
-
-        /**
-         * Return the total number of receivers in all BroadcastRecord in
-         * {@link #mDeferredBootCompletedBroadcasts} or
-         * {@link #mDeferredLockedBootCompletedBroadcasts}
-         */
-        private int getBootCompletedBroadcastsReceiversSize(String action) {
-            SparseArray<BroadcastRecord> brs = getDeferredList(action);
-            if (brs == null) {
-                return 0;
-            }
-            int size = 0;
-            for (int i = 0, s = brs.size(); i < s; i++) {
-                size += brs.valueAt(i).receivers.size();
-            }
-            return size;
-        }
-
-        @NeverCompile
-        public void dump(Dumper dumper, String action) {
-            SparseArray<BroadcastRecord> brs = getDeferredList(action);
-            if (brs == null) {
-                return;
-            }
-            for (int i = 0, size = brs.size(); i < size; i++) {
-                dumper.dump(brs.valueAt(i));
-            }
-        }
-
-        @NeverCompile
-        public void dumpDebug(ProtoOutputStream proto, long fieldId) {
-            for (int i = 0, size = mDeferredLockedBootCompletedBroadcasts.size(); i < size; i++) {
-                mDeferredLockedBootCompletedBroadcasts.valueAt(i).dumpDebug(proto, fieldId);
-            }
-            for (int i = 0, size = mDeferredBootCompletedBroadcasts.size(); i < size; i++) {
-                mDeferredBootCompletedBroadcasts.valueAt(i).dumpDebug(proto, fieldId);
-            }
-        }
-
-        @NeverCompile
-        private void dumpBootCompletedBroadcastRecord(SparseArray<BroadcastRecord> brs) {
-            for (int i = 0, size = brs.size(); i < size; i++) {
-                final Object receiver = brs.valueAt(i).receivers.get(0);
-                String packageName = null;
-                if (receiver instanceof BroadcastFilter) {
-                    BroadcastFilter recv = (BroadcastFilter) receiver;
-                    packageName = recv.receiverList.app.processName;
-                } else /* if (receiver instanceof ResolveInfo) */ {
-                    ResolveInfo info = (ResolveInfo) receiver;
-                    packageName = info.activityInfo.applicationInfo.packageName;
-                }
-                Slog.i(TAG, "uid:" + brs.keyAt(i)
-                        + " packageName:" + packageName
-                        + " receivers:" + brs.valueAt(i).receivers.size());
-            }
-        }
-    }
-
-    private DeferredBootCompletedBroadcastPerUser getDeferredPerUser(int userId) {
-        if (mUser2Deferred.contains(userId)) {
-            return mUser2Deferred.get(userId);
-        } else {
-            final DeferredBootCompletedBroadcastPerUser temp =
-                    new DeferredBootCompletedBroadcastPerUser(userId);
-            mUser2Deferred.put(userId, temp);
-            return temp;
-        }
-    }
-
-    /**
-     * ActivityManagerService.attachApplication() call this method to notify that the UID is ready
-     * to accept deferred LOCKED_BOOT_COMPLETED and BOOT_COMPLETED broadcasts.
-     * @param uid
-     */
-    public void updateUidReadyForBootCompletedBroadcastLocked(int uid) {
-        getDeferredPerUser(UserHandle.getUserId(uid)).updateUidReady(uid);
-    }
-
-    private @Nullable BroadcastRecord dequeueDeferredBootCompletedBroadcast() {
-        final boolean isAllUidReady = (mQueue.mService.mConstants.mDeferBootCompletedBroadcast
-                == DEFER_BOOT_COMPLETED_BROADCAST_NONE);
-        BroadcastRecord next = null;
-        for (int i = 0, size = mUser2Deferred.size(); i < size; i++) {
-            next = mUser2Deferred.valueAt(i).dequeueDeferredBootCompletedBroadcast(isAllUidReady);
-            if (next != null) {
-                break;
-            }
-        }
-        return next;
-    }
-
-    /**
-     * Constructed & sharing a lock with its associated BroadcastQueue instance
-     */
-    public BroadcastDispatcher(BroadcastQueueImpl queue, BroadcastConstants constants,
-            Handler handler, Object lock) {
-        mQueue = queue;
-        mConstants = constants;
-        mHandler = handler;
-        mLock = lock;
-    }
-
-    /**
-     * Spin up the integration with the alarm manager service; done lazily to manage
-     * service availability ordering during boot.
-     */
-    public void start() {
-        // Set up broadcast alarm tracking
-        mAlarm = LocalServices.getService(AlarmManagerInternal.class);
-        mAlarm.registerInFlightListener(mAlarmListener);
-    }
-
-    /**
-     * Standard contents-are-empty check
-     */
-    public boolean isEmpty() {
-        synchronized (mLock) {
-            return isIdle()
-                    && getBootCompletedBroadcastsUidsSize(Intent.ACTION_LOCKED_BOOT_COMPLETED) == 0
-                    && getBootCompletedBroadcastsUidsSize(Intent.ACTION_BOOT_COMPLETED) == 0;
-        }
-    }
-
-    /**
-     * Have less check than {@link #isEmpty()}.
-     * The dispatcher is considered as idle even with deferred LOCKED_BOOT_COMPLETED/BOOT_COMPLETED
-     * broadcasts because those can be deferred until the first time the uid's process is started.
-     * @return
-     */
-    public boolean isIdle() {
-        synchronized (mLock) {
-            return mCurrentBroadcast == null
-                    && mOrderedBroadcasts.isEmpty()
-                    && mAlarmQueue.isEmpty()
-                    && isDeferralsListEmpty(mDeferredBroadcasts)
-                    && isDeferralsListEmpty(mAlarmDeferrals);
-        }
-    }
-
-    private static boolean isDeferralsBeyondBarrier(@NonNull ArrayList<Deferrals> list,
-            @UptimeMillisLong long barrierTime) {
-        for (int i = 0; i < list.size(); i++) {
-            if (!isBeyondBarrier(list.get(i).broadcasts, barrierTime)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private static boolean isBeyondBarrier(@NonNull ArrayList<BroadcastRecord> list,
-            @UptimeMillisLong long barrierTime) {
-        for (int i = 0; i < list.size(); i++) {
-            if (list.get(i).enqueueTime <= barrierTime) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    public boolean isBeyondBarrier(@UptimeMillisLong long barrierTime) {
-        synchronized (mLock) {
-            if ((mCurrentBroadcast != null) && mCurrentBroadcast.enqueueTime <= barrierTime) {
-                return false;
-            }
-            return isBeyondBarrier(mOrderedBroadcasts, barrierTime)
-                    && isBeyondBarrier(mAlarmQueue, barrierTime)
-                    && isDeferralsBeyondBarrier(mDeferredBroadcasts, barrierTime)
-                    && isDeferralsBeyondBarrier(mAlarmDeferrals, barrierTime);
-        }
-    }
-
-    private static boolean isDispatchedInDeferrals(@NonNull ArrayList<Deferrals> list,
-            @NonNull Intent intent) {
-        for (int i = 0; i < list.size(); i++) {
-            if (!isDispatched(list.get(i).broadcasts, intent)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private static boolean isDispatched(@NonNull ArrayList<BroadcastRecord> list,
-            @NonNull Intent intent) {
-        for (int i = 0; i < list.size(); i++) {
-            if (intent.filterEquals(list.get(i).intent)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    public boolean isDispatched(@NonNull Intent intent) {
-        synchronized (mLock) {
-            if ((mCurrentBroadcast != null) && intent.filterEquals(mCurrentBroadcast.intent)) {
-                return false;
-            }
-            return isDispatched(mOrderedBroadcasts, intent)
-                    && isDispatched(mAlarmQueue, intent)
-                    && isDispatchedInDeferrals(mDeferredBroadcasts, intent)
-                    && isDispatchedInDeferrals(mAlarmDeferrals, intent);
-        }
-    }
-
-    private static int pendingInDeferralsList(ArrayList<Deferrals> list) {
-        int pending = 0;
-        final int numEntries = list.size();
-        for (int i = 0; i < numEntries; i++) {
-            pending += list.get(i).size();
-        }
-        return pending;
-    }
-
-    private static boolean isDeferralsListEmpty(ArrayList<Deferrals> list) {
-        return pendingInDeferralsList(list) == 0;
-    }
-
-    /**
-     * Strictly for logging, describe the currently pending contents in a human-
-     * readable way
-     */
-    public String describeStateLocked() {
-        final StringBuilder sb = new StringBuilder(128);
-        if (mCurrentBroadcast != null) {
-            sb.append("1 in flight, ");
-        }
-        sb.append(mOrderedBroadcasts.size());
-        sb.append(" ordered");
-        int n = mAlarmQueue.size();
-        if (n > 0) {
-            sb.append(", ");
-            sb.append(n);
-            sb.append(" alarms");
-        }
-        n = pendingInDeferralsList(mAlarmDeferrals);
-        if (n > 0) {
-            sb.append(", ");
-            sb.append(n);
-            sb.append(" deferrals in alarm recipients");
-        }
-        n = pendingInDeferralsList(mDeferredBroadcasts);
-        if (n > 0) {
-            sb.append(", ");
-            sb.append(n);
-            sb.append(" deferred");
-        }
-        n = getBootCompletedBroadcastsUidsSize(Intent.ACTION_LOCKED_BOOT_COMPLETED);
-        if (n > 0) {
-            sb.append(", ");
-            sb.append(n);
-            sb.append(" deferred LOCKED_BOOT_COMPLETED/");
-            sb.append(getBootCompletedBroadcastsReceiversSize(Intent.ACTION_LOCKED_BOOT_COMPLETED));
-            sb.append(" receivers");
-        }
-
-        n = getBootCompletedBroadcastsUidsSize(Intent.ACTION_BOOT_COMPLETED);
-        if (n > 0) {
-            sb.append(", ");
-            sb.append(n);
-            sb.append(" deferred BOOT_COMPLETED/");
-            sb.append(getBootCompletedBroadcastsReceiversSize(Intent.ACTION_BOOT_COMPLETED));
-            sb.append(" receivers");
-        }
-        return sb.toString();
-    }
-
-    // ----------------------------------
-    // BroadcastQueue operation support
-    void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
-        final ArrayList<BroadcastRecord> queue =
-                (r.alarm && mQueue.mService.mConstants.mPrioritizeAlarmBroadcasts)
-                        ? mAlarmQueue
-                        : mOrderedBroadcasts;
-
-        if (r.receivers == null || r.receivers.isEmpty()) {
-            // Fast no-op path for broadcasts that won't actually be dispatched to
-            // receivers - we still need to handle completion callbacks and historical
-            // records, but we don't need to consider the fancy cases.
-            queue.add(r);
-            return;
-        }
-
-        if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(r.intent.getAction())) {
-            // Create one BroadcastRecord for each UID that can be deferred.
-            final SparseArray<BroadcastRecord> deferred =
-                    r.splitDeferredBootCompletedBroadcastLocked(mQueue.mService.mInternal,
-                            mQueue.mService.mConstants.mDeferBootCompletedBroadcast);
-            getDeferredPerUser(r.userId).enqueueBootCompletedBroadcasts(
-                    Intent.ACTION_LOCKED_BOOT_COMPLETED, deferred);
-            if (!r.receivers.isEmpty()) {
-                // The non-deferred receivers.
-                mOrderedBroadcasts.add(r);
-                return;
-            }
-        } else if (Intent.ACTION_BOOT_COMPLETED.equals(r.intent.getAction())) {
-            // Create one BroadcastRecord for each UID that can be deferred.
-            final SparseArray<BroadcastRecord> deferred =
-                    r.splitDeferredBootCompletedBroadcastLocked(mQueue.mService.mInternal,
-                            mQueue.mService.mConstants.mDeferBootCompletedBroadcast);
-            getDeferredPerUser(r.userId).enqueueBootCompletedBroadcasts(
-                    Intent.ACTION_BOOT_COMPLETED, deferred);
-            if (!r.receivers.isEmpty()) {
-                // The non-deferred receivers.
-                mOrderedBroadcasts.add(r);
-                return;
-            }
-        } else {
-            // Ordinary broadcast, so put it on the appropriate queue and carry on
-            queue.add(r);
-        }
-    }
-
-    /**
-     * Return the total number of UIDs in all deferred boot completed BroadcastRecord.
-     */
-    private int getBootCompletedBroadcastsUidsSize(String action) {
-        int size = 0;
-        for (int i = 0, s = mUser2Deferred.size(); i < s; i++) {
-            size += mUser2Deferred.valueAt(i).getBootCompletedBroadcastsUidsSize(action);
-        }
-        return size;
-    }
-
-    /**
-     * Return the total number of receivers in all deferred boot completed BroadcastRecord.
-     */
-    private int getBootCompletedBroadcastsReceiversSize(String action) {
-        int size = 0;
-        for (int i = 0, s = mUser2Deferred.size(); i < s; i++) {
-            size += mUser2Deferred.valueAt(i).getBootCompletedBroadcastsReceiversSize(action);
-        }
-        return size;
-    }
-
-    // Returns the now-replaced broadcast record, or null if none
-    BroadcastRecord replaceBroadcastLocked(BroadcastRecord r, String typeForLogging) {
-        // Simple case, in the ordinary queue.
-        BroadcastRecord old = replaceBroadcastLocked(mOrderedBroadcasts, r, typeForLogging);
-        // ... or possibly in the simple alarm queue
-        if (old == null) {
-            old = replaceBroadcastLocked(mAlarmQueue, r, typeForLogging);
-        }
-        // If we didn't find it, less-simple:  in a deferral queue?
-        if (old == null) {
-            old = replaceDeferredBroadcastLocked(mAlarmDeferrals, r, typeForLogging);
-        }
-        if (old == null) {
-            old = replaceDeferredBroadcastLocked(mDeferredBroadcasts, r, typeForLogging);
-        }
-        return old;
-    }
-
-    private BroadcastRecord replaceDeferredBroadcastLocked(ArrayList<Deferrals> list,
-            BroadcastRecord r, String typeForLogging) {
-        BroadcastRecord old;
-        final int numEntries = list.size();
-        for (int i = 0; i < numEntries; i++) {
-            final Deferrals d = list.get(i);
-            old = replaceBroadcastLocked(d.broadcasts, r, typeForLogging);
-            if (old != null) {
-                return old;
-            }
-        }
-        return null;
-    }
-
-    private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> list,
-            BroadcastRecord r, String typeForLogging) {
-        BroadcastRecord old;
-        final Intent intent = r.intent;
-        // Any in-flight broadcast has already been popped, and cannot be replaced.
-        // (This preserves existing behavior of the replacement API)
-        for (int i = list.size() - 1; i >= 0; i--) {
-            old = list.get(i);
-            if (old.userId == r.userId && intent.filterEquals(old.intent)) {
-                if (DEBUG_BROADCAST) {
-                    Slog.v(TAG, "***** Replacing " + typeForLogging
-                            + " [" + mQueue.mQueueName + "]: " + intent);
-                }
-                // Clone deferral state too if any
-                r.deferred = old.deferred;
-                list.set(i, r);
-                return old;
-            }
-        }
-        return null;
-    }
-
-    boolean cleanupDisabledPackageReceiversLocked(final String packageName,
-            Set<String> filterByClasses, final int userId, final boolean doit) {
-        // Note: fast short circuits when 'doit' is false, as soon as we hit any
-        // "yes we would do something" circumstance
-        boolean didSomething = cleanupBroadcastListDisabledReceiversLocked(mOrderedBroadcasts,
-                packageName, filterByClasses, userId, doit);
-        if (doit || !didSomething) {
-            didSomething = cleanupBroadcastListDisabledReceiversLocked(mAlarmQueue,
-                    packageName, filterByClasses, userId, doit);
-        }
-        if (doit || !didSomething) {
-            ArrayList<BroadcastRecord> lockedBootCompletedBroadcasts = new ArrayList<>();
-            for (int u = 0, usize = mUser2Deferred.size(); u < usize; u++) {
-                SparseArray<BroadcastRecord> brs =
-                        mUser2Deferred.valueAt(u).mDeferredLockedBootCompletedBroadcasts;
-                for (int i = 0, size = brs.size(); i < size; i++) {
-                    lockedBootCompletedBroadcasts.add(brs.valueAt(i));
-                }
-            }
-            didSomething = cleanupBroadcastListDisabledReceiversLocked(
-                    lockedBootCompletedBroadcasts,
-                    packageName, filterByClasses, userId, doit);
-        }
-        if (doit || !didSomething) {
-            ArrayList<BroadcastRecord> bootCompletedBroadcasts = new ArrayList<>();
-            for (int u = 0, usize = mUser2Deferred.size(); u < usize; u++) {
-                SparseArray<BroadcastRecord> brs =
-                        mUser2Deferred.valueAt(u).mDeferredBootCompletedBroadcasts;
-                for (int i = 0, size = brs.size(); i < size; i++) {
-                    bootCompletedBroadcasts.add(brs.valueAt(i));
-                }
-            }
-            didSomething = cleanupBroadcastListDisabledReceiversLocked(bootCompletedBroadcasts,
-                    packageName, filterByClasses, userId, doit);
-        }
-        if (doit || !didSomething) {
-            didSomething |= cleanupDeferralsListDisabledReceiversLocked(mAlarmDeferrals,
-                    packageName, filterByClasses, userId, doit);
-        }
-        if (doit || !didSomething) {
-            didSomething |= cleanupDeferralsListDisabledReceiversLocked(mDeferredBroadcasts,
-                    packageName, filterByClasses, userId, doit);
-        }
-        if ((doit || !didSomething) && mCurrentBroadcast != null) {
-            didSomething |= mCurrentBroadcast.cleanupDisabledPackageReceiversLocked(
-                    packageName, filterByClasses, userId, doit);
-        }
-
-        return didSomething;
-    }
-
-    private boolean cleanupDeferralsListDisabledReceiversLocked(ArrayList<Deferrals> list,
-            final String packageName, Set<String> filterByClasses, final int userId,
-            final boolean doit) {
-        boolean didSomething = false;
-        for (Deferrals d : list) {
-            didSomething = cleanupBroadcastListDisabledReceiversLocked(d.broadcasts,
-                    packageName, filterByClasses, userId, doit);
-            if (!doit && didSomething) {
-                return true;
-            }
-        }
-        return didSomething;
-    }
-
-    private boolean cleanupBroadcastListDisabledReceiversLocked(ArrayList<BroadcastRecord> list,
-            final String packageName, Set<String> filterByClasses, final int userId,
-            final boolean doit) {
-        boolean didSomething = false;
-        for (BroadcastRecord br : list) {
-            didSomething |= br.cleanupDisabledPackageReceiversLocked(packageName,
-                    filterByClasses, userId, doit);
-            if (!doit && didSomething) {
-                return true;
-            }
-        }
-        return didSomething;
-    }
-
-    /**
-     * Standard proto dump entry point
-     */
-    @NeverCompile
-    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
-        if (mCurrentBroadcast != null) {
-            mCurrentBroadcast.dumpDebug(proto, fieldId);
-        }
-        for (Deferrals d : mAlarmDeferrals) {
-            d.dumpDebug(proto, fieldId);
-        }
-        for (BroadcastRecord br : mOrderedBroadcasts) {
-            br.dumpDebug(proto, fieldId);
-        }
-        for (BroadcastRecord br : mAlarmQueue) {
-            br.dumpDebug(proto, fieldId);
-        }
-        for (Deferrals d : mDeferredBroadcasts) {
-            d.dumpDebug(proto, fieldId);
-        }
-
-        for (int i = 0, size = mUser2Deferred.size(); i < size; i++) {
-            mUser2Deferred.valueAt(i).dumpDebug(proto, fieldId);
-        }
-    }
-
-    // ----------------------------------
-    // Dispatch & deferral management
-
-    public BroadcastRecord getActiveBroadcastLocked() {
-        return mCurrentBroadcast;
-    }
-
-    /**
-     * If there is a deferred broadcast that is being sent to an alarm target, return
-     * that one.  If there's no deferred alarm target broadcast but there is one
-     * that has reached the end of its deferral, return that.
-     *
-     * This stages the broadcast internally until it is retired, and returns that
-     * staged record if this is called repeatedly, until retireBroadcast(r) is called.
-     */
-    public BroadcastRecord getNextBroadcastLocked(final long now) {
-        if (mCurrentBroadcast != null) {
-            return mCurrentBroadcast;
-        }
-
-        BroadcastRecord next = null;
-
-        // Alarms in flight take precedence over everything else.  This queue
-        // will be non-empty only when the relevant policy is in force, but if
-        // policy has changed on the fly we still need to drain this before we
-        // settle into the legacy behavior.
-        if (!mAlarmQueue.isEmpty()) {
-            next = mAlarmQueue.remove(0);
-        }
-
-        // Next in precedence are deferred BOOT_COMPLETED broadcasts
-        if (next == null) {
-            next = dequeueDeferredBootCompletedBroadcast();
-        }
-
-        // Alarm-related deferrals are next in precedence...
-        if (next == null && !mAlarmDeferrals.isEmpty()) {
-            next = popLocked(mAlarmDeferrals);
-            if (DEBUG_BROADCAST_DEFERRAL && next != null) {
-                Slog.i(TAG, "Next broadcast from alarm targets: " + next);
-            }
-        }
-
-        final boolean someQueued = !mOrderedBroadcasts.isEmpty();
-
-        if (next == null && !mDeferredBroadcasts.isEmpty()) {
-            // A this point we're going to deliver either:
-            // 1. the next "overdue" deferral; or
-            // 2. the next ordinary ordered broadcast; *or*
-            // 3. the next not-yet-overdue deferral.
-
-            for (int i = 0; i < mDeferredBroadcasts.size(); i++) {
-                Deferrals d = mDeferredBroadcasts.get(i);
-                if (now < d.deferUntil && someQueued) {
-                    // stop looking when we haven't hit the next time-out boundary
-                    // but only if we have un-deferred broadcasts waiting,
-                    // otherwise we can deliver whatever deferred broadcast
-                    // is next available.
-                    break;
-                }
-
-                if (d.broadcasts.size() > 0) {
-                    next = d.broadcasts.remove(0);
-                    // apply deferral-interval decay policy and move this uid's
-                    // deferred broadcasts down in the delivery queue accordingly
-                    mDeferredBroadcasts.remove(i); // already 'd'
-                    d.deferredBy = calculateDeferral(d.deferredBy);
-                    d.deferUntil += d.deferredBy;
-                    insertLocked(mDeferredBroadcasts, d);
-                    if (DEBUG_BROADCAST_DEFERRAL) {
-                        Slog.i(TAG, "Next broadcast from deferrals " + next
-                                + ", deferUntil now " + d.deferUntil);
-                    }
-                    break;
-                }
-            }
-        }
-
-        if (next == null && someQueued) {
-            next = mOrderedBroadcasts.remove(0);
-            if (DEBUG_BROADCAST_DEFERRAL) {
-                Slog.i(TAG, "Next broadcast from main queue: " + next);
-            }
-        }
-
-        mCurrentBroadcast = next;
-        return next;
-    }
-
-    /**
-     * Called after the broadcast queue finishes processing the currently
-     * active broadcast (obtained by calling getNextBroadcastLocked()).
-     */
-    public void retireBroadcastLocked(final BroadcastRecord r) {
-        // ERROR if 'r' is not the active broadcast
-        if (r != mCurrentBroadcast) {
-            Slog.wtf(TAG, "Retiring broadcast " + r
-                    + " doesn't match current outgoing " + mCurrentBroadcast);
-        }
-        mCurrentBroadcast = null;
-    }
-
-    /**
-     * Called prior to broadcast dispatch to check whether the intended
-     * recipient is currently subject to deferral policy.
-     */
-    public boolean isDeferringLocked(final int uid) {
-        Deferrals d = findUidLocked(uid);
-        if (d != null && d.broadcasts.isEmpty()) {
-            // once we've caught up with deferred broadcasts to this uid
-            // and time has advanced sufficiently that we wouldn't be
-            // deferring newly-enqueued ones, we're back to normal policy.
-            if (SystemClock.uptimeMillis() >= d.deferUntil) {
-                if (DEBUG_BROADCAST_DEFERRAL) {
-                    Slog.i(TAG, "No longer deferring broadcasts to uid " + d.uid);
-                }
-                removeDeferral(d);
-                return false;
-            }
-        }
-        return (d != null);
-    }
-
-    /**
-     * Defer broadcasts for the given app.  If 'br' is non-null, this also makes
-     * sure that broadcast record is enqueued as the next upcoming broadcast for
-     * the app.
-     */
-    public void startDeferring(final int uid) {
-        synchronized (mLock) {
-            Deferrals d = findUidLocked(uid);
-
-            // If we're not yet tracking this app, set up that bookkeeping
-            if (d == null) {
-                // Start a new deferral
-                final long now = SystemClock.uptimeMillis();
-                d = new Deferrals(uid,
-                        now,
-                        mConstants.DEFERRAL,
-                        mAlarmUids.get(uid, 0));
-                if (DEBUG_BROADCAST_DEFERRAL) {
-                    Slog.i(TAG, "Now deferring broadcasts to " + uid
-                            + " until " + d.deferUntil);
-                }
-                // where it goes depends on whether it is coming into an alarm-related situation
-                if (d.alarmCount == 0) {
-                    // common case, put it in the ordinary priority queue
-                    insertLocked(mDeferredBroadcasts, d);
-                    scheduleDeferralCheckLocked(true);
-                } else {
-                    // alarm-related: strict order-encountered
-                    mAlarmDeferrals.add(d);
-                }
-            } else {
-                // We're already deferring, but something was slow again.  Reset the
-                // deferral decay progression.
-                d.deferredBy = mConstants.DEFERRAL;
-                if (DEBUG_BROADCAST_DEFERRAL) {
-                    Slog.i(TAG, "Uid " + uid + " slow again, deferral interval reset to "
-                            + d.deferredBy);
-                }
-            }
-        }
-    }
-
-    /**
-     * Key entry point when a broadcast about to be delivered is instead
-     * set aside for deferred delivery
-     */
-    public void addDeferredBroadcast(final int uid, BroadcastRecord br) {
-        if (DEBUG_BROADCAST_DEFERRAL) {
-            Slog.i(TAG, "Enqueuing deferred broadcast " + br);
-        }
-        synchronized (mLock) {
-            Deferrals d = findUidLocked(uid);
-            if (d == null) {
-                Slog.wtf(TAG, "Adding deferred broadcast but not tracking " + uid);
-            } else {
-                if (br == null) {
-                    Slog.wtf(TAG, "Deferring null broadcast to " + uid);
-                } else {
-                    br.deferred = true;
-                    d.add(br);
-                }
-            }
-        }
-    }
-
-    /**
-     * When there are deferred broadcasts, we need to make sure to recheck the
-     * dispatch queue when they come due.  Alarm-sensitive deferrals get dispatched
-     * aggressively, so we only need to use the ordinary deferrals timing to figure
-     * out when to recheck.
-     */
-    public void scheduleDeferralCheckLocked(boolean force) {
-        if ((force || !mRecheckScheduled) && !mDeferredBroadcasts.isEmpty()) {
-            final Deferrals d = mDeferredBroadcasts.get(0);
-            if (!d.broadcasts.isEmpty()) {
-                mHandler.removeCallbacks(mScheduleRunnable);
-                mHandler.postAtTime(mScheduleRunnable, d.deferUntil);
-                mRecheckScheduled = true;
-                if (DEBUG_BROADCAST_DEFERRAL) {
-                    Slog.i(TAG, "Scheduling deferred broadcast recheck at " + d.deferUntil);
-                }
-            }
-        }
-    }
-
-    /**
-     * Cancel all current deferrals; that is, make all currently-deferred broadcasts
-     * immediately deliverable.  Used by the wait-for-broadcast-idle mechanism.
-     */
-    public void cancelDeferralsLocked() {
-        zeroDeferralTimes(mAlarmDeferrals);
-        zeroDeferralTimes(mDeferredBroadcasts);
-    }
-
-    private static void zeroDeferralTimes(ArrayList<Deferrals> list) {
-        final int num = list.size();
-        for (int i = 0; i < num; i++) {
-            Deferrals d = list.get(i);
-            // Safe to do this in-place because it won't break ordering
-            d.deferUntil = d.deferredBy = 0;
-        }
-    }
-
-    // ----------------------------------
-
-    /**
-     * If broadcasts to this uid are being deferred, find the deferrals record about it.
-     * @return null if this uid's broadcasts are not being deferred
-     */
-    private Deferrals findUidLocked(final int uid) {
-        // The common case is that they it isn't also an alarm target...
-        Deferrals d = findUidLocked(uid, mDeferredBroadcasts);
-        // ...but if not there, also check alarm-prioritized deferrals
-        if (d == null) {
-            d = findUidLocked(uid, mAlarmDeferrals);
-        }
-        return d;
-    }
-
-    /**
-     * Remove the given deferral record from whichever queue it might be in at present
-     * @return true if the deferral was in fact found, false if this made no changes
-     */
-    private boolean removeDeferral(Deferrals d) {
-        boolean didRemove = mDeferredBroadcasts.remove(d);
-        if (!didRemove) {
-            didRemove = mAlarmDeferrals.remove(d);
-        }
-        return didRemove;
-    }
-
-    /**
-     * Find the deferrals record for the given uid in the given list
-     */
-    private static Deferrals findUidLocked(final int uid, ArrayList<Deferrals> list) {
-        final int numElements = list.size();
-        for (int i = 0; i < numElements; i++) {
-            Deferrals d = list.get(i);
-            if (uid == d.uid) {
-                return d;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Pop the next broadcast record from the head of the given deferrals list,
-     * if one exists.
-     */
-    private static BroadcastRecord popLocked(ArrayList<Deferrals> list) {
-        final Deferrals d = list.get(0);
-        return d.broadcasts.isEmpty() ? null : d.broadcasts.remove(0);
-    }
-
-    /**
-     * Insert the given Deferrals into the priority queue, sorted by defer-until milestone
-     */
-    private static void insertLocked(ArrayList<Deferrals> list, Deferrals d) {
-        // Simple linear search is appropriate here because we expect to
-        // have very few entries in the deferral lists (i.e. very few badly-
-        // behaving apps currently facing deferral)
-        int i;
-        final int numElements = list.size();
-        for (i = 0; i < numElements; i++) {
-            if (d.deferUntil < list.get(i).deferUntil) {
-                break;
-            }
-        }
-        list.add(i, d);
-    }
-
-    /**
-     * Calculate a new deferral time based on the previous time.  This should decay
-     * toward zero, though a small nonzero floor is an option.
-     */
-    private long calculateDeferral(long previous) {
-        return Math.max(mConstants.DEFERRAL_FLOOR,
-                (long) (previous * mConstants.DEFERRAL_DECAY_FACTOR));
-    }
-
-    // ----------------------------------
-
-    @NeverCompile
-    boolean dumpLocked(PrintWriter pw, String dumpPackage, String queueName,
-            SimpleDateFormat sdf) {
-        final Dumper dumper = new Dumper(pw, queueName, dumpPackage, sdf);
-        boolean printed = false;
-
-        dumper.setHeading("Currently in flight");
-        dumper.setLabel("In-Flight Ordered Broadcast");
-        if (mCurrentBroadcast != null) {
-            dumper.dump(mCurrentBroadcast);
-        } else {
-            pw.println("  (null)");
-        }
-        printed |= dumper.didPrint();
-
-        dumper.setHeading("Active alarm broadcasts");
-        dumper.setLabel("Active Alarm Broadcast");
-        for (BroadcastRecord br : mAlarmQueue) {
-            dumper.dump(br);
-        }
-        printed |= dumper.didPrint();
-
-        dumper.setHeading("Active ordered broadcasts");
-        dumper.setLabel("Active Ordered Broadcast");
-        for (Deferrals d : mAlarmDeferrals) {
-            d.dumpLocked(dumper);
-        }
-        for (BroadcastRecord br : mOrderedBroadcasts) {
-            dumper.dump(br);
-        }
-        printed |= dumper.didPrint();
-
-        dumper.setHeading("Deferred ordered broadcasts");
-        dumper.setLabel("Deferred Ordered Broadcast");
-        for (Deferrals d : mDeferredBroadcasts) {
-            d.dumpLocked(dumper);
-        }
-        printed |= dumper.didPrint();
-
-        dumper.setHeading("Deferred LOCKED_BOOT_COMPLETED broadcasts");
-        dumper.setLabel("Deferred LOCKED_BOOT_COMPLETED Broadcast");
-        for (int i = 0, size = mUser2Deferred.size(); i < size; i++) {
-            mUser2Deferred.valueAt(i).dump(dumper, Intent.ACTION_LOCKED_BOOT_COMPLETED);
-        }
-        printed |= dumper.didPrint();
-
-        dumper.setHeading("Deferred BOOT_COMPLETED broadcasts");
-        dumper.setLabel("Deferred BOOT_COMPLETED Broadcast");
-        for (int i = 0, size = mUser2Deferred.size(); i < size; i++) {
-            mUser2Deferred.valueAt(i).dump(dumper, Intent.ACTION_BOOT_COMPLETED);
-        }
-        printed |= dumper.didPrint();
-
-        return printed;
-    }
-}
diff --git a/services/core/java/com/android/server/am/BroadcastHistory.java b/services/core/java/com/android/server/am/BroadcastHistory.java
index 34658ca..d6e3d43 100644
--- a/services/core/java/com/android/server/am/BroadcastHistory.java
+++ b/services/core/java/com/android/server/am/BroadcastHistory.java
@@ -50,6 +50,11 @@
     }
 
     /**
+     * List of broadcasts in frozen processes that are yet to be enqueued.
+     */
+    private final ArrayList<BroadcastRecord> mFrozenBroadcasts = new ArrayList<>();
+
+    /**
      * List of broadcasts which are being delivered or yet to be delivered.
      */
     private final ArrayList<BroadcastRecord> mPendingBroadcasts = new ArrayList<>();
@@ -77,7 +82,12 @@
     final long[] mSummaryHistoryDispatchTime;
     final long[] mSummaryHistoryFinishTime;
 
+    void onBroadcastFrozenLocked(@NonNull BroadcastRecord r) {
+        mFrozenBroadcasts.add(r);
+    }
+
     void onBroadcastEnqueuedLocked(@NonNull BroadcastRecord r) {
+        mFrozenBroadcasts.remove(r);
         mPendingBroadcasts.add(r);
     }
 
@@ -101,7 +111,7 @@
         mSummaryHistoryNext = ringAdvance(mSummaryHistoryNext, 1, MAX_BROADCAST_SUMMARY_HISTORY);
     }
 
-    private final int ringAdvance(int x, final int increment, final int ringSize) {
+    private int ringAdvance(int x, final int increment, final int ringSize) {
         x += increment;
         if (x < 0) return (ringSize - 1);
         else if (x >= ringSize) return 0;
@@ -114,6 +124,10 @@
             final BroadcastRecord r = mPendingBroadcasts.get(i);
             r.dumpDebug(proto, BroadcastQueueProto.PENDING_BROADCASTS);
         }
+        for (int i = 0; i < mFrozenBroadcasts.size(); ++i) {
+            final BroadcastRecord r = mFrozenBroadcasts.get(i);
+            r.dumpDebug(proto, BroadcastQueueProto.FROZEN_BROADCASTS);
+        }
 
         int lastIndex = mHistoryNext;
         int ringIndex = lastIndex;
@@ -151,16 +165,8 @@
     public boolean dumpLocked(@NonNull PrintWriter pw, @Nullable String dumpPackage,
             @NonNull String queueName, @NonNull SimpleDateFormat sdf,
             boolean dumpAll, boolean needSep) {
-        pw.println("  Pending broadcasts:");
-        if (mPendingBroadcasts.isEmpty()) {
-            pw.println("    <empty>");
-        } else {
-            for (int idx = mPendingBroadcasts.size() - 1; idx >= 0; --idx) {
-                final BroadcastRecord r = mPendingBroadcasts.get(idx);
-                pw.print("  Broadcast #"); pw.print(idx); pw.println(":");
-                r.dump(pw, "    ", sdf);
-            }
-        }
+        dumpBroadcastList(pw, sdf, mFrozenBroadcasts, "Frozen");
+        dumpBroadcastList(pw, sdf, mPendingBroadcasts, "Pending");
 
         int i;
         boolean printed = false;
@@ -268,4 +274,18 @@
         }
         return needSep;
     }
+
+    private void dumpBroadcastList(@NonNull PrintWriter pw, @NonNull SimpleDateFormat sdf,
+            @NonNull ArrayList<BroadcastRecord> broadcasts, @NonNull String flavor) {
+        pw.print("  "); pw.print(flavor); pw.println(" broadcasts:");
+        if (broadcasts.isEmpty()) {
+            pw.println("    <empty>");
+        } else {
+            for (int idx = broadcasts.size() - 1; idx >= 0; --idx) {
+                final BroadcastRecord r = broadcasts.get(idx);
+                pw.print(flavor); pw.print("  broadcast #"); pw.print(idx); pw.println(":");
+                r.dump(pw, "    ", sdf);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index d1c8c30..e98e1ba 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -44,6 +44,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayDeque;
+import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.Objects;
 
@@ -181,6 +182,12 @@
     private boolean mActiveWasStopped;
 
     /**
+     * Flag indicating that the currently active broadcast is being dispatched
+     * to a package that was never launched before.
+     */
+    private boolean mActiveFirstLaunch;
+
+    /**
      * Number of consecutive urgent broadcasts that have been dispatched
      * since the last non-urgent dispatch.
      */
@@ -233,6 +240,11 @@
      */
     private long mForcedDelayedDurationMs;
 
+    /**
+     * List of outgoing broadcasts from a freezable process.
+     */
+    private final ArrayList<BroadcastRecord> mOutgoingBroadcasts = new ArrayList<>();
+
     public BroadcastProcessQueue(@NonNull BroadcastConstants constants,
             @NonNull String processName, int uid) {
         this.constants = Objects.requireNonNull(constants);
@@ -250,6 +262,21 @@
         }
     }
 
+    public void enqueueOutgoingBroadcast(@NonNull BroadcastRecord record) {
+        mOutgoingBroadcasts.add(record);
+    }
+
+    public int getOutgoingBroadcastCount() {
+        return mOutgoingBroadcasts.size();
+    }
+
+    public void enqueueOutgoingBroadcasts(@NonNull BroadcastRecordConsumer consumer) {
+        for (int i = 0; i < mOutgoingBroadcasts.size(); ++i) {
+            consumer.accept(mOutgoingBroadcasts.get(i));
+        }
+        mOutgoingBroadcasts.clear();
+    }
+
     /**
      * Enqueue the given broadcast to be dispatched to this process at some
      * future point in time. The target receiver is indicated by the given index
@@ -276,6 +303,11 @@
                 && record.getDeliveryGroupPolicy() == BroadcastOptions.DELIVERY_GROUP_POLICY_ALL) {
             final BroadcastRecord replacedBroadcastRecord = replaceBroadcast(record, recordIndex);
             if (replacedBroadcastRecord != null) {
+                if (mLastDeferredStates && shouldBeDeferred()
+                        && (record.getDeliveryState(recordIndex)
+                                == BroadcastRecord.DELIVERY_PENDING)) {
+                    deferredStatesApplyConsumer.accept(record, recordIndex);
+                }
                 return replacedBroadcastRecord;
             }
         }
@@ -381,8 +413,8 @@
     }
 
     /**
-     * Functional interface that tests a {@link BroadcastRecord} that has been
-     * previously enqueued in {@link BroadcastProcessQueue}.
+     * Functional interface that tests a {@link BroadcastRecord} and an index in the
+     * {@link BroadcastRecord} that has been previously enqueued in {@link BroadcastProcessQueue}.
      */
     @FunctionalInterface
     public interface BroadcastPredicate {
@@ -390,8 +422,8 @@
     }
 
     /**
-     * Functional interface that consumes a {@link BroadcastRecord} that has
-     * been previously enqueued in {@link BroadcastProcessQueue}.
+     * Functional interface that consumes a {@link BroadcastRecord} and an index in the
+     * {@link BroadcastRecord} that has been previously enqueued in {@link BroadcastProcessQueue}.
      */
     @FunctionalInterface
     public interface BroadcastConsumer {
@@ -399,6 +431,15 @@
     }
 
     /**
+     * Functional interface that consumes a {@link BroadcastRecord} that has
+     * been previously enqueued in {@link BroadcastProcessQueue}.
+     */
+    @FunctionalInterface
+    public interface BroadcastRecordConsumer {
+        void accept(@NonNull BroadcastRecord r);
+    }
+
+    /**
      * Invoke given consumer for any broadcasts matching given predicate. If
      * requested, matching broadcasts will also be removed from this queue.
      * <p>
@@ -591,6 +632,10 @@
         mActiveWasStopped = activeWasStopped;
     }
 
+    public void setActiveFirstLaunch(boolean activeFirstLaunch) {
+        mActiveFirstLaunch = activeFirstLaunch;
+    }
+
     public boolean getActiveViaColdStart() {
         return mActiveViaColdStart;
     }
@@ -599,6 +644,10 @@
         return mActiveWasStopped;
     }
 
+    public boolean getActiveFirstLaunch() {
+        return mActiveFirstLaunch;
+    }
+
     /**
      * Get package name of the first application loaded into this process.
      */
@@ -769,6 +818,10 @@
         return mActiveIndex;
     }
 
+    public boolean isOutgoingEmpty() {
+        return mOutgoingBroadcasts.isEmpty();
+    }
+
     public boolean isEmpty() {
         return mPending.isEmpty() && mPendingUrgent.isEmpty() && mPendingOffload.isEmpty();
     }
@@ -1438,7 +1491,7 @@
 
     @NeverCompile
     public void dumpLocked(@UptimeMillisLong long now, @NonNull IndentingPrintWriter pw) {
-        if ((mActive == null) && isEmpty()) return;
+        if ((mActive == null) && isEmpty() && isOutgoingEmpty()) return;
 
         pw.print(toShortString());
         pw.print(" ");
@@ -1449,6 +1502,12 @@
         dumpProcessState(pw);
         dumpBroadcastCounts(pw);
 
+        if (!mOutgoingBroadcasts.isEmpty()) {
+            for (int i = 0; i < mOutgoingBroadcasts.size(); ++i) {
+                dumpOutgoingRecord(now, pw, mOutgoingBroadcasts.get(i));
+            }
+        }
+
         if (mActive != null) {
             dumpRecord("ACTIVE", now, pw, mActive, mActiveIndex);
         }
@@ -1520,6 +1579,15 @@
     }
 
     @NeverCompile
+    private void dumpOutgoingRecord(@UptimeMillisLong long now,
+            @NonNull IndentingPrintWriter pw, @NonNull BroadcastRecord record) {
+        pw.print("OUTGOING ");
+        TimeUtils.formatDuration(record.enqueueTime, now, pw);
+        pw.print(' ');
+        pw.println(record.toShortString());
+    }
+
+    @NeverCompile
     private void dumpRecord(@Nullable String flavor, @UptimeMillisLong long now,
             @NonNull IndentingPrintWriter pw, @NonNull BroadcastRecord record, int recordIndex) {
         TimeUtils.formatDuration(record.enqueueTime, now, pw);
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
deleted file mode 100644
index 3c56752..0000000
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ /dev/null
@@ -1,1983 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-package com.android.server.am;
-
-import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
-import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
-import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED;
-import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
-import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
-import static android.text.TextUtils.formatSimple;
-
-import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED;
-import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
-import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME;
-import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
-import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.ApplicationExitInfo;
-import android.app.BroadcastOptions;
-import android.app.IApplicationThread;
-import android.app.usage.UsageEvents.Event;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.IIntentReceiver;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.UserInfo;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerExemptionManager.ReasonCode;
-import android.os.PowerExemptionManager.TempAllowListType;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.EventLog;
-import android.util.IndentingPrintWriter;
-import android.util.Slog;
-import android.util.SparseIntArray;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.os.TimeoutRecord;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.LocalServices;
-import com.android.server.pm.UserJourneyLogger;
-import com.android.server.pm.UserManagerInternal;
-
-import dalvik.annotation.optimization.NeverCompile;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Set;
-import java.util.function.BooleanSupplier;
-
-/**
- * BROADCASTS
- *
- * We keep three broadcast queues and associated bookkeeping, one for those at
- * foreground priority, and one for normal (background-priority) broadcasts, and one to
- * offload special broadcasts that we know take a long time, such as BOOT_COMPLETED.
- */
-public class BroadcastQueueImpl extends BroadcastQueue {
-    private static final String TAG_MU = TAG + POSTFIX_MU;
-    private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
-
-    final BroadcastConstants mConstants;
-
-    /**
-     * If true, we can delay broadcasts while waiting services to finish in the previous
-     * receiver's process.
-     */
-    final boolean mDelayBehindServices;
-
-    final int mSchedGroup;
-
-    /**
-     * Lists of all active broadcasts that are to be executed immediately
-     * (without waiting for another broadcast to finish).  Currently this only
-     * contains broadcasts to registered receivers, to avoid spinning up
-     * a bunch of processes to execute IntentReceiver components.  Background-
-     * and foreground-priority broadcasts are queued separately.
-     */
-    final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<>();
-
-    /**
-     * Tracking of the ordered broadcast queue, including deferral policy and alarm
-     * prioritization.
-     */
-    final BroadcastDispatcher mDispatcher;
-
-    /**
-     * Refcounting for completion callbacks of split/deferred broadcasts.  The key
-     * is an opaque integer token assigned lazily when a broadcast is first split
-     * into multiple BroadcastRecord objects.
-     */
-    final SparseIntArray mSplitRefcounts = new SparseIntArray();
-    private int mNextToken = 0;
-
-    /**
-     * Set when we current have a BROADCAST_INTENT_MSG in flight.
-     */
-    boolean mBroadcastsScheduled = false;
-
-    /**
-     * True if we have a pending unexpired BROADCAST_TIMEOUT_MSG posted to our handler.
-     */
-    boolean mPendingBroadcastTimeoutMessage;
-
-    /**
-     * Intent broadcasts that we have tried to start, but are
-     * waiting for the application's process to be created.  We only
-     * need one per scheduling class (instead of a list) because we always
-     * process broadcasts one at a time, so no others can be started while
-     * waiting for this one.
-     */
-    BroadcastRecord mPendingBroadcast = null;
-
-    /**
-     * The receiver index that is pending, to restart the broadcast if needed.
-     */
-    int mPendingBroadcastRecvIndex;
-
-    static final int BROADCAST_INTENT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG;
-    static final int BROADCAST_TIMEOUT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG + 1;
-
-    // log latency metrics for ordered broadcasts during BOOT_COMPLETED processing
-    boolean mLogLatencyMetrics = true;
-
-    final BroadcastHandler mHandler;
-
-    private final class BroadcastHandler extends Handler {
-        public BroadcastHandler(Looper looper) {
-            super(looper, null);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case BROADCAST_INTENT_MSG: {
-                    if (DEBUG_BROADCAST) Slog.v(
-                            TAG_BROADCAST, "Received BROADCAST_INTENT_MSG ["
-                            + mQueueName + "]");
-                    processNextBroadcast(true);
-                } break;
-                case BROADCAST_TIMEOUT_MSG: {
-                    synchronized (mService) {
-                        broadcastTimeoutLocked(true);
-                    }
-                } break;
-            }
-        }
-    }
-
-    BroadcastQueueImpl(ActivityManagerService service, Handler handler,
-            String name, BroadcastConstants constants, boolean allowDelayBehindServices,
-            int schedGroup) {
-        this(service, handler, name, constants, new BroadcastSkipPolicy(service),
-                new BroadcastHistory(constants), allowDelayBehindServices, schedGroup);
-    }
-
-    BroadcastQueueImpl(ActivityManagerService service, Handler handler,
-            String name, BroadcastConstants constants, BroadcastSkipPolicy skipPolicy,
-            BroadcastHistory history, boolean allowDelayBehindServices, int schedGroup) {
-        super(service, handler, name, skipPolicy, history);
-        mHandler = new BroadcastHandler(handler.getLooper());
-        mConstants = constants;
-        mDelayBehindServices = allowDelayBehindServices;
-        mSchedGroup = schedGroup;
-        mDispatcher = new BroadcastDispatcher(this, mConstants, mHandler, mService);
-    }
-
-    public void start(ContentResolver resolver) {
-        mDispatcher.start();
-        mConstants.startObserving(mHandler, resolver);
-    }
-
-    public boolean isDelayBehindServices() {
-        return mDelayBehindServices;
-    }
-
-    public BroadcastRecord getPendingBroadcastLocked() {
-        return mPendingBroadcast;
-    }
-
-    public BroadcastRecord getActiveBroadcastLocked() {
-        return mDispatcher.getActiveBroadcastLocked();
-    }
-
-    public int getPreferredSchedulingGroupLocked(ProcessRecord app) {
-        final BroadcastRecord active = getActiveBroadcastLocked();
-        if (active != null && active.curApp == app) {
-            return mSchedGroup;
-        }
-        final BroadcastRecord pending = getPendingBroadcastLocked();
-        if (pending != null && pending.curApp == app) {
-            return mSchedGroup;
-        }
-        return ProcessList.SCHED_GROUP_UNDEFINED;
-    }
-
-    public void enqueueBroadcastLocked(BroadcastRecord r) {
-        r.applySingletonPolicy(mService);
-
-        final boolean replacePending = (r.intent.getFlags()
-                & Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
-
-        // Ordered broadcasts obviously need to be dispatched in serial order,
-        // but this implementation expects all manifest receivers to also be
-        // dispatched in a serial fashion
-        boolean serialDispatch = r.ordered;
-        if (!serialDispatch) {
-            final int N = (r.receivers != null) ? r.receivers.size() : 0;
-            for (int i = 0; i < N; i++) {
-                if (r.receivers.get(i) instanceof ResolveInfo) {
-                    serialDispatch = true;
-                    break;
-                }
-            }
-        }
-
-        if (serialDispatch) {
-            final BroadcastRecord oldRecord =
-                    replacePending ? replaceOrderedBroadcastLocked(r) : null;
-            if (oldRecord != null) {
-                // Replaced, fire the result-to receiver.
-                if (oldRecord.resultTo != null) {
-                    try {
-                        oldRecord.mIsReceiverAppRunning = true;
-                        performReceiveLocked(oldRecord, oldRecord.resultToApp, oldRecord.resultTo,
-                                oldRecord.intent,
-                                Activity.RESULT_CANCELED, null, null,
-                                false, false, oldRecord.shareIdentity, oldRecord.userId,
-                                oldRecord.callingUid, r.callingUid, r.callerPackage,
-                                SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0, 0,
-                                oldRecord.resultToApp != null
-                                        ? oldRecord.resultToApp.mState.getCurProcState()
-                                        : ActivityManager.PROCESS_STATE_UNKNOWN);
-                    } catch (RemoteException e) {
-                        Slog.w(TAG, "Failure ["
-                                + mQueueName + "] sending broadcast result of "
-                                + oldRecord.intent, e);
-
-                    }
-                }
-            } else {
-                enqueueOrderedBroadcastLocked(r);
-                scheduleBroadcastsLocked();
-            }
-        } else {
-            final boolean replaced = replacePending
-                    && (replaceParallelBroadcastLocked(r) != null);
-            // Note: We assume resultTo is null for non-ordered broadcasts.
-            if (!replaced) {
-                enqueueParallelBroadcastLocked(r);
-                scheduleBroadcastsLocked();
-            }
-        }
-    }
-
-    public void enqueueParallelBroadcastLocked(BroadcastRecord r) {
-        r.enqueueClockTime = System.currentTimeMillis();
-        r.enqueueTime = SystemClock.uptimeMillis();
-        r.enqueueRealTime = SystemClock.elapsedRealtime();
-        mParallelBroadcasts.add(r);
-        enqueueBroadcastHelper(r);
-    }
-
-    public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
-        r.enqueueClockTime = System.currentTimeMillis();
-        r.enqueueTime = SystemClock.uptimeMillis();
-        r.enqueueRealTime = SystemClock.elapsedRealtime();
-        mDispatcher.enqueueOrderedBroadcastLocked(r);
-        enqueueBroadcastHelper(r);
-    }
-
-    /**
-     * Don't call this method directly; call enqueueParallelBroadcastLocked or
-     * enqueueOrderedBroadcastLocked.
-     */
-    private void enqueueBroadcastHelper(BroadcastRecord r) {
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
-                System.identityHashCode(r));
-        }
-    }
-
-    /**
-     * Find the same intent from queued parallel broadcast, replace with a new one and return
-     * the old one.
-     */
-    public final BroadcastRecord replaceParallelBroadcastLocked(BroadcastRecord r) {
-        return replaceBroadcastLocked(mParallelBroadcasts, r, "PARALLEL");
-    }
-
-    /**
-     * Find the same intent from queued ordered broadcast, replace with a new one and return
-     * the old one.
-     */
-    public final BroadcastRecord replaceOrderedBroadcastLocked(BroadcastRecord r) {
-        return mDispatcher.replaceBroadcastLocked(r, "ORDERED");
-    }
-
-    private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> queue,
-            BroadcastRecord r, String typeForLogging) {
-        final Intent intent = r.intent;
-        for (int i = queue.size() - 1; i >= 0; i--) {
-            final BroadcastRecord old = queue.get(i);
-            if (old.userId == r.userId && intent.filterEquals(old.intent)) {
-                if (DEBUG_BROADCAST) {
-                    Slog.v(TAG_BROADCAST, "***** DROPPING "
-                            + typeForLogging + " [" + mQueueName + "]: " + intent);
-                }
-                queue.set(i, r);
-                return old;
-            }
-        }
-        return null;
-    }
-
-    private final void processCurBroadcastLocked(BroadcastRecord r,
-            ProcessRecord app) throws RemoteException {
-        if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                "Process cur broadcast " + r + " for app " + app);
-        final IApplicationThread thread = app.getThread();
-        if (thread == null) {
-            throw new RemoteException();
-        }
-        if (app.isInFullBackup()) {
-            skipReceiverLocked(r);
-            return;
-        }
-
-        r.curApp = app;
-        r.curAppLastProcessState = app.mState.getCurProcState();
-        final ProcessReceiverRecord prr = app.mReceivers;
-        prr.addCurReceiver(r);
-        app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
-        // Don't bump its LRU position if it's in the background restricted.
-        if (mService.mInternal.getRestrictionLevel(app.info.packageName, app.userId)
-                < RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
-            mService.updateLruProcessLocked(app, false, null);
-        }
-        // Make sure the oom adj score is updated before delivering the broadcast.
-        // Force an update, even if there are other pending requests, overall it still saves time,
-        // because time(updateOomAdj(N apps)) <= N * time(updateOomAdj(1 app)).
-        mService.enqueueOomAdjTargetLocked(app);
-        mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
-
-        // Tell the application to launch this receiver.
-        maybeReportBroadcastDispatchedEventLocked(r, r.curReceiver.applicationInfo.uid);
-        r.intent.setComponent(r.curComponent);
-
-        // See if we need to delay the freezer based on BroadcastOptions
-        if (r.options != null
-                && r.options.getTemporaryAppAllowlistDuration() > 0
-                && r.options.getTemporaryAppAllowlistType()
-                    == TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED) {
-            mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(app,
-                    CachedAppOptimizer.UNFREEZE_REASON_START_RECEIVER,
-                    r.options.getTemporaryAppAllowlistDuration());
-        }
-
-        boolean started = false;
-        try {
-            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
-                    "Delivering to component " + r.curComponent
-                    + ": " + r);
-            mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
-                                      PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
-            final boolean assumeDelivered = false;
-            thread.scheduleReceiver(
-                    prepareReceiverIntent(r.intent, r.curFilteredExtras),
-                    r.curReceiver, null /* compatInfo (unused but need to keep method signature) */,
-                    r.resultCode, r.resultData, r.resultExtras, r.ordered, assumeDelivered,
-                    r.userId, r.shareIdentity ? r.callingUid : Process.INVALID_UID,
-                    app.mState.getReportedProcState(),
-                    r.shareIdentity ? r.callerPackage : null);
-            if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                    "Process cur broadcast " + r + " DELIVERED for app " + app);
-            started = true;
-        } finally {
-            if (!started) {
-                if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                        "Process cur broadcast " + r + ": NOT STARTED!");
-                r.curApp = null;
-                r.curAppLastProcessState = ActivityManager.PROCESS_STATE_UNKNOWN;
-                prr.removeCurReceiver(r);
-            }
-        }
-
-        // if something bad happens here, launch the app and try again
-        if (app.isKilled()) {
-            throw new RemoteException("app gets killed during broadcasting");
-        }
-    }
-
-    /**
-     * Called by ActivityManagerService to notify that the uid has process started, if there is any
-     * deferred BOOT_COMPLETED broadcast, the BroadcastDispatcher can dispatch the broadcast now.
-     * @param uid
-     */
-    public void updateUidReadyForBootCompletedBroadcastLocked(int uid) {
-        mDispatcher.updateUidReadyForBootCompletedBroadcastLocked(uid);
-        scheduleBroadcastsLocked();
-    }
-
-    public boolean onApplicationAttachedLocked(ProcessRecord app)
-            throws BroadcastDeliveryFailedException {
-        updateUidReadyForBootCompletedBroadcastLocked(app.uid);
-
-        if (mPendingBroadcast != null && mPendingBroadcast.curApp == app) {
-            return sendPendingBroadcastsLocked(app);
-        } else {
-            return false;
-        }
-    }
-
-    public void onApplicationTimeoutLocked(ProcessRecord app) {
-        skipCurrentOrPendingReceiverLocked(app);
-    }
-
-    public void onApplicationProblemLocked(ProcessRecord app) {
-        skipCurrentOrPendingReceiverLocked(app);
-    }
-
-    public void onApplicationCleanupLocked(ProcessRecord app) {
-        skipCurrentOrPendingReceiverLocked(app);
-    }
-
-    public void onProcessFreezableChangedLocked(ProcessRecord app) {
-        // Not supported; ignore
-    }
-
-    public boolean sendPendingBroadcastsLocked(ProcessRecord app)
-            throws BroadcastDeliveryFailedException {
-        boolean didSomething = false;
-        final BroadcastRecord br = mPendingBroadcast;
-        if (br != null && br.curApp.getPid() > 0 && br.curApp.getPid() == app.getPid()) {
-            if (br.curApp != app) {
-                Slog.e(TAG, "App mismatch when sending pending broadcast to "
-                        + app.processName + ", intended target is " + br.curApp.processName);
-                return false;
-            }
-            try {
-                mPendingBroadcast = null;
-                br.mIsReceiverAppRunning = false;
-                processCurBroadcastLocked(br, app);
-                didSomething = true;
-            } catch (Exception e) {
-                Slog.w(TAG, "Exception in new application when starting receiver "
-                        + br.curComponent.flattenToShortString(), e);
-                logBroadcastReceiverDiscardLocked(br);
-                finishReceiverLocked(br, br.resultCode, br.resultData,
-                        br.resultExtras, br.resultAbort, false);
-                scheduleBroadcastsLocked();
-                // We need to reset the state if we failed to start the receiver.
-                br.state = BroadcastRecord.IDLE;
-                throw new BroadcastDeliveryFailedException(e);
-            }
-        }
-        return didSomething;
-    }
-
-    // Skip the current receiver, if any, that is in flight to the given process
-    public boolean skipCurrentOrPendingReceiverLocked(ProcessRecord app) {
-        BroadcastRecord r = null;
-        final BroadcastRecord curActive = mDispatcher.getActiveBroadcastLocked();
-        if (curActive != null && curActive.curApp == app) {
-            // confirmed: the current active broadcast is to the given app
-            r = curActive;
-        }
-
-        // If the current active broadcast isn't this BUT we're waiting for
-        // mPendingBroadcast to spin up the target app, that's what we use.
-        if (r == null && mPendingBroadcast != null && mPendingBroadcast.curApp == app) {
-            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
-                    "[" + mQueueName + "] skip & discard pending app " + r);
-            r = mPendingBroadcast;
-        }
-
-        if (r != null) {
-            skipReceiverLocked(r);
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    private void skipReceiverLocked(BroadcastRecord r) {
-        logBroadcastReceiverDiscardLocked(r);
-        finishReceiverLocked(r, r.resultCode, r.resultData,
-                r.resultExtras, r.resultAbort, false);
-        scheduleBroadcastsLocked();
-    }
-
-    public void scheduleBroadcastsLocked() {
-        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Schedule broadcasts ["
-                + mQueueName + "]: current="
-                + mBroadcastsScheduled);
-
-        if (mBroadcastsScheduled) {
-            return;
-        }
-        mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
-        mBroadcastsScheduled = true;
-    }
-
-    public BroadcastRecord getMatchingOrderedReceiver(ProcessRecord app) {
-        BroadcastRecord br = mDispatcher.getActiveBroadcastLocked();
-        if (br == null) {
-            Slog.w(TAG_BROADCAST, "getMatchingOrderedReceiver [" + mQueueName
-                    + "] no active broadcast");
-            return null;
-        }
-        if (br.curApp != app) {
-            Slog.w(TAG_BROADCAST, "getMatchingOrderedReceiver [" + mQueueName
-                    + "] active broadcast " + br.curApp + " doesn't match " + app);
-            return null;
-        }
-        return br;
-    }
-
-    // > 0 only, no worry about "eventual" recycling
-    private int nextSplitTokenLocked() {
-        int next = mNextToken + 1;
-        if (next <= 0) {
-            next = 1;
-        }
-        mNextToken = next;
-        return next;
-    }
-
-    private void postActivityStartTokenRemoval(ProcessRecord app, BroadcastRecord r) {
-        // the receiver had run for less than allowed bg activity start timeout,
-        // so allow the process to still start activities from bg for some more time
-        String msgToken = (app.toShortString() + r.toString()).intern();
-        // first, if there exists a past scheduled request to remove this token, drop
-        // that request - we don't want the token to be swept from under our feet...
-        mHandler.removeCallbacksAndMessages(msgToken);
-        // ...then schedule the removal of the token after the extended timeout
-        mHandler.postAtTime(() -> {
-            synchronized (mService) {
-                app.removeBackgroundStartPrivileges(r);
-            }
-        }, msgToken, (r.receiverTime + mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT));
-    }
-
-    public boolean finishReceiverLocked(ProcessRecord app, int resultCode,
-            String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) {
-        final BroadcastRecord r = getMatchingOrderedReceiver(app);
-        if (r != null) {
-            return finishReceiverLocked(r, resultCode,
-                    resultData, resultExtras, resultAbort, waitForServices);
-        } else {
-            return false;
-        }
-    }
-
-    public boolean finishReceiverLocked(BroadcastRecord r, int resultCode,
-            String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) {
-        final int state = r.state;
-        final ActivityInfo receiver = r.curReceiver;
-        final long finishTime = SystemClock.uptimeMillis();
-        final long elapsed = finishTime - r.receiverTime;
-        r.state = BroadcastRecord.IDLE;
-        final int curIndex = r.nextReceiver - 1;
-
-        final int packageState = r.mWasReceiverAppStopped
-                ? SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED
-                : SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
-
-        if (curIndex >= 0 && curIndex < r.receivers.size() && r.curApp != null) {
-            final Object curReceiver = r.receivers.get(curIndex);
-            FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, r.curApp.uid,
-                    r.callingUid == -1 ? Process.SYSTEM_UID : r.callingUid,
-                    r.intent.getAction(),
-                    curReceiver instanceof BroadcastFilter
-                    ? BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME
-                    : BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST,
-                    r.mIsReceiverAppRunning
-                    ? BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM
-                    : BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD,
-                    r.dispatchTime - r.enqueueTime,
-                    r.receiverTime - r.dispatchTime,
-                    finishTime - r.receiverTime,
-                    packageState,
-                    r.curApp.info.packageName,
-                    r.callerPackage,
-                    r.calculateTypeForLogging(),
-                    r.getDeliveryGroupPolicy(),
-                    r.intent.getFlags(),
-                    BroadcastRecord.getReceiverPriority(curReceiver),
-                    r.callerProcState,
-                    r.curAppLastProcessState);
-        }
-        if (state == BroadcastRecord.IDLE) {
-            Slog.w(TAG_BROADCAST, "finishReceiver [" + mQueueName + "] called but state is IDLE");
-        }
-        if (r.mBackgroundStartPrivileges.allowsAny() && r.curApp != null) {
-            if (elapsed > mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT) {
-                // if the receiver has run for more than allowed bg activity start timeout,
-                // just remove the token for this process now and we're done
-                r.curApp.removeBackgroundStartPrivileges(r);
-            } else {
-                // It gets more time; post the removal to happen at the appropriate moment
-                postActivityStartTokenRemoval(r.curApp, r);
-            }
-        }
-        // If we're abandoning this broadcast before any receivers were actually spun up,
-        // nextReceiver is zero; in which case time-to-process bookkeeping doesn't apply.
-        if (r.nextReceiver > 0) {
-            r.terminalTime[r.nextReceiver - 1] = finishTime;
-        }
-
-        // if this receiver was slow, impose deferral policy on the app.  This will kick in
-        // when processNextBroadcastLocked() next finds this uid as a receiver identity.
-        if (!r.timeoutExempt) {
-            // r.curApp can be null if finish has raced with process death - benign
-            // edge case, and we just ignore it because we're already cleaning up
-            // as expected.
-            if (r.curApp != null
-                    && mConstants.SLOW_TIME > 0 && elapsed > mConstants.SLOW_TIME) {
-                // Core system packages are exempt from deferral policy
-                if (!UserHandle.isCore(r.curApp.uid)) {
-                    if (DEBUG_BROADCAST_DEFERRAL) {
-                        Slog.i(TAG_BROADCAST, "Broadcast receiver " + (r.nextReceiver - 1)
-                                + " was slow: " + receiver + " br=" + r);
-                    }
-                    mDispatcher.startDeferring(r.curApp.uid);
-                } else {
-                    if (DEBUG_BROADCAST_DEFERRAL) {
-                        Slog.i(TAG_BROADCAST, "Core uid " + r.curApp.uid
-                                + " receiver was slow but not deferring: "
-                                + receiver + " br=" + r);
-                    }
-                }
-            }
-        } else {
-            if (DEBUG_BROADCAST_DEFERRAL) {
-                Slog.i(TAG_BROADCAST, "Finished broadcast " + r.intent.getAction()
-                        + " is exempt from deferral policy");
-            }
-        }
-
-        r.intent.setComponent(null);
-        if (r.curApp != null && r.curApp.mReceivers.hasCurReceiver(r)) {
-            r.curApp.mReceivers.removeCurReceiver(r);
-            mService.enqueueOomAdjTargetLocked(r.curApp);
-        }
-        if (r.curFilter != null) {
-            r.curFilter.receiverList.curBroadcast = null;
-        }
-        r.curFilter = null;
-        r.curReceiver = null;
-        r.curApp = null;
-        r.curAppLastProcessState = ActivityManager.PROCESS_STATE_UNKNOWN;
-        r.curFilteredExtras = null;
-        r.mWasReceiverAppStopped = false;
-        mPendingBroadcast = null;
-
-        r.resultCode = resultCode;
-        r.resultData = resultData;
-        r.resultExtras = resultExtras;
-        if (resultAbort && (r.intent.getFlags()&Intent.FLAG_RECEIVER_NO_ABORT) == 0) {
-            r.resultAbort = resultAbort;
-        } else {
-            r.resultAbort = false;
-        }
-
-        // If we want to wait behind services *AND* we're finishing the head/
-        // active broadcast on its queue
-        if (waitForServices && r.curComponent != null && r.queue.isDelayBehindServices()
-                && ((BroadcastQueueImpl) r.queue).getActiveBroadcastLocked() == r) {
-            ActivityInfo nextReceiver;
-            if (r.nextReceiver < r.receivers.size()) {
-                Object obj = r.receivers.get(r.nextReceiver);
-                nextReceiver = (obj instanceof ActivityInfo) ? (ActivityInfo)obj : null;
-            } else {
-                nextReceiver = null;
-            }
-            // Don't do this if the next receive is in the same process as the current one.
-            if (receiver == null || nextReceiver == null
-                    || receiver.applicationInfo.uid != nextReceiver.applicationInfo.uid
-                    || !receiver.processName.equals(nextReceiver.processName)) {
-                // In this case, we are ready to process the next receiver for the current broadcast,
-                // but are on a queue that would like to wait for services to finish before moving
-                // on.  If there are background services currently starting, then we will go into a
-                // special state where we hold off on continuing this broadcast until they are done.
-                if (mService.mServices.hasBackgroundServicesLocked(r.userId)) {
-                    Slog.i(TAG, "Delay finish: " + r.curComponent.flattenToShortString());
-                    r.state = BroadcastRecord.WAITING_SERVICES;
-                    return false;
-                }
-            }
-        }
-
-        r.curComponent = null;
-
-        // We will process the next receiver right now if this is finishing
-        // an app receiver (which is always asynchronous) or after we have
-        // come back from calling a receiver.
-        final boolean doNext = (state == BroadcastRecord.APP_RECEIVE)
-                || (state == BroadcastRecord.CALL_DONE_RECEIVE);
-        if (doNext) {
-            processNextBroadcastLocked(/* fromMsg= */ false, /* skipOomAdj= */ true);
-        }
-        return doNext;
-    }
-
-    public void backgroundServicesFinishedLocked(int userId) {
-        BroadcastRecord br = mDispatcher.getActiveBroadcastLocked();
-        if (br != null) {
-            if (br.userId == userId && br.state == BroadcastRecord.WAITING_SERVICES) {
-                Slog.i(TAG, "Resuming delayed broadcast");
-                br.curComponent = null;
-                br.state = BroadcastRecord.IDLE;
-                processNextBroadcastLocked(false, false);
-            }
-        }
-    }
-
-    public void performReceiveLocked(BroadcastRecord r, ProcessRecord app, IIntentReceiver receiver,
-            Intent intent, int resultCode, String data, Bundle extras,
-            boolean ordered, boolean sticky, boolean shareIdentity, int sendingUser,
-            int receiverUid, int callingUid, String callingPackage,
-            long dispatchDelay, long receiveDelay, int priority,
-            int receiverProcessState) throws RemoteException {
-        // If the broadcaster opted-in to sharing their identity, then expose package visibility for
-        // the receiver.
-        if (shareIdentity) {
-            mService.mPackageManagerInt.grantImplicitAccess(sendingUser, intent,
-                    UserHandle.getAppId(receiverUid), callingUid, true);
-        }
-        // Send the intent to the receiver asynchronously using one-way binder calls.
-        if (app != null) {
-            final IApplicationThread thread = app.getThread();
-            if (thread != null) {
-                // If we have an app thread, do the call through that so it is
-                // correctly ordered with other one-way calls.
-                try {
-                    final boolean assumeDelivered = !ordered;
-                    thread.scheduleRegisteredReceiver(
-                            receiver, intent, resultCode,
-                            data, extras, ordered, sticky, assumeDelivered, sendingUser,
-                            app.mState.getReportedProcState(),
-                            shareIdentity ? callingUid : Process.INVALID_UID,
-                            shareIdentity ? callingPackage : null);
-                } catch (RemoteException ex) {
-                    // Failed to call into the process. It's either dying or wedged. Kill it gently.
-                    synchronized (mService) {
-                        final String msg = "Failed to schedule " + intent + " to " + receiver
-                                + " via " + app + ": " + ex;
-                        Slog.w(TAG, msg);
-                        app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER,
-                                ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
-                    }
-                    throw ex;
-                }
-            } else {
-                // Application has died. Receiver doesn't exist.
-                throw new RemoteException("app.thread must not be null");
-            }
-        } else {
-            receiver.performReceive(intent, resultCode, data, extras, ordered,
-                    sticky, sendingUser);
-        }
-        if (!ordered) {
-            FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED,
-                    receiverUid == -1 ? Process.SYSTEM_UID : receiverUid,
-                    callingUid == -1 ? Process.SYSTEM_UID : callingUid,
-                    intent.getAction(),
-                    BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME,
-                    BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
-                    dispatchDelay, receiveDelay, 0 /* finish_delay */,
-                    SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
-                    app != null ? app.info.packageName : null, callingPackage,
-                    r.calculateTypeForLogging(), r.getDeliveryGroupPolicy(), r.intent.getFlags(),
-                    priority, r.callerProcState, receiverProcessState);
-        }
-    }
-
-    private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
-            BroadcastFilter filter, boolean ordered, int index) {
-        boolean skip = mSkipPolicy.shouldSkip(r, filter);
-
-        // Filter packages in the intent extras, skipping delivery if none of the packages is
-        // visible to the receiver.
-        Bundle filteredExtras = null;
-        if (!skip && r.filterExtrasForReceiver != null) {
-            final Bundle extras = r.intent.getExtras();
-            if (extras != null) {
-                filteredExtras = r.filterExtrasForReceiver.apply(filter.receiverList.uid, extras);
-                if (filteredExtras == null) {
-                    if (DEBUG_BROADCAST) {
-                        Slog.v(TAG, "Skipping delivery to "
-                                + filter.receiverList.app
-                                + " : receiver is filtered by the package visibility");
-                    }
-                    skip = true;
-                }
-            }
-        }
-
-        if (skip) {
-            r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED;
-            return;
-        }
-
-        r.delivery[index] = BroadcastRecord.DELIVERY_DELIVERED;
-
-        // If this is not being sent as an ordered broadcast, then we
-        // don't want to touch the fields that keep track of the current
-        // state of ordered broadcasts.
-        if (ordered) {
-            r.curFilter = filter;
-            filter.receiverList.curBroadcast = r;
-            r.state = BroadcastRecord.CALL_IN_RECEIVE;
-            if (filter.receiverList.app != null) {
-                // Bump hosting application to no longer be in background
-                // scheduling class.  Note that we can't do that if there
-                // isn't an app...  but we can only be in that case for
-                // things that directly call the IActivityManager API, which
-                // are already core system stuff so don't matter for this.
-                r.curApp = filter.receiverList.app;
-                r.curAppLastProcessState = r.curApp.mState.getCurProcState();
-                filter.receiverList.app.mReceivers.addCurReceiver(r);
-                mService.enqueueOomAdjTargetLocked(r.curApp);
-                mService.updateOomAdjPendingTargetsLocked(
-                        OOM_ADJ_REASON_START_RECEIVER);
-            }
-        } else if (filter.receiverList.app != null) {
-            mService.mOomAdjuster.unfreezeTemporarily(filter.receiverList.app,
-                    CachedAppOptimizer.UNFREEZE_REASON_START_RECEIVER);
-        }
-
-        try {
-            if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST,
-                    "Delivering to " + filter + " : " + r);
-            final boolean isInFullBackup = (filter.receiverList.app != null)
-                    && filter.receiverList.app.isInFullBackup();
-            final boolean isKilled = (filter.receiverList.app != null)
-                    && filter.receiverList.app.isKilled();
-            if (isInFullBackup || isKilled) {
-                // Skip delivery if full backup in progress
-                // If it's an ordered broadcast, we need to continue to the next receiver.
-                if (ordered) {
-                    skipReceiverLocked(r);
-                }
-            } else {
-                r.receiverTime = SystemClock.uptimeMillis();
-                r.scheduledTime[index] = r.receiverTime;
-                maybeAddBackgroundStartPrivileges(filter.receiverList.app, r);
-                maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options);
-                maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid);
-                performReceiveLocked(r, filter.receiverList.app, filter.receiverList.receiver,
-                        prepareReceiverIntent(r.intent, filteredExtras), r.resultCode, r.resultData,
-                        r.resultExtras, r.ordered, r.initialSticky, r.shareIdentity, r.userId,
-                        filter.receiverList.uid, r.callingUid, r.callerPackage,
-                        r.dispatchTime - r.enqueueTime,
-                        r.receiverTime - r.dispatchTime, filter.getPriority(),
-                        filter.receiverList.app != null
-                                ? filter.receiverList.app.mState.getCurProcState()
-                                : ActivityManager.PROCESS_STATE_UNKNOWN);
-                // parallel broadcasts are fire-and-forget, not bookended by a call to
-                // finishReceiverLocked(), so we manage their activity-start token here
-                if (filter.receiverList.app != null
-                        && r.mBackgroundStartPrivileges.allowsAny()
-                        && !r.ordered) {
-                    postActivityStartTokenRemoval(filter.receiverList.app, r);
-                }
-            }
-            if (ordered) {
-                r.state = BroadcastRecord.CALL_DONE_RECEIVE;
-            }
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Failure sending broadcast " + r.intent, e);
-            // Clean up ProcessRecord state related to this broadcast attempt
-            if (filter.receiverList.app != null) {
-                filter.receiverList.app.removeBackgroundStartPrivileges(r);
-                if (ordered) {
-                    filter.receiverList.app.mReceivers.removeCurReceiver(r);
-                    // Something wrong, its oom adj could be downgraded, but not in a hurry.
-                    mService.enqueueOomAdjTargetLocked(r.curApp);
-                }
-            }
-            // And BroadcastRecord state related to ordered delivery, if appropriate
-            if (ordered) {
-                r.curFilter = null;
-                filter.receiverList.curBroadcast = null;
-            }
-        }
-    }
-
-    void maybeScheduleTempAllowlistLocked(int uid, BroadcastRecord r,
-            @Nullable BroadcastOptions brOptions) {
-        if (brOptions == null || brOptions.getTemporaryAppAllowlistDuration() <= 0) {
-            return;
-        }
-        long duration = brOptions.getTemporaryAppAllowlistDuration();
-        final @TempAllowListType int type = brOptions.getTemporaryAppAllowlistType();
-        final @ReasonCode int reasonCode = brOptions.getTemporaryAppAllowlistReasonCode();
-        final String reason = brOptions.getTemporaryAppAllowlistReason();
-
-        if (duration > Integer.MAX_VALUE) {
-            duration = Integer.MAX_VALUE;
-        }
-        // XXX ideally we should pause the broadcast until everything behind this is done,
-        // or else we will likely start dispatching the broadcast before we have opened
-        // access to the app (there is a lot of asynchronicity behind this).  It is probably
-        // not that big a deal, however, because the main purpose here is to allow apps
-        // to hold wake locks, and they will be able to acquire their wake lock immediately
-        // it just won't be enabled until we get through this work.
-        StringBuilder b = new StringBuilder();
-        b.append("broadcast:");
-        UserHandle.formatUid(b, r.callingUid);
-        b.append(":");
-        if (r.intent.getAction() != null) {
-            b.append(r.intent.getAction());
-        } else if (r.intent.getComponent() != null) {
-            r.intent.getComponent().appendShortString(b);
-        } else if (r.intent.getData() != null) {
-            b.append(r.intent.getData());
-        }
-        b.append(",reason:");
-        b.append(reason);
-        if (DEBUG_BROADCAST) {
-            Slog.v(TAG, "Broadcast temp allowlist uid=" + uid + " duration=" + duration
-                    + " type=" + type + " : " + b.toString());
-        }
-
-        // Only add to temp allowlist if it's not the APP_FREEZING_DELAYED type. That will be
-        // handled when the broadcast is actually being scheduled on the app thread.
-        if (type != TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED) {
-            mService.tempAllowlistUidLocked(uid, duration, reasonCode, b.toString(), type,
-                    r.callingUid);
-        }
-    }
-
-    private void processNextBroadcast(boolean fromMsg) {
-        synchronized (mService) {
-            processNextBroadcastLocked(fromMsg, false);
-        }
-    }
-
-    private static Intent prepareReceiverIntent(@NonNull Intent originalIntent,
-            @Nullable Bundle filteredExtras) {
-        final Intent intent = new Intent(originalIntent);
-        if (filteredExtras != null) {
-            intent.replaceExtras(filteredExtras);
-        }
-        return intent;
-    }
-
-    public void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
-        BroadcastRecord r;
-
-        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "processNextBroadcast ["
-                + mQueueName + "]: "
-                + mParallelBroadcasts.size() + " parallel broadcasts; "
-                + mDispatcher.describeStateLocked());
-
-        mService.updateCpuStats();
-
-        if (fromMsg) {
-            mBroadcastsScheduled = false;
-        }
-
-        // First, deliver any non-serialized broadcasts right away.
-        while (mParallelBroadcasts.size() > 0) {
-            r = mParallelBroadcasts.remove(0);
-            r.dispatchTime = SystemClock.uptimeMillis();
-            r.dispatchRealTime = SystemClock.elapsedRealtime();
-            r.dispatchClockTime = System.currentTimeMillis();
-            r.mIsReceiverAppRunning = true;
-
-            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-                Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
-                    System.identityHashCode(r));
-                Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED),
-                    System.identityHashCode(r));
-            }
-
-            final int N = r.receivers.size();
-            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing parallel broadcast ["
-                    + mQueueName + "] " + r);
-            for (int i=0; i<N; i++) {
-                Object target = r.receivers.get(i);
-                if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                        "Delivering non-ordered on [" + mQueueName + "] to registered "
-                        + target + ": " + r);
-                deliverToRegisteredReceiverLocked(r,
-                        (BroadcastFilter) target, false, i);
-            }
-            addBroadcastToHistoryLocked(r);
-            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Done with parallel broadcast ["
-                    + mQueueName + "] " + r);
-        }
-
-        // Now take care of the next serialized one...
-
-        // If we are waiting for a process to come up to handle the next
-        // broadcast, then do nothing at this point.  Just in case, we
-        // check that the process we're waiting for still exists.
-        if (mPendingBroadcast != null) {
-            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
-                    "processNextBroadcast [" + mQueueName + "]: waiting for "
-                    + mPendingBroadcast.curApp);
-
-            boolean isDead;
-            if (mPendingBroadcast.curApp.getPid() > 0) {
-                synchronized (mService.mPidsSelfLocked) {
-                    ProcessRecord proc = mService.mPidsSelfLocked.get(
-                            mPendingBroadcast.curApp.getPid());
-                    isDead = proc == null || proc.mErrorState.isCrashing();
-                }
-            } else {
-                final ProcessRecord proc = mService.mProcessList.getProcessNamesLOSP().get(
-                        mPendingBroadcast.curApp.processName, mPendingBroadcast.curApp.uid);
-                isDead = proc == null || !proc.isPendingStart();
-            }
-            if (!isDead) {
-                // It's still alive, so keep waiting
-                return;
-            } else {
-                Slog.w(TAG, "pending app  ["
-                        + mQueueName + "]" + mPendingBroadcast.curApp
-                        + " died before responding to broadcast");
-                mPendingBroadcast.state = BroadcastRecord.IDLE;
-                mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
-                mPendingBroadcast = null;
-            }
-        }
-
-        boolean looped = false;
-
-        do {
-            final long now = SystemClock.uptimeMillis();
-            r = mDispatcher.getNextBroadcastLocked(now);
-
-            if (r == null) {
-                // No more broadcasts are deliverable right now, so all done!
-                mDispatcher.scheduleDeferralCheckLocked(false);
-                synchronized (mService.mAppProfiler.mProfilerLock) {
-                    mService.mAppProfiler.scheduleAppGcsLPf();
-                }
-                if (looped && !skipOomAdj) {
-                    // If we had finished the last ordered broadcast, then
-                    // make sure all processes have correct oom and sched
-                    // adjustments.
-                    mService.updateOomAdjPendingTargetsLocked(
-                            OOM_ADJ_REASON_START_RECEIVER);
-                }
-
-                // when we have no more ordered broadcast on this queue, stop logging
-                if (mService.mUserController.mBootCompleted && mLogLatencyMetrics) {
-                    mLogLatencyMetrics = false;
-                }
-
-                return;
-            }
-
-            boolean forceReceive = false;
-
-            // Ensure that even if something goes awry with the timeout
-            // detection, we catch "hung" broadcasts here, discard them,
-            // and continue to make progress.
-            //
-            // This is only done if the system is ready so that early-stage receivers
-            // don't get executed with timeouts; and of course other timeout-
-            // exempt broadcasts are ignored.
-            int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
-            if (mService.mProcessesReady && !r.timeoutExempt && r.dispatchTime > 0) {
-                if ((numReceivers > 0) &&
-                        (now > r.dispatchTime + (2 * mConstants.TIMEOUT * numReceivers))) {
-                    Slog.w(TAG, "Hung broadcast ["
-                            + mQueueName + "] discarded after timeout failure:"
-                            + " now=" + now
-                            + " dispatchTime=" + r.dispatchTime
-                            + " startTime=" + r.receiverTime
-                            + " intent=" + r.intent
-                            + " numReceivers=" + numReceivers
-                            + " nextReceiver=" + r.nextReceiver
-                            + " state=" + r.state);
-                    broadcastTimeoutLocked(false); // forcibly finish this broadcast
-                    forceReceive = true;
-                    r.state = BroadcastRecord.IDLE;
-                }
-            }
-
-            if (r.state != BroadcastRecord.IDLE) {
-                if (DEBUG_BROADCAST) Slog.d(TAG_BROADCAST,
-                        "processNextBroadcast("
-                        + mQueueName + ") called when not idle (state="
-                        + r.state + ")");
-                return;
-            }
-
-            // Is the current broadcast is done for any reason?
-            if (r.receivers == null || r.nextReceiver >= numReceivers
-                    || r.resultAbort || forceReceive) {
-                // Send the final result if requested
-                if (r.resultTo != null) {
-                    boolean sendResult = true;
-
-                    // if this was part of a split/deferral complex, update the refcount and only
-                    // send the completion when we clear all of them
-                    if (r.splitToken != 0) {
-                        int newCount = mSplitRefcounts.get(r.splitToken) - 1;
-                        if (newCount == 0) {
-                            // done!  clear out this record's bookkeeping and deliver
-                            if (DEBUG_BROADCAST_DEFERRAL) {
-                                Slog.i(TAG_BROADCAST,
-                                        "Sending broadcast completion for split token "
-                                        + r.splitToken + " : " + r.intent.getAction());
-                            }
-                            mSplitRefcounts.delete(r.splitToken);
-                        } else {
-                            // still have some split broadcast records in flight; update refcount
-                            // and hold off on the callback
-                            if (DEBUG_BROADCAST_DEFERRAL) {
-                                Slog.i(TAG_BROADCAST,
-                                        "Result refcount now " + newCount + " for split token "
-                                        + r.splitToken + " : " + r.intent.getAction()
-                                        + " - not sending completion yet");
-                            }
-                            sendResult = false;
-                            mSplitRefcounts.put(r.splitToken, newCount);
-                        }
-                    }
-                    if (sendResult) {
-                        if (r.callerApp != null) {
-                            mService.mOomAdjuster.unfreezeTemporarily(
-                                    r.callerApp,
-                                    CachedAppOptimizer.UNFREEZE_REASON_FINISH_RECEIVER);
-                        }
-                        try {
-                            if (DEBUG_BROADCAST) {
-                                Slog.i(TAG_BROADCAST, "Finishing broadcast [" + mQueueName + "] "
-                                        + r.intent.getAction() + " app=" + r.callerApp);
-                            }
-                            if (r.dispatchTime == 0) {
-                                // The dispatch time here could be 0, in case it's a parallel
-                                // broadcast but it has a result receiver. Set it to now.
-                                r.dispatchTime = now;
-                            }
-                            r.mIsReceiverAppRunning = true;
-                            performReceiveLocked(r, r.resultToApp, r.resultTo,
-                                    new Intent(r.intent), r.resultCode,
-                                    r.resultData, r.resultExtras, false, false, r.shareIdentity,
-                                    r.userId, r.callingUid, r.callingUid, r.callerPackage,
-                                    r.dispatchTime - r.enqueueTime,
-                                    now - r.dispatchTime, 0,
-                                    r.resultToApp != null
-                                            ? r.resultToApp.mState.getCurProcState()
-                                            : ActivityManager.PROCESS_STATE_UNKNOWN);
-                            logBootCompletedBroadcastCompletionLatencyIfPossible(r);
-                            // Set this to null so that the reference
-                            // (local and remote) isn't kept in the mBroadcastHistory.
-                            r.resultTo = null;
-                        } catch (RemoteException e) {
-                            r.resultTo = null;
-                            Slog.w(TAG, "Failure ["
-                                    + mQueueName + "] sending broadcast result of "
-                                    + r.intent, e);
-                        }
-                    }
-                }
-
-                if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Cancelling BROADCAST_TIMEOUT_MSG");
-                cancelBroadcastTimeoutLocked();
-
-                if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
-                        "Finished with ordered broadcast " + r);
-
-                // ... and on to the next...
-                addBroadcastToHistoryLocked(r);
-                if (r.intent.getComponent() == null && r.intent.getPackage() == null
-                        && (r.intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
-                    // This was an implicit broadcast... let's record it for posterity.
-                    mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage,
-                            r.manifestCount, r.manifestSkipCount, r.finishTime-r.dispatchTime);
-                }
-                mDispatcher.retireBroadcastLocked(r);
-                r = null;
-                looped = true;
-                continue;
-            }
-
-            // Check whether the next receiver is under deferral policy, and handle that
-            // accordingly.  If the current broadcast was already part of deferred-delivery
-            // tracking, we know that it must now be deliverable as-is without re-deferral.
-            if (!r.deferred) {
-                final int receiverUid = r.getReceiverUid(r.receivers.get(r.nextReceiver));
-                if (mDispatcher.isDeferringLocked(receiverUid)) {
-                    if (DEBUG_BROADCAST_DEFERRAL) {
-                        Slog.i(TAG_BROADCAST, "Next receiver in " + r + " uid " + receiverUid
-                                + " at " + r.nextReceiver + " is under deferral");
-                    }
-                    // If this is the only (remaining) receiver in the broadcast, "splitting"
-                    // doesn't make sense -- just defer it as-is and retire it as the
-                    // currently active outgoing broadcast.
-                    BroadcastRecord defer;
-                    if (r.nextReceiver + 1 == numReceivers) {
-                        if (DEBUG_BROADCAST_DEFERRAL) {
-                            Slog.i(TAG_BROADCAST, "Sole receiver of " + r
-                                    + " is under deferral; setting aside and proceeding");
-                        }
-                        defer = r;
-                        mDispatcher.retireBroadcastLocked(r);
-                    } else {
-                        // Nontrivial case; split out 'uid's receivers to a new broadcast record
-                        // and defer that, then loop and pick up continuing delivery of the current
-                        // record (now absent those receivers).
-
-                        // The split operation is guaranteed to match at least at 'nextReceiver'
-                        defer = r.splitRecipientsLocked(receiverUid, r.nextReceiver);
-                        if (DEBUG_BROADCAST_DEFERRAL) {
-                            Slog.i(TAG_BROADCAST, "Post split:");
-                            Slog.i(TAG_BROADCAST, "Original broadcast receivers:");
-                            for (int i = 0; i < r.receivers.size(); i++) {
-                                Slog.i(TAG_BROADCAST, "  " + r.receivers.get(i));
-                            }
-                            Slog.i(TAG_BROADCAST, "Split receivers:");
-                            for (int i = 0; i < defer.receivers.size(); i++) {
-                                Slog.i(TAG_BROADCAST, "  " + defer.receivers.get(i));
-                            }
-                        }
-                        // Track completion refcount as well if relevant
-                        if (r.resultTo != null) {
-                            int token = r.splitToken;
-                            if (token == 0) {
-                                // first split of this record; refcount for 'r' and 'deferred'
-                                r.splitToken = defer.splitToken = nextSplitTokenLocked();
-                                mSplitRefcounts.put(r.splitToken, 2);
-                                if (DEBUG_BROADCAST_DEFERRAL) {
-                                    Slog.i(TAG_BROADCAST,
-                                            "Broadcast needs split refcount; using new token "
-                                            + r.splitToken);
-                                }
-                            } else {
-                                // new split from an already-refcounted situation; increment count
-                                final int curCount = mSplitRefcounts.get(token);
-                                if (DEBUG_BROADCAST_DEFERRAL) {
-                                    if (curCount == 0) {
-                                        Slog.wtf(TAG_BROADCAST,
-                                                "Split refcount is zero with token for " + r);
-                                    }
-                                }
-                                mSplitRefcounts.put(token, curCount + 1);
-                                if (DEBUG_BROADCAST_DEFERRAL) {
-                                    Slog.i(TAG_BROADCAST, "New split count for token " + token
-                                            + " is " + (curCount + 1));
-                                }
-                            }
-                        }
-                    }
-                    mDispatcher.addDeferredBroadcast(receiverUid, defer);
-                    r = null;
-                    looped = true;
-                    continue;
-                }
-            }
-        } while (r == null);
-
-        // Get the next receiver...
-        int recIdx = r.nextReceiver++;
-
-        // Keep track of when this receiver started, and make sure there
-        // is a timeout message pending to kill it if need be.
-        r.receiverTime = SystemClock.uptimeMillis();
-        r.scheduledTime[recIdx] = r.receiverTime;
-        if (recIdx == 0) {
-            r.dispatchTime = r.receiverTime;
-            r.dispatchRealTime = SystemClock.elapsedRealtime();
-            r.dispatchClockTime = System.currentTimeMillis();
-
-            if (mLogLatencyMetrics) {
-                FrameworkStatsLog.write(
-                        FrameworkStatsLog.BROADCAST_DISPATCH_LATENCY_REPORTED,
-                        r.dispatchClockTime - r.enqueueClockTime);
-            }
-
-            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-                Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
-                    System.identityHashCode(r));
-                Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED),
-                    System.identityHashCode(r));
-            }
-            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing ordered broadcast ["
-                    + mQueueName + "] " + r);
-        }
-        if (! mPendingBroadcastTimeoutMessage) {
-            long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
-            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
-                    "Submitting BROADCAST_TIMEOUT_MSG ["
-                    + mQueueName + "] for " + r + " at " + timeoutTime);
-            setBroadcastTimeoutLocked(timeoutTime);
-        }
-
-        final BroadcastOptions brOptions = r.options;
-        final Object nextReceiver = r.receivers.get(recIdx);
-
-        if (nextReceiver instanceof BroadcastFilter) {
-            // Simple case: this is a registered receiver who gets
-            // a direct call.
-            BroadcastFilter filter = (BroadcastFilter)nextReceiver;
-            if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                    "Delivering ordered ["
-                    + mQueueName + "] to registered "
-                    + filter + ": " + r);
-            r.mIsReceiverAppRunning = true;
-            deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
-            if ((r.curReceiver == null && r.curFilter == null) || !r.ordered) {
-                // The receiver has already finished, so schedule to
-                // process the next one.
-                if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Quick finishing ["
-                        + mQueueName + "]: ordered=" + r.ordered
-                        + " curFilter=" + r.curFilter
-                        + " curReceiver=" + r.curReceiver);
-                r.state = BroadcastRecord.IDLE;
-                scheduleBroadcastsLocked();
-            } else {
-                if (filter.receiverList != null) {
-                    maybeAddBackgroundStartPrivileges(filter.receiverList.app, r);
-                    // r is guaranteed ordered at this point, so we know finishReceiverLocked()
-                    // will get a callback and handle the activity start token lifecycle.
-                }
-            }
-            return;
-        }
-
-        // Hard case: need to instantiate the receiver, possibly
-        // starting its application process to host it.
-
-        final ResolveInfo info =
-            (ResolveInfo)nextReceiver;
-        final ComponentName component = new ComponentName(
-                info.activityInfo.applicationInfo.packageName,
-                info.activityInfo.name);
-        final int receiverUid = info.activityInfo.applicationInfo.uid;
-
-        final String targetProcess = info.activityInfo.processName;
-        final ProcessRecord app = mService.getProcessRecordLocked(targetProcess,
-                info.activityInfo.applicationInfo.uid);
-
-        boolean skip = mSkipPolicy.shouldSkip(r, info);
-
-        // Filter packages in the intent extras, skipping delivery if none of the packages is
-        // visible to the receiver.
-        Bundle filteredExtras = null;
-        if (!skip && r.filterExtrasForReceiver != null) {
-            final Bundle extras = r.intent.getExtras();
-            if (extras != null) {
-                filteredExtras = r.filterExtrasForReceiver.apply(receiverUid, extras);
-                if (filteredExtras == null) {
-                    if (DEBUG_BROADCAST) {
-                        Slog.v(TAG, "Skipping delivery to "
-                                + info.activityInfo.packageName + " / " + receiverUid
-                                + " : receiver is filtered by the package visibility");
-                    }
-                    skip = true;
-                }
-            }
-        }
-
-        if (skip) {
-            if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                    "Skipping delivery of ordered [" + mQueueName + "] "
-                    + r + " for reason described above");
-            r.delivery[recIdx] = BroadcastRecord.DELIVERY_SKIPPED;
-            r.curFilter = null;
-            r.state = BroadcastRecord.IDLE;
-            r.manifestSkipCount++;
-            scheduleBroadcastsLocked();
-            return;
-        }
-        r.manifestCount++;
-
-        r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED;
-        r.state = BroadcastRecord.APP_RECEIVE;
-        r.curComponent = component;
-        r.curReceiver = info.activityInfo;
-        r.curFilteredExtras = filteredExtras;
-        if (DEBUG_MU && r.callingUid > UserHandle.PER_USER_RANGE) {
-            Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, "
-                    + info.activityInfo + ", callingUid = " + r.callingUid + ", uid = "
-                    + receiverUid);
-        }
-        final boolean isActivityCapable =
-                (brOptions != null && brOptions.getTemporaryAppAllowlistDuration() > 0);
-        maybeScheduleTempAllowlistLocked(receiverUid, r, brOptions);
-
-        // Report that a component is used for explicit broadcasts.
-        if (r.intent.getComponent() != null && r.curComponent != null
-                && !TextUtils.equals(r.curComponent.getPackageName(), r.callerPackage)) {
-            mService.mUsageStatsService.reportEvent(
-                    r.curComponent.getPackageName(), r.userId, Event.APP_COMPONENT_USED);
-        }
-
-        try {
-            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);
-        }
-
-        // Is this receiver's application already running?
-        if (app != null && app.getThread() != null && !app.isKilled()) {
-            try {
-                app.addPackage(info.activityInfo.packageName,
-                        info.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
-                maybeAddBackgroundStartPrivileges(app, r);
-                r.mIsReceiverAppRunning = true;
-                processCurBroadcastLocked(r, app);
-                return;
-            } catch (RemoteException e) {
-                final String msg = "Failed to schedule " + r.intent + " to " + info
-                        + " via " + app + ": " + e;
-                Slog.w(TAG, msg);
-                app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER,
-                        ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
-            } catch (RuntimeException e) {
-                Slog.wtf(TAG, "Failed sending broadcast to "
-                        + r.curComponent + " with " + r.intent, e);
-                // If some unexpected exception happened, just skip
-                // this broadcast.  At this point we are not in the call
-                // from a client, so throwing an exception out from here
-                // will crash the entire system instead of just whoever
-                // sent the broadcast.
-                logBroadcastReceiverDiscardLocked(r);
-                finishReceiverLocked(r, r.resultCode, r.resultData,
-                        r.resultExtras, r.resultAbort, false);
-                scheduleBroadcastsLocked();
-                // We need to reset the state if we failed to start the receiver.
-                r.state = BroadcastRecord.IDLE;
-                return;
-            }
-
-            // If a dead object exception was thrown -- fall through to
-            // restart the application.
-        }
-
-        // Registered whether we're bringing this package out of a stopped state
-        r.mWasReceiverAppStopped =
-                (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0;
-        // Not running -- get it started, to be executed when the app comes up.
-        if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                "Need to start app ["
-                + mQueueName + "] " + targetProcess + " for broadcast " + r);
-        r.curApp = mService.startProcessLocked(targetProcess,
-                info.activityInfo.applicationInfo, true,
-                r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
-                new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST, r.curComponent,
-                        r.intent.getAction(), r.getHostingRecordTriggerType()),
-                isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
-                (r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
-        r.curAppLastProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
-        if (r.curApp == null) {
-            // Ah, this recipient is unavailable.  Finish it if necessary,
-            // and mark the broadcast record as ready for the next.
-            Slog.w(TAG, "Unable to launch app "
-                    + info.activityInfo.applicationInfo.packageName + "/"
-                    + receiverUid + " for broadcast "
-                    + r.intent + ": process is bad");
-            logBroadcastReceiverDiscardLocked(r);
-            finishReceiverLocked(r, r.resultCode, r.resultData,
-                    r.resultExtras, r.resultAbort, false);
-            scheduleBroadcastsLocked();
-            r.state = BroadcastRecord.IDLE;
-            return;
-        }
-
-        maybeAddBackgroundStartPrivileges(r.curApp, r);
-        mPendingBroadcast = r;
-        mPendingBroadcastRecvIndex = recIdx;
-    }
-
-    @Nullable
-    private String getTargetPackage(BroadcastRecord r) {
-        if (r.intent == null) {
-            return null;
-        }
-        if (r.intent.getPackage() != null) {
-            return r.intent.getPackage();
-        } else if (r.intent.getComponent() != null) {
-            return r.intent.getComponent().getPackageName();
-        }
-        return null;
-    }
-
-    static void logBootCompletedBroadcastCompletionLatencyIfPossible(BroadcastRecord r) {
-        // Only log after last receiver.
-        // In case of split BOOT_COMPLETED broadcast, make sure only call this method on the
-        // last BroadcastRecord of the split broadcast which has non-null resultTo.
-        final int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
-        if (r.nextReceiver < numReceivers) {
-            return;
-        }
-        final String action = r.intent.getAction();
-        int event = 0;
-        if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) {
-            event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
-        } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
-            event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
-        }
-        if (event != 0) {
-            final int dispatchLatency = (int)(r.dispatchTime - r.enqueueTime);
-            final int completeLatency = (int)
-                    (SystemClock.uptimeMillis() - r.enqueueTime);
-            final int dispatchRealLatency = (int)(r.dispatchRealTime - r.enqueueRealTime);
-            final int completeRealLatency = (int)
-                    (SystemClock.elapsedRealtime() - r.enqueueRealTime);
-            int userType = FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
-            // This method is called very infrequently, no performance issue we call
-            // LocalServices.getService() here.
-            final UserManagerInternal umInternal = LocalServices.getService(
-                    UserManagerInternal.class);
-            final UserInfo userInfo =
-                    (umInternal != null) ? umInternal.getUserInfo(r.userId) : null;
-            if (userInfo != null) {
-                userType = UserJourneyLogger.getUserTypeForStatsd(userInfo.userType);
-            }
-            Slog.i(TAG_BROADCAST,
-                    "BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED action:"
-                            + action
-                            + " dispatchLatency:" + dispatchLatency
-                            + " completeLatency:" + completeLatency
-                            + " dispatchRealLatency:" + dispatchRealLatency
-                            + " completeRealLatency:" + completeRealLatency
-                            + " receiversSize:" + numReceivers
-                            + " userId:" + r.userId
-                            + " userType:" + (userInfo != null? userInfo.userType : null));
-            FrameworkStatsLog.write(
-                    BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED,
-                    event,
-                    dispatchLatency,
-                    completeLatency,
-                    dispatchRealLatency,
-                    completeRealLatency,
-                    r.userId,
-                    userType);
-        }
-    }
-
-    private void maybeReportBroadcastDispatchedEventLocked(BroadcastRecord r, int targetUid) {
-        if (r.options == null || r.options.getIdForResponseEvent() <= 0) {
-            return;
-        }
-        final String targetPackage = getTargetPackage(r);
-        // Ignore non-explicit broadcasts
-        if (targetPackage == null) {
-            return;
-        }
-        mService.mUsageStatsService.reportBroadcastDispatched(
-                r.callingUid, targetPackage, UserHandle.of(r.userId),
-                r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime(),
-                mService.getUidStateLocked(targetUid));
-    }
-
-    private void maybeAddBackgroundStartPrivileges(ProcessRecord proc, BroadcastRecord r) {
-        if (r == null || proc == null || !r.mBackgroundStartPrivileges.allowsAny()) {
-            return;
-        }
-        String msgToken = (proc.toShortString() + r.toString()).intern();
-        // first, if there exists a past scheduled request to remove this token, drop
-        // that request - we don't want the token to be swept from under our feet...
-        mHandler.removeCallbacksAndMessages(msgToken);
-        // ...then add the token
-        proc.addOrUpdateBackgroundStartPrivileges(r, r.mBackgroundStartPrivileges);
-    }
-
-    final void setBroadcastTimeoutLocked(long timeoutTime) {
-        if (! mPendingBroadcastTimeoutMessage) {
-            Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
-            mHandler.sendMessageAtTime(msg, timeoutTime);
-            mPendingBroadcastTimeoutMessage = true;
-        }
-    }
-
-    final void cancelBroadcastTimeoutLocked() {
-        if (mPendingBroadcastTimeoutMessage) {
-            mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);
-            mPendingBroadcastTimeoutMessage = false;
-        }
-    }
-
-    final void broadcastTimeoutLocked(boolean fromMsg) {
-        if (fromMsg) {
-            mPendingBroadcastTimeoutMessage = false;
-        }
-
-        if (mDispatcher.isEmpty() || mDispatcher.getActiveBroadcastLocked() == null) {
-            return;
-        }
-        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastTimeoutLocked()");
-        try {
-            long now = SystemClock.uptimeMillis();
-            BroadcastRecord r = mDispatcher.getActiveBroadcastLocked();
-            if (fromMsg) {
-                if (!mService.mProcessesReady) {
-                    // Only process broadcast timeouts if the system is ready; some early
-                    // broadcasts do heavy work setting up system facilities
-                    return;
-                }
-
-                // If the broadcast is generally exempt from timeout tracking, we're done
-                if (r.timeoutExempt) {
-                    if (DEBUG_BROADCAST) {
-                        Slog.i(TAG_BROADCAST, "Broadcast timeout but it's exempt: "
-                                + r.intent.getAction());
-                    }
-                    return;
-                }
-                long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
-                if (timeoutTime > now) {
-                    // We can observe premature timeouts because we do not cancel and reset the
-                    // broadcast timeout message after each receiver finishes.  Instead, we set up
-                    // an initial timeout then kick it down the road a little further as needed
-                    // when it expires.
-                    if (DEBUG_BROADCAST) {
-                        Slog.v(TAG_BROADCAST,
-                                "Premature timeout ["
-                                + mQueueName + "] @ " + now
-                                + ": resetting BROADCAST_TIMEOUT_MSG for "
-                                + timeoutTime);
-                    }
-                    setBroadcastTimeoutLocked(timeoutTime);
-                    return;
-                }
-            }
-
-            if (r.state == BroadcastRecord.WAITING_SERVICES) {
-                // In this case the broadcast had already finished, but we had decided to wait
-                // for started services to finish as well before going on.  So if we have actually
-                // waited long enough time timeout the broadcast, let's give up on the whole thing
-                // and just move on to the next.
-                Slog.i(TAG, "Waited long enough for: " + (r.curComponent != null
-                        ? r.curComponent.flattenToShortString() : "(null)"));
-                r.curComponent = null;
-                r.state = BroadcastRecord.IDLE;
-                processNextBroadcastLocked(false, false);
-                return;
-            }
-
-            // If the receiver app is being debugged we quietly ignore unresponsiveness, just
-            // tidying up and moving on to the next broadcast without crashing or ANRing this
-            // app just because it's stopped at a breakpoint.
-            final boolean debugging = (r.curApp != null && r.curApp.isDebugging());
-
-            long timeoutDurationMs = now - r.receiverTime;
-            Slog.w(TAG, "Timeout of broadcast " + r + " - curFilter=" + r.curFilter
-                    + " curReceiver=" + r.curReceiver + ", started " + timeoutDurationMs
-                    + "ms ago");
-            r.receiverTime = now;
-            if (!debugging) {
-                r.anrCount++;
-            }
-
-            ProcessRecord app = null;
-            Object curReceiver;
-            if (r.nextReceiver > 0) {
-                curReceiver = r.receivers.get(r.nextReceiver - 1);
-                r.delivery[r.nextReceiver - 1] = BroadcastRecord.DELIVERY_TIMEOUT;
-            } else {
-                curReceiver = r.curReceiver;
-            }
-            Slog.w(TAG, "Receiver during timeout of " + r + " : " + curReceiver);
-            logBroadcastReceiverDiscardLocked(r);
-            TimeoutRecord timeoutRecord = TimeoutRecord.forBroadcastReceiver(r.intent,
-                    timeoutDurationMs);
-            if (curReceiver != null && curReceiver instanceof BroadcastFilter) {
-                BroadcastFilter bf = (BroadcastFilter) curReceiver;
-                if (bf.receiverList.pid != 0
-                        && bf.receiverList.pid != ActivityManagerService.MY_PID) {
-                    timeoutRecord.mLatencyTracker.waitingOnPidLockStarted();
-                    synchronized (mService.mPidsSelfLocked) {
-                        timeoutRecord.mLatencyTracker.waitingOnPidLockEnded();
-                        app = mService.mPidsSelfLocked.get(
-                                bf.receiverList.pid);
-                    }
-                }
-            } else {
-                app = r.curApp;
-            }
-
-            if (mPendingBroadcast == r) {
-                mPendingBroadcast = null;
-            }
-
-            // Move on to the next receiver.
-            finishReceiverLocked(r, r.resultCode, r.resultData,
-                    r.resultExtras, r.resultAbort, false);
-            scheduleBroadcastsLocked();
-
-            // The ANR should only be triggered if we have a process record (app is non-null)
-            if (!debugging && app != null) {
-                mService.appNotResponding(app, timeoutRecord);
-            }
-
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-        }
-
-    }
-
-    private final void addBroadcastToHistoryLocked(BroadcastRecord original) {
-        if (original.callingUid < 0) {
-            // This was from a registerReceiver() call; ignore it.
-            return;
-        }
-        original.finishTime = SystemClock.uptimeMillis();
-
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                createBroadcastTraceTitle(original, BroadcastRecord.DELIVERY_DELIVERED),
-                System.identityHashCode(original));
-        }
-
-        mService.notifyBroadcastFinishedLocked(original);
-        mHistory.addBroadcastToHistoryLocked(original);
-    }
-
-    public boolean cleanupDisabledPackageReceiversLocked(
-            String packageName, Set<String> filterByClasses, int userId) {
-        boolean didSomething = false;
-        for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
-            didSomething |= mParallelBroadcasts.get(i).cleanupDisabledPackageReceiversLocked(
-                    packageName, filterByClasses, userId, true);
-        }
-
-        didSomething |= mDispatcher.cleanupDisabledPackageReceiversLocked(packageName,
-                filterByClasses, userId, true);
-
-        return didSomething;
-    }
-
-    final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) {
-        final int logIndex = r.nextReceiver - 1;
-        if (logIndex >= 0 && logIndex < r.receivers.size()) {
-            Object curReceiver = r.receivers.get(logIndex);
-            if (curReceiver instanceof BroadcastFilter) {
-                BroadcastFilter bf = (BroadcastFilter) curReceiver;
-                EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_FILTER,
-                        bf.owningUserId, System.identityHashCode(r),
-                        r.intent.getAction(), logIndex, System.identityHashCode(bf));
-            } else {
-                ResolveInfo ri = (ResolveInfo) curReceiver;
-                EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
-                        UserHandle.getUserId(ri.activityInfo.applicationInfo.uid),
-                        System.identityHashCode(r), r.intent.getAction(), logIndex, ri.toString());
-            }
-        } else {
-            if (logIndex < 0) Slog.w(TAG,
-                    "Discarding broadcast before first receiver is invoked: " + r);
-            EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
-                    -1, System.identityHashCode(r),
-                    r.intent.getAction(),
-                    r.nextReceiver,
-                    "NONE");
-        }
-    }
-
-    private String createBroadcastTraceTitle(BroadcastRecord record, int state) {
-        return formatSimple("Broadcast %s from %s (%s) %s",
-                state == BroadcastRecord.DELIVERY_PENDING ? "in queue" : "dispatched",
-                record.callerPackage == null ? "" : record.callerPackage,
-                record.callerApp == null ? "process unknown" : record.callerApp.toShortString(),
-                record.intent == null ? "" : record.intent.getAction());
-    }
-
-    public boolean isIdleLocked() {
-        return mParallelBroadcasts.isEmpty() && mDispatcher.isIdle()
-                && (mPendingBroadcast == null);
-    }
-
-    public boolean isBeyondBarrierLocked(long barrierTime) {
-        // If nothing active, we're beyond barrier
-        if (isIdleLocked()) return true;
-
-        // Check if parallel broadcasts are beyond barrier
-        for (int i = 0; i < mParallelBroadcasts.size(); i++) {
-            if (mParallelBroadcasts.get(i).enqueueTime <= barrierTime) {
-                return false;
-            }
-        }
-
-        // Check if pending broadcast is beyond barrier
-        final BroadcastRecord pending = getPendingBroadcastLocked();
-        if ((pending != null) && pending.enqueueTime <= barrierTime) {
-            return false;
-        }
-
-        return mDispatcher.isBeyondBarrier(barrierTime);
-    }
-
-    public boolean isDispatchedLocked(Intent intent) {
-        if (isIdleLocked()) return true;
-
-        for (int i = 0; i < mParallelBroadcasts.size(); i++) {
-            if (intent.filterEquals(mParallelBroadcasts.get(i).intent)) {
-                return false;
-            }
-        }
-
-        final BroadcastRecord pending = getPendingBroadcastLocked();
-        if ((pending != null) && intent.filterEquals(pending.intent)) {
-            return false;
-        }
-
-        return mDispatcher.isDispatched(intent);
-    }
-
-    public void waitForIdle(PrintWriter pw) {
-        waitFor(() -> isIdleLocked(), pw, "idle");
-    }
-
-    public void waitForBarrier(PrintWriter pw) {
-        final long barrierTime = SystemClock.uptimeMillis();
-        waitFor(() -> isBeyondBarrierLocked(barrierTime), pw, "barrier");
-    }
-
-    public void waitForDispatched(Intent intent, PrintWriter pw) {
-        waitFor(() -> isDispatchedLocked(intent), pw, "dispatch");
-    }
-
-    private void waitFor(BooleanSupplier condition, PrintWriter pw, String conditionName) {
-        long lastPrint = 0;
-        while (true) {
-            synchronized (mService) {
-                if (condition.getAsBoolean()) {
-                    final String msg = "Queue [" + mQueueName + "] reached " + conditionName
-                            + " condition";
-                    Slog.v(TAG, msg);
-                    if (pw != null) {
-                        pw.println(msg);
-                        pw.flush();
-                    }
-                    return;
-                }
-            }
-
-            // Print at most every second
-            final long now = SystemClock.uptimeMillis();
-            if (now >= lastPrint + 1000) {
-                lastPrint = now;
-                final String msg = "Queue [" + mQueueName + "] waiting for " + conditionName
-                        + " condition; state is " + describeStateLocked();
-                Slog.v(TAG, msg);
-                if (pw != null) {
-                    pw.println(msg);
-                    pw.flush();
-                }
-            }
-
-            // Push through any deferrals to try meeting our condition
-            cancelDeferrals();
-            SystemClock.sleep(100);
-        }
-    }
-
-    // Used by wait-for-broadcast-idle : fast-forward all current deferrals to
-    // be immediately deliverable.
-    public void cancelDeferrals() {
-        synchronized (mService) {
-            mDispatcher.cancelDeferralsLocked();
-            scheduleBroadcastsLocked();
-        }
-    }
-
-    public String describeStateLocked() {
-        return mParallelBroadcasts.size() + " parallel; "
-                + mDispatcher.describeStateLocked();
-    }
-
-    @NeverCompile
-    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
-        long token = proto.start(fieldId);
-        proto.write(BroadcastQueueProto.QUEUE_NAME, mQueueName);
-        int N;
-        N = mParallelBroadcasts.size();
-        for (int i = N - 1; i >= 0; i--) {
-            mParallelBroadcasts.get(i).dumpDebug(proto, BroadcastQueueProto.PARALLEL_BROADCASTS);
-        }
-        mDispatcher.dumpDebug(proto, BroadcastQueueProto.ORDERED_BROADCASTS);
-        if (mPendingBroadcast != null) {
-            mPendingBroadcast.dumpDebug(proto, BroadcastQueueProto.PENDING_BROADCAST);
-        }
-        mHistory.dumpDebug(proto);
-        proto.end(token);
-    }
-
-    @NeverCompile
-    public boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
-            int opti, boolean dumpConstants, boolean dumpHistory, boolean dumpAll,
-            String dumpPackage, boolean needSep) {
-        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
-        if (!mParallelBroadcasts.isEmpty() || !mDispatcher.isEmpty()
-                || mPendingBroadcast != null) {
-            boolean printed = false;
-            for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
-                BroadcastRecord br = mParallelBroadcasts.get(i);
-                if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) {
-                    continue;
-                }
-                if (!printed) {
-                    if (needSep) {
-                        pw.println();
-                    }
-                    needSep = true;
-                    printed = true;
-                    pw.println("  Active broadcasts [" + mQueueName + "]:");
-                }
-                pw.println("  Active Broadcast " + mQueueName + " #" + i + ":");
-                br.dump(pw, "    ", sdf);
-            }
-
-            mDispatcher.dumpLocked(pw, dumpPackage, mQueueName, sdf);
-
-            if (dumpPackage == null || (mPendingBroadcast != null
-                    && dumpPackage.equals(mPendingBroadcast.callerPackage))) {
-                pw.println();
-                pw.println("  Pending broadcast [" + mQueueName + "]:");
-                if (mPendingBroadcast != null) {
-                    mPendingBroadcast.dump(pw, "    ", sdf);
-                } else {
-                    pw.println("    (null)");
-                }
-                needSep = true;
-            }
-        }
-        if (dumpConstants) {
-            mConstants.dump(new IndentingPrintWriter(pw));
-        }
-        if (dumpHistory) {
-            needSep = mHistory.dumpLocked(pw, dumpPackage, mQueueName, sdf, dumpAll, needSep);
-        }
-        return needSep;
-    }
-}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 98263df..5521381 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -20,6 +20,9 @@
 import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
 
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
 import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
 import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
 import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_UNKNOWN;
@@ -29,6 +32,7 @@
 import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
 import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
 import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
 import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
 import static com.android.server.am.BroadcastProcessQueue.reasonToString;
@@ -59,6 +63,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
 import android.os.Bundle;
 import android.os.BundleMerger;
 import android.os.Handler;
@@ -85,9 +90,13 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.os.TimeoutRecord;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.LocalServices;
 import com.android.server.am.BroadcastProcessQueue.BroadcastConsumer;
 import com.android.server.am.BroadcastProcessQueue.BroadcastPredicate;
+import com.android.server.am.BroadcastProcessQueue.BroadcastRecordConsumer;
 import com.android.server.am.BroadcastRecord.DeliveryState;
+import com.android.server.pm.UserJourneyLogger;
+import com.android.server.pm.UserManagerInternal;
 import com.android.server.utils.AnrTimer;
 
 import dalvik.annotation.optimization.NeverCompile;
@@ -277,6 +286,9 @@
     // when the flag is fused on.
     private static final int MSG_DELIVERY_TIMEOUT_SOFT = 8;
 
+    // TODO: Use the trunk stable flag.
+    private static final boolean DEFER_FROZEN_OUTGOING_BCASTS = false;
+
     private void enqueueUpdateRunningList() {
         mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
         mLocalHandler.sendEmptyMessage(MSG_UPDATE_RUNNING_LIST);
@@ -325,9 +337,7 @@
                 return true;
             }
             case MSG_PROCESS_FREEZABLE_CHANGED: {
-                synchronized (mService) {
-                    refreshProcessQueueLocked((ProcessRecord) msg.obj);
-                }
+                handleProcessFreezableChanged((ProcessRecord) msg.obj);
                 return true;
             }
             case MSG_UID_STATE_CHANGED: {
@@ -428,7 +438,8 @@
         }
 
         // If app isn't running, and there's nothing in the queue, clean up
-        if (queue.isEmpty() && !queue.isActive() && !queue.isProcessWarm()) {
+        if (queue.isEmpty() && queue.isOutgoingEmpty() && !queue.isActive()
+                && !queue.isProcessWarm()) {
             removeProcessQueue(queue.processName, queue.uid);
         }
     }
@@ -752,6 +763,21 @@
 
     @Override
     public void enqueueBroadcastLocked(@NonNull BroadcastRecord r) {
+        // TODO: Apply delivery group policies and FLAG_REPLACE_PENDING to collapse the
+        // outgoing broadcasts.
+        // TODO: Add traces/logs for the enqueueing outgoing broadcasts logic.
+        if (DEFER_FROZEN_OUTGOING_BCASTS && isProcessFreezable(r.callerApp)) {
+            final BroadcastProcessQueue queue = getOrCreateProcessQueue(
+                    r.callerApp.processName, r.callerApp.uid);
+            if (queue.getOutgoingBroadcastCount() >= mConstants.MAX_FROZEN_OUTGOING_BROADCASTS) {
+                // TODO: Kill the process if the outgoing broadcasts count is
+                // beyond a certain limit.
+            }
+            queue.enqueueOutgoingBroadcast(r);
+            mHistory.onBroadcastFrozenLocked(r);
+            mService.mOomAdjuster.mCachedAppOptimizer.freezeAppAsyncImmediateLSP(r.callerApp);
+            return;
+        }
         if (DEBUG_BROADCAST) logv("Enqueuing " + r + " for " + r.receivers.size() + " receivers");
 
         final int cookie = traceBegin("enqueueBroadcast");
@@ -959,6 +985,9 @@
             queue.setActiveWasStopped(true);
         }
         final int intentFlags = r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND;
+        final boolean firstLaunch = !mService.wasPackageEverLaunched(info.packageName, r.userId);
+        queue.setActiveFirstLaunch(firstLaunch);
+
         final HostingRecord hostingRecord = new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST,
                 component, r.intent.getAction(), r.getHostingRecordTriggerType());
         final boolean isActivityCapable = (r.options != null
@@ -1627,6 +1656,8 @@
                 "mBroadcastConsumerDeferClear");
     };
 
+    final BroadcastRecordConsumer mBroadcastRecordConsumerEnqueue = this::enqueueBroadcastLocked;
+
     /**
      * Verify that all known {@link #mProcessQueues} are in the state tested by
      * the given {@link Predicate}.
@@ -1923,8 +1954,9 @@
         }
     }
 
+    @VisibleForTesting
     @GuardedBy("mService")
-    private boolean isProcessFreezable(@Nullable ProcessRecord app) {
+    boolean isProcessFreezable(@Nullable ProcessRecord app) {
         if (app == null) {
             return false;
         }
@@ -1949,16 +1981,25 @@
         enqueueUpdateRunningList();
     }
 
+    private void handleProcessFreezableChanged(@NonNull ProcessRecord app) {
+        synchronized (mService) {
+            final BroadcastProcessQueue queue = getProcessQueue(app.processName, app.uid);
+            if (queue == null || queue.app == null || queue.app.getPid() != app.getPid()) {
+                return;
+            }
+            if (!isProcessFreezable(app)) {
+                queue.enqueueOutgoingBroadcasts(mBroadcastRecordConsumerEnqueue);
+            }
+            refreshProcessQueueLocked(queue);
+        }
+    }
+
     /**
      * Refresh the process queue corresponding to {@code app} with the latest process state
      * so that runnableAt can be updated.
      */
     @GuardedBy("mService")
-    private void refreshProcessQueueLocked(@NonNull ProcessRecord app) {
-        final BroadcastProcessQueue queue = getProcessQueue(app.processName, app.uid);
-        if (queue == null || queue.app == null || queue.app.getPid() != app.getPid()) {
-            return;
-        }
+    private void refreshProcessQueueLocked(@NonNull BroadcastProcessQueue queue) {
         setQueueProcess(queue, queue.app);
         enqueueUpdateRunningList();
     }
@@ -2101,6 +2142,12 @@
         final long dispatchDelay = r.scheduledTime[index] - r.enqueueTime;
         final long receiveDelay = 0;
         final long finishDelay = r.terminalTime[index] - r.scheduledTime[index];
+        if (DEBUG_PROCESSES) {
+            Slog.d(TAG, "Logging broadcast for "
+                    + (app != null ? app.info.packageName : "<null>")
+                    + ", stopped=" + queue.getActiveWasStopped()
+                    + ", firstLaunch=" + queue.getActiveFirstLaunch());
+        }
         if (queue != null) {
             final int packageState = queue.getActiveWasStopped()
                     ? SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED
@@ -2110,7 +2157,11 @@
                     app != null ? app.info.packageName : null, r.callerPackage,
                     r.calculateTypeForLogging(), r.getDeliveryGroupPolicy(), r.intent.getFlags(),
                     BroadcastRecord.getReceiverPriority(receiver), r.callerProcState,
-                    receiverProcessState);
+                    receiverProcessState, queue.getActiveFirstLaunch(),
+                    0L /* TODO: stoppedDuration */);
+            // Reset the states after logging
+            queue.setActiveFirstLaunch(false);
+            queue.setActiveWasStopped(false);
         }
     }
 
@@ -2120,7 +2171,7 @@
         r.nextReceiver = r.receivers.size();
         mHistory.onBroadcastFinishedLocked(r);
 
-        BroadcastQueueImpl.logBootCompletedBroadcastCompletionLatencyIfPossible(r);
+        logBootCompletedBroadcastCompletionLatencyIfPossible(r);
 
         if (r.intent.getComponent() == null && r.intent.getPackage() == null
                 && (r.intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
@@ -2216,6 +2267,59 @@
         return null;
     }
 
+    private void logBootCompletedBroadcastCompletionLatencyIfPossible(BroadcastRecord r) {
+        // Only log after last receiver.
+        // In case of split BOOT_COMPLETED broadcast, make sure only call this method on the
+        // last BroadcastRecord of the split broadcast which has non-null resultTo.
+        final int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
+        if (r.nextReceiver < numReceivers) {
+            return;
+        }
+        final String action = r.intent.getAction();
+        int event = 0;
+        if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) {
+            event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
+        } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+            event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
+        }
+        if (event != 0) {
+            final int dispatchLatency = (int) (r.dispatchTime - r.enqueueTime);
+            final int completeLatency = (int) (SystemClock.uptimeMillis() - r.enqueueTime);
+            final int dispatchRealLatency = (int) (r.dispatchRealTime - r.enqueueRealTime);
+            final int completeRealLatency = (int)
+                    (SystemClock.elapsedRealtime() - r.enqueueRealTime);
+            int userType =
+                    FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
+            // This method is called very infrequently, no performance issue we call
+            // LocalServices.getService() here.
+            final UserManagerInternal umInternal = LocalServices.getService(
+                    UserManagerInternal.class);
+            final UserInfo userInfo =
+                    (umInternal != null) ? umInternal.getUserInfo(r.userId) : null;
+            if (userInfo != null) {
+                userType = UserJourneyLogger.getUserTypeForStatsd(userInfo.userType);
+            }
+            Slog.i(TAG, "BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED action:"
+                    + action
+                    + " dispatchLatency:" + dispatchLatency
+                    + " completeLatency:" + completeLatency
+                    + " dispatchRealLatency:" + dispatchRealLatency
+                    + " completeRealLatency:" + completeRealLatency
+                    + " receiversSize:" + numReceivers
+                    + " userId:" + r.userId
+                    + " userType:" + (userInfo != null ? userInfo.userType : null));
+            FrameworkStatsLog.write(
+                    BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED,
+                    event,
+                    dispatchLatency,
+                    completeLatency,
+                    dispatchRealLatency,
+                    completeRealLatency,
+                    r.userId,
+                    userType);
+        }
+    }
+
     @Override
     @NeverCompile
     public void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId) {
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 150f406..0cf5575 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -1415,7 +1415,7 @@
 
     @GuardedBy({"mAm", "mProcLock"})
     private void freezeAppAsyncLSP(ProcessRecord app, @UptimeMillisLong long delayMillis) {
-        freezeAppAsyncInternalLSP(app, delayMillis, false);
+        freezeAppAsyncInternalLSP(app, delayMillis, false, false);
     }
 
     @GuardedBy({"mAm", "mProcLock"})
@@ -1423,11 +1423,25 @@
         freezeAppAsyncLSP(app, updateEarliestFreezableTime(app, 0));
     }
 
+    // TODO: Update freezeAppAsyncAtEarliestLSP to actually freeze the app at the earliest
+    // and remove this method.
+    @GuardedBy({"mAm", "mProcLock"})
+    void freezeAppAsyncImmediateLSP(ProcessRecord app) {
+        freezeAppAsyncInternalLSP(app, 0, false, true);
+    }
+
+    // TODO: Update this method to be private and have the existing clients call different methods.
+    // This "internal" method should not be directly triggered by clients outside this class.
     @GuardedBy({"mAm", "mProcLock"})
     void freezeAppAsyncInternalLSP(ProcessRecord app, @UptimeMillisLong long delayMillis,
-            boolean force) {
+            boolean force, boolean immediate) {
         final ProcessCachedOptimizerRecord opt = app.mOptRecord;
         if (opt.isPendingFreeze()) {
+            if (immediate) {
+                mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);
+                mFreezeHandler.sendMessage(mFreezeHandler.obtainMessage(
+                        SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app));
+            }
             // Skip redundant DO_FREEZE message
             return;
         }
@@ -2210,6 +2224,9 @@
                 case SET_FROZEN_PROCESS_MSG: {
                     ProcessRecord proc = (ProcessRecord) msg.obj;
                     synchronized (mAm) {
+                        if (!proc.mOptRecord.isPendingFreeze()) {
+                            return;
+                        }
                         freezeProcess(proc);
                     }
                     if (proc.mOptRecord.isFrozen()) {
diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java
index 4ef31bf..f2b9b25 100644
--- a/services/core/java/com/android/server/am/ConnectionRecord.java
+++ b/services/core/java/com/android/server/am/ConnectionRecord.java
@@ -17,6 +17,7 @@
 package com.android.server.am;
 
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ProcessList.UNKNOWN_ADJ;
 
 import android.annotation.Nullable;
 import android.app.IServiceConnection;
@@ -37,7 +38,7 @@
 /**
  * Description of a single binding to a service.
  */
-final class ConnectionRecord {
+final class ConnectionRecord implements OomAdjusterModernImpl.Connection{
     final AppBindRecord binding;    // The application/service binding.
     final ActivityServiceConnectionsHolder<ConnectionRecord> activity;  // If non-null, the owning activity.
     final IServiceConnection conn;  // The client connection.
@@ -127,6 +128,14 @@
         aliasComponent = _aliasComponent;
     }
 
+    @Override
+    public void computeHostOomAdjLSP(OomAdjuster oomAdjuster, ProcessRecord host,
+            ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
+            int oomAdjReason, int cachedAdj) {
+        oomAdjuster.computeServiceHostOomAdjLSP(this, host, client, now, topApp, doingAll, false,
+                false, oomAdjReason, UNKNOWN_ADJ, false, false);
+    }
+
     public long getFlags() {
         return flags;
     }
diff --git a/services/core/java/com/android/server/am/ContentProviderConnection.java b/services/core/java/com/android/server/am/ContentProviderConnection.java
index 825b938..3988277 100644
--- a/services/core/java/com/android/server/am/ContentProviderConnection.java
+++ b/services/core/java/com/android/server/am/ContentProviderConnection.java
@@ -18,6 +18,7 @@
 
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROVIDER;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ProcessList.UNKNOWN_ADJ;
 
 import android.annotation.UserIdInt;
 import android.os.Binder;
@@ -32,7 +33,8 @@
 /**
  * Represents a link between a content provider and client.
  */
-public final class ContentProviderConnection extends Binder {
+public final class ContentProviderConnection extends Binder implements
+        OomAdjusterModernImpl.Connection {
     public final ContentProviderRecord provider;
     public final ProcessRecord client;
     public final String clientPackage;
@@ -72,6 +74,14 @@
         createTime = SystemClock.elapsedRealtime();
     }
 
+    @Override
+    public void computeHostOomAdjLSP(OomAdjuster oomAdjuster, ProcessRecord host,
+            ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
+            int oomAdjReason, int cachedAdj) {
+        oomAdjuster.computeProviderHostOomAdjLSP(this, host, client, now, topApp, doingAll, false,
+                false, oomAdjReason, UNKNOWN_ADJ, false, false);
+    }
+
     public void startAssociationIfNeeded() {
         // If we don't already have an active association, create one...  but only if this
         // is an association between two different processes.
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index cb7898d..f76bf37 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -34,6 +34,7 @@
 import static com.android.internal.util.FrameworkStatsLog.PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
 import static com.android.internal.util.FrameworkStatsLog.PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
 import static com.android.server.am.ActivityManagerService.TAG_MU;
 import static com.android.server.am.Flags.serviceBindingOomAdjPolicy;
 
@@ -290,7 +291,8 @@
                             PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
                             PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
                             cpi.packageName, callingPackage,
-                            callingProcessState, callingProcessState);
+                            callingProcessState, callingProcessState,
+                            false, 0L);
                     return holder;
                 }
 
@@ -368,7 +370,7 @@
                                 PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
                                 PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
                                 cpi.packageName, callingPackage,
-                                callingProcessState, providerProcessState);
+                                callingProcessState, providerProcessState, false, 0L);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(origId);
@@ -546,12 +548,16 @@
                                     PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
                                     PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
                                     cpi.packageName, callingPackage,
-                                    callingProcessState, proc.mState.getCurProcState());
+                                    callingProcessState, proc.mState.getCurProcState(),
+                                    false, 0L);
                         } else {
-                            final int packageState =
-                                    ((cpr.appInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0)
+                            final boolean stopped =
+                                    (cpr.appInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0;
+                            final int packageState = stopped
                                     ? PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED
                                     : PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
+                            final boolean firstLaunch = !mService.wasPackageEverLaunched(
+                                    cpi.packageName, userId);
                             checkTime(startTime, "getContentProviderImpl: before start process");
                             proc = mService.startProcessLocked(
                                     cpi.processName, cpr.appInfo, false, 0,
@@ -567,12 +573,18 @@
                                         + ": process is bad");
                                 return null;
                             }
+                            if (DEBUG_PROCESSES) {
+                                Slog.d(TAG, "Logging provider access for " + cpi.packageName
+                                        + ", stopped=" + stopped + ", firstLaunch=" + firstLaunch);
+                            }
                             FrameworkStatsLog.write(
                                     PROVIDER_ACQUISITION_EVENT_REPORTED,
                                     proc.uid, callingUid,
                                     PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD,
                                     packageState, cpi.packageName, callingPackage,
-                                    callingProcessState, ActivityManager.PROCESS_STATE_NONEXISTENT);
+                                    callingProcessState, ActivityManager.PROCESS_STATE_NONEXISTENT,
+                                    firstLaunch,
+                                    0L /* TODO: stoppedDuration */);
                         }
                         cpr.launchingApp = proc;
                         mLaunchingProviders.add(cpr);
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 982076d..91b64f8 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -80,6 +80,7 @@
         sSecureSettingToTypeMap.put(Settings.Secure.MULTI_PRESS_TIMEOUT, int.class);
         sSecureSettingToTypeMap.put(Settings.Secure.KEY_REPEAT_TIMEOUT_MS, int.class);
         sSecureSettingToTypeMap.put(Settings.Secure.KEY_REPEAT_DELAY_MS, int.class);
+        sSecureSettingToTypeMap.put(Settings.Secure.STYLUS_POINTER_ICON_ENABLED, int.class);
         // add other secure settings here...
 
         sSystemSettingToTypeMap.put(Settings.System.TIME_12_24, String.class);
diff --git a/services/core/java/com/android/server/am/HostingRecord.java b/services/core/java/com/android/server/am/HostingRecord.java
index 30811a1..1a78a13 100644
--- a/services/core/java/com/android/server/am/HostingRecord.java
+++ b/services/core/java/com/android/server/am/HostingRecord.java
@@ -325,4 +325,15 @@
                 return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_UNKNOWN;
         }
     }
+
+    private static boolean isTypeActivity(String hostingType) {
+        return HOSTING_TYPE_ACTIVITY.equals(hostingType)
+                || HOSTING_TYPE_NEXT_ACTIVITY.equals(hostingType)
+                || HOSTING_TYPE_NEXT_TOP_ACTIVITY.equals(hostingType)
+                || HOSTING_TYPE_TOP_ACTIVITY.equals(hostingType);
+    }
+
+    public boolean isTypeActivity() {
+        return isTypeActivity(mHostingType);
+    }
 }
diff --git a/services/core/java/com/android/server/am/IntentBindRecord.java b/services/core/java/com/android/server/am/IntentBindRecord.java
index db47e3f..1a3652e 100644
--- a/services/core/java/com/android/server/am/IntentBindRecord.java
+++ b/services/core/java/com/android/server/am/IntentBindRecord.java
@@ -49,14 +49,6 @@
 
     String stringName;      // caching of toString
 
-    /**
-     * Mark if we've skipped oom adj update before calling into the {@link Service#onBind()}
-     * or {@link Service#onUnbind()}.
-     *
-     * <p>If it's true, we'll skip the oom adj update too during the serviceDoneExecuting.
-     */
-    boolean mSkippedOomAdj;
-
     void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("service="); pw.println(service);
         dumpInService(pw, prefix);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 31328ae..7f6d62c 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -587,7 +587,7 @@
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    private void performUpdateOomAdjLSP(@OomAdjReason int oomAdjReason) {
+    protected void performUpdateOomAdjLSP(@OomAdjReason int oomAdjReason) {
         final ProcessRecord topApp = mService.getTopApp();
         // Clear any pending ones because we are doing a full update now.
         mPendingProcessSet.clear();
@@ -913,7 +913,7 @@
     }
 
     @GuardedBy("mService")
-    private void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
+    protected void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
         final ProcessRecord topApp = mService.getTopApp();
 
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
@@ -941,7 +941,7 @@
      * must have called {@link collectReachableProcessesLocked} on it.
      */
     @GuardedBy({"mService", "mProcLock"})
-    protected void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
+    private void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
             ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles,
             boolean startProfiling) {
         final boolean fullUpdate = processes == null;
@@ -1051,7 +1051,7 @@
 
         assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
 
-        postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
+        postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime, true);
 
         if (startProfiling) {
             mService.mOomAdjProfiler.oomAdjEnded();
@@ -1073,12 +1073,12 @@
 
     @GuardedBy({"mService", "mProcLock"})
     protected void postUpdateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, ActiveUids activeUids,
-            long now, long nowElapsed, long oldTime) {
+            long now, long nowElapsed, long oldTime, boolean doingAll) {
         mNumNonCachedProcs = 0;
         mNumCachedHiddenProcs = 0;
 
         final boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids,
-                oomAdjReason);
+                oomAdjReason, doingAll);
         mNumServiceProcs = mNewNumServiceProcs;
 
         if (mService.mAlwaysFinishActivities) {
@@ -1288,7 +1288,8 @@
 
     @GuardedBy({"mService", "mProcLock"})
     private boolean updateAndTrimProcessLSP(final long now, final long nowElapsed,
-            final long oldTime, final ActiveUids activeUids, @OomAdjReason int oomAdjReason) {
+            final long oldTime, final ActiveUids activeUids, @OomAdjReason int oomAdjReason,
+            boolean doingAll) {
         ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP();
         final int numLru = lruList.size();
 
@@ -1321,7 +1322,7 @@
             if (!app.isKilledByAm() && app.getThread() != null) {
                 // We don't need to apply the update for the process which didn't get computed
                 if (state.getCompletedAdjSeq() == mAdjSeq) {
-                    applyOomAdjLSP(app, true, now, nowElapsed, oomAdjReason);
+                    applyOomAdjLSP(app, doingAll, now, nowElapsed, oomAdjReason);
                 }
 
                 if (app.isPendingFinishAttach()) {
@@ -1775,12 +1776,11 @@
             state.setAdjSeq(mAdjSeq);
             state.setCurrentSchedulingGroup(SCHED_GROUP_BACKGROUND);
             state.setCurProcState(PROCESS_STATE_CACHED_EMPTY);
+            state.setCurRawProcState(PROCESS_STATE_CACHED_EMPTY);
             state.setCurAdj(CACHED_APP_MAX_ADJ);
             state.setCurRawAdj(CACHED_APP_MAX_ADJ);
             state.setCompletedAdjSeq(state.getAdjSeq());
             state.setCurCapability(PROCESS_CAPABILITY_NONE);
-            onProcessStateChanged(app, prevProcState);
-            onProcessOomAdjChanged(app, prevAppAdj);
             return false;
         }
 
@@ -1843,8 +1843,6 @@
             state.setCurRawProcState(state.getCurProcState());
             state.setCurAdj(state.getMaxAdj());
             state.setCompletedAdjSeq(state.getAdjSeq());
-            onProcessStateChanged(app, prevProcState);
-            onProcessOomAdjChanged(app, prevAppAdj);
             // if curAdj is less than prevAppAdj, then this process was promoted
             return state.getCurAdj() < prevAppAdj || state.getCurProcState() < prevProcState;
         }
@@ -2561,7 +2559,7 @@
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    protected boolean computeServiceHostOomAdjLSP(ConnectionRecord cr, ProcessRecord app,
+    public boolean computeServiceHostOomAdjLSP(ConnectionRecord cr, ProcessRecord app,
             ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
             boolean cycleReEval, boolean computeClients, int oomAdjReason, int cachedAdj,
             boolean couldRecurse, boolean dryRun) {
@@ -2991,7 +2989,11 @@
         return updated;
     }
 
-    protected boolean computeProviderHostOomAdjLSP(ContentProviderConnection conn,
+    /**
+     * Computes the impact on {@code app} the provider connections from {@code client} has.
+     */
+    @GuardedBy({"mService", "mProcLock"})
+    public boolean computeProviderHostOomAdjLSP(ContentProviderConnection conn,
             ProcessRecord app, ProcessRecord client, long now, ProcessRecord topApp,
             boolean doingAll, boolean cycleReEval, boolean computeClients, int oomAdjReason,
             int cachedAdj, boolean couldRecurse, boolean dryRun) {
@@ -3572,7 +3574,7 @@
         int initialCapability =  PROCESS_CAPABILITY_NONE;
         boolean initialCached = true;
         final ProcessStateRecord state = app.mState;
-        final int prevProcState = state.getCurRawProcState();
+        final int prevProcState = state.getCurProcState();
         final int prevAdj = state.getCurRawAdj();
         // If the process has been marked as foreground, it is starting as the top app (with
         // Zygote#START_AS_TOP_APP_ARG), so boost the thread priority of its default UI thread.
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 1bf779a..46bdfe8 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -38,7 +38,6 @@
 import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
 import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
 
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
 import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS;
 import static com.android.server.am.ProcessList.BACKUP_APP_ADJ;
@@ -67,8 +66,10 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal.OomAdjReason;
 import android.content.pm.ServiceInfo;
+import android.os.IBinder;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
 
@@ -80,6 +81,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 /**
@@ -275,6 +277,7 @@
                 mProcessRecordNodes[i] = new LinkedProcessRecordList(type);
             }
             mLastNode = new ProcessRecordNode[size];
+            resetLastNodes();
         }
 
         int size() {
@@ -285,7 +288,7 @@
         void reset() {
             for (int i = 0; i < mProcessRecordNodes.length; i++) {
                 mProcessRecordNodes[i].reset();
-                mLastNode[i] = null;
+                setLastNodeToHead(i);
             }
         }
 
@@ -509,26 +512,118 @@
     }
 
     /**
-     * A helper consumer for collecting processes that have not been reached yet. To avoid object
-     * allocations every OomAdjuster update, the results will be stored in
-     * {@link UnreachedProcessCollector#processList}. The process list reader is responsible
-     * for setting it before usage, as well as, clearing the reachable state of each process in the
-     * list.
+     * A {@link Connection} represents any connection between two processes that can cause a
+     * change in importance in the host process based on the client process and connection state.
      */
-    private static class UnreachedProcessCollector implements Consumer<ProcessRecord> {
-        public ArrayList<ProcessRecord> processList = null;
+    public interface Connection {
+        /**
+         * Compute the impact this connection has on the host's importance values.
+         */
+        void computeHostOomAdjLSP(OomAdjuster oomAdjuster, ProcessRecord host, ProcessRecord client,
+                long now, ProcessRecord topApp, boolean doingAll, int oomAdjReason, int cachedAdj);
+    }
+
+    /**
+     * A helper consumer for marking and collecting reachable processes.
+     */
+    private static class ReachableCollectingConsumer implements
+            BiConsumer<Connection, ProcessRecord> {
+        ArrayList<ProcessRecord> mReachables = null;
+
+        public void init(ArrayList<ProcessRecord> reachables) {
+            mReachables = reachables;
+        }
+
         @Override
-        public void accept(ProcessRecord process) {
-            if (process.mState.isReachable()) {
+        public void accept(Connection unused, ProcessRecord host) {
+            if (host.mState.isReachable()) {
                 return;
             }
-            process.mState.setReachable(true);
-            processList.add(process);
+            host.mState.setReachable(true);
+            mReachables.add(host);
         }
     }
 
-    private final UnreachedProcessCollector mUnreachedProcessCollector =
-            new UnreachedProcessCollector();
+    private final ReachableCollectingConsumer mReachableCollectingConsumer =
+            new ReachableCollectingConsumer();
+
+    /**
+     * A helper consumer for computing the importance of a connection from a client.
+     * Connections for clients marked reachable will be ignored.
+     */
+    private class ComputeConnectionIgnoringReachableClientsConsumer implements
+            BiConsumer<Connection, ProcessRecord> {
+        public OomAdjusterArgs args = null;
+
+        @Override
+        public void accept(Connection conn, ProcessRecord client) {
+            final ProcessRecord host = args.mApp;
+            final ProcessRecord topApp = args.mTopApp;
+            final long now = args.mNow;
+            final @OomAdjReason int oomAdjReason = args.mOomAdjReason;
+
+            if (client.mState.isReachable()) return;
+
+            conn.computeHostOomAdjLSP(OomAdjusterModernImpl.this, host, client, now, topApp, false,
+                    oomAdjReason, UNKNOWN_ADJ);
+        }
+    }
+
+    private final ComputeConnectionIgnoringReachableClientsConsumer
+            mComputeConnectionIgnoringReachableClientsConsumer =
+            new ComputeConnectionIgnoringReachableClientsConsumer();
+
+    /**
+     * A helper consumer for computing host process importance from a connection from a client app.
+     */
+    private class ComputeHostConsumer implements BiConsumer<Connection, ProcessRecord> {
+        public OomAdjusterArgs args = null;
+
+        @Override
+        public void accept(Connection conn, ProcessRecord host) {
+            final ProcessRecord client = args.mApp;
+            final int cachedAdj = args.mCachedAdj;
+            final ProcessRecord topApp = args.mTopApp;
+            final long now = args.mNow;
+            final @OomAdjReason int oomAdjReason = args.mOomAdjReason;
+            final boolean fullUpdate = args.mFullUpdate;
+
+            final int prevProcState = host.mState.getCurProcState();
+            final int prevAdj = host.mState.getCurRawAdj();
+
+            conn.computeHostOomAdjLSP(OomAdjusterModernImpl.this, host, client, now, topApp,
+                    fullUpdate, oomAdjReason, cachedAdj);
+
+            updateProcStateSlotIfNecessary(host, prevProcState);
+            updateAdjSlotIfNecessary(host, prevAdj);
+        }
+    }
+    private final ComputeHostConsumer mComputeHostConsumer = new ComputeHostConsumer();
+
+    /**
+     * A helper consumer for computing all connections from an app.
+     */
+    private class ComputeConnectionsConsumer implements Consumer<OomAdjusterArgs> {
+        @Override
+        public void accept(OomAdjusterArgs args) {
+            final ProcessRecord app = args.mApp;
+            final ActiveUids uids = args.mUids;
+
+            // This process was updated in some way, mark that it was last calculated this sequence.
+            app.mState.setCompletedAdjSeq(mAdjSeq);
+            if (uids != null) {
+                final UidRecord uidRec = app.getUidRecord();
+
+                if (uidRec != null) {
+                    uids.put(uidRec.getUid(), uidRec);
+                }
+            }
+            mComputeHostConsumer.args = args;
+            forEachConnectionLSP(app, mComputeHostConsumer);
+        }
+    }
+    private final ComputeConnectionsConsumer mComputeConnectionsConsumer =
+            new ComputeConnectionsConsumer();
 
     OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
             ActiveUids activeUids) {
@@ -617,6 +712,12 @@
         }
     }
 
+    private void updateAdjSlot(ProcessRecord app, int prevRawAdj) {
+        final int slot = adjToSlot(app.mState.getCurRawAdj());
+        final int prevSlot = adjToSlot(prevRawAdj);
+        mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot);
+    }
+
     private void updateProcStateSlotIfNecessary(ProcessRecord app, int prevProcState) {
         if (app.mState.getCurProcState() != prevProcState) {
             final int slot = processStateToSlot(app.mState.getCurProcState());
@@ -627,359 +728,247 @@
         }
     }
 
+    private void updateProcStateSlot(ProcessRecord app, int prevProcState) {
+        final int slot = processStateToSlot(app.mState.getCurProcState());
+        final int prevSlot = processStateToSlot(prevProcState);
+        mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot);
+    }
+
     @Override
-    protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
+    protected void performUpdateOomAdjLSP(@OomAdjReason int oomAdjReason) {
         final ProcessRecord topApp = mService.getTopApp();
+        // Clear any pending ones because we are doing a full update now.
+        mPendingProcessSet.clear();
+        mService.mAppProfiler.mHasPreviousProcess = mService.mAppProfiler.mHasHomeProcess = false;
 
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
         mService.mOomAdjProfiler.oomAdjStarted();
-        mAdjSeq++;
 
-        final ProcessStateRecord state = app.mState;
-        final int oldAdj = state.getCurRawAdj();
-        final int cachedAdj = oldAdj >= CACHED_APP_MIN_ADJ
-                ? oldAdj : UNKNOWN_ADJ;
-
-        final ActiveUids uids = mTmpUidRecords;
-        final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet;
-        final ArrayList<ProcessRecord> reachableProcesses = mTmpProcessList;
-        final long now = SystemClock.uptimeMillis();
-        final long nowElapsed = SystemClock.elapsedRealtime();
-
-        uids.clear();
-        targetProcesses.clear();
-        targetProcesses.add(app);
-        reachableProcesses.clear();
-
-        // Find out all reachable processes from this app.
-        collectReachableProcessesLocked(targetProcesses, reachableProcesses, uids);
-
-        // Copy all of the reachable processes into the target process set.
-        targetProcesses.addAll(reachableProcesses);
-        reachableProcesses.clear();
-
-        final boolean result = performNewUpdateOomAdjLSP(oomAdjReason,
-                topApp, targetProcesses, uids, false, now, cachedAdj);
-
-        reachableProcesses.addAll(targetProcesses);
-        assignCachedAdjIfNecessary(reachableProcesses);
-        for (int  i = uids.size() - 1; i >= 0; i--) {
-            final UidRecord uidRec = uids.valueAt(i);
-            uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP);
-        }
-        updateUidsLSP(uids, nowElapsed);
-        for (int i = 0, size = targetProcesses.size(); i < size; i++) {
-            applyOomAdjLSP(targetProcesses.valueAt(i), false, now, nowElapsed, oomAdjReason);
-        }
-        targetProcesses.clear();
-        reachableProcesses.clear();
+        fullUpdateLSP(oomAdjReason);
 
         mService.mOomAdjProfiler.oomAdjEnded();
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-        return result;
     }
 
     @GuardedBy({"mService", "mProcLock"})
     @Override
-    protected void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
-            ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles,
-            boolean startProfiling) {
-        final boolean fullUpdate = processes == null;
-        final ArrayList<ProcessRecord> activeProcesses = fullUpdate
-                ? mProcessList.getLruProcessesLOSP() : processes;
-        ActiveUids activeUids = uids;
-        if (activeUids == null) {
-            final int numUids = mActiveUids.size();
-            activeUids = mTmpUidRecords;
-            activeUids.clear();
-            for (int i = 0; i < numUids; i++) {
-                UidRecord uidRec = mActiveUids.valueAt(i);
-                activeUids.put(uidRec.getUid(), uidRec);
-            }
-        }
-
-        if (startProfiling) {
-            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
-            mService.mOomAdjProfiler.oomAdjStarted();
-        }
-        final long now = SystemClock.uptimeMillis();
-        final long nowElapsed = SystemClock.elapsedRealtime();
-        final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
-        final int numProc = activeProcesses.size();
-
-        mAdjSeq++;
-        if (fullUpdate) {
-            mNewNumServiceProcs = 0;
-            mNewNumAServiceProcs = 0;
-        }
-
-        final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet;
-        targetProcesses.clear();
-        if (!fullUpdate) {
-            targetProcesses.addAll(activeProcesses);
-        }
-
-        performNewUpdateOomAdjLSP(oomAdjReason, topApp, targetProcesses, activeUids,
-                fullUpdate, now, UNKNOWN_ADJ);
-
-        // TODO: b/319163103 - optimize cache adj assignment to not require the whole lru list.
-        assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
-        postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
-        targetProcesses.clear();
-
-        if (startProfiling) {
-            mService.mOomAdjProfiler.oomAdjEnded();
-            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-        }
-        return;
-    }
-
-    /**
-     * Perform the oom adj update on the given {@code targetProcesses}.
-     *
-     * <p>Note: The expectation to the given {@code targetProcesses} is, the caller
-     * must have called {@link collectReachableProcessesLocked} on it.
-     */
-    private boolean performNewUpdateOomAdjLSP(@OomAdjReason int oomAdjReason,
-            ProcessRecord topApp,  ArraySet<ProcessRecord> targetProcesses, ActiveUids uids,
-            boolean fullUpdate, long now, int cachedAdj) {
-
-        final ArrayList<ProcessRecord> clientProcesses = mTmpProcessList2;
-        clientProcesses.clear();
-
-        // We'll need to collect the upstream processes of the target apps here, because those
-        // processes would potentially impact the procstate/adj via bindings.
-        if (!fullUpdate) {
-            collectExcludedClientProcessesLocked(targetProcesses, clientProcesses);
-
-            for (int i = 0, size = targetProcesses.size(); i < size; i++) {
-                final ProcessRecord app = targetProcesses.valueAt(i);
-                app.mState.resetCachedInfo();
-                final UidRecord uidRec = app.getUidRecord();
-                if (uidRec != null) {
-                    if (DEBUG_UID_OBSERVERS) {
-                        Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
-                    }
-                    uidRec.reset();
-                }
-            }
-        } else {
-            final ArrayList<ProcessRecord> lru = mProcessList.getLruProcessesLOSP();
-            for (int i = 0, size = lru.size(); i < size; i++) {
-                final ProcessRecord app = lru.get(i);
-                app.mState.resetCachedInfo();
-                final UidRecord uidRec = app.getUidRecord();
-                if (uidRec != null) {
-                    if (DEBUG_UID_OBSERVERS) {
-                        Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
-                    }
-                    uidRec.reset();
-                }
-            }
-        }
-
-        updateNewOomAdjInnerLSP(oomAdjReason, topApp, targetProcesses, clientProcesses, uids,
-                cachedAdj, now, fullUpdate);
-
-        clientProcesses.clear();
-
+    protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
+        mPendingProcessSet.add(app);
+        performUpdateOomAdjPendingTargetsLocked(oomAdjReason);
         return true;
     }
 
-    /**
-     * Collect the client processes from the given {@code apps}, the result will be returned in the
-     * given {@code clientProcesses}, which will <em>NOT</em> include the processes from the given
-     * {@code apps}.
-     */
     @GuardedBy("mService")
-    private void collectExcludedClientProcessesLocked(ArraySet<ProcessRecord> apps,
-            ArrayList<ProcessRecord> clientProcesses) {
-        // Mark all of the provided apps as reachable to avoid including them in the client list.
-        final int appsSize = apps.size();
-        for (int i = 0; i < appsSize; i++) {
-            final ProcessRecord app = apps.valueAt(i);
-            app.mState.setReachable(true);
-        }
+    @Override
+    protected void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
+        mService.mOomAdjProfiler.oomAdjStarted();
 
-        clientProcesses.clear();
-        mUnreachedProcessCollector.processList = clientProcesses;
-        for (int i = 0; i < appsSize; i++) {
-            final ProcessRecord app = apps.valueAt(i);
-            app.forEachClient(mUnreachedProcessCollector);
+        synchronized (mProcLock) {
+            partialUpdateLSP(oomAdjReason, mPendingProcessSet);
         }
-        mUnreachedProcessCollector.processList = null;
+        mPendingProcessSet.clear();
 
-        // Reset the temporary bits.
-        for (int i = clientProcesses.size() - 1; i >= 0; i--) {
-            clientProcesses.get(i).mState.setReachable(false);
-        }
-        for (int i = 0, size = apps.size(); i < size; i++) {
-            final ProcessRecord app = apps.valueAt(i);
-            app.mState.setReachable(false);
-        }
+        mService.mOomAdjProfiler.oomAdjEnded();
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 
+    /**
+     * Perform a full update on the entire process list.
+     */
     @GuardedBy({"mService", "mProcLock"})
-    private void updateNewOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
-            ArraySet<ProcessRecord> targetProcesses, ArrayList<ProcessRecord> clientProcesses,
-            ActiveUids uids, int cachedAdj, long now, boolean fullUpdate) {
-        mTmpOomAdjusterArgs.update(topApp, now, cachedAdj, oomAdjReason, uids, fullUpdate);
-
-        mProcessRecordProcStateNodes.resetLastNodes();
-        mProcessRecordAdjNodes.resetLastNodes();
-
-        final int procStateTarget = mProcessRecordProcStateNodes.size() - 1;
-        final int adjTarget = mProcessRecordAdjNodes.size() - 1;
+    private void fullUpdateLSP(@OomAdjReason int oomAdjReason) {
+        final ProcessRecord topApp = mService.getTopApp();
+        final long now = SystemClock.uptimeMillis();
+        final long nowElapsed = SystemClock.elapsedRealtime();
+        final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
 
         mAdjSeq++;
-        // All apps to be updated will be moved to the lowest slot.
-        if (fullUpdate) {
-            // Move all the process record node to the lowest slot, we'll do recomputation on all of
-            // them. Use the processes from the lru list, because the scanning order matters here.
-            final ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP();
-            for (int i = procStateTarget; i >= 0; i--) {
-                mProcessRecordProcStateNodes.reset(i);
-                // Force the last node to the head since we'll recompute all of them.
-                mProcessRecordProcStateNodes.setLastNodeToHead(i);
+
+        mNewNumServiceProcs = 0;
+        mNewNumAServiceProcs = 0;
+
+        // Clear the priority queues.
+        mProcessRecordProcStateNodes.reset();
+        mProcessRecordAdjNodes.reset();
+
+        final ArrayList<ProcessRecord> lru = mProcessList.getLruProcessesLOSP();
+        for (int i = lru.size() - 1; i >= 0; i--) {
+            final ProcessRecord app = lru.get(i);
+            final int prevProcState = app.mState.getCurProcState();
+            final int prevAdj = app.mState.getCurRawAdj();
+            app.mState.resetCachedInfo();
+            final UidRecord uidRec = app.getUidRecord();
+            if (uidRec != null) {
+                if (DEBUG_UID_OBSERVERS) {
+                    Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
+                }
+                uidRec.reset();
             }
-            // enqueue the targets in the reverse order of the lru list.
-            for (int i = lruList.size() - 1; i >= 0; i--) {
-                mProcessRecordProcStateNodes.append(lruList.get(i), procStateTarget);
-            }
-            // Do the same to the adj nodes.
-            for (int i = adjTarget; i >= 0; i--) {
-                mProcessRecordAdjNodes.reset(i);
-                // Force the last node to the head since we'll recompute all of them.
-                mProcessRecordAdjNodes.setLastNodeToHead(i);
-            }
-            for (int i = lruList.size() - 1; i >= 0; i--) {
-                mProcessRecordAdjNodes.append(lruList.get(i), adjTarget);
-            }
-        } else {
-            // Move the target processes to the lowest slot.
-            for (int i = 0, size = targetProcesses.size(); i < size; i++) {
-                final ProcessRecord app = targetProcesses.valueAt(i);
-                final int procStateSlot = processStateToSlot(app.mState.getCurProcState());
-                final int adjSlot = adjToSlot(app.mState.getCurRawAdj());
-                mProcessRecordProcStateNodes.moveAppTo(app, procStateSlot, procStateTarget);
-                mProcessRecordAdjNodes.moveAppTo(app, adjSlot, adjTarget);
-            }
-            // Move the "lastNode" to head to make sure we scan all nodes in this slot.
-            mProcessRecordProcStateNodes.setLastNodeToHead(procStateTarget);
-            mProcessRecordAdjNodes.setLastNodeToHead(adjTarget);
+
+            // Compute initial values, the procState and adj priority queues will be populated here.
+            computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, true, now, false, false, oomAdjReason,
+                    false);
+            updateProcStateSlot(app, prevProcState);
+            updateAdjSlot(app, prevAdj);
         }
 
-        // All apps to be updated have been moved to the lowest slot.
-        // Do an initial pass of the computation.
-        mProcessRecordProcStateNodes.forEachNewNode(mProcessRecordProcStateNodes.size() - 1,
-                this::computeInitialOomAdjLSP);
-
-        if (!fullUpdate) {
-            // We didn't update the client processes with the computeInitialOomAdjLSP
-            // because they don't need to do so. But they'll be playing vital roles in
-            // computing the bindings. So include them into the scan list below.
-            for (int i = 0, size = clientProcesses.size(); i < size; i++) {
-                mProcessRecordProcStateNodes.moveAppToTail(clientProcesses.get(i));
-            }
-            // We don't update the adj list since we're resetting it below.
-        }
-
-        // Now nodes are set into their slots, without factoring in the bindings.
-        // The nodes between the `lastNode` pointer and the TAIL should be the new nodes.
-        //
-        // The whole rationale here is that, the bindings from client to host app, won't elevate
-        // the host app's procstate/adj higher than the client app's state (BIND_ABOVE_CLIENT
-        // is a special case here, but client app's raw adj is still no less than the host app's).
-        // Therefore, starting from the top to the bottom, for each slot, scan all of the new nodes,
-        // check its bindings, elevate its host app's slot if necessary.
-        //
-        // We'd have to do this in two passes: 1) scan procstate node list; 2) scan adj node list.
-        // Because the procstate and adj are not always in sync - there are cases where
-        // the processes with lower proc state could be getting a higher oom adj score.
-        // And because of this, the procstate and adj node lists are basically two priority heaps.
-        //
-        // As the 2nd pass with the adj node lists potentially includes a significant amount of
-        // duplicated scans as the 1st pass has done, we'll reset the last node pointers for
-        // the adj node list before the 1st pass; so during the 1st pass, if any app's adj slot
-        // gets bumped, we'll only scan those in 2nd pass.
-
+        // Set adj last nodes now, this way a process will only be reevaluated during the adj node
+        // iteration if they adj score changed during the procState node iteration.
         mProcessRecordAdjNodes.resetLastNodes();
+        mTmpOomAdjusterArgs.update(topApp, now, UNKNOWN_ADJ, oomAdjReason, null, true);
+        computeConnectionsLSP();
 
+        assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+        postUpdateOomAdjInnerLSP(oomAdjReason, mActiveUids, now, nowElapsed, oldTime, true);
+    }
+
+    /**
+     * Traverse the process graph and update processes based on changes in connection importances.
+     */
+    @GuardedBy({"mService", "mProcLock"})
+    private void computeConnectionsLSP() {
         // 1st pass, scan each slot in the procstate node list.
         for (int i = 0, end = mProcessRecordProcStateNodes.size() - 1; i < end; i++) {
-            mProcessRecordProcStateNodes.forEachNewNode(i, this::computeHostOomAdjLSP);
+            mProcessRecordProcStateNodes.forEachNewNode(i, mComputeConnectionsConsumer);
         }
 
         // 2nd pass, scan each slot in the adj node list.
         for (int i = 0, end = mProcessRecordAdjNodes.size() - 1; i < end; i++) {
-            mProcessRecordAdjNodes.forEachNewNode(i, this::computeHostOomAdjLSP);
+            mProcessRecordAdjNodes.forEachNewNode(i, mComputeConnectionsConsumer);
         }
     }
 
-    @GuardedBy({"mService", "mProcLock"})
-    private void computeInitialOomAdjLSP(OomAdjusterArgs args) {
-        final ProcessRecord app = args.mApp;
-        final int cachedAdj = args.mCachedAdj;
-        final ProcessRecord topApp = args.mTopApp;
-        final long now = args.mNow;
-        final int oomAdjReason = args.mOomAdjReason;
-        final ActiveUids uids = args.mUids;
-        final boolean fullUpdate = args.mFullUpdate;
-
-        if (DEBUG_OOM_ADJ) {
-            Slog.i(TAG, "OOM ADJ initial args app=" + app
-                    + " cachedAdj=" + cachedAdj
-                    + " topApp=" + topApp
-                    + " now=" + now
-                    + " oomAdjReason=" + oomAdjReasonToString(oomAdjReason)
-                    + " fullUpdate=" + fullUpdate);
-        }
-
-        if (uids != null) {
-            final UidRecord uidRec = app.getUidRecord();
-
-            if (uidRec != null) {
-                uids.put(uidRec.getUid(), uidRec);
-            }
-        }
-
-        computeOomAdjLSP(app, cachedAdj, topApp, fullUpdate, now, false, false, oomAdjReason,
-                false);
-    }
-
     /**
-     * @return The proposed change to the schedGroup.
+     * Perform a partial update on the target processes and their reachable processes.
      */
     @GuardedBy({"mService", "mProcLock"})
-    @Override
-    protected int setIntermediateAdjLSP(ProcessRecord app, int adj, int prevRawAppAdj,
-            int schedGroup) {
-        schedGroup = super.setIntermediateAdjLSP(app, adj, prevRawAppAdj, schedGroup);
+    private void partialUpdateLSP(@OomAdjReason int oomAdjReason, ArraySet<ProcessRecord> targets) {
+        final ProcessRecord topApp = mService.getTopApp();
+        final long now = SystemClock.uptimeMillis();
+        final long nowElapsed = SystemClock.elapsedRealtime();
+        final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
 
-        updateAdjSlotIfNecessary(app, prevRawAppAdj);
+        ActiveUids activeUids = mTmpUidRecords;
+        activeUids.clear();
+        mTmpOomAdjusterArgs.update(topApp, now, UNKNOWN_ADJ, oomAdjReason, activeUids, false);
 
-        return schedGroup;
+        mAdjSeq++;
+
+        final ArrayList<ProcessRecord> reachables = mTmpProcessList;
+        reachables.clear();
+
+        for (int i = 0, size = targets.size(); i < size; i++) {
+            final ProcessRecord target = targets.valueAtUnchecked(i);
+            target.mState.resetCachedInfo();
+            target.mState.setReachable(true);
+            reachables.add(target);
+        }
+
+        // Collect all processes that are reachable.
+        // Any process not found in this step will not change in importance during this update.
+        collectAndMarkReachableProcessesLSP(reachables);
+
+        // Initialize the reachable processes based on their own values plus any
+        // connections from processes not found in the previous step. Since those non-reachable
+        // processes cannot change as a part of this update, their current values can be used
+        // right now.
+        mProcessRecordProcStateNodes.resetLastNodes();
+        initReachableStatesLSP(reachables, mTmpOomAdjusterArgs);
+
+        // Set adj last nodes now, this way a process will only be reevaluated during the adj node
+        // iteration if they adj score changed during the procState node iteration.
+        mProcessRecordAdjNodes.resetLastNodes();
+        // Now traverse and compute the connections of processes with changed importance.
+        computeConnectionsLSP();
+
+        boolean unassignedAdj = false;
+        for (int i = 0, size = reachables.size(); i < size; i++) {
+            final ProcessStateRecord state = reachables.get(i).mState;
+            state.setReachable(false);
+            state.setCompletedAdjSeq(mAdjSeq);
+            if (state.getCurAdj() >= UNKNOWN_ADJ) {
+                unassignedAdj = true;
+            }
+        }
+
+        // If all processes have an assigned adj, no need to calculate and assign cached adjs.
+        if (unassignedAdj) {
+            // TODO: b/319163103 - optimize cache adj assignment to not require the whole lru list.
+            assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+        }
+
+        // Repopulate any uid record that may have changed.
+        for (int i = 0, size = activeUids.size(); i < size; i++) {
+            final UidRecord ur = activeUids.valueAt(i);
+            ur.reset();
+            for (int j = ur.getNumOfProcs() - 1; j >= 0; j--) {
+                final ProcessRecord proc = ur.getProcessRecordByIndex(j);
+                updateAppUidRecIfNecessaryLSP(proc);
+            }
+        }
+
+        postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime, false);
     }
 
+    /**
+     * Mark all processes reachable from the {@code reachables} processes and add them to the
+     * provided {@code reachables} list (targets excluded).
+     *
+     * Returns true if a cycle exists within the reachable process graph.
+     */
     @GuardedBy({"mService", "mProcLock"})
-    @Override
-    protected void setIntermediateProcStateLSP(ProcessRecord app, int procState,
-            int prevProcState) {
-        super.setIntermediateProcStateLSP(app, procState, prevProcState);
-
-        updateProcStateSlotIfNecessary(app, prevProcState);
+    private void collectAndMarkReachableProcessesLSP(ArrayList<ProcessRecord> reachables) {
+        mReachableCollectingConsumer.init(reachables);
+        for (int i = 0; i < reachables.size(); i++) {
+            ProcessRecord pr = reachables.get(i);
+            forEachConnectionLSP(pr, mReachableCollectingConsumer);
+        }
     }
 
+    /**
+     * Calculate initial importance states for {@code reachables} and update their slot position
+     * if necessary.
+     */
+    private void initReachableStatesLSP(ArrayList<ProcessRecord> reachables, OomAdjusterArgs args) {
+        for (int i = 0, size = reachables.size(); i < size; i++) {
+            final ProcessRecord reachable = reachables.get(i);
+            final int prevProcState = reachable.mState.getCurProcState();
+            final int prevAdj = reachable.mState.getCurRawAdj();
+
+            args.mApp = reachable;
+            computeOomAdjIgnoringReachablesLSP(args);
+
+            updateProcStateSlot(reachable, prevProcState);
+            updateAdjSlot(reachable, prevAdj);
+        }
+    }
+
+    /**
+     * Calculate initial importance states for {@code app}.
+     * Processes not marked reachable cannot change as a part of this update, so connections from
+     * those process can be calculated now.
+     */
     @GuardedBy({"mService", "mProcLock"})
-    private void computeHostOomAdjLSP(OomAdjusterArgs args) {
+    private void computeOomAdjIgnoringReachablesLSP(OomAdjusterArgs args) {
         final ProcessRecord app = args.mApp;
-        final int cachedAdj = args.mCachedAdj;
         final ProcessRecord topApp = args.mTopApp;
         final long now = args.mNow;
         final @OomAdjReason int oomAdjReason = args.mOomAdjReason;
-        final boolean fullUpdate = args.mFullUpdate;
-        final ActiveUids uids = args.mUids;
 
+        computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, false, now, false, false, oomAdjReason, false);
+
+        mComputeConnectionIgnoringReachableClientsConsumer.args = args;
+        forEachClientConnectionLSP(app, mComputeConnectionIgnoringReachableClientsConsumer);
+    }
+
+    /**
+     * Stream the connections with {@code app} as a client to
+     * {@code connectionConsumer}.
+     */
+    @GuardedBy({"mService", "mProcLock"})
+    private static void forEachConnectionLSP(ProcessRecord app,
+            BiConsumer<Connection, ProcessRecord> connectionConsumer) {
         final ProcessServiceRecord psr = app.mServices;
         for (int i = psr.numberOfConnections() - 1; i >= 0; i--) {
             ConnectionRecord cr = psr.getConnectionAt(i);
@@ -987,16 +976,14 @@
                     ? cr.binding.service.isolationHostProc : cr.binding.service.app;
             if (service == null || service == app
                     || (service.mState.getMaxAdj() >= SYSTEM_ADJ
-                            && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+                    && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
                     || (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ
-                            && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
-                            && service.mState.getCurProcState() <= PROCESS_STATE_TOP)
+                    && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+                    && service.mState.getCurProcState() <= PROCESS_STATE_TOP)
                     || (service.isSdkSandbox && cr.binding.attributedClient != null)) {
                 continue;
             }
-
-            computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false,
-                    oomAdjReason, cachedAdj, false, false);
+            connectionConsumer.accept(cr, service);
         }
 
         for (int i = psr.numberOfSdkSandboxConnections() - 1; i >= 0; i--) {
@@ -1004,15 +991,13 @@
             final ProcessRecord service = cr.binding.service.app;
             if (service == null || service == app
                     || (service.mState.getMaxAdj() >= SYSTEM_ADJ
-                            && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+                    && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
                     || (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ
-                            && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
-                            && service.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
+                    && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+                    && service.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
                 continue;
             }
-
-            computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false,
-                    oomAdjReason, cachedAdj, false, false);
+            connectionConsumer.accept(cr, service);
         }
 
         final ProcessProviderRecord ppr = app.mProviders;
@@ -1021,15 +1006,51 @@
             ProcessRecord provider = cpc.provider.proc;
             if (provider == null || provider == app
                     || (provider.mState.getMaxAdj() >= ProcessList.SYSTEM_ADJ
-                            && provider.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+                    && provider.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
                     || (provider.mState.getCurAdj() <= FOREGROUND_APP_ADJ
-                            && provider.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
-                            && provider.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
+                    && provider.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+                    && provider.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
                 continue;
             }
+            connectionConsumer.accept(cpc, provider);
+        }
+    }
 
-            computeProviderHostOomAdjLSP(cpc, provider, app, now, topApp, fullUpdate, false, false,
-                    oomAdjReason, cachedAdj, false, false);
+    /**
+     * Stream the connections from clients with {@code app} as the host to {@code
+     * connectionConsumer}.
+     */
+    @GuardedBy({"mService", "mProcLock"})
+    private static void forEachClientConnectionLSP(ProcessRecord app,
+            BiConsumer<Connection, ProcessRecord> connectionConsumer) {
+        final ProcessServiceRecord psr = app.mServices;
+
+        for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
+            final ServiceRecord s = psr.getRunningServiceAt(i);
+            final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
+                    s.getConnections();
+            for (int j = serviceConnections.size() - 1; j >= 0; j--) {
+                final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j);
+                for (int k = clist.size() - 1; k >= 0; k--) {
+                    final ConnectionRecord cr = clist.get(k);
+                    final ProcessRecord client;
+                    if (app.isSdkSandbox && cr.binding.attributedClient != null) {
+                        client = cr.binding.attributedClient;
+                    } else {
+                        client = cr.binding.client;
+                    }
+                    connectionConsumer.accept(cr, client);
+                }
+            }
+        }
+
+        final ProcessProviderRecord ppr = app.mProviders;
+        for (int i = ppr.numberOfProviders() - 1; i >= 0; i--) {
+            final ContentProviderRecord cpr = ppr.getProviderAt(i);
+            for (int j = cpr.connections.size() - 1; j >= 0; j--) {
+                final ContentProviderConnection conn = cpr.connections.get(j);
+                connectionConsumer.accept(conn, conn.client);
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 2ef433c..0aa1a69 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -718,9 +718,7 @@
                         mService.mContext, mApp.info.packageName, mApp.info.flags);
             }
         }
-        for (BroadcastQueue queue : mService.mBroadcastQueues) {
-            queue.onApplicationProblemLocked(mApp);
-        }
+        mService.getBroadcastQueue().onApplicationProblemLocked(mApp);
     }
 
     @GuardedBy("mService")
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 89c8994..48a9d6a 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -59,6 +59,8 @@
 import static com.android.server.am.ActivityManagerService.TAG_NETWORK;
 import static com.android.server.am.ActivityManagerService.TAG_PROCESSES;
 import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS;
+import static com.android.server.wm.WindowProcessController.STOPPED_STATE_FIRST_LAUNCH;
+import static com.android.server.wm.WindowProcessController.STOPPED_STATE_FORCE_STOPPED;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -208,7 +210,7 @@
 
     // Number of levels we have available for different service connection group importance
     // levels.
-    static final int CACHED_APP_IMPORTANCE_LEVELS = 5;
+    public static final int CACHED_APP_IMPORTANCE_LEVELS = 5;
 
     // The B list of SERVICE_ADJ -- these are the old and decrepit
     // services that aren't as shiny and interesting as the ones in the A list.
@@ -2086,8 +2088,10 @@
                     + " with non-zero pid:" + app.getPid());
         }
         app.setDisabledCompatChanges(null);
+        app.setLoggableCompatChanges(null);
         if (mPlatformCompat != null) {
             app.setDisabledCompatChanges(mPlatformCompat.getDisabledChanges(app.info));
+            app.setLoggableCompatChanges(mPlatformCompat.getLoggableChanges(app.info));
         }
         final long startSeq = ++mProcStartSeqCounter;
         app.setStartSeq(startSeq);
@@ -3327,19 +3331,24 @@
                 hostingRecord.getDefiningUid(), hostingRecord.getDefiningProcessName());
         final ProcessStateRecord state = r.mState;
 
+        final boolean wasStopped = (info.flags & ApplicationInfo.FLAG_STOPPED) != 0;
         // Check if we should mark the processrecord for first launch after force-stopping
-        if ((r.getApplicationInfo().flags & ApplicationInfo.FLAG_STOPPED) != 0) {
-            try {
-                final boolean wasPackageEverLaunched = mService.getPackageManagerInternal()
+        if (wasStopped) {
+            // Check if the hosting record is for an activity or not. Since the stopped
+            // state tracking is handled differently to avoid WM calling back into AM,
+            // store the state in the correct record
+            if (hostingRecord.isTypeActivity()) {
+                final boolean wasPackageEverLaunched = mService
                         .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);
-                }
-            } catch (IllegalArgumentException e) {
-                // App doesn't have state yet, so wasn't forcestopped
+                // If the package was launched in the past but is currently stopped, only then
+                // should it be considered as force-stopped.
+                @WindowProcessController.StoppedState int stoppedState = wasPackageEverLaunched
+                        ? STOPPED_STATE_FORCE_STOPPED
+                        : STOPPED_STATE_FIRST_LAUNCH;
+                r.getWindowProcessController().setStoppedState(stoppedState);
+            } else {
+                r.setWasForceStopped(true);
+                // first launch is computed just before logging, for non-activity types
             }
         }
 
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 7009bd0..b939089 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -69,10 +69,8 @@
 import com.android.server.wm.WindowProcessListener;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.function.Consumer;
 
 /**
  * Full information about a particular process that
@@ -261,6 +259,12 @@
     private long[] mDisabledCompatChanges;
 
     /**
+     * Set of compat changes for the process that are intended to be logged to logcat.
+     */
+    @GuardedBy("mService")
+    private long[] mLoggableCompatChanges;
+
+    /**
      * Who is watching for the death.
      */
     @GuardedBy("mService")
@@ -441,6 +445,7 @@
     final ProcessRecordNode[] mLinkedNodes = new ProcessRecordNode[NUM_NODE_TYPE];
 
     /** Whether the app was launched from a stopped state and is being unstopped. */
+    @GuardedBy("mService")
     volatile boolean mWasForceStopped;
 
     void setStartParams(int startUid, HostingRecord hostingRecord, String seInfo,
@@ -686,6 +691,11 @@
 
     @GuardedBy({"mService", "mProcLock"})
     void setPid(int pid) {
+        // If the pid is changing and not the first time pid is being assigned, clear stopped state
+        // So if the process record is re-used for a different pid, it wouldn't keep the state.
+        if (pid != mPid && mPid != 0) {
+            setWasForceStopped(false);
+        }
         mPid = pid;
         mWindowProcessController.setPid(pid);
         mShortStringName = null;
@@ -931,11 +941,21 @@
     }
 
     @GuardedBy("mService")
+    long[] getLoggableCompatChanges() {
+        return mLoggableCompatChanges;
+    }
+
+    @GuardedBy("mService")
     void setDisabledCompatChanges(long[] disabledCompatChanges) {
         mDisabledCompatChanges = disabledCompatChanges;
     }
 
     @GuardedBy("mService")
+    void setLoggableCompatChanges(long[] loggableCompatChanges) {
+        mLoggableCompatChanges = loggableCompatChanges;
+    }
+
+    @GuardedBy("mService")
     void unlinkDeathRecipient() {
         if (mDeathRecipient != null && mThread != null) {
             mThread.asBinder().unlinkToDeath(mDeathRecipient, 0);
@@ -1659,34 +1679,4 @@
                 && !mOptRecord.shouldNotFreeze()
                 && mState.getCurAdj() >= ProcessList.FREEZER_CUTOFF_ADJ;
     }
-
-    /**
-     * Traverses all client processes and feed them to consumer.
-     */
-    @GuardedBy("mProcLock")
-    void forEachClient(@NonNull Consumer<ProcessRecord> consumer) {
-        for (int i = mServices.numberOfRunningServices() - 1; i >= 0; i--) {
-            final ServiceRecord s = mServices.getRunningServiceAt(i);
-            final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
-                    s.getConnections();
-            for (int j = serviceConnections.size() - 1; j >= 0; j--) {
-                final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j);
-                for (int k = clist.size() - 1; k >= 0; k--) {
-                    final ConnectionRecord cr = clist.get(k);
-                    if (isSdkSandbox && cr.binding.attributedClient != null) {
-                        consumer.accept(cr.binding.attributedClient);
-                    } else {
-                        consumer.accept(cr.binding.client);
-                    }
-                }
-            }
-        }
-        for (int i = mProviders.numberOfProviders() - 1; i >= 0; i--) {
-            final ContentProviderRecord cpr = mProviders.getProviderAt(i);
-            for (int j = cpr.connections.size() - 1; j >= 0; j--) {
-                final ContentProviderConnection conn = cpr.connections.get(j);
-                consumer.accept(conn.client);
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 562beaf..3d695bc 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -541,7 +541,7 @@
     private void removeSdkSandboxConnectionIfNecessary(ConnectionRecord connection) {
         final ProcessRecord attributedClient = connection.binding.attributedClient;
         if (attributedClient != null && connection.binding.service.isSdkSandbox) {
-            if (attributedClient.mServices.mSdkSandboxConnections == null) {
+            if (attributedClient.mServices.mSdkSandboxConnections != null) {
                 attributedClient.mServices.mSdkSandboxConnections.remove(connection);
             }
         }
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 3c8d7fc..e3aac02 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -267,6 +267,11 @@
     int mAllowStart_byBindings = REASON_DENIED;
 
     /**
+     * Whether or not we've bumped its oom adj scores during its execution.
+     */
+    boolean mOomAdjBumpedInExec;
+
+    /**
      * Whether to use the new "while-in-use permission" logic for FGS start
      */
     private boolean useNewWiuLogic_forStart() {
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 966fe5b..7df5fdd 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -126,6 +126,7 @@
         "arc_next",
         "avic",
         "bluetooth",
+        "brownout_mitigation_audio",
         "build",
         "biometrics",
         "biometrics_framework",
@@ -169,6 +170,7 @@
         "pixel_connectivity_gps",
         "pixel_system_sw_video",
         "pixel_watch",
+        "platform_compat",
         "platform_security",
         "pmw",
         "power",
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index feab2c05..bac5132 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -8,6 +8,7 @@
         { "include-filter": "android.app.cts.ActivityManagerProcessStateTest" },
         { "include-filter": "android.app.cts.ServiceTest" },
         { "include-filter": "android.app.cts.ActivityManagerFgsBgStartTest" },
+        { "include-filter": "android.app.cts.ForceStopTest" },
         {
           "include-annotation": "android.platform.test.annotations.Presubmit"
         },
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 34ba7f0..70d447f 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -646,7 +646,7 @@
                 new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
                 AppOpsManager.OP_NONE,
                 getTemporaryAppAllowlistBroadcastOptions(REASON_LOCKED_BOOT_COMPLETED)
-                        .toBundle(), true,
+                        .toBundle(),
                 false, MY_PID, SYSTEM_UID,
                 Binder.getCallingUid(), Binder.getCallingPid(), userId);
     }
@@ -740,7 +740,7 @@
             unlockedIntent.addFlags(
                     Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
             mInjector.broadcastIntent(unlockedIntent, null, null, 0, null,
-                    null, null, AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+                    null, null, AppOpsManager.OP_NONE, null, false, MY_PID, SYSTEM_UID,
                     Binder.getCallingUid(), Binder.getCallingPid(), userId);
         }
 
@@ -765,7 +765,7 @@
                                     | Intent.FLAG_RECEIVER_FOREGROUND);
                     mInjector.broadcastIntent(profileUnlockedIntent,
                             null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                            null, false, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
+                            null, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
                             Binder.getCallingPid(), parent.id);
                 }
             }
@@ -824,7 +824,7 @@
                                 initializeUser.run();
                             }
                         }, 0, null, null, null, AppOpsManager.OP_NONE,
-                        null, true, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
+                        null, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
                         Binder.getCallingPid(), userId);
             }
         }
@@ -876,7 +876,7 @@
                     new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
                     AppOpsManager.OP_NONE,
                     getTemporaryAppAllowlistBroadcastOptions(REASON_BOOT_COMPLETED).toBundle(),
-                    true, false, MY_PID, SYSTEM_UID, callingUid, callingPid, userId);
+                    false, MY_PID, SYSTEM_UID, callingUid, callingPid, userId);
         });
     }
 
@@ -1124,7 +1124,7 @@
                 mInjector.broadcastIntent(stoppingIntent,
                         null, stoppingReceiver, 0, null, null,
                         new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
-                        null, true, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
+                        null, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
                         Binder.getCallingPid(), UserHandle.USER_ALL);
             });
         }
@@ -1187,7 +1187,7 @@
         mInjector.broadcastIntent(shutdownIntent,
                 null, shutdownReceiver, 0, null, null, null,
                 AppOpsManager.OP_NONE,
-                null, true, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
+                null, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
                 Binder.getCallingPid(), userId);
     }
 
@@ -1419,7 +1419,8 @@
 
     private boolean allowBiometricUnlockForPrivateProfile() {
         return android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace();
+                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures();
     }
 
     /**
@@ -1463,12 +1464,12 @@
         intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
         mInjector.broadcastIntent(intent,
                 null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                null, false, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
+                null, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
                 Binder.getCallingPid(), UserHandle.USER_ALL);
 
         // Send PROFILE_INACCESSIBLE broadcast if a profile was stopped
         final UserInfo userInfo = getUserInfo(userId);
-        if (userInfo.isProfile()) {
+        if (userInfo != null && userInfo.isProfile()) {
             UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
             if (parent != null) {
                 broadcastProfileAccessibleStateChanged(userId, parent.id,
@@ -2403,7 +2404,7 @@
         mInjector.broadcastIntent(intent, /* resolvedType= */ null, /* resultTo= */ null,
                 /* resultCode= */ 0, /* resultData= */ null, /* resultExtras= */ null,
                 /* requiredPermissions= */ null, AppOpsManager.OP_NONE, /* bOptions= */ null,
-                /* ordered= */ false, /* sticky= */ false, MY_PID, SYSTEM_UID,
+                /* sticky= */ false, MY_PID, SYSTEM_UID,
                 callingUid, callingPid, userId);
     }
 
@@ -2431,7 +2432,7 @@
                     }
                 }, /* resultCode= */ 0, /* resultData= */ null, /* resultExtras= */ null,
                 new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE, /* bOptions= */ null,
-                /* ordered= */ true, /* sticky= */ false, MY_PID, SYSTEM_UID,
+                /* sticky= */ false, MY_PID, SYSTEM_UID,
                 callingUid, callingPid, UserHandle.USER_ALL);
     }
 
@@ -2456,7 +2457,7 @@
                     intent.putExtra(Intent.EXTRA_USER, UserHandle.of(profileUserId));
                     mInjector.broadcastIntent(intent,
                             null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                            null, false, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
+                            null, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
                             profileUserId);
                 }
             }
@@ -2475,7 +2476,7 @@
                     intent.putExtra(Intent.EXTRA_USER, UserHandle.of(profileUserId));
                     mInjector.broadcastIntent(intent,
                             null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                            null, false, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
+                            null, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
                             profileUserId);
                 }
                 intent = new Intent(Intent.ACTION_USER_SWITCHED);
@@ -2488,7 +2489,7 @@
                 mInjector.broadcastIntent(intent,
                         null, null, 0, null, null,
                         new String[] {android.Manifest.permission.MANAGE_USERS},
-                        AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID, callingUid,
+                        AppOpsManager.OP_NONE, null, false, MY_PID, SYSTEM_UID, callingUid,
                         callingPid, UserHandle.USER_ALL);
             }
         } finally {
@@ -2512,7 +2513,7 @@
         mInjector.broadcastIntent(intent, /* resolvedType= */ null, /* resultTo= */
                 null, /* resultCode= */ 0, /* resultData= */ null, /* resultExtras= */
                 null, /* requiredPermissions= */ null, AppOpsManager.OP_NONE, /* bOptions= */
-                null, /* ordered= */ false, /* sticky= */ false, MY_PID, SYSTEM_UID,
+                null, /* sticky= */ false, MY_PID, SYSTEM_UID,
                 Binder.getCallingUid(), Binder.getCallingPid(), parentId);
     }
 
@@ -3575,7 +3576,7 @@
         protected int broadcastIntent(Intent intent, String resolvedType,
                 IIntentReceiver resultTo, int resultCode, String resultData,
                 Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
-                boolean ordered, boolean sticky, int callingPid, int callingUid, int realCallingUid,
+                boolean sticky, int callingPid, int callingUid, int realCallingUid,
                 int realCallingPid, @UserIdInt int userId) {
 
             int logUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
@@ -3584,21 +3585,13 @@
             }
             EventLog.writeEvent(EventLogTags.UC_SEND_USER_BROADCAST, logUserId, intent.getAction());
 
-            // When the modern broadcast stack is enabled, deliver all our
-            // broadcasts as unordered, since the modern stack has better
-            // support for sequencing cold-starts, and it supports delivering
-            // resultTo for non-ordered broadcasts
-            if (mService.mEnableModernQueue) {
-                ordered = false;
-            }
-
             TimingsTraceAndSlog t = new TimingsTraceAndSlog();
             // TODO b/64165549 Verify that mLock is not held before calling AMS methods
             synchronized (mService) {
                 t.traceBegin("broadcastIntent-" + userId + "-" + intent.getAction());
                 final int result = mService.broadcastIntentLocked(null, null, null, intent,
                         resolvedType, resultTo, resultCode, resultData, resultExtras,
-                        requiredPermissions, null, null, appOp, bOptions, ordered, sticky,
+                        requiredPermissions, null, null, appOp, bOptions, false, sticky,
                         callingPid, callingUid, realCallingUid, realCallingPid, userId);
                 t.traceEnd();
                 return result;
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 16dbe18..b1823b4 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -23,6 +23,13 @@
 }
 
 flag {
+    name: "fgs_disable_saw"
+    namespace: "backstage_power"
+    description: "Disable System Alert Window FGS start"
+    bug: "296558535"
+}
+
+flag {
     name: "bfgs_managed_network_access"
     namespace: "backstage_power"
     description: "Restrict network access for certain applications in BFGS process state"
@@ -53,3 +60,10 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    namespace: "backstage_power"
+    name: "defer_outgoing_bcasts"
+    description: "Defer outgoing broadcasts from processes in freezable state"
+    bug: "327496592"
+}
diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
index 5f12ce1..9f31f37 100644
--- a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
+++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
@@ -73,8 +73,7 @@
     private static final Set<Integer> DEFAULT_EVENT_SET = Sets.newHashSet(
             AmbientContextEvent.EVENT_COUGH,
             AmbientContextEvent.EVENT_SNORE,
-            AmbientContextEvent.EVENT_BACK_DOUBLE_TAP,
-            AmbientContextEvent.EVENT_HEART_RATE);
+            AmbientContextEvent.EVENT_BACK_DOUBLE_TAP);
 
     /** Default value in absence of {@link DeviceConfig} override. */
     private static final boolean DEFAULT_SERVICE_ENABLED = true;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 0cc6494..fb62785 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -661,8 +661,12 @@
         @NonNull OpEntry createEntryLocked(String persistentDeviceId) {
             // TODO(b/308201969): Update this method when we introduce disk persistence of events
             // for accesses on external devices.
-            final ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.get(
+            ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.get(
                     persistentDeviceId);
+            if (attributedOps == null) {
+                attributedOps = new ArrayMap<>();
+            }
+
             final ArrayMap<String, AppOpsManager.AttributedOpEntry> attributionEntries =
                     new ArrayMap<>(attributedOps.size());
             for (int i = 0; i < attributedOps.size(); i++) {
@@ -680,8 +684,12 @@
         @NonNull OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) {
             // TODO(b/308201969): Update this method when we introduce disk persistence of events
             // for accesses on external devices.
-            final ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.get(
+            ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.get(
                     PERSISTENT_DEVICE_ID_DEFAULT);
+            if (attributedOps == null) {
+                attributedOps = new ArrayMap<>();
+            }
+
             final ArrayMap<String, AttributedOpEntry> attributionEntries = new ArrayMap<>(1);
             if (attributedOps.get(attributionTag) != null) {
                 attributionEntries.put(attributionTag,
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index cb6d26f..d4f04b5 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3713,7 +3713,7 @@
                     && AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
                     && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
                 if (DEBUG_VOL) {
-                    Log.d(TAG, "adjustSreamVolume: postSetAvrcpAbsoluteVolumeIndex index="
+                    Log.d(TAG, "adjustStreamVolume: postSetAvrcpAbsoluteVolumeIndex index="
                             + newIndex + "stream=" + streamType);
                 }
                 mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(newIndex / 10);
@@ -3727,7 +3727,7 @@
                     && streamType == getBluetoothContextualVolumeStream()
                     && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
                 if (DEBUG_VOL) {
-                    Log.d(TAG, "adjustSreamVolume postSetLeAudioVolumeIndex index="
+                    Log.d(TAG, "adjustStreamVolume postSetLeAudioVolumeIndex index="
                             + newIndex + " stream=" + streamType);
                 }
                 mDeviceBroker.postSetLeAudioVolumeIndex(newIndex,
@@ -3740,7 +3740,7 @@
                 // the one expected by the hearing aid
                 if (streamType == getBluetoothContextualVolumeStream()) {
                     if (DEBUG_VOL) {
-                        Log.d(TAG, "adjustSreamVolume postSetHearingAidVolumeIndex index="
+                        Log.d(TAG, "adjustStreamVolume postSetHearingAidVolumeIndex index="
                                 + newIndex + " stream=" + streamType);
                     }
                     mDeviceBroker.postSetHearingAidVolumeIndex(newIndex, streamType);
@@ -4722,7 +4722,7 @@
                 && streamType == getBluetoothContextualVolumeStream()
                 && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
             if (DEBUG_VOL) {
-                Log.d(TAG, "adjustSreamVolume postSetLeAudioVolumeIndex index="
+                Log.d(TAG, "setStreamVolume postSetLeAudioVolumeIndex index="
                         + index + " stream=" + streamType);
             }
             mDeviceBroker.postSetLeAudioVolumeIndex(index, mStreamStates[streamType].getMaxIndex(),
@@ -12528,6 +12528,16 @@
             if (app == null) {
                 return AudioManager.ERROR;
             }
+            if (android.media.audiopolicy.Flags.audioMixOwnership()) {
+                for (AudioMix mix : policyConfig.getMixes()) {
+                    if (!app.getMixes().contains(mix)) {
+                        Slog.e(TAG,
+                                "removeMixForPolicy attempted to unregister AudioMix(es) not "
+                                        + "belonging to the AudioPolicy");
+                        return AudioManager.ERROR;
+                    }
+                }
+            }
             return app.removeMixes(policyConfig.getMixes()) == AudioSystem.SUCCESS
                 ? AudioManager.SUCCESS : AudioManager.ERROR;
         }
@@ -13306,7 +13316,13 @@
             }
             final long identity = Binder.clearCallingIdentity();
             try {
-                mAudioSystem.registerPolicyMixes(mMixes, false);
+                if (android.media.audiopolicy.Flags.audioMixOwnership()) {
+                    synchronized (mMixes) {
+                        removeMixes(new ArrayList(mMixes));
+                    }
+                } else {
+                    mAudioSystem.registerPolicyMixes(mMixes, false);
+                }
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -13350,6 +13366,17 @@
 
         int addMixes(@NonNull ArrayList<AudioMix> mixes) {
             synchronized (mMixes) {
+                if (android.media.audiopolicy.Flags.audioMixOwnership()) {
+                    for (AudioMix mix : mixes) {
+                        setMixRegistration(mix);
+                    }
+
+                    int result = mAudioSystem.registerPolicyMixes(mixes, true);
+                    if (result == AudioSystem.SUCCESS) {
+                        this.add(mixes);
+                    }
+                    return result;
+                }
                 this.add(mixes);
                 return mAudioSystem.registerPolicyMixes(mixes, true);
             }
diff --git a/services/core/java/com/android/server/biometrics/log/ALSProbe.java b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
index d584c99..d4e46a9 100644
--- a/services/core/java/com/android/server/biometrics/log/ALSProbe.java
+++ b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
@@ -179,15 +179,18 @@
             nextConsumer.consume(current);
         } else if (mNextConsumer != null) {
             mNextConsumer.add(nextConsumer);
-        } else {
+        } else if (mLightSensor != null) {
             mDestroyed = false;
             mNextConsumer = nextConsumer;
             enableLightSensorLoggingLocked();
+        } else {
+            Slog.w(TAG, "No light sensor - use current to consume");
+            nextConsumer.consume(current);
         }
     }
 
     private void enableLightSensorLoggingLocked() {
-        if (!mEnabled) {
+        if (!mEnabled && mLightSensor != null) {
             mEnabled = true;
             mLastAmbientLux = -1;
             mSensorManager.registerListener(mLightSensorListener, mLightSensor,
@@ -201,7 +204,7 @@
     private void disableLightSensorLoggingLocked(boolean destroying) {
         resetTimerLocked(false /* start */);
 
-        if (mEnabled) {
+        if (mEnabled && mLightSensor != null) {
             mEnabled = false;
             if (!destroying) {
                 mLastAmbientLux = -1;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 68b4e3f..7ee2a7a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -149,11 +149,7 @@
             return proto.getBytes();
         }
 
-        @android.annotation.EnforcePermission(
-                anyOf = {
-                        android.Manifest.permission.USE_BIOMETRIC_INTERNAL,
-                        android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION
-                })
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(
                 String opPackageName) {
@@ -297,29 +293,6 @@
                     restricted, statsClient, isKeyguard);
         }
 
-        @android.annotation.EnforcePermission(
-                android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION)
-        @Override // Binder call
-        public long authenticateInBackground(final IBinder token, final long operationId,
-                final IFaceServiceReceiver receiver, final FaceAuthenticateOptions options) {
-            // TODO(b/152413782): If the sensor supports face detect and the device is encrypted or
-            //  lockdown, something wrong happened. See similar path in FingerprintService.
-
-            super.authenticateInBackground_enforcePermission();
-
-            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
-            if (provider == null) {
-                Slog.w(TAG, "Null provider for authenticate");
-                return -1;
-            }
-            options.setSensorId(provider.first);
-
-            return provider.second.scheduleAuthenticate(token, operationId,
-                    0 /* cookie */, new ClientMonitorCallbackConverter(receiver), options,
-                    false /* restricted */, BiometricsProtoEnums.CLIENT_UNKNOWN /* statsClient */,
-                    true /* allowBackgroundAuthentication */);
-        }
-
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public long detectFace(final IBinder token,
@@ -583,11 +556,7 @@
             return provider.getEnrolledFaces(sensorId, userId);
         }
 
-        @android.annotation.EnforcePermission(
-                anyOf = {
-                        android.Manifest.permission.USE_BIOMETRIC_INTERNAL,
-                        android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION
-                })
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName) {
             super.hasEnrolledFaces_enforcePermission();
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 007b746..fb826c8 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
@@ -24,6 +24,7 @@
 import android.app.TaskStackListener;
 import android.content.Context;
 import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricFaceConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.ComponentInfoInternal;
@@ -39,6 +40,7 @@
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -64,6 +66,7 @@
 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.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
@@ -359,6 +362,17 @@
                     null /* callback */);
         }
 
+        if (Build.isDebuggable()) {
+            BiometricUtils<Face> utils = FaceUtils.getInstance(
+                    mFaceSensors.keyAt(0));
+            for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
+                List<Face> enrollments = utils.getBiometricsForUser(mContext, user.id);
+                Slog.d(getTag(), "Expecting enrollments for user " + user.id + ": "
+                        + enrollments.stream().map(
+                        BiometricAuthenticator.Identifier::getBiometricId).toList());
+            }
+        }
+
         return mDaemon;
     }
 
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 a104cf4..c04c47e 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
@@ -27,6 +27,7 @@
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.content.res.TypedArray;
+import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.IInvalidationCallback;
@@ -46,6 +47,7 @@
 import android.hardware.fingerprint.ISidefpsController;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -71,6 +73,7 @@
 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.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
@@ -382,6 +385,17 @@
                     null /* callback */);
         }
 
+        if (Build.isDebuggable()) {
+            BiometricUtils<Fingerprint> utils = FingerprintUtils.getInstance(
+                    mFingerprintSensors.keyAt(0));
+            for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
+                List<Fingerprint> enrollments = utils.getBiometricsForUser(mContext, user.id);
+                Slog.d(getTag(), "Expecting enrollments for user " + user.id + ": "
+                        + enrollments.stream().map(
+                                BiometricAuthenticator.Identifier::getBiometricId).toList());
+            }
+        }
+
         return mDaemon;
     }
 
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
index 03acf72..d93ff9d 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
@@ -82,13 +82,6 @@
                     Slogf.w(TAG, "No module %s with id %d (HAL AIDL)", name, moduleId);
                     return;
                 }
-                try {
-                    radioModule.setInternalHalCallback();
-                } catch (RemoteException ex) {
-                    Slogf.wtf(TAG, ex, "Broadcast radio module %s with id %d (HAL AIDL) "
-                            + "cannot register HAL callback", name, moduleId);
-                    return;
-                }
                 if (DEBUG) {
                     Slogf.d(TAG, "Loaded broadcast radio module %s with id %d (HAL AIDL)",
                             name, moduleId);
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
index 4b3444d..cd86510 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
@@ -246,10 +246,6 @@
         return mProperties;
     }
 
-    void setInternalHalCallback() throws RemoteException {
-        mService.setTunerCallback(mHalTunerCallback);
-    }
-
     TunerSession openSession(android.hardware.radio.ITunerCallback userCb)
             throws RemoteException {
         mLogger.logRadioEvent("Open TunerSession");
@@ -257,10 +253,14 @@
         Boolean antennaConnected;
         RadioManager.ProgramInfo currentProgramInfo;
         synchronized (mLock) {
+            boolean isFirstTunerSession = mAidlTunerSessions.isEmpty();
             tunerSession = new TunerSession(this, mService, userCb);
             mAidlTunerSessions.add(tunerSession);
             antennaConnected = mAntennaConnected;
             currentProgramInfo = mCurrentProgramInfo;
+            if (isFirstTunerSession) {
+                mService.setTunerCallback(mHalTunerCallback);
+            }
         }
         // Propagate state to new client.
         // Note: These callbacks are invoked while holding mLock to prevent race conditions
@@ -284,7 +284,6 @@
         synchronized (mLock) {
             tunerSessions = new TunerSession[mAidlTunerSessions.size()];
             mAidlTunerSessions.toArray(tunerSessions);
-            mAidlTunerSessions.clear();
         }
 
         for (TunerSession tunerSession : tunerSessions) {
@@ -402,6 +401,14 @@
             mAidlTunerSessions.remove(tunerSession);
         }
         onTunerSessionProgramListFilterChanged(null);
+        if (mAidlTunerSessions.isEmpty()) {
+            try {
+                mService.unsetTunerCallback();
+            } catch (RemoteException ex) {
+                Slogf.wtf(TAG, ex, "Failed to unregister HAL callback for module %d",
+                        mProperties.getId());
+            }
+        }
     }
 
     // add to mHandler queue
diff --git a/services/core/java/com/android/server/clipboard/ArcClipboardMonitor.java b/services/core/java/com/android/server/clipboard/ArcClipboardMonitor.java
new file mode 100644
index 0000000..413020e
--- /dev/null
+++ b/services/core/java/com/android/server/clipboard/ArcClipboardMonitor.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.clipboard;
+
+import android.annotation.Nullable;
+import android.content.ClipData;
+
+import com.android.server.LocalServices;
+
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+public class ArcClipboardMonitor implements Consumer<ClipData> {
+    private static final String TAG = "ArcClipboardMonitor";
+
+    public interface ArcClipboardBridge {
+        /**
+         * Called when a clipboard content is updated.
+         */
+        void onPrimaryClipChanged(ClipData data);
+
+        /**
+         * Passes the callback to set a new clipboard content with a uid.
+         */
+        void setHandler(BiConsumer<ClipData, Integer> setAndroidClipboard);
+    }
+
+    private ArcClipboardBridge mBridge;
+    private BiConsumer<ClipData, Integer> mAndroidClipboardSetter;
+
+    ArcClipboardMonitor(final BiConsumer<ClipData, Integer> setAndroidClipboard) {
+        mAndroidClipboardSetter = setAndroidClipboard;
+        LocalServices.addService(ArcClipboardMonitor.class, this);
+    }
+
+    @Override
+    public void accept(final @Nullable ClipData clip) {
+        if (mBridge != null) {
+            mBridge.onPrimaryClipChanged(clip);
+        }
+    }
+
+    /**
+     * Sets the other end of the clipboard bridge.
+     */
+    public void setClipboardBridge(ArcClipboardBridge bridge) {
+        mBridge = bridge;
+        mBridge.setHandler(mAndroidClipboardSetter);
+    }
+}
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 49f6070..4c3020f 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -151,7 +151,7 @@
     private final ContentCaptureManagerInternal mContentCaptureInternal;
     private final AutofillManagerInternal mAutofillInternal;
     private final IBinder mPermissionOwner;
-    private final Consumer<ClipData> mEmulatorClipboardMonitor;
+    private final Consumer<ClipData> mClipboardMonitor;
     private final Handler mWorkerHandler;
 
     @GuardedBy("mLock")
@@ -192,7 +192,7 @@
         final IBinder permOwner = mUgmInternal.newUriPermissionOwner("clipboard");
         mPermissionOwner = permOwner;
         if (Build.IS_EMULATOR) {
-            mEmulatorClipboardMonitor = new EmulatorClipboardMonitor((clip) -> {
+            mClipboardMonitor = new EmulatorClipboardMonitor((clip) -> {
                 synchronized (mLock) {
                     Clipboard clipboard = getClipboardLocked(0, DEVICE_ID_DEFAULT);
                     if (clipboard != null) {
@@ -201,8 +201,12 @@
                     }
                 }
             });
+        } else if (Build.IS_ARC) {
+            mClipboardMonitor = new ArcClipboardMonitor((clip, uid) -> {
+                setPrimaryClipInternal(clip, uid);
+            });
         } else {
-            mEmulatorClipboardMonitor = (clip) -> {};
+            mClipboardMonitor = (clip) -> {};
         }
 
         updateConfig();
@@ -937,7 +941,7 @@
     private void setPrimaryClipInternalLocked(
             @Nullable ClipData clip, int uid, int deviceId, @Nullable String sourcePackage) {
         if (deviceId == DEVICE_ID_DEFAULT) {
-            mEmulatorClipboardMonitor.accept(clip);
+            mClipboardMonitor.accept(clip);
         }
 
         final int userId = UserHandle.getUserId(uid);
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 9102cfd..79025d0 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.os.Build;
 import android.os.Environment;
 import android.text.TextUtils;
 import android.util.LongArray;
@@ -72,7 +73,6 @@
  * been configured.
  */
 final class CompatConfig {
-
     private static final String TAG = "CompatConfig";
     private static final String APP_COMPAT_DATA_DIR = "/data/misc/appcompat";
     private static final String STATIC_OVERRIDES_PRODUCT_DIR = "/product/etc/appcompat";
@@ -149,6 +149,56 @@
     }
 
     /**
+     * Retrieves the set of changes that are intended to be logged. This includes changes that
+     * target the most recent SDK version and are not disabled.
+     *
+     * @param app the app in question
+     * @return a sorted long array of change IDs
+     */
+    long[] getLoggableChanges(ApplicationInfo app) {
+        LongArray loggable = new LongArray(mChanges.size());
+        for (CompatChange c : mChanges.values()) {
+            long changeId = c.getId();
+            boolean isLatestSdk = isChangeTargetingLatestSdk(c, app.targetSdkVersion);
+            if (c.isEnabled(app, mAndroidBuildClassifier) && isLatestSdk) {
+                loggable.add(changeId);
+            }
+        }
+        final long[] sortedChanges = loggable.toArray();
+        Arrays.sort(sortedChanges);
+        return sortedChanges;
+    }
+
+    /**
+     * Whether the change indicated by the given changeId is targeting the latest SDK version.
+     * @param c             the change for which to check the target SDK version
+     * @param appSdkVersion the target sdk version of the app
+     * @return true if the changeId targets the current sdk version or the current development
+     * version.
+     */
+    boolean isChangeTargetingLatestSdk(CompatChange c, int appSdkVersion) {
+        int maxTargetSdk = maxTargetSdkForCompatChange(c) + 1;
+        if (maxTargetSdk <= 0) {
+            // No max target sdk found.
+            return false;
+        }
+
+        return maxTargetSdk == Build.VERSION_CODES.CUR_DEVELOPMENT || maxTargetSdk == appSdkVersion;
+    }
+
+    /**
+     * Retrieves the CompatChange associated with the given changeId. Will return null if the
+     * changeId is not found. Used only for performance improvement purposes, in order to reduce
+     * lookups.
+     *
+     * @param changeId for which to look up the CompatChange
+     * @return the found compat change, or null if not found.
+     */
+    CompatChange getCompatChange(long changeId) {
+        return mChanges.get(changeId);
+    }
+
+    /**
      * Looks up a change ID by name.
      *
      * @param name name of the change to look up
@@ -164,7 +214,7 @@
     }
 
     /**
-     * Checks if a given change is enabled for a given application.
+     * Checks if a given change id is enabled for a given application.
      *
      * @param changeId the ID of the change in question
      * @param app      app to check for
@@ -173,6 +223,18 @@
      */
     boolean isChangeEnabled(long changeId, ApplicationInfo app) {
         CompatChange c = mChanges.get(changeId);
+        return isChangeEnabled(c, app);
+    }
+
+    /**
+     * Checks if a given change is enabled for a given application.
+     *
+     * @param c   the CompatChange in question
+     * @param app the app to check for
+     * @return {@code true} if the change is enabled for this app. Also returns {@code true} if the
+     * change ID is not known, as unknown changes are enabled by default.
+     */
+    boolean isChangeEnabled(CompatChange c, ApplicationInfo app) {
         if (c == null) {
             // we know nothing about this change: default behaviour is enabled.
             return true;
@@ -301,9 +363,21 @@
     /**
      * Returns the maximum SDK version for which this change can be opted in (or -1 if it is not
      * target SDK gated).
+     *
+     * @param changeId the id of the CompatChange to check for the max target sdk
      */
     int maxTargetSdkForChangeIdOptIn(long changeId) {
         CompatChange c = mChanges.get(changeId);
+        return maxTargetSdkForCompatChange(c);
+    }
+
+    /**
+     * Returns the maximum SDK version for which this change can be opted in (or -1 if it is not
+     * target SDK gated).
+     *
+     * @param c the CompatChange to check for the max target sdk
+     */
+    int maxTargetSdkForCompatChange(CompatChange c) {
         if (c != null && c.getEnableSinceTargetSdk() != -1) {
             return c.getEnableSinceTargetSdk() - 1;
         }
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 6cca130..f8fd0a0 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -120,8 +120,16 @@
         reportChangeInternal(changeId, uid, ChangeReporter.STATE_LOGGED);
     }
 
+    /**
+     * Report the change, but skip over the sdk target version check. This can be used to force the
+     * debug logs.
+     *
+     * @param changeId        of the change to report
+     * @param uid             of the user
+     * @param state           of the change - enabled/disabled/logged
+     */
     private void reportChangeInternal(long changeId, int uid, int state) {
-        mChangeReporter.reportChange(uid, changeId, state);
+        mChangeReporter.reportChange(uid, changeId, state, true);
     }
 
     @Override
@@ -164,15 +172,25 @@
     }
 
     /**
-     * Internal version of {@link #isChangeEnabled(long, ApplicationInfo)}.
+     * Internal version of {@link #isChangeEnabled(long, ApplicationInfo)}. If the provided appInfo
+     * is not null, also reports the change.
+     *
+     * @param changeId of the change to report
+     * @param appInfo  the app to check
      *
      * <p>Does not perform costly permission check.
      */
     public boolean isChangeEnabledInternal(long changeId, ApplicationInfo appInfo) {
-        boolean enabled = isChangeEnabledInternalNoLogging(changeId, appInfo);
+        // Fetch the CompatChange. This is done here instead of in mCompatConfig to avoid multiple
+        // fetches.
+        CompatChange c = mCompatConfig.getCompatChange(changeId);
+
+        boolean enabled = mCompatConfig.isChangeEnabled(c, appInfo);
+        int state = enabled ? ChangeReporter.STATE_ENABLED : ChangeReporter.STATE_DISABLED;
         if (appInfo != null) {
-            reportChangeInternal(changeId, appInfo.uid,
-                    enabled ? ChangeReporter.STATE_ENABLED : ChangeReporter.STATE_DISABLED);
+            boolean isTargetingLatestSdk =
+                    mCompatConfig.isChangeTargetingLatestSdk(c, appInfo.targetSdkVersion);
+            mChangeReporter.reportChange(appInfo.uid, changeId, state, isTargetingLatestSdk);
         }
         return enabled;
     }
@@ -399,6 +417,19 @@
     }
 
     /**
+     * Retrieves the set of changes that should be logged for a given app. Any change ID not in the
+     * returned array is ignored for logging purposes.
+     *
+     * @param appInfo The app in question
+     * @return A sorted long array of change IDs. We use a primitive array to minimize memory
+     * footprint: Every app process will store this array statically so we aim to reduce
+     * overhead as much as possible.
+     */
+    public long[] getLoggableChanges(ApplicationInfo appInfo) {
+        return mCompatConfig.getLoggableChanges(appInfo);
+    }
+
+    /**
      * Look up a change ID by name.
      *
      * @param name Name of the change to look up
diff --git a/services/core/java/com/android/server/connectivity/TEST_MAPPING b/services/core/java/com/android/server/connectivity/TEST_MAPPING
index 687d4b0..55601bc 100644
--- a/services/core/java/com/android/server/connectivity/TEST_MAPPING
+++ b/services/core/java/com/android/server/connectivity/TEST_MAPPING
@@ -7,7 +7,20 @@
                   "exclude-annotation": "com.android.testutils.SkipPresubmit"
               }
           ],
-          "file_patterns": ["Vpn\\.java", "VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
+          "file_patterns": ["VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
+      },
+      {
+          "name":"FrameworksVpnTests",
+          "options": [
+              {
+                "exclude-annotation": "com.android.testutils.SkipPresubmit"
+              }
+          ],
+          "file_patterns":[
+              "Vpn\\.java",
+              "VpnIkeV2Utils\\.java",
+              "VpnProfileStore\\.java"
+          ]
       }
   ],
   "presubmit-large": [
diff --git a/services/core/java/com/android/server/criticalevents/CriticalEventLog.java b/services/core/java/com/android/server/criticalevents/CriticalEventLog.java
index 816c349..036284f 100644
--- a/services/core/java/com/android/server/criticalevents/CriticalEventLog.java
+++ b/services/core/java/com/android/server/criticalevents/CriticalEventLog.java
@@ -30,6 +30,7 @@
 import com.android.server.criticalevents.nano.CriticalEventProto.AppNotResponding;
 import com.android.server.criticalevents.nano.CriticalEventProto.HalfWatchdog;
 import com.android.server.criticalevents.nano.CriticalEventProto.InstallPackages;
+import com.android.server.criticalevents.nano.CriticalEventProto.ExcessiveBinderCalls;
 import com.android.server.criticalevents.nano.CriticalEventProto.JavaCrash;
 import com.android.server.criticalevents.nano.CriticalEventProto.NativeCrash;
 import com.android.server.criticalevents.nano.CriticalEventProto.SystemServerStarted;
@@ -143,6 +144,15 @@
         return System.currentTimeMillis();
     }
 
+   /** Logs when a uid sends an excessive number of binder calls. */
+    public void logExcessiveBinderCalls(int uid) {
+        CriticalEventProto event = new CriticalEventProto();
+        ExcessiveBinderCalls calls = new ExcessiveBinderCalls();
+        calls.uid = uid;
+        event.setExcessiveBinderCalls(calls);
+        log(event);
+    }
+
     /** Logs when one or more packages are installed. */
     public void logInstallPackagesStarted() {
         CriticalEventProto event = new CriticalEventProto();
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 38051c1..177c345 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -19,8 +19,9 @@
 import static android.Manifest.permission.CONTROL_DEVICE_STATE;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.hardware.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
-import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
 import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE_IDENTIFIER;
 import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE_IDENTIFIER;
 
@@ -75,6 +76,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
@@ -109,6 +111,12 @@
     private static final String TAG = "DeviceStateManagerService";
     private static final boolean DEBUG = false;
 
+    /** {@link DeviceState} to model an invalid device state */
+    // TODO(b/328314031): Investigate how we can remove this constant
+    private static final DeviceState INVALID_DEVICE_STATE = new DeviceState(
+            new DeviceState.Configuration.Builder(INVALID_DEVICE_STATE_IDENTIFIER,
+                    "INVALID").build());
+
     private final Object mLock = new Object();
     // Handler on the {@link DisplayThread} used to dispatch calls to the policy and to registered
     // callbacks though its handler (mHandler). Provides a guarantee of callback order when
@@ -354,16 +362,22 @@
     }
 
     /** Returns the list of currently supported device states. */
-    DeviceState[] getSupportedStates() {
+    List<DeviceState> getSupportedStates() {
         synchronized (mLock) {
-            DeviceState[] supportedStates = new DeviceState[mDeviceStates.size()];
-            for (int i = 0; i < supportedStates.length; i++) {
-                supportedStates[i] = mDeviceStates.valueAt(i);
-            }
-            return supportedStates;
+            return getSupportedStatesLocked();
         }
     }
 
+    /** Returns the list of currently supported device states */
+    @GuardedBy("mLock")
+    private List<DeviceState> getSupportedStatesLocked() {
+        List<DeviceState> supportedStates = new ArrayList<>(mDeviceStates.size());
+        for (int i = 0; i < mDeviceStates.size(); i++) {
+            supportedStates.add(i, mDeviceStates.valueAt(i));
+        }
+        return supportedStates;
+    }
+
     /** Returns the list of currently supported device state identifiers. */
     private int[] getSupportedStateIdentifiersLocked() {
         int[] supportedStates = new int[mDeviceStates.size()];
@@ -375,20 +389,46 @@
 
     /**
      * Returns the current {@link DeviceStateInfo} of the device. If there has been no base state
-     * or committed state provided, {@link DeviceStateManager#INVALID_DEVICE_STATE} will be returned
+     * or committed state provided, {@link #INVALID_DEVICE_STATE} will be returned
      * respectively. The supported states will always be included.
      *
      */
     @GuardedBy("mLock")
     @NonNull
     private DeviceStateInfo getDeviceStateInfoLocked() {
-        final int[] supportedStates = getSupportedStateIdentifiersLocked();
-        final int baseState =
-                mBaseState.isPresent() ? mBaseState.get().getIdentifier() : INVALID_DEVICE_STATE;
-        final int currentState = mCommittedState.isPresent() ? mCommittedState.get().getIdentifier()
-                : INVALID_DEVICE_STATE;
+        final List<DeviceState> supportedStates = getSupportedStatesLocked();
+        final DeviceState baseState = mBaseState.orElse(null);
+        final DeviceState currentState = mCommittedState.orElse(null);
 
-        return new DeviceStateInfo(supportedStates, baseState, currentState);
+        return new DeviceStateInfo(supportedStates,
+                baseState != null ? baseState : INVALID_DEVICE_STATE,
+                createMergedDeviceState(currentState, baseState));
+    }
+
+    /**
+     * Returns a {@link DeviceState} with the combined properties of the current system state, as
+     * well as the physical property that corresponds to the base state (physical hardware state) of
+     * the device.
+     */
+    private DeviceState createMergedDeviceState(@Nullable DeviceState committedState,
+            @Nullable DeviceState baseState) {
+        if (committedState == null) {
+            return INVALID_DEVICE_STATE;
+        }
+
+        Set<@DeviceState.DeviceStateProperties Integer> systemProperties =
+                committedState.getConfiguration().getSystemProperties();
+
+        Set<@DeviceState.DeviceStateProperties Integer> physicalProperties =
+                baseState != null ? baseState.getConfiguration().getPhysicalProperties()
+                        : Collections.emptySet();
+
+        DeviceState.Configuration deviceStateConfiguration = new DeviceState.Configuration.Builder(
+                committedState.getIdentifier(), committedState.getName())
+                .setSystemProperties(systemProperties)
+                .setPhysicalProperties(physicalProperties)
+                .build();
+        return new DeviceState(deviceStateConfiguration);
     }
 
     @VisibleForTesting
@@ -408,7 +448,7 @@
             mDeviceStates.clear();
             for (int i = 0; i < supportedDeviceStates.length; i++) {
                 DeviceState state = supportedDeviceStates[i];
-                if (state.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) {
+                if (state.hasProperty(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS)) {
                     hasTerminalDeviceState = true;
                 }
                 mDeviceStates.put(state.getIdentifier(), state);
@@ -436,7 +476,7 @@
     private void setRearDisplayStateLocked() {
         int rearDisplayIdentifier = getContext().getResources().getInteger(
                 R.integer.config_deviceStateRearDisplay);
-        if (rearDisplayIdentifier != INVALID_DEVICE_STATE) {
+        if (rearDisplayIdentifier != INVALID_DEVICE_STATE_IDENTIFIER) {
             mRearDisplayState = mDeviceStates.get(rearDisplayIdentifier);
         }
     }
@@ -453,7 +493,7 @@
      * Returns the {@link DeviceState} with the supplied {@code identifier}, or {@code null} if
      * there is no device state with the identifier.
      */
-    @Nullable
+    @NonNull
     private Optional<DeviceState> getStateLocked(int identifier) {
         return Optional.ofNullable(mDeviceStates.get(identifier));
     }
@@ -468,7 +508,7 @@
     private void setBaseState(int identifier) {
         synchronized (mLock) {
             final Optional<DeviceState> baseStateOptional = getStateLocked(identifier);
-            if (!baseStateOptional.isPresent()) {
+            if (baseStateOptional.isEmpty()) {
                 throw new IllegalArgumentException("Base state is not supported");
             }
 
@@ -484,7 +524,7 @@
             }
             mBaseState = Optional.of(baseState);
 
-            if (baseState.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) {
+            if (baseState.hasProperty(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS)) {
                 mOverrideRequestController.cancelOverrideRequest();
             }
             mOverrideRequestController.handleBaseStateChanged(identifier);
@@ -1023,7 +1063,7 @@
     }
 
     private Set<Integer> readFoldedStates() {
-        Set<Integer> foldedStates = new HashSet();
+        Set<Integer> foldedStates = new HashSet<>();
         int[] mFoldedStatesArray = getContext().getResources().getIntArray(
                 com.android.internal.R.array.config_foldedDeviceStates);
         for (int i = 0; i < mFoldedStatesArray.length; i++) {
@@ -1338,7 +1378,7 @@
         }
         int identifier = mActiveOverride.get().getRequestedStateIdentifier();
         DeviceState deviceState = mDeviceStates.get(identifier);
-        return deviceState.hasFlag(DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
+        return deviceState.hasProperty(PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
     }
 
     private class OverrideRequestScreenObserver implements
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
index 02c9bb3..97913de3 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
@@ -25,7 +25,7 @@
 import android.os.ShellCommand;
 
 import java.io.PrintWriter;
-import java.util.Arrays;
+import java.util.List;
 import java.util.Optional;
 import java.util.stream.Collectors;
 
@@ -177,17 +177,18 @@
     }
 
     private int runPrintStates(PrintWriter pw) {
-        DeviceState[] states = mService.getSupportedStates();
+        List<DeviceState> states = mService.getSupportedStates();
         pw.print("Supported states: [\n");
-        for (int i = 0; i < states.length; i++) {
-            pw.print("  " + states[i] + ",\n");
+        for (int i = 0; i < states.size(); i++) {
+            pw.print("  " + states.get(i) + ",\n");
         }
         pw.println("]");
         return 0;
     }
 
     private int runPrintStatesSimple(PrintWriter pw) {
-        pw.print(Arrays.stream(mService.getSupportedStates())
+        pw.print(mService.getSupportedStates()
+                .stream()
                 .map(DeviceState::getIdentifier)
                 .map(Object::toString)
                 .collect(Collectors.joining(",")));
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java b/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java
index f9aefd0..46478c1 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java
@@ -323,7 +323,7 @@
 
             for (int i = 0; i < stateIdentifiers.length; i++) {
                 int identifier = stateIdentifiers[i];
-                if (identifier == DeviceStateManager.INVALID_DEVICE_STATE) {
+                if (identifier == DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER) {
                     continue;
                 }
 
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
index b865c1d9..8d07609 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -28,8 +28,8 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * Responsible for providing the set of supported {@link DeviceState device states} as well as the
- * current device state.
+ * Responsible for providing the set of supported {@link DeviceState.Configuration device states} as
+ * well as the current device state.
  *
  * @see DeviceStatePolicy
  */
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequest.java b/services/core/java/com/android/server/devicestate/OverrideRequest.java
index d92629f..df7301e 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequest.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequest.java
@@ -72,8 +72,9 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface OverrideRequestType {}
 
-    OverrideRequest(IBinder token, int pid, int uid, @NonNull DeviceState requestedState,
-            @DeviceStateRequest.RequestFlags int flags, @OverrideRequestType int requestType) {
+    OverrideRequest(IBinder token, int pid, int uid,
+            @NonNull DeviceState requestedState, @DeviceStateRequest.RequestFlags int flags,
+            @OverrideRequestType int requestType) {
         mToken = token;
         mPid = pid;
         mUid = uid;
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
index 6c3fd83d..041ab3a 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
@@ -205,8 +205,8 @@
         }
 
         if (mRequest != null && mRequest.getPid() == pid) {
-            if (mRequest.getRequestedDeviceState().hasFlag(
-                    DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP)) {
+            if (mRequest.getRequestedDeviceState().hasProperty(
+                    DeviceState.PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP)) {
                 cancelCurrentRequestLocked();
                 return;
             }
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index fb4976d..3d95fee 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -403,15 +403,14 @@
             brightnessEvent.setRecommendedBrightness(mScreenAutoBrightness);
             brightnessEvent.setFlags(brightnessEvent.getFlags()
                     | (!mAmbientLuxValid ? BrightnessEvent.FLAG_INVALID_LUX : 0)
-                    | (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE
-                        ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
+                    | (shouldApplyDozeScaleFactor() ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
             brightnessEvent.setAutoBrightnessMode(getMode());
         }
 
         if (!mAmbientLuxValid) {
             return PowerManager.BRIGHTNESS_INVALID_FLOAT;
         }
-        if (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE) {
+        if (shouldApplyDozeScaleFactor()) {
             return mScreenAutoBrightness * mDozeScaleFactor;
         }
         return mScreenAutoBrightness;
@@ -434,7 +433,7 @@
 
         float brightness = mCurrentBrightnessMapper.getBrightness(mLastObservedLux,
                 mForegroundAppPackageName, mForegroundAppCategory);
-        if (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE) {
+        if (shouldApplyDozeScaleFactor()) {
             brightness *= mDozeScaleFactor;
         }
 
@@ -443,8 +442,7 @@
             brightnessEvent.setRecommendedBrightness(brightness);
             brightnessEvent.setFlags(brightnessEvent.getFlags()
                     | (mLastObservedLux == INVALID_LUX ? BrightnessEvent.FLAG_INVALID_LUX : 0)
-                    | (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE
-                    ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
+                    | (shouldApplyDozeScaleFactor() ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
             brightnessEvent.setAutoBrightnessMode(getMode());
         }
         return brightness;
@@ -463,12 +461,6 @@
             boolean userChangedAutoBrightnessAdjustment, int displayPolicy,
             boolean shouldResetShortTermModel) {
         mState = state;
-        // While dozing, the application processor may be suspended which will prevent us from
-        // receiving new information from the light sensor. On some devices, we may be able to
-        // switch to a wake-up light sensor instead but for now we will simply disable the sensor
-        // and hold onto the last computed screen auto brightness.  We save the dozing flag for
-        // debugging purposes.
-        boolean dozing = (displayPolicy == DisplayPowerRequest.POLICY_DOZE);
         boolean changed = setBrightnessConfiguration(configuration, shouldResetShortTermModel);
         changed |= setDisplayPolicy(displayPolicy);
         if (userChangedAutoBrightnessAdjustment) {
@@ -482,10 +474,10 @@
         }
         final boolean userInitiatedChange =
                 userChangedBrightness || userChangedAutoBrightnessAdjustment;
-        if (userInitiatedChange && enable && !dozing) {
+        if (userInitiatedChange && enable) {
             prepareBrightnessAdjustmentSample();
         }
-        changed |= setLightSensorEnabled(enable && !dozing);
+        changed |= setLightSensorEnabled(enable);
 
         if (mIsBrightnessThrottled != mBrightnessThrottler.isThrottled()) {
             // Maximum brightness has changed, so recalculate display brightness.
@@ -1269,6 +1261,12 @@
         }
     }
 
+    private boolean shouldApplyDozeScaleFactor() {
+        // Don't apply the doze scale factor if we have a designated brightness curve for doze
+        return mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE
+                && getMode() != AUTO_BRIGHTNESS_MODE_DOZE;
+    }
+
     private class ShortTermModel {
         // When the short term model is invalidated, we don't necessarily reset it (i.e. clear the
         // user's adjustment) immediately, but wait for a drastic enough change in the ambient
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index e54f30f..146810f 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -51,7 +51,7 @@
 class DeviceStateToLayoutMap {
     private static final String TAG = "DeviceStateToLayoutMap";
 
-    public static final int STATE_DEFAULT = DeviceStateManager.INVALID_DEVICE_STATE;
+    public static final int STATE_DEFAULT = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
 
     // Direction of the display relative to the default display, whilst in this state
     private static final int POSITION_UNKNOWN = Layout.Display.POSITION_UNKNOWN;
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 3b05b47..a7748f4 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -44,6 +44,15 @@
  * </p>
  */
 abstract class DisplayDevice {
+    /**
+     * Maximum acceptable anisotropy for the output image.
+     *
+     * Necessary to avoid unnecessary scaling when pixels are almost square, as they are non ideal
+     * anyway. For external displays, we expect an anisotropy of about 2% even if the pixels
+     * are, in fact, square due to the imprecision of the display's actual size (parsed from edid
+     * and rounded to the nearest cm).
+     */
+    static final float MAX_ANISOTROPY = 1.025f;
     private static final String TAG = "DisplayDevice";
     private static final Display.Mode EMPTY_DISPLAY_MODE = new Display.Mode.Builder().build();
 
@@ -69,13 +78,21 @@
     // Do not use for any other purpose.
     DisplayDeviceInfo mDebugLastLoggedDeviceInfo;
 
-    public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId,
+    private final boolean mIsAnisotropyCorrectionEnabled;
+
+    DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId,
             Context context) {
+        this(displayAdapter, displayToken, uniqueId, context, false);
+    }
+
+    DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId,
+            Context context, boolean isAnisotropyCorrectionEnabled) {
         mDisplayAdapter = displayAdapter;
         mDisplayToken = displayToken;
         mUniqueId = uniqueId;
         mDisplayDeviceConfig = null;
         mContext = context;
+        mIsAnisotropyCorrectionEnabled = isAnisotropyCorrectionEnabled;
     }
 
     /**
@@ -143,8 +160,17 @@
         DisplayDeviceInfo displayDeviceInfo = getDisplayDeviceInfoLocked();
         final boolean isRotated = mCurrentOrientation == ROTATION_90
                 || mCurrentOrientation == ROTATION_270;
-        return isRotated ? new Point(displayDeviceInfo.height, displayDeviceInfo.width)
-                : new Point(displayDeviceInfo.width, displayDeviceInfo.height);
+        var width = displayDeviceInfo.width;
+        var height = displayDeviceInfo.height;
+        if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.yDpi > 0
+                    && displayDeviceInfo.xDpi > 0) {
+            if (displayDeviceInfo.xDpi > displayDeviceInfo.yDpi * MAX_ANISOTROPY) {
+                height = (int) (height * displayDeviceInfo.xDpi / displayDeviceInfo.yDpi + 0.5);
+            } else if (displayDeviceInfo.xDpi * MAX_ANISOTROPY < displayDeviceInfo.yDpi) {
+                width = (int) (width * displayDeviceInfo.yDpi / displayDeviceInfo.xDpi  + 0.5);
+            }
+        }
+        return isRotated ? new Point(height, width) : new Point(width, height);
     }
 
     /**
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index ad89444..ce7c224 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -284,6 +284,8 @@
 
     // Display mode chosen by user.
     private Display.Mode mUserPreferredMode;
+    @HdrConversionMode.ConversionMode
+    private final int mDefaultHdrConversionMode;
     // HDR conversion mode chosen by user
     @GuardedBy("mSyncRoot")
     private HdrConversionMode mHdrConversionMode = null;
@@ -582,6 +584,10 @@
         mDefaultDisplayDefaultColorMode = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_defaultDisplayDefaultColorMode);
         mDefaultDisplayTopInset = SystemProperties.getInt(PROP_DEFAULT_DISPLAY_TOP_INSET, -1);
+        mDefaultHdrConversionMode = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_enableDefaultHdrConversionPassthrough)
+                        ? HdrConversionMode.HDR_CONVERSION_PASSTHROUGH
+                        : HdrConversionMode.HDR_CONVERSION_SYSTEM;
         float[] lux = getFloatArray(resources.obtainTypedArray(
                 com.android.internal.R.array.config_minimumBrightnessCurveLux));
         float[] nits = getFloatArray(resources.obtainTypedArray(
@@ -2236,7 +2242,7 @@
     @GuardedBy("mSyncRoot")
     void updateHdrConversionModeSettingsLocked() {
         final int conversionMode = Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.HDR_CONVERSION_MODE, HdrConversionMode.HDR_CONVERSION_SYSTEM);
+                Settings.Global.HDR_CONVERSION_MODE, mDefaultHdrConversionMode);
         final int preferredHdrOutputType = conversionMode == HdrConversionMode.HDR_CONVERSION_FORCE
                 ? Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.HDR_FORCE_CONVERSION_TYPE,
@@ -2461,7 +2467,7 @@
                 return mHdrConversionMode;
             }
         }
-        return new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM);
+        return new HdrConversionMode(mDefaultHdrConversionMode);
     }
 
     HdrConversionMode getHdrConversionModeInternal() {
@@ -2473,6 +2479,14 @@
             mode = mOverrideHdrConversionMode != null
                     ? mOverrideHdrConversionMode
                     : mHdrConversionMode;
+            // Handle default: PASSTHROUGH. Don't include the system-preferred type.
+            if (mode == null
+                    && mDefaultHdrConversionMode == HdrConversionMode.HDR_CONVERSION_PASSTHROUGH) {
+                return new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH);
+            }
+            // Handle default or current mode: SYSTEM. Include the system preferred type.
+            // mOverrideHdrConversionMode and mHdrConversionMode do not include the system
+            // preferred type, it is kept separately in mSystemPreferredHdrOutputType.
             if (mode == null
                     || mode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM) {
                 return new HdrConversionMode(
@@ -5021,7 +5035,7 @@
      */
     class DeviceStateListener implements DeviceStateManager.DeviceStateCallback {
         // Base state corresponds to the physical state of the device
-        private int mBaseState = DeviceStateManager.INVALID_DEVICE_STATE;
+        private int mBaseState = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
 
         @Override
         public void onStateChanged(int deviceState) {
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index a43f93a..b1defe9 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -20,7 +20,6 @@
 
 import android.annotation.Nullable;
 import android.hardware.display.DisplayManagerInternal;
-import android.os.PowerManager;
 import android.os.Trace;
 
 /**
@@ -110,9 +109,18 @@
         try {
             mDisplayOffloader.stopOffload();
             mIsActive = false;
-            mDisplayPowerController.setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_POWER);
         }
     }
+
+    @Override
+    public float getBrightness() {
+        return mDisplayPowerController.getScreenBrightnessSetting();
+    }
+
+    @Override
+    public float getDozeBrightness() {
+        return mDisplayPowerController.getDozeBrightnessForOffload();
+    }
 }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index dbe85ea..77a43d0 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -375,7 +375,8 @@
     // information.
     // At the time of this writing, this value is changed within updatePowerState() only, which is
     // limited to the thread used by DisplayControllerHandler.
-    private final BrightnessReason mBrightnessReason = new BrightnessReason();
+    @VisibleForTesting
+    final BrightnessReason mBrightnessReason = new BrightnessReason();
     private final BrightnessReason mBrightnessReasonTemp = new BrightnessReason();
 
     // Brightness animation ramp rates in brightness units per second
@@ -486,6 +487,9 @@
 
     private DisplayOffloadSession mDisplayOffloadSession;
 
+    // Used to scale the brightness in doze mode
+    private float mDozeScaleFactor;
+
     /**
      * Creates the display power controller.
      */
@@ -546,6 +550,9 @@
         loadBrightnessRampRates();
         mSkipScreenOnBrightnessRamp = resources.getBoolean(
                 R.bool.config_skipScreenOnBrightnessRamp);
+        mDozeScaleFactor = context.getResources().getFraction(
+                R.fraction.config_screenAutoBrightnessDozeScaleFactor,
+                1, 1);
 
         Runnable modeChangeCallback = () -> {
             sendUpdatePowerState();
@@ -1041,10 +1048,6 @@
         }
 
         if (defaultModeBrightnessMapper != null) {
-            final float dozeScaleFactor = context.getResources().getFraction(
-                    R.fraction.config_screenAutoBrightnessDozeScaleFactor,
-                    1, 1);
-
             // Ambient Lux - Active Mode Brightness Thresholds
             float[] ambientBrighteningThresholds =
                     mDisplayDeviceConfig.getAmbientBrighteningPercentages();
@@ -1155,7 +1158,7 @@
             mAutomaticBrightnessController = mInjector.getAutomaticBrightnessController(
                     this, handler.getLooper(), mSensorManager, mLightSensor,
                     brightnessMappers, lightSensorWarmUpTimeConfig, PowerManager.BRIGHTNESS_MIN,
-                    PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, lightSensorRate,
+                    PowerManager.BRIGHTNESS_MAX, mDozeScaleFactor, lightSensorRate,
                     initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce,
                     brighteningLightDebounceIdle, darkeningLightDebounceIdle,
                     autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds,
@@ -1379,7 +1382,7 @@
         // Switch to doze auto-brightness mode if needed
         if (mFlags.areAutoBrightnessModesEnabled() && mAutomaticBrightnessController != null
                 && !mAutomaticBrightnessController.isInIdleMode()) {
-            mAutomaticBrightnessController.switchMode(mPowerRequest.policy == POLICY_DOZE
+            mAutomaticBrightnessController.switchMode(Display.isDozeState(state)
                     ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT);
         }
 
@@ -1434,7 +1437,9 @@
         float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness();
         // Apply auto-brightness.
         int brightnessAdjustmentFlags = 0;
-        if (Float.isNaN(brightnessState)) {
+        // AutomaticBrightnessStrategy has higher priority than OffloadBrightnessStrategy
+        if (Float.isNaN(brightnessState)
+                || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_OFFLOAD) {
             if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
                 brightnessState = mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
                         mTempBrightnessEvent);
@@ -1455,8 +1460,11 @@
                     if (mScreenOffBrightnessSensorController != null) {
                         mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
                     }
+                    setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
                 } else {
                     mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
+                    // Restore the lower-priority brightness strategy
+                    brightnessState = displayBrightnessState.getBrightness();
                 }
             }
         } else {
@@ -1467,16 +1475,22 @@
             mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
         }
 
-        // If there's an offload session and auto-brightness is on, we need to set the initial doze
-        // brightness using the doze auto-brightness curve before the offload session starts
-        // controlling the brightness.
-        if (Float.isNaN(brightnessState) && mFlags.areAutoBrightnessModesEnabled()
-                && mFlags.isDisplayOffloadEnabled()
-                && mPowerRequest.policy == POLICY_DOZE
-                && mDisplayOffloadSession != null
-                && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) {
-            rawBrightnessState = mAutomaticBrightnessController
-                    .getAutomaticScreenBrightnessBasedOnLastObservedLux(mTempBrightnessEvent);
+        // If there's an offload session, we need to set the initial doze brightness before
+        // the offload session starts controlling the brightness.
+        if (Float.isNaN(brightnessState) && mFlags.isDisplayOffloadEnabled()
+                && mPowerRequest.policy == POLICY_DOZE && mDisplayOffloadSession != null) {
+            if (mAutomaticBrightnessController != null
+                    && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) {
+                // Use the auto-brightness curve and the last observed lux
+                rawBrightnessState = mAutomaticBrightnessController
+                        .getAutomaticScreenBrightnessBasedOnLastObservedLux(
+                                mTempBrightnessEvent);
+            } else {
+                rawBrightnessState = getDozeBrightnessForOffload();
+                mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
+                        | BrightnessEvent.FLAG_DOZE_SCALE);
+            }
+
             if (BrightnessUtils.isValidBrightnessValue(rawBrightnessState)) {
                 brightnessState = clampScreenBrightness(rawBrightnessState);
                 mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_INITIAL);
@@ -1484,8 +1498,7 @@
         }
 
         // Use default brightness when dozing unless overridden.
-        if ((Float.isNaN(brightnessState))
-                && Display.isDozeState(state)) {
+        if (Float.isNaN(brightnessState) && mPowerRequest.policy == POLICY_DOZE) {
             rawBrightnessState = mScreenBrightnessDozeConfig;
             brightnessState = clampScreenBrightness(rawBrightnessState);
             mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT);
@@ -1724,6 +1737,7 @@
         mTempBrightnessEvent.setBrightness(brightnessState);
         mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId);
         mTempBrightnessEvent.setDisplayState(state);
+        mTempBrightnessEvent.setDisplayPolicy(mPowerRequest.policy);
         mTempBrightnessEvent.setReason(mBrightnessReason);
         mTempBrightnessEvent.setHbmMax(mBrightnessRangeController.getCurrentBrightnessMax());
         mTempBrightnessEvent.setHbmMode(mBrightnessRangeController.getHighBrightnessMode());
@@ -2437,6 +2451,11 @@
     }
 
     @Override
+    public float getDozeBrightnessForOffload() {
+        return mDisplayBrightnessController.getCurrentBrightness() * mDozeScaleFactor;
+    }
+
+    @Override
     public void setBrightness(float brightness) {
         mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness));
     }
@@ -2589,6 +2608,7 @@
         }
         pw.println("  mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
         pw.println("  mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
+        pw.println("  mDozeScaleFactor=" + mDozeScaleFactor);
         mHandler.runWithScissors(() -> dumpLocal(pw), 1000);
     }
 
@@ -3019,9 +3039,10 @@
                     setDwbcLoggingEnabled(msg.arg1);
                     break;
                 case MSG_SET_BRIGHTNESS_FROM_OFFLOAD:
-                    mDisplayBrightnessController.setBrightnessFromOffload(
-                            Float.intBitsToFloat(msg.arg1));
-                    updatePowerState();
+                    if (mDisplayBrightnessController.setBrightnessFromOffload(
+                            Float.intBitsToFloat(msg.arg1))) {
+                        updatePowerState();
+                    }
                     break;
             }
         }
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index ecf1635..408d610 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -161,6 +161,11 @@
     float getScreenBrightnessSetting();
 
     /**
+     * Gets the brightness value used when the device is in doze
+     */
+    float getDozeBrightnessForOffload();
+
+    /**
      * Sets up the temporary brightness for the associated display
      */
     void setTemporaryBrightness(float brightness);
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 88c24e0..b2fd9ed 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -257,7 +257,8 @@
                 SurfaceControl.DynamicDisplayInfo dynamicInfo,
                 SurfaceControl.DesiredDisplayModeSpecs modeSpecs, boolean isFirstDisplay) {
             super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + physicalDisplayId,
-                    getContext());
+                    getContext(),
+                    getFeatureFlags().isPixelAnisotropyCorrectionInLogicalDisplayEnabled());
             mPhysicalDisplayId = physicalDisplayId;
             mIsFirstDisplay = isFirstDisplay;
             updateDisplayPropertiesLocked(staticDisplayInfo, dynamicInfo, modeSpecs);
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index db636d6..5eaaf35 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -35,7 +35,6 @@
 
 import com.android.server.display.layout.Layout;
 import com.android.server.display.mode.DisplayModeDirector;
-import com.android.server.wm.utils.DisplayInfoOverrides;
 import com.android.server.wm.utils.InsetUtils;
 
 import java.io.PrintWriter;
@@ -204,7 +203,28 @@
     private SparseArray<SurfaceControl.RefreshRateRange> mThermalRefreshRateThrottling =
             new SparseArray<>();
 
+    /**
+     * If the aspect ratio of the resolution of the display does not match the physical aspect
+     * ratio of the display, then without this feature enabled, picture would appear stretched to
+     * the user. This is because applications assume that they are rendered on square pixels
+     * (meaning density of pixels in x and y directions are equal). This would result into circles
+     * appearing as ellipses to the user.
+     * To compensate for non-square (anisotropic) pixels, if this feature is enabled:
+     * 1. LogicalDisplay will add more pixels for the applications to render on, as if the pixels
+     * were square and occupied the full display.
+     * 2. SurfaceFlinger will squeeze this taller/wider surface into the available number of
+     * physical pixels in the current display resolution.
+     * 3. If a setting on the display itself is set to "fill the entire display panel" then the
+     * display will stretch the pixels to fill the display fully.
+     */
+    private final boolean mIsAnisotropyCorrectionEnabled;
+
     LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
+        this(displayId, layerStack, primaryDisplayDevice, false);
+    }
+
+    LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice,
+            boolean isAnisotropyCorrectionEnabled) {
         mDisplayId = displayId;
         mLayerStack = layerStack;
         mPrimaryDisplayDevice = primaryDisplayDevice;
@@ -215,6 +235,7 @@
         mThermalBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
         mPowerThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
         mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId;
+        mIsAnisotropyCorrectionEnabled = isAnisotropyCorrectionEnabled;
     }
 
     public void setDevicePositionLocked(int position) {
@@ -453,6 +474,14 @@
             int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right;
             int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom;
 
+            if (mIsAnisotropyCorrectionEnabled && deviceInfo.xDpi > 0 && deviceInfo.yDpi > 0) {
+                if (deviceInfo.xDpi > deviceInfo.yDpi * DisplayDevice.MAX_ANISOTROPY) {
+                    maskedHeight = (int) (maskedHeight * deviceInfo.xDpi / deviceInfo.yDpi + 0.5);
+                } else if (deviceInfo.xDpi * DisplayDevice.MAX_ANISOTROPY < deviceInfo.yDpi) {
+                    maskedWidth = (int) (maskedWidth * deviceInfo.yDpi / deviceInfo.xDpi + 0.5);
+                }
+            }
+
             mBaseDisplayInfo.type = deviceInfo.type;
             mBaseDisplayInfo.address = deviceInfo.address;
             mBaseDisplayInfo.deviceProductInfo = deviceInfo.deviceProductInfo;
@@ -666,6 +695,31 @@
         physWidth -= maskingInsets.left + maskingInsets.right;
         physHeight -= maskingInsets.top + maskingInsets.bottom;
 
+        var displayLogicalWidth = displayInfo.logicalWidth;
+        var displayLogicalHeight = displayInfo.logicalHeight;
+
+        if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.xDpi > 0
+                    && displayDeviceInfo.yDpi > 0) {
+            if (displayDeviceInfo.xDpi > displayDeviceInfo.yDpi * DisplayDevice.MAX_ANISOTROPY) {
+                var scalingFactor = displayDeviceInfo.yDpi / displayDeviceInfo.xDpi;
+                if (rotated) {
+                    displayLogicalWidth = (int) ((float) displayLogicalWidth * scalingFactor + 0.5);
+                } else {
+                    displayLogicalHeight = (int) ((float) displayLogicalHeight * scalingFactor
+                                                          + 0.5);
+                }
+            } else if (displayDeviceInfo.xDpi * DisplayDevice.MAX_ANISOTROPY
+                               < displayDeviceInfo.yDpi) {
+                var scalingFactor = displayDeviceInfo.xDpi / displayDeviceInfo.yDpi;
+                if (rotated) {
+                    displayLogicalHeight = (int) ((float) displayLogicalHeight * scalingFactor
+                                                          + 0.5);
+                } else {
+                    displayLogicalWidth = (int) ((float) displayLogicalWidth * scalingFactor + 0.5);
+                }
+            }
+        }
+
         // Determine whether the width or height is more constrained to be scaled.
         //    physWidth / displayInfo.logicalWidth    => letter box
         // or physHeight / displayInfo.logicalHeight  => pillar box
@@ -675,16 +729,16 @@
         // comparing them.
         int displayRectWidth, displayRectHeight;
         if ((displayInfo.flags & Display.FLAG_SCALING_DISABLED) != 0 || mDisplayScalingDisabled) {
-            displayRectWidth = displayInfo.logicalWidth;
-            displayRectHeight = displayInfo.logicalHeight;
-        } else if (physWidth * displayInfo.logicalHeight
-                < physHeight * displayInfo.logicalWidth) {
+            displayRectWidth = displayLogicalWidth;
+            displayRectHeight = displayLogicalHeight;
+        } else if (physWidth * displayLogicalHeight
+                < physHeight * displayLogicalWidth) {
             // Letter box.
             displayRectWidth = physWidth;
-            displayRectHeight = displayInfo.logicalHeight * physWidth / displayInfo.logicalWidth;
+            displayRectHeight = displayLogicalHeight * physWidth / displayLogicalWidth;
         } else {
             // Pillar box.
-            displayRectWidth = displayInfo.logicalWidth * physHeight / displayInfo.logicalHeight;
+            displayRectWidth = displayLogicalWidth * physHeight / displayLogicalHeight;
             displayRectHeight = physHeight;
         }
         int displayRectTop = (physHeight - displayRectHeight) / 2;
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 2e8de31..e092fda 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -192,9 +192,10 @@
     private final DisplayIdProducer mIdProducer = (isDefault) ->
             isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++;
     private Layout mCurrentLayout = null;
-    private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
-    private int mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
-    private int mDeviceStateToBeAppliedAfterBoot = DeviceStateManager.INVALID_DEVICE_STATE;
+    private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
+    private int mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
+    private int mDeviceStateToBeAppliedAfterBoot =
+            DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
     private boolean mBootCompleted = false;
     private boolean mInteractive;
     private final DisplayManagerFlags mFlags;
@@ -460,7 +461,7 @@
         // temporarily turned off.
         resetLayoutLocked(mDeviceState, state, /* transitionValue= */ true);
         mPendingDeviceState = state;
-        mDeviceStateToBeAppliedAfterBoot = DeviceStateManager.INVALID_DEVICE_STATE;
+        mDeviceStateToBeAppliedAfterBoot = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
         final boolean wakeDevice = shouldDeviceBeWoken(mPendingDeviceState, mDeviceState,
                 mInteractive, mBootCompleted);
         final boolean sleepDevice = shouldDeviceBePutToSleep(mPendingDeviceState, mDeviceState,
@@ -510,7 +511,8 @@
     void onBootCompleted() {
         synchronized (mSyncRoot) {
             mBootCompleted = true;
-            if (mDeviceStateToBeAppliedAfterBoot != DeviceStateManager.INVALID_DEVICE_STATE) {
+            if (mDeviceStateToBeAppliedAfterBoot
+                    != DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER) {
                 setDeviceStateLocked(mDeviceStateToBeAppliedAfterBoot,
                         /* isOverrideActive= */ false);
             }
@@ -568,7 +570,7 @@
     @VisibleForTesting
     boolean shouldDeviceBePutToSleep(int pendingState, int currentState, boolean isOverrideActive,
             boolean isInteractive, boolean isBootCompleted) {
-        return currentState != DeviceStateManager.INVALID_DEVICE_STATE
+        return currentState != DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER
                 && mDeviceStatesOnWhichToSleep.get(pendingState)
                 && !mDeviceStatesOnWhichToSleep.get(currentState)
                 && !isOverrideActive
@@ -598,13 +600,13 @@
     private void transitionToPendingStateLocked() {
         resetLayoutLocked(mDeviceState, mPendingDeviceState, /* transitionValue= */ false);
         mDeviceState = mPendingDeviceState;
-        mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
+        mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
         applyLayoutLocked();
         updateLogicalDisplaysLocked();
     }
 
     private void finishStateTransitionLocked(boolean force) {
-        if (mPendingDeviceState == DeviceStateManager.INVALID_DEVICE_STATE) {
+        if (mPendingDeviceState == DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER) {
             return;
         }
 
@@ -1149,7 +1151,8 @@
      */
     private LogicalDisplay createNewLogicalDisplayLocked(DisplayDevice device, int displayId) {
         final int layerStack = assignLayerStackLocked(displayId);
-        final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
+        final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device,
+                mFlags.isPixelAnisotropyCorrectionInLogicalDisplayEnabled());
         display.updateLocked(mDisplayDeviceRepo);
 
         final DisplayInfo info = display.getDisplayInfoLocked();
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index 5423b74..82b401a 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -16,6 +16,9 @@
 
 package com.android.server.display.brightness;
 
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.policyToString;
+
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
 import static com.android.server.display.config.DisplayBrightnessMappingConfig.autoBrightnessModeToString;
 
@@ -46,6 +49,7 @@
     private int mDisplayId;
     private String mPhysicalDisplayId;
     private int mDisplayState;
+    private int mDisplayPolicy;
     private long mTime;
     private float mLux;
     private float mPreThresholdLux;
@@ -85,6 +89,7 @@
         mDisplayId = that.getDisplayId();
         mPhysicalDisplayId = that.getPhysicalDisplayId();
         mDisplayState = that.mDisplayState;
+        mDisplayPolicy = that.mDisplayPolicy;
         mTime = that.getTime();
         // Lux values
         mLux = that.getLux();
@@ -117,6 +122,7 @@
         mTime = SystemClock.uptimeMillis();
         mPhysicalDisplayId = "";
         mDisplayState = Display.STATE_UNKNOWN;
+        mDisplayPolicy = POLICY_OFF;
         // Lux values
         mLux = 0;
         mPreThresholdLux = 0;
@@ -155,6 +161,7 @@
                 && mDisplayId == that.mDisplayId
                 && mPhysicalDisplayId.equals(that.mPhysicalDisplayId)
                 && mDisplayState == that.mDisplayState
+                && mDisplayPolicy == that.mDisplayPolicy
                 && Float.floatToRawIntBits(mLux) == Float.floatToRawIntBits(that.mLux)
                 && Float.floatToRawIntBits(mPreThresholdLux)
                 == Float.floatToRawIntBits(that.mPreThresholdLux)
@@ -191,6 +198,7 @@
                 + "disp=" + mDisplayId
                 + ", physDisp=" + mPhysicalDisplayId
                 + ", displayState=" + Display.stateToString(mDisplayState)
+                + ", displayPolicy=" + policyToString(mDisplayPolicy)
                 + ", brt=" + mBrightness + ((mFlags & FLAG_USER_SET) != 0 ? "(user_set)" : "")
                 + ", initBrt=" + mInitialBrightness
                 + ", rcmdBrt=" + mRecommendedBrightness
@@ -251,6 +259,10 @@
         mDisplayState = state;
     }
 
+    public void setDisplayPolicy(int policy) {
+        mDisplayPolicy = policy;
+    }
+
     public float getLux() {
         return mLux;
     }
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index f6d02db..34d53be 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -26,6 +26,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.server.display.AutomaticBrightnessController;
 import com.android.server.display.BrightnessMappingStrategy;
 import com.android.server.display.BrightnessSetting;
@@ -175,14 +176,19 @@
 
     /**
      * Sets the brightness from the offload session.
+     * @return Whether the offload brightness has changed
      */
-    public void setBrightnessFromOffload(float brightness) {
+    public boolean setBrightnessFromOffload(float brightness) {
         synchronized (mLock) {
-            if (mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy() != null) {
+            if (mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy() != null
+                    && !BrightnessSynchronizer.floatEquals(mDisplayBrightnessStrategySelector
+                    .getOffloadBrightnessStrategy().getOffloadScreenBrightness(), brightness)) {
                 mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()
                         .setOffloadScreenBrightness(brightness);
+                return true;
             }
         }
+        return false;
     }
 
     /**
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index 055f94a..03fb147 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -71,6 +71,14 @@
     @Nullable
     private final OffloadBrightnessStrategy mOffloadBrightnessStrategy;
 
+    // A collective representation of all the strategies that the selector is aware of. This is
+    // non null, but the strategies this is tracking can be null
+    @NonNull
+    private final DisplayBrightnessStrategy[] mDisplayBrightnessStrategies;
+
+    @NonNull
+    private final DisplayManagerFlags mDisplayManagerFlags;
+
     // We take note of the old brightness strategy so that we can know when the strategy changes.
     private String mOldBrightnessStrategyName;
 
@@ -84,6 +92,7 @@
         if (injector == null) {
             injector = new Injector();
         }
+        mDisplayManagerFlags = flags;
         mDisplayId = displayId;
         mDozeBrightnessStrategy = injector.getDozeBrightnessStrategy();
         mScreenOffBrightnessStrategy = injector.getScreenOffBrightnessStrategy();
@@ -98,6 +107,10 @@
         } else {
             mOffloadBrightnessStrategy = null;
         }
+        mDisplayBrightnessStrategies = new DisplayBrightnessStrategy[]{mInvalidBrightnessStrategy,
+                mScreenOffBrightnessStrategy, mDozeBrightnessStrategy, mFollowerBrightnessStrategy,
+                mBoostBrightnessStrategy, mOverrideBrightnessStrategy, mTemporaryBrightnessStrategy,
+                mOffloadBrightnessStrategy};
         mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean(
                 R.bool.config_allowAutoBrightnessWhileDozing);
         mOldBrightnessStrategyName = mInvalidBrightnessStrategy.getName();
@@ -127,11 +140,16 @@
         } else if (BrightnessUtils.isValidBrightnessValue(
                 mTemporaryBrightnessStrategy.getTemporaryScreenBrightness())) {
             displayBrightnessStrategy = mTemporaryBrightnessStrategy;
-        } else if (mOffloadBrightnessStrategy != null && BrightnessUtils.isValidBrightnessValue(
+        } else if (mAutomaticBrightnessStrategy.shouldUseAutoBrightness()
+                && mOffloadBrightnessStrategy != null && BrightnessUtils.isValidBrightnessValue(
                 mOffloadBrightnessStrategy.getOffloadScreenBrightness())) {
             displayBrightnessStrategy = mOffloadBrightnessStrategy;
         }
 
+        if (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) {
+            postProcess(constructStrategySelectionNotifyRequest(displayBrightnessStrategy));
+        }
+
         if (!mOldBrightnessStrategyName.equals(displayBrightnessStrategy.getName())) {
             Slog.i(TAG,
                     "Changing the DisplayBrightnessStrategy from " + mOldBrightnessStrategyName
@@ -179,9 +197,24 @@
                 "  mAllowAutoBrightnessWhileDozingConfig= "
                         + mAllowAutoBrightnessWhileDozingConfig);
         IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
-        mTemporaryBrightnessStrategy.dump(ipw);
-        if (mOffloadBrightnessStrategy != null) {
-            mOffloadBrightnessStrategy.dump(ipw);
+        for (DisplayBrightnessStrategy displayBrightnessStrategy : mDisplayBrightnessStrategies) {
+            if (displayBrightnessStrategy != null) {
+                displayBrightnessStrategy.dump(ipw);
+            }
+        }
+    }
+
+    private StrategySelectionNotifyRequest constructStrategySelectionNotifyRequest(
+            DisplayBrightnessStrategy selectedDisplayBrightnessStrategy) {
+        return new StrategySelectionNotifyRequest(selectedDisplayBrightnessStrategy);
+    }
+
+    private void postProcess(StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+        for (DisplayBrightnessStrategy displayBrightnessStrategy : mDisplayBrightnessStrategies) {
+            if (displayBrightnessStrategy != null) {
+                displayBrightnessStrategy.strategySelectionPostProcessor(
+                        strategySelectionNotifyRequest);
+            }
         }
     }
 
@@ -193,12 +226,9 @@
         // We are not checking the targetDisplayState, but rather relying on the policy because
         // a user can define a different display state(displayPowerRequest.dozeScreenState) too
         // in the request with the Doze policy
-        if (displayPowerRequest.policy == DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE) {
-            if (!mAllowAutoBrightnessWhileDozingConfig) {
-                return true;
-            }
-        }
-        return false;
+        return displayPowerRequest.policy == DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE
+                && !mAllowAutoBrightnessWhileDozingConfig
+                && BrightnessUtils.isValidBrightnessValue(displayPowerRequest.dozeScreenBrightness);
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java b/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java
new file mode 100644
index 0000000..d8bd2e4
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness;
+
+import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
+
+import java.util.Objects;
+
+/**
+ * A wrapper class to encapsulate the request to notify the strategies about the selection of a
+ * DisplayBrightnessStrategy
+ */
+public final class StrategySelectionNotifyRequest {
+    // The strategy that was selected with the current request
+    private final DisplayBrightnessStrategy mSelectedDisplayBrightnessStrategy;
+
+    public StrategySelectionNotifyRequest(DisplayBrightnessStrategy displayBrightnessStrategy) {
+        mSelectedDisplayBrightnessStrategy = displayBrightnessStrategy;
+    }
+
+    public DisplayBrightnessStrategy getSelectedDisplayBrightnessStrategy() {
+        return mSelectedDisplayBrightnessStrategy;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof StrategySelectionNotifyRequest)) {
+            return false;
+        }
+        StrategySelectionNotifyRequest other = (StrategySelectionNotifyRequest) obj;
+        return other.getSelectedDisplayBrightnessStrategy()
+                == getSelectedDisplayBrightnessStrategy();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSelectedDisplayBrightnessStrategy);
+    }
+}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index 8eaecef..8b54b22 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -15,6 +15,8 @@
  */
 package com.android.server.display.brightness.strategy;
 
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.display.BrightnessConfiguration;
@@ -102,12 +104,10 @@
             boolean allowAutoBrightnessWhileDozingConfig, int brightnessReason, int policy,
             float lastUserSetScreenBrightness, boolean userSetBrightnessChanged) {
         final boolean autoBrightnessEnabledInDoze =
-                allowAutoBrightnessWhileDozingConfig
-                        && Display.isDozeState(targetDisplayState);
+                allowAutoBrightnessWhileDozingConfig && policy == POLICY_DOZE;
         mIsAutoBrightnessEnabled = shouldUseAutoBrightness()
                 && (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze)
                 && brightnessReason != BrightnessReason.REASON_OVERRIDE
-                && brightnessReason != BrightnessReason.REASON_OFFLOAD
                 && mAutomaticBrightnessController != null;
         mAutoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness()
                 && !(targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze);
diff --git a/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
index dd400d9..11edde9 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
@@ -22,6 +22,9 @@
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
+
+import java.io.PrintWriter;
 
 /**
  * Manages the brightness of the display when the system brightness boost is requested.
@@ -48,4 +51,13 @@
     public String getName() {
         return "BoostBrightnessStrategy";
     }
+
+    @Override
+    public void dump(PrintWriter writer) {}
+
+    @Override
+    public void strategySelectionPostProcessor(
+            StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+        // DO NOTHING
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
index 27d04fd..7b49957 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
@@ -20,6 +20,9 @@
 import android.hardware.display.DisplayManagerInternal;
 
 import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
+
+import java.io.PrintWriter;
 
 /**
  * Decides the DisplayBrighntessState that the display should change to based on strategy-specific
@@ -40,4 +43,16 @@
      */
     @NonNull
     String getName();
+
+    /**
+     * Dumps the state of the Strategy
+     * @param writer
+     */
+    void dump(PrintWriter writer);
+
+     /**
+     * Notifies this strategy about the selection of a DisplayBrightnessStrategy
+     */
+    void strategySelectionPostProcessor(
+            StrategySelectionNotifyRequest strategySelectionNotifyRequest);
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
index 8299586..5afdc42 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
@@ -21,6 +21,9 @@
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
+
+import java.io.PrintWriter;
 
 /**
  * Manages the brightness of the display when the system is in the doze state.
@@ -42,4 +45,12 @@
         return "DozeBrightnessStrategy";
     }
 
+    @Override
+    public void dump(PrintWriter writer) {}
+
+    @Override
+    public void strategySelectionPostProcessor(
+            StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+        // DO NOTHING
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
index 585f576..0650c1c 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
@@ -22,6 +22,7 @@
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
 
 import java.io.PrintWriter;
 
@@ -75,10 +76,17 @@
     /**
      * Dumps the state of this class.
      */
+    @Override
     public void dump(PrintWriter writer) {
         writer.println("FollowerBrightnessStrategy:");
         writer.println("  mDisplayId=" + mDisplayId);
         writer.println("  mBrightnessToFollow:" + mBrightnessToFollow);
         writer.println("  mBrightnessToFollowSlowChange:" + mBrightnessToFollowSlowChange);
     }
+
+    @Override
+    public void strategySelectionPostProcessor(
+            StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+        // DO NOTHING
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
index bc24196..bf37ee0 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
@@ -22,6 +22,9 @@
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
+
+import java.io.PrintWriter;
 
 /**
  * Manages the brightness of the display when the system is in the invalid state.
@@ -39,4 +42,13 @@
     public String getName() {
         return "InvalidBrightnessStrategy";
     }
+
+    @Override
+    public void dump(PrintWriter writer) {}
+
+    @Override
+    public void strategySelectionPostProcessor(
+            StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+        // DO NOTHING
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
index 55f8914..d2bb1e2 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
@@ -21,6 +21,7 @@
 
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
 
 import java.io.PrintWriter;
 
@@ -67,8 +68,15 @@
     /**
      * Dumps the state of this class.
      */
+    @Override
     public void dump(PrintWriter writer) {
         writer.println("OffloadBrightnessStrategy:");
         writer.println("  mOffloadScreenBrightness:" + mOffloadScreenBrightness);
     }
+
+    @Override
+    public void strategySelectionPostProcessor(
+            StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+        // DO NOTHING
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
index 13327cb..653170c 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
@@ -21,6 +21,9 @@
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
+
+import java.io.PrintWriter;
 
 /**
  * Manages the brightness of the display when the system brightness is overridden
@@ -40,4 +43,13 @@
     public String getName() {
         return "OverrideBrightnessStrategy";
     }
+
+    @Override
+    public void dump(PrintWriter writer) {}
+
+    @Override
+    public void strategySelectionPostProcessor(
+            StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+        // DO NOTHING
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
index 3d411d3..f0cce23 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
@@ -22,6 +22,9 @@
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
+
+import java.io.PrintWriter;
 
 /**
  * Manages the brightness of the display when the system is in the ScreenOff state.
@@ -41,4 +44,13 @@
     public String getName() {
         return "ScreenOffBrightnessStrategy";
     }
+
+    @Override
+    public void dump(PrintWriter writer) {}
+
+    @Override
+    public void strategySelectionPostProcessor(
+            StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+        // DO NOTHING
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
index 35f7dd0..91e1d09 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
@@ -22,6 +22,7 @@
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
 
 import java.io.PrintWriter;
 
@@ -68,8 +69,15 @@
     /**
      * Dumps the state of this class.
      */
+    @Override
     public void dump(PrintWriter writer) {
         writer.println("TemporaryBrightnessStrategy:");
         writer.println("  mTemporaryScreenBrightness:" + mTemporaryScreenBrightness);
     }
+
+    @Override
+    public void strategySelectionPostProcessor(
+            StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+        // DO NOTHING
+    }
 }
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 516d4b1..15ee937 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -121,11 +121,23 @@
             Flags::refreshRateVotingTelemetry
     );
 
+    private final FlagState mPixelAnisotropyCorrectionEnabled = new FlagState(
+            Flags.FLAG_ENABLE_PIXEL_ANISOTROPY_CORRECTION,
+            Flags::enablePixelAnisotropyCorrection
+    );
+
     private final FlagState mSensorBasedBrightnessThrottling = new FlagState(
             Flags.FLAG_SENSOR_BASED_BRIGHTNESS_THROTTLING,
             Flags::sensorBasedBrightnessThrottling
     );
 
+
+    private final FlagState mRefactorDisplayPowerController = new FlagState(
+            Flags.FLAG_REFACTOR_DISPLAY_POWER_CONTROLLER,
+            Flags::refactorDisplayPowerController
+    );
+
+
     /**
      * @return {@code true} if 'port' is allowed in display layout configuration file.
      */
@@ -252,10 +264,18 @@
         return mRefreshRateVotingTelemetry.isEnabled();
     }
 
+    public boolean isPixelAnisotropyCorrectionInLogicalDisplayEnabled() {
+        return mPixelAnisotropyCorrectionEnabled.isEnabled();
+    }
+
     public boolean isSensorBasedBrightnessThrottlingEnabled() {
         return mSensorBasedBrightnessThrottling.isEnabled();
     }
 
+    public boolean isRefactorDisplayPowerControllerEnabled() {
+        return mRefactorDisplayPowerController.isEnabled();
+    }
+
     /**
      * dumps all flagstates
      * @param pw printWriter
@@ -279,7 +299,9 @@
         pw.println(" " + mAutoBrightnessModesFlagState);
         pw.println(" " + mFastHdrTransitions);
         pw.println(" " + mRefreshRateVotingTelemetry);
+        pw.println(" " + mPixelAnisotropyCorrectionEnabled);
         pw.println(" " + mSensorBasedBrightnessThrottling);
+        pw.println(" " + mRefactorDisplayPowerController);
     }
 
     private static class FlagState {
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 63ab3a9..9bf36e4 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
@@ -186,9 +186,25 @@
 }
 
 flag {
+    name: "enable_pixel_anisotropy_correction"
+    namespace: "display_manager"
+    description: "Feature flag for enabling display anisotropy correction through LogicalDisplay upscaling"
+    bug: "317363416"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "sensor_based_brightness_throttling"
     namespace: "display_manager"
     description: "Feature flag for enabling brightness throttling using sensor from config."
     bug: "294900859"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "refactor_display_power_controller"
+    namespace: "display_manager"
+    description: "Feature flag for refactoring   the DisplayPowerController and associated components"
+    bug: "294444204"
+    is_fixed_read_only: true
+}
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 8b4e1ff..64cbd54 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -1015,12 +1015,15 @@
                 // Infinity means that we want the highest possible refresh rate
                 minRefreshRate = highestRefreshRate;
 
-                if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled) {
-                    // The flag had been turned off, we need to restore the original value
+                if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+                        && displayId == Display.DEFAULT_DISPLAY) {
+                    // The flag has been turned off, we need to restore the original value. We'll
+                    // use the peak refresh rate of the default display.
                     Settings.System.putFloatForUser(cr, Settings.System.MIN_REFRESH_RATE,
                             highestRefreshRate, cr.getUserId());
                 }
             } else if (mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+                    && displayId == Display.DEFAULT_DISPLAY
                     && Math.round(minRefreshRate) == Math.round(highestRefreshRate)) {
                 // The flag has been turned on, we need to upgrade the setting
                 Settings.System.putFloatForUser(cr, Settings.System.MIN_REFRESH_RATE,
@@ -1033,12 +1036,15 @@
                 // Infinity means that we want the highest possible refresh rate
                 peakRefreshRate = highestRefreshRate;
 
-                if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled) {
-                    // The flag had been turned off, we need to restore the original value
+                if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+                        && displayId == Display.DEFAULT_DISPLAY) {
+                    // The flag has been turned off, we need to restore the original value. We'll
+                    // use the peak refresh rate of the default display.
                     Settings.System.putFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE,
                             highestRefreshRate, cr.getUserId());
                 }
             } else if (mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+                    && displayId == Display.DEFAULT_DISPLAY
                     && Math.round(peakRefreshRate) == Math.round(highestRefreshRate)) {
                 // The flag has been turned on, we need to upgrade the setting
                 Settings.System.putFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE,
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index d2aff25..d997020 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -67,6 +67,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.UiEventLoggerImpl;
 import com.android.internal.util.DumpUtils;
@@ -112,7 +113,7 @@
     private final Object mLock = new Object();
 
     private final Context mContext;
-    private final DreamHandler mHandler;
+    private final Handler mHandler;
     private final DreamController mController;
     private final PowerManager mPowerManager;
     private final PowerManagerInternal mPowerManagerInternal;
@@ -211,9 +212,14 @@
     }
 
     public DreamManagerService(Context context) {
+        this(context, new DreamHandler(FgThread.get().getLooper()));
+    }
+
+    @VisibleForTesting
+    DreamManagerService(Context context, Handler handler) {
         super(context);
         mContext = context;
-        mHandler = new DreamHandler(FgThread.get().getLooper());
+        mHandler = handler;
         mController = new DreamController(context, mHandler, mControllerListener);
 
         mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
@@ -244,7 +250,6 @@
                 com.android.internal.R.bool.config_keepDreamingWhenUnplugging);
         mDreamsDisabledByAmbientModeSuppressionConfig = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig);
-
     }
 
     @Override
@@ -977,6 +982,18 @@
         }
 
         @Override // Binder call
+        public boolean canStartDreaming(boolean isScreenOn) {
+            checkPermission(android.Manifest.permission.READ_DREAM_STATE);
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return canStartDreamingInternal(isScreenOn);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override // Binder call
         public void testDream(int userId, ComponentName dream) {
             if (dream == null) {
                 throw new IllegalArgumentException("dream must not be null");
diff --git a/services/core/java/com/android/server/flags/services.aconfig b/services/core/java/com/android/server/flags/services.aconfig
new file mode 100644
index 0000000..10b5eff
--- /dev/null
+++ b/services/core/java/com/android/server/flags/services.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.flags"
+
+flag {
+     namespace: "wear_frameworks"
+     name: "enable_odp_feature_guard"
+     description: "Enable guard based on system feature to prevent OnDevicePersonalization service from starting on form factors."
+     bug: "322249125"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 252ea4b..e6bf2c9 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -61,7 +61,6 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 
 /**
@@ -126,14 +125,18 @@
 
         @Override
         public void setSystemWideGrammaticalGender(int grammaticalGender, int userId) {
-            checkCallerIsSystem();
+            isCallerAllowed();
             GrammaticalInflectionService.this.setSystemWideGrammaticalGender(grammaticalGender,
                     userId);
         }
 
         @Override
         public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
-            return canGetSystemGrammaticalGender(attributionSource)
+            if (!checkSystemGrammaticalGenderPermission(mPermissionManager, attributionSource)) {
+                throw new SecurityException("AttributionSource: " + attributionSource
+                        + " does not have READ_SYSTEM_GRAMMATICAL_GENDER permission.");
+            }
+            return checkSystemTermsOfAddressIsEnabled()
                     ? GrammaticalInflectionService.this.getSystemGrammaticalGender(
                     attributionSource, userId)
                     : GRAMMATICAL_GENDER_NOT_SPECIFIED;
@@ -154,7 +157,7 @@
         @Override
         @Nullable
         public byte[] getBackupPayload(int userId) {
-            checkCallerIsSystem();
+            isCallerAllowed();
             return mBackupHelper.getBackupPayload(userId);
         }
 
@@ -333,11 +336,13 @@
         return GRAMMATICAL_GENDER_NOT_SPECIFIED;
     }
 
-    private void checkCallerIsSystem() {
+    private void isCallerAllowed() {
         int callingUid = Binder.getCallingUid();
         if (callingUid != Process.SYSTEM_UID && callingUid != Process.SHELL_UID
                 && callingUid != Process.ROOT_UID) {
-            throw new SecurityException("Caller is not system, shell and root.");
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.CHANGE_CONFIGURATION,
+                    "Caller must be system, shell, root or has CHANGE_CONFIGURATION permission.");
         }
     }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 70993ca..1e90ab2 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -317,6 +317,10 @@
         if ((systemAudioOnPowerOnProp == ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
                 || ((systemAudioOnPowerOnProp == USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
                 && lastSystemAudioControlStatus && isSystemAudioControlFeatureEnabled())) {
+            if (hasAction(SystemAudioInitiationActionFromAvr.class)) {
+                Slog.i(TAG, "SystemAudioInitiationActionFromAvr is in progress. Restarting.");
+                removeAction(SystemAudioInitiationActionFromAvr.class);
+            }
             addAndStartAction(new SystemAudioInitiationActionFromAvr(this));
         }
     }
@@ -1032,6 +1036,10 @@
     void onSystemAudioControlFeatureSupportChanged(boolean enabled) {
         setSystemAudioControlFeatureEnabled(enabled);
         if (enabled) {
+            if (hasAction(SystemAudioInitiationActionFromAvr.class)) {
+                Slog.i(TAG, "SystemAudioInitiationActionFromAvr is in progress. Restarting.");
+                removeAction(SystemAudioInitiationActionFromAvr.class);
+            }
             addAndStartAction(new SystemAudioInitiationActionFromAvr(this));
         }
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index a23c08a..d876a38 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -198,7 +198,7 @@
         addValidationInfo(Constants.MESSAGE_CEC_VERSION,
                 oneByteValidator, ADDR_NOT_UNREGISTERED, ADDR_DIRECT);
         addValidationInfo(Constants.MESSAGE_SET_MENU_LANGUAGE,
-                new AsciiValidator(3), ADDR_NOT_UNREGISTERED, ADDR_BROADCAST);
+                new AsciiValidator(3), ADDR_TV, ADDR_BROADCAST);
 
         ParameterValidator statusRequestValidator = new MinimumOneByteRangeValidator(0x01, 0x03);
         addValidationInfo(Constants.MESSAGE_DECK_CONTROL,
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index c8c66238..d0532b99 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -4665,15 +4665,27 @@
         public void onAudioDeviceVolumeChanged(
                 @NonNull AudioDeviceAttributes audioDevice,
                 @NonNull VolumeInfo volumeInfo) {
+            int localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress();
 
-            // Do nothing if the System Audio device does not support <Set Audio Volume Level>
+            // We can't send <Set Audio Volume Level> if the System Audio device doesn't support it.
+            // But AudioService has already updated its volume and expects us to set it.
+            // So the best we can do is to send <Give Audio Status>, which triggers
+            // <Report Audio Status>, which should update AudioService with its correct volume.
             if (mSystemAudioDevice.getDeviceFeatures().getSetAudioVolumeLevelSupport()
                     != DeviceFeatures.FEATURE_SUPPORTED) {
+                // Update the volume tracked in AbsoluteVolumeAudioStatusAction
+                // so it correctly processes the next <Report Audio Status>
+                HdmiCecLocalDevice avbDevice = isTvDevice() ? tv() : playback();
+                avbDevice.updateAvbVolume(volumeInfo.getVolumeIndex());
+                // Send <Give Audio Status>
+                sendCecCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(
+                        localDeviceAddress,
+                        mSystemAudioDevice.getLogicalAddress()
+                ));
                 return;
             }
 
             // Send <Set Audio Volume Level> to notify the System Audio device of the volume change
-            int localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress();
             sendCecCommand(SetAudioVolumeLevelMessage.build(
                             localDeviceAddress,
                             mSystemAudioDevice.getLogicalAddress(),
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index dbef427..05b1cb69 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -38,7 +38,6 @@
 import android.hardware.SensorPrivacyManager;
 import android.hardware.SensorPrivacyManager.Sensors;
 import android.hardware.SensorPrivacyManagerInternal;
-import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.DisplayViewport;
 import android.hardware.input.HostUsiVersion;
@@ -55,6 +54,7 @@
 import android.hardware.input.InputSensorInfo;
 import android.hardware.input.InputSettings;
 import android.hardware.input.KeyboardLayout;
+import android.hardware.input.KeyboardLayoutSelectionResult;
 import android.hardware.input.TouchCalibration;
 import android.hardware.lights.Light;
 import android.hardware.lights.LightState;
@@ -86,9 +86,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
 import android.view.Display;
-import android.view.DisplayInfo;
 import android.view.IInputFilter;
 import android.view.IInputFilterHost;
 import android.view.IInputMonitorHost;
@@ -117,7 +115,6 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.DisplayThread;
 import com.android.server.LocalServices;
-import com.android.server.UiThread;
 import com.android.server.Watchdog;
 import com.android.server.input.InputManagerInternal.LidSwitchCallback;
 import com.android.server.input.debug.FocusEventDebugView;
@@ -325,6 +322,9 @@
     // Manages Keyboard modifier keys remapping
     private final KeyRemapper mKeyRemapper;
 
+    // Manages loading PointerIcons
+    private final PointerIconCache mPointerIconCache;
+
     // Maximum number of milliseconds to wait for input event injection.
     private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000;
 
@@ -411,74 +411,6 @@
     private boolean mShowKeyPresses = false;
     private boolean mShowRotaryInput = false;
 
-    @GuardedBy("mLoadedPointerIconsByDisplayAndType")
-    final SparseArray<SparseArray<PointerIcon>> mLoadedPointerIconsByDisplayAndType =
-            new SparseArray<>();
-    @GuardedBy("mLoadedPointerIconsByDisplayAndType")
-    boolean mUseLargePointerIcons = false;
-    @GuardedBy("mLoadedPointerIconsByDisplayAndType")
-    final SparseArray<Context> mDisplayContexts = new SparseArray<>();
-    @GuardedBy("mLoadedPointerIconsByDisplayAndType")
-    final SparseIntArray mDisplayDensities = new SparseIntArray();
-
-    final DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() {
-        @Override
-        public void onDisplayAdded(int displayId) {
-            synchronized (mLoadedPointerIconsByDisplayAndType) {
-                updateDisplayDensity(displayId);
-            }
-        }
-
-        @Override
-        public void onDisplayRemoved(int displayId) {
-            synchronized (mLoadedPointerIconsByDisplayAndType) {
-                mLoadedPointerIconsByDisplayAndType.remove(displayId);
-                mDisplayContexts.remove(displayId);
-                mDisplayDensities.delete(displayId);
-            }
-        }
-
-        @Override
-        public void onDisplayChanged(int displayId) {
-            synchronized (mLoadedPointerIconsByDisplayAndType) {
-                if (!updateDisplayDensity(displayId)) {
-                    return;
-                }
-                // The display density changed, so force all cached pointer icons to be
-                // reloaded for the display.
-                Slog.i(TAG,
-                        "Reloading pointer icons due to density change on display: " + displayId);
-                var iconsByType = mLoadedPointerIconsByDisplayAndType.get(displayId);
-                if (iconsByType == null) {
-                    return;
-                }
-                iconsByType.clear();
-                mDisplayContexts.remove(displayId);
-            }
-            mNative.reloadPointerIcons();
-        }
-
-        // Updates the cached display density for the given displayId, and returns true if
-        // the cached density changed.
-        @GuardedBy("mLoadedPointerIconsByDisplayAndType")
-        private boolean updateDisplayDensity(int displayId) {
-            final DisplayManager displayManager = Objects.requireNonNull(
-                    mContext.getSystemService(DisplayManager.class));
-            final Display display = displayManager.getDisplay(displayId);
-            if (display == null) {
-                return false;
-            }
-            DisplayInfo info = new DisplayInfo();
-            display.getDisplayInfo(info);
-            final int oldDensity = mDisplayDensities.get(displayId, 0 /* default */);
-            if (oldDensity == info.logicalDensityDpi) {
-                return false;
-            }
-            mDisplayDensities.put(displayId, info.logicalDensityDpi);
-            return true;
-        }
-    };
-
     /** Point of injection for test dependencies. */
     @VisibleForTesting
     static class Injector {
@@ -537,6 +469,7 @@
                 : new KeyboardBacklightControllerInterface() {};
         mStickyModifierStateController = new StickyModifierStateController();
         mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper());
+        mPointerIconCache = new PointerIconCache(mContext, mNative);
 
         mUseDevInputEventForAudioJack =
                 mContext.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
@@ -646,18 +579,11 @@
             mWiredAccessoryCallbacks.systemReady();
         }
 
-        final DisplayManager displayManager = Objects.requireNonNull(
-                mContext.getSystemService(DisplayManager.class));
-        displayManager.registerDisplayListener(mDisplayListener, UiThread.getHandler());
-        final Display[] displays = displayManager.getDisplays();
-        for (int i = 0; i < displays.length; i++) {
-            mDisplayListener.onDisplayAdded(displays[i].getDisplayId());
-        }
-
         mKeyboardLayoutManager.systemRunning();
         mBatteryController.systemRunning();
         mKeyboardBacklightController.systemRunning();
         mKeyRemapper.systemRunning();
+        mPointerIconCache.systemRunning();
     }
 
     private void reloadDeviceAliases() {
@@ -1319,9 +1245,9 @@
     }
 
     @Override // Binder call
-    public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
-            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
-            @Nullable InputMethodSubtype imeSubtype) {
+    public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
+            InputDeviceIdentifier identifier, @UserIdInt int userId,
+            @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) {
         return mKeyboardLayoutManager.getKeyboardLayoutForInputDevice(identifier, userId,
                 imeInfo, imeSubtype);
     }
@@ -2411,8 +2337,8 @@
         synchronized (mLidSwitchLock) { /* Test if blocked by lid switch lock. */ }
         synchronized (mInputMonitors) { /* Test if blocked by input monitor lock. */ }
         synchronized (mAdditionalDisplayInputPropertiesLock) { /* Test if blocked by props lock */ }
-        synchronized (mLoadedPointerIconsByDisplayAndType) { /* Test if blocked by pointer lock */}
         mBatteryController.monitor();
+        mPointerIconCache.monitor();
         mNative.monitor();
     }
 
@@ -2806,21 +2732,7 @@
     // Native callback.
     @SuppressWarnings("unused")
     private @NonNull PointerIcon getLoadedPointerIcon(int displayId, int type) {
-        synchronized (mLoadedPointerIconsByDisplayAndType) {
-            SparseArray<PointerIcon> iconsByType = mLoadedPointerIconsByDisplayAndType.get(
-                    displayId);
-            if (iconsByType == null) {
-                iconsByType = new SparseArray<>();
-                mLoadedPointerIconsByDisplayAndType.put(displayId, iconsByType);
-            }
-            PointerIcon icon = iconsByType.get(type);
-            if (icon == null) {
-                icon = PointerIcon.getLoadedSystemIcon(getContextForDisplay(displayId), type,
-                        mUseLargePointerIcons);
-                iconsByType.put(type, icon);
-            }
-            return Objects.requireNonNull(icon);
-        }
+        return mPointerIconCache.getLoadedPointerIcon(displayId, type);
     }
 
     // Native callback.
@@ -2833,33 +2745,6 @@
         return sc.mNativeObject;
     }
 
-    @GuardedBy("mLoadedPointerIconsByDisplayAndType")
-    @NonNull
-    private Context getContextForDisplay(int displayId) {
-        if (displayId == Display.INVALID_DISPLAY) {
-            // Fallback to using the default context.
-            return mContext;
-        }
-        if (displayId == mContext.getDisplay().getDisplayId()) {
-            return mContext;
-        }
-
-        Context displayContext = mDisplayContexts.get(displayId);
-        if (displayContext == null) {
-            final DisplayManager displayManager = Objects.requireNonNull(
-                    mContext.getSystemService(DisplayManager.class));
-            final Display display = displayManager.getDisplay(displayId);
-            if (display == null) {
-                // Fallback to using the default context.
-                return mContext;
-            }
-
-            displayContext = mContext.createDisplayContext(display);
-            mDisplayContexts.put(displayId, displayContext);
-        }
-        return displayContext;
-    }
-
     // Native callback.
     @SuppressWarnings("unused")
     private String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier, String languageTag,
@@ -3665,15 +3550,7 @@
     }
 
     void setUseLargePointerIcons(boolean useLargeIcons) {
-        synchronized (mLoadedPointerIconsByDisplayAndType) {
-            if (mUseLargePointerIcons == useLargeIcons) {
-                return;
-            }
-            mUseLargePointerIcons = useLargeIcons;
-            // Clear all cached icons on all displays.
-            mLoadedPointerIconsByDisplayAndType.clear();
-        }
-        UiThread.getHandler().post(mNative::reloadPointerIcons);
+        mPointerIconCache.setUseLargePointerIcons(useLargeIcons);
     }
 
     interface KeyboardBacklightControllerInterface {
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index c02d524..a1341b7 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -258,6 +258,7 @@
     }
 
     private void updateStylusPointerIconEnabled() {
-        mNative.setStylusPointerIconEnabled(InputSettings.isStylusPointerIconEnabled(mContext));
+        mNative.setStylusPointerIconEnabled(
+                InputSettings.isStylusPointerIconEnabled(mContext, true /* forceReloadSetting */));
     }
 }
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 46668de..283e692 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -16,10 +16,11 @@
 
 package com.android.server.input;
 
-import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEFAULT;
-import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE;
-import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER;
-import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
+import static android.hardware.input.KeyboardLayoutSelectionResult.FAILED;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT;
 
 import android.annotation.AnyThread;
 import android.annotation.MainThread;
@@ -46,6 +47,7 @@
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
 import android.hardware.input.KeyboardLayout;
+import android.hardware.input.KeyboardLayoutSelectionResult;
 import android.icu.lang.UScript;
 import android.icu.util.ULocale;
 import android.os.Bundle;
@@ -79,7 +81,6 @@
 import com.android.server.LocalServices;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.input.KeyboardMetricsCollector.KeyboardConfigurationEvent;
-import com.android.server.input.KeyboardMetricsCollector.LayoutSelectionCriteria;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 
 import libcore.io.Streams;
@@ -130,7 +131,8 @@
     // This cache stores "best-matched" layouts so that we don't need to run the matching
     // algorithm repeatedly.
     @GuardedBy("mKeyboardLayoutCache")
-    private final Map<String, KeyboardLayoutInfo> mKeyboardLayoutCache = new ArrayMap<>();
+    private final Map<String, KeyboardLayoutSelectionResult> mKeyboardLayoutCache =
+            new ArrayMap<>();
     private final Object mImeInfoLock = new Object();
     @Nullable
     @GuardedBy("mImeInfoLock")
@@ -222,17 +224,17 @@
         } else {
             Set<String> selectedLayouts = new HashSet<>();
             List<ImeInfo> imeInfoList = getImeInfoListForLayoutMapping();
-            List<KeyboardLayoutInfo> layoutInfoList = new ArrayList<>();
+            List<KeyboardLayoutSelectionResult> resultList = new ArrayList<>();
             boolean hasMissingLayout = false;
             for (ImeInfo imeInfo : imeInfoList) {
                 // Check if the layout has been previously configured
-                KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(
+                KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
                         keyboardIdentifier, imeInfo);
-                boolean noLayoutFound = layoutInfo == null || layoutInfo.mDescriptor == null;
+                boolean noLayoutFound = result.getLayoutDescriptor() == null;
                 if (!noLayoutFound) {
-                    selectedLayouts.add(layoutInfo.mDescriptor);
+                    selectedLayouts.add(result.getLayoutDescriptor());
                 }
-                layoutInfoList.add(layoutInfo);
+                resultList.add(result);
                 hasMissingLayout |= noLayoutFound;
             }
 
@@ -260,7 +262,7 @@
                     }
 
                     if (shouldLogConfiguration) {
-                        logKeyboardConfigurationEvent(inputDevice, imeInfoList, layoutInfoList,
+                        logKeyboardConfigurationEvent(inputDevice, imeInfoList, resultList,
                                 !mDataStore.hasInputDeviceEntry(key));
                     }
                 } finally {
@@ -757,10 +759,10 @@
         String keyboardLayoutDescriptor;
         if (useNewSettingsUi()) {
             synchronized (mImeInfoLock) {
-                KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(
+                KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
                         new KeyboardIdentifier(identifier, languageTag, layoutType),
                         mCurrentImeInfo);
-                keyboardLayoutDescriptor = layoutInfo == null ? null : layoutInfo.mDescriptor;
+                keyboardLayoutDescriptor = result.getLayoutDescriptor();
             }
         } else {
             keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
@@ -788,26 +790,26 @@
     }
 
     @AnyThread
-    @Nullable
-    public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
-            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
-            @Nullable InputMethodSubtype imeSubtype) {
+    @NonNull
+    public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
+            InputDeviceIdentifier identifier, @UserIdInt int userId,
+            @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) {
         if (!useNewSettingsUi()) {
             Slog.e(TAG, "getKeyboardLayoutForInputDevice() API not supported");
-            return null;
+            return FAILED;
         }
         InputDevice inputDevice = getInputDevice(identifier);
         if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
-            return null;
+            return FAILED;
         }
         KeyboardIdentifier keyboardIdentifier = new KeyboardIdentifier(inputDevice);
-        KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(
+        KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
                 keyboardIdentifier, new ImeInfo(userId, imeInfo, imeSubtype));
         if (DEBUG) {
             Slog.d(TAG, "getKeyboardLayoutForInputDevice() " + identifier.toString() + ", userId : "
-                    + userId + ", subtype = " + imeSubtype + " -> " + layoutInfo);
+                    + userId + ", subtype = " + imeSubtype + " -> " + result);
         }
-        return layoutInfo != null ? layoutInfo.mDescriptor : null;
+        return result;
     }
 
     @AnyThread
@@ -942,13 +944,13 @@
     }
 
     @Nullable
-    private KeyboardLayoutInfo getKeyboardLayoutForInputDeviceInternal(
+    private KeyboardLayoutSelectionResult getKeyboardLayoutForInputDeviceInternal(
             KeyboardIdentifier keyboardIdentifier, @Nullable ImeInfo imeInfo) {
         String layoutKey = new LayoutKey(keyboardIdentifier, imeInfo).toString();
         synchronized (mDataStore) {
             String layout = mDataStore.getKeyboardLayout(keyboardIdentifier.toString(), layoutKey);
             if (layout != null) {
-                return new KeyboardLayoutInfo(layout, LAYOUT_SELECTION_CRITERIA_USER);
+                return new KeyboardLayoutSelectionResult(layout, LAYOUT_SELECTION_CRITERIA_USER);
             }
         }
 
@@ -961,17 +963,17 @@
                 KeyboardLayout[] layoutList = getKeyboardLayoutListForInputDeviceInternal(
                         keyboardIdentifier, imeInfo);
                 // Call auto-matching algorithm to find the best matching layout
-                KeyboardLayoutInfo layoutInfo =
+                KeyboardLayoutSelectionResult result =
                         getDefaultKeyboardLayoutBasedOnImeInfo(keyboardIdentifier, imeInfo,
                                 layoutList);
-                mKeyboardLayoutCache.put(layoutKey, layoutInfo);
-                return layoutInfo;
+                mKeyboardLayoutCache.put(layoutKey, result);
+                return result;
             }
         }
     }
 
-    @Nullable
-    private static KeyboardLayoutInfo getDefaultKeyboardLayoutBasedOnImeInfo(
+    @NonNull
+    private static KeyboardLayoutSelectionResult getDefaultKeyboardLayoutBasedOnImeInfo(
             KeyboardIdentifier keyboardIdentifier, @Nullable ImeInfo imeInfo,
             KeyboardLayout[] layoutList) {
         Arrays.sort(layoutList);
@@ -986,7 +988,7 @@
                                     + "vendor and product Ids. " + keyboardIdentifier
                                     + " : " + layout.getDescriptor());
                 }
-                return new KeyboardLayoutInfo(layout.getDescriptor(),
+                return new KeyboardLayoutSelectionResult(layout.getDescriptor(),
                         LAYOUT_SELECTION_CRITERIA_DEVICE);
             }
         }
@@ -1004,13 +1006,14 @@
                                     + "HW information (Language tag and Layout type). "
                                     + keyboardIdentifier + " : " + layoutDesc);
                 }
-                return new KeyboardLayoutInfo(layoutDesc, LAYOUT_SELECTION_CRITERIA_DEVICE);
+                return new KeyboardLayoutSelectionResult(layoutDesc,
+                        LAYOUT_SELECTION_CRITERIA_DEVICE);
             }
         }
 
         if (imeInfo == null || imeInfo.mImeSubtypeHandle == null || imeInfo.mImeSubtype == null) {
             // Can't auto select layout based on IME info is null
-            return null;
+            return FAILED;
         }
 
         InputMethodSubtype subtype = imeInfo.mImeSubtype;
@@ -1027,9 +1030,10 @@
                             + layoutDesc);
         }
         if (layoutDesc != null) {
-            return new KeyboardLayoutInfo(layoutDesc, LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD);
+            return new KeyboardLayoutSelectionResult(layoutDesc,
+                    LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD);
         }
-        return null;
+        return FAILED;
     }
 
     @Nullable
@@ -1246,21 +1250,23 @@
     }
 
     private void logKeyboardConfigurationEvent(@NonNull InputDevice inputDevice,
-            @NonNull List<ImeInfo> imeInfoList, @NonNull List<KeyboardLayoutInfo> layoutInfoList,
+            @NonNull List<ImeInfo> imeInfoList,
+            @NonNull List<KeyboardLayoutSelectionResult> resultList,
             boolean isFirstConfiguration) {
-        if (imeInfoList.isEmpty() || layoutInfoList.isEmpty()) {
+        if (imeInfoList.isEmpty() || resultList.isEmpty()) {
             return;
         }
         KeyboardConfigurationEvent.Builder configurationEventBuilder =
                 new KeyboardConfigurationEvent.Builder(inputDevice).setIsFirstTimeConfiguration(
                         isFirstConfiguration);
         for (int i = 0; i < imeInfoList.size(); i++) {
-            KeyboardLayoutInfo layoutInfo = layoutInfoList.get(i);
+            KeyboardLayoutSelectionResult result = resultList.get(i);
             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 (result != null && result.getLayoutDescriptor() != null) {
+                layoutSelectionCriteria = result.getSelectionCriteria();
+                KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse(
+                        result.getLayoutDescriptor());
                 if (d != null) {
                     layoutName = d.keyboardLayoutName;
                 }
@@ -1477,33 +1483,6 @@
         }
     }
 
-    private static class KeyboardLayoutInfo {
-        @Nullable
-        private final String mDescriptor;
-        @LayoutSelectionCriteria
-        private final int mSelectionCriteria;
-
-        private KeyboardLayoutInfo(@Nullable String descriptor,
-                @LayoutSelectionCriteria int selectionCriteria) {
-            mDescriptor = descriptor;
-            mSelectionCriteria = selectionCriteria;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (obj instanceof KeyboardLayoutInfo) {
-                return Objects.equals(mDescriptor, ((KeyboardLayoutInfo) obj).mDescriptor)
-                        && mSelectionCriteria == ((KeyboardLayoutInfo) obj).mSelectionCriteria;
-            }
-            return false;
-        }
-
-        @Override
-        public int hashCode() {
-            return 31 * mSelectionCriteria + mDescriptor.hashCode();
-        }
-    }
-
     private interface KeyboardLayoutVisitor {
         void visitKeyboardLayout(Resources resources,
                 int keyboardLayoutResId, KeyboardLayout layout);
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index 4b9f2cf..b8ae737 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -16,13 +16,18 @@
 
 package com.android.server.input;
 
-import static java.lang.annotation.RetentionPolicy.SOURCE;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT;
+import static android.hardware.input.KeyboardLayoutSelectionResult.layoutSelectionCriteriaToString;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.role.RoleManager;
 import android.content.Intent;
 import android.hardware.input.KeyboardLayout;
+import android.hardware.input.KeyboardLayoutSelectionResult.LayoutSelectionCriteria;
 import android.icu.util.ULocale;
 import android.text.TextUtils;
 import android.util.Log;
@@ -37,8 +42,8 @@
 import com.android.internal.os.KeyboardConfiguredProto.KeyboardLayoutConfig;
 import com.android.internal.os.KeyboardConfiguredProto.RepeatedKeyboardLayoutConfig;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.policy.ModifierShortcutManager;
 
-import java.lang.annotation.Retention;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -55,32 +60,6 @@
     // (requires restart)
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    @Retention(SOURCE)
-    @IntDef(prefix = {"LAYOUT_SELECTION_CRITERIA_"}, value = {
-            LAYOUT_SELECTION_CRITERIA_UNSPECIFIED,
-            LAYOUT_SELECTION_CRITERIA_USER,
-            LAYOUT_SELECTION_CRITERIA_DEVICE,
-            LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
-            LAYOUT_SELECTION_CRITERIA_DEFAULT
-    })
-    public @interface LayoutSelectionCriteria {
-    }
-
-    /** Unspecified layout selection criteria */
-    public static final int LAYOUT_SELECTION_CRITERIA_UNSPECIFIED = 0;
-
-    /** Manual selection by user */
-    public static final int LAYOUT_SELECTION_CRITERIA_USER = 1;
-
-    /** Auto-detection based on device provided language tag and layout type */
-    public static final int LAYOUT_SELECTION_CRITERIA_DEVICE = 2;
-
-    /** Auto-detection based on IME provided language tag and layout type */
-    public static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD = 3;
-
-    /** Default selection */
-    public static final int LAYOUT_SELECTION_CRITERIA_DEFAULT = 4;
-
     @VisibleForTesting
     static final String DEFAULT_LAYOUT_NAME = "Default";
 
@@ -231,7 +210,10 @@
         DESKTOP_MODE(
                 FrameworkStatsLog
                         .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__DESKTOP_MODE,
-                "DESKTOP_MODE");
+                "DESKTOP_MODE"),
+        MULTI_WINDOW_NAVIGATION(FrameworkStatsLog
+                .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MULTI_WINDOW_NAVIGATION,
+                "MULTIWINDOW_NAVIGATION");
 
 
         private final int mValue;
@@ -315,6 +297,13 @@
                 }
             }
 
+            // The shortcut may be targeting a system role rather than using an intent selector,
+            // so check for that.
+            String role = intent.getStringExtra(ModifierShortcutManager.EXTRA_ROLE);
+            if (!TextUtils.isEmpty(role)) {
+                return getLogEventFromRole(role);
+            }
+
             Set<String> intentCategories = intent.getCategories();
             if (intentCategories == null || intentCategories.isEmpty()
                     || !intentCategories.contains(Intent.CATEGORY_LAUNCHER)) {
@@ -360,6 +349,23 @@
                     return null;
             }
         }
+
+        /**
+         * Find KeyboardLogEvent corresponding to the provide system role name.
+         * Returns {@code null} if no matching event found.
+         */
+        @Nullable
+        private static KeyboardLogEvent getLogEventFromRole(String role) {
+            if (RoleManager.ROLE_BROWSER.equals(role)) {
+                return LAUNCH_DEFAULT_BROWSER;
+            } else if (RoleManager.ROLE_SMS.equals(role)) {
+                return LAUNCH_DEFAULT_MESSAGING;
+            } else {
+                Log.w(TAG, "Keyboard shortcut to launch "
+                        + role + " not supported for logging");
+                return null;
+            }
+        }
     }
 
     /**
@@ -600,30 +606,16 @@
 
         @Override
         public String toString() {
-            return "{keyboardLanguageTag = " + keyboardLanguageTag + " keyboardLayoutType = "
+            return "{keyboardLanguageTag = " + keyboardLanguageTag
+                    + " keyboardLayoutType = "
                     + KeyboardLayout.LayoutType.getLayoutNameFromValue(keyboardLayoutType)
-                    + " keyboardLayoutName = " + keyboardLayoutName + " layoutSelectionCriteria = "
-                    + getStringForSelectionCriteria(layoutSelectionCriteria)
-                    + "imeLanguageTag = " + imeLanguageTag + " imeLayoutType = "
-                    + KeyboardLayout.LayoutType.getLayoutNameFromValue(imeLayoutType) + "}";
-        }
-    }
-
-    private static String getStringForSelectionCriteria(
-            @LayoutSelectionCriteria int layoutSelectionCriteria) {
-        switch (layoutSelectionCriteria) {
-            case LAYOUT_SELECTION_CRITERIA_UNSPECIFIED:
-                return "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED";
-            case LAYOUT_SELECTION_CRITERIA_USER:
-                return "LAYOUT_SELECTION_CRITERIA_USER";
-            case LAYOUT_SELECTION_CRITERIA_DEVICE:
-                return "LAYOUT_SELECTION_CRITERIA_DEVICE";
-            case LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD:
-                return "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD";
-            case LAYOUT_SELECTION_CRITERIA_DEFAULT:
-                return "LAYOUT_SELECTION_CRITERIA_DEFAULT";
-            default:
-                return "INVALID_CRITERIA";
+                    + " keyboardLayoutName = " + keyboardLayoutName
+                    + " layoutSelectionCriteria = "
+                    + layoutSelectionCriteriaToString(layoutSelectionCriteria)
+                    + " imeLanguageTag = " + imeLanguageTag
+                    + " imeLayoutType = " + KeyboardLayout.LayoutType.getLayoutNameFromValue(
+                    imeLayoutType)
+                    + "}";
         }
     }
 
diff --git a/services/core/java/com/android/server/input/PointerIconCache.java b/services/core/java/com/android/server/input/PointerIconCache.java
new file mode 100644
index 0000000..233b865
--- /dev/null
+++ b/services/core/java/com/android/server/input/PointerIconCache.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.PointerIcon;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.UiThread;
+
+import java.util.Objects;
+
+/**
+ * A thread-safe component of {@link InputManagerService} responsible for caching loaded
+ * {@link PointerIcon}s and triggering reloading of the icons.
+ */
+final class PointerIconCache {
+    private static final String TAG = PointerIconCache.class.getSimpleName();
+
+    private final Context mContext;
+
+    // Do not hold the lock when calling into native code.
+    private final NativeInputManagerService mNative;
+
+    // We use the UI thread for loading pointer icons.
+    private final Handler mUiThreadHandler = UiThread.getHandler();
+
+    @GuardedBy("mLoadedPointerIconsByDisplayAndType")
+    private final SparseArray<SparseArray<PointerIcon>> mLoadedPointerIconsByDisplayAndType =
+            new SparseArray<>();
+    @GuardedBy("mLoadedPointerIconsByDisplayAndType")
+    private boolean mUseLargePointerIcons = false;
+    @GuardedBy("mLoadedPointerIconsByDisplayAndType")
+    private final SparseArray<Context> mDisplayContexts = new SparseArray<>();
+    @GuardedBy("mLoadedPointerIconsByDisplayAndType")
+    private final SparseIntArray mDisplayDensities = new SparseIntArray();
+
+    private final DisplayManager.DisplayListener mDisplayListener =
+            new DisplayManager.DisplayListener() {
+                @Override
+                public void onDisplayAdded(int displayId) {
+                    synchronized (mLoadedPointerIconsByDisplayAndType) {
+                        updateDisplayDensityLocked(displayId);
+                    }
+                }
+
+                @Override
+                public void onDisplayRemoved(int displayId) {
+                    synchronized (mLoadedPointerIconsByDisplayAndType) {
+                        mLoadedPointerIconsByDisplayAndType.remove(displayId);
+                        mDisplayContexts.remove(displayId);
+                        mDisplayDensities.delete(displayId);
+                    }
+                }
+
+                @Override
+                public void onDisplayChanged(int displayId) {
+                    handleDisplayChanged(displayId);
+                }
+            };
+
+    /* package */ PointerIconCache(Context context, NativeInputManagerService nativeService) {
+        mContext = context;
+        mNative = nativeService;
+    }
+
+    public void systemRunning() {
+        final DisplayManager displayManager = Objects.requireNonNull(
+                mContext.getSystemService(DisplayManager.class));
+        displayManager.registerDisplayListener(mDisplayListener, mUiThreadHandler);
+        final Display[] displays = displayManager.getDisplays();
+        for (int i = 0; i < displays.length; i++) {
+            mDisplayListener.onDisplayAdded(displays[i].getDisplayId());
+        }
+    }
+
+    public void monitor() {
+        synchronized (mLoadedPointerIconsByDisplayAndType) { /* Test if blocked by lock */}
+    }
+
+    /** Set whether the large pointer icons should be used for accessibility. */
+    public void setUseLargePointerIcons(boolean useLargeIcons) {
+        mUiThreadHandler.post(() -> handleSetUseLargePointerIcons(useLargeIcons));
+    }
+
+    /**
+     * Get a loaded system pointer icon. This will fetch the icon from the cache, or load it if
+     * it isn't already cached.
+     */
+    public @NonNull PointerIcon getLoadedPointerIcon(int displayId, int type) {
+        synchronized (mLoadedPointerIconsByDisplayAndType) {
+            SparseArray<PointerIcon> iconsByType = mLoadedPointerIconsByDisplayAndType.get(
+                    displayId);
+            if (iconsByType == null) {
+                iconsByType = new SparseArray<>();
+                mLoadedPointerIconsByDisplayAndType.put(displayId, iconsByType);
+            }
+            PointerIcon icon = iconsByType.get(type);
+            if (icon == null) {
+                icon = PointerIcon.getLoadedSystemIcon(getContextForDisplayLocked(displayId), type,
+                        mUseLargePointerIcons);
+                iconsByType.put(type, icon);
+            }
+            return Objects.requireNonNull(icon);
+        }
+    }
+
+    @GuardedBy("mLoadedPointerIconsByDisplayAndType")
+    private @NonNull Context getContextForDisplayLocked(int displayId) {
+        if (displayId == Display.INVALID_DISPLAY) {
+            // Fallback to using the default context.
+            return mContext;
+        }
+        if (displayId == mContext.getDisplay().getDisplayId()) {
+            return mContext;
+        }
+
+        Context displayContext = mDisplayContexts.get(displayId);
+        if (displayContext == null) {
+            final DisplayManager displayManager = Objects.requireNonNull(
+                    mContext.getSystemService(DisplayManager.class));
+            final Display display = displayManager.getDisplay(displayId);
+            if (display == null) {
+                // Fallback to using the default context.
+                return mContext;
+            }
+
+            displayContext = mContext.createDisplayContext(display);
+            mDisplayContexts.put(displayId, displayContext);
+        }
+        return displayContext;
+    }
+
+    @android.annotation.UiThread
+    private void handleDisplayChanged(int displayId) {
+        synchronized (mLoadedPointerIconsByDisplayAndType) {
+            if (!updateDisplayDensityLocked(displayId)) {
+                return;
+            }
+            // The display density changed, so force all cached pointer icons to be
+            // reloaded for the display.
+            Slog.i(TAG, "Reloading pointer icons due to density change on display: " + displayId);
+            var iconsByType = mLoadedPointerIconsByDisplayAndType.get(displayId);
+            if (iconsByType == null) {
+                return;
+            }
+            iconsByType.clear();
+            mDisplayContexts.remove(displayId);
+        }
+        mNative.reloadPointerIcons();
+    }
+
+    @android.annotation.UiThread
+    private void handleSetUseLargePointerIcons(boolean useLargeIcons) {
+        synchronized (mLoadedPointerIconsByDisplayAndType) {
+            if (mUseLargePointerIcons == useLargeIcons) {
+                return;
+            }
+            mUseLargePointerIcons = useLargeIcons;
+            // Clear all cached icons on all displays.
+            mLoadedPointerIconsByDisplayAndType.clear();
+        }
+        mNative.reloadPointerIcons();
+    }
+
+    // Updates the cached display density for the given displayId, and returns true if
+    // the cached density changed.
+    @GuardedBy("mLoadedPointerIconsByDisplayAndType")
+    private boolean updateDisplayDensityLocked(int displayId) {
+        final DisplayManager displayManager = Objects.requireNonNull(
+                mContext.getSystemService(DisplayManager.class));
+        final Display display = displayManager.getDisplay(displayId);
+        if (display == null) {
+            return false;
+        }
+        DisplayInfo info = new DisplayInfo();
+        display.getDisplayInfo(info);
+        final int oldDensity = mDisplayDensities.get(displayId, 0 /* default */);
+        if (oldDensity == info.logicalDensityDpi) {
+            return false;
+        }
+        mDisplayDensities.put(displayId, info.logicalDensityDpi);
+        return true;
+    }
+}
diff --git a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
index b30f5ec..6eae9a4 100644
--- a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
+++ b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
@@ -229,7 +229,8 @@
     /** Report a key event to the debug view. */
     @AnyThread
     public void reportKeyEvent(KeyEvent event) {
-        post(() -> handleKeyEvent(KeyEvent.obtain((KeyEvent) event)));
+        KeyEvent keyEvent = KeyEvent.obtain(event);
+        post(() -> handleKeyEvent(keyEvent));
     }
 
     /** Report a motion event to the debug view. */
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
new file mode 100644
index 0000000..c7b60da
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+/**
+ * Provides accesses to per-user additional {@link android.view.inputmethod.InputMethodSubtype}
+ * persistent storages.
+ */
+final class AdditionalSubtypeMapRepository {
+    @GuardedBy("ImfLock.class")
+    @NonNull
+    private static final SparseArray<AdditionalSubtypeMap> sPerUserMap = new SparseArray<>();
+
+    /**
+     * Not intended to be instantiated.
+     */
+    private AdditionalSubtypeMapRepository() {
+    }
+
+    @NonNull
+    @GuardedBy("ImfLock.class")
+    static AdditionalSubtypeMap get(@UserIdInt int userId) {
+        final AdditionalSubtypeMap map = sPerUserMap.get(userId);
+        if (map != null) {
+            return map;
+        }
+        final AdditionalSubtypeMap newMap = AdditionalSubtypeUtils.load(userId);
+        sPerUserMap.put(userId, newMap);
+        return newMap;
+    }
+
+    @GuardedBy("ImfLock.class")
+    static void putAndSave(@UserIdInt int userId, @NonNull AdditionalSubtypeMap map,
+            @NonNull InputMethodMap inputMethodMap) {
+        final AdditionalSubtypeMap previous = sPerUserMap.get(userId);
+        if (previous == map) {
+            return;
+        }
+        sPerUserMap.put(userId, map);
+        // TODO: Offload this to a background thread.
+        // TODO: Skip if the previous data is exactly the same as new one.
+        AdditionalSubtypeUtils.save(map, inputMethodMap, userId);
+    }
+
+    static void initialize(@NonNull Handler handler) {
+        final UserManagerInternal userManagerInternal =
+                LocalServices.getService(UserManagerInternal.class);
+        handler.post(() -> {
+            userManagerInternal.addUserLifecycleListener(
+                    new UserManagerInternal.UserLifecycleListener() {
+                        @Override
+                        public void onUserCreated(UserInfo user, @Nullable Object token) {
+                            final int userId = user.id;
+                            handler.post(() -> {
+                                synchronized (ImfLock.class) {
+                                    if (!sPerUserMap.contains(userId)) {
+                                        sPerUserMap.put(userId,
+                                                AdditionalSubtypeUtils.load(userId));
+                                    }
+                                }
+                            });
+                        }
+
+                        @Override
+                        public void onUserRemoved(UserInfo user) {
+                            final int userId = user.id;
+                            handler.post(() -> {
+                                synchronized (ImfLock.class) {
+                                    sPerUserMap.remove(userId);
+                                }
+                            });
+                        }
+                    });
+            synchronized (ImfLock.class) {
+                for (int userId : userManagerInternal.getUserIds()) {
+                    sPerUserMap.put(userId, AdditionalSubtypeUtils.load(userId));
+                }
+            }
+        });
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index f1698dd..73f1aad 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -36,6 +36,7 @@
 import android.os.ResultReceiver;
 import android.util.EventLog;
 import android.util.Slog;
+import android.view.MotionEvent;
 import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodManager;
@@ -75,7 +76,7 @@
 
     @GuardedBy("ImfLock.class")
     @Override
-    public void performShowIme(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
+    public void performShowIme(IBinder showInputToken, @NonNull ImeTracker.Token statsToken,
             @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver,
             @SoftInputShowHideReason int reason) {
         final IInputMethodInvoker curMethod = mService.getCurMethodLocked();
@@ -88,11 +89,12 @@
             // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
             if (curMethod.showSoftInput(showInputToken, statsToken, showFlags, resultReceiver)) {
                 if (DEBUG_IME_VISIBILITY) {
-                    EventLog.writeEvent(IMF_SHOW_IME, statsToken.getTag(),
-                            Objects.toString(mService.mCurFocusedWindow),
+                    EventLog.writeEvent(IMF_SHOW_IME,
+                            statsToken != null ? statsToken.getTag() : ImeTracker.TOKEN_NONE,
+                            Objects.toString(mService.mImeBindingState.mFocusedWindow),
                             InputMethodDebug.softInputDisplayReasonToString(reason),
                             InputMethodDebug.softInputModeToString(
-                                    mService.mCurFocusedWindowSoftInputMode));
+                                    mService.mImeBindingState.mFocusedWindowSoftInputMode));
                 }
                 mService.onShowHideSoftInputRequested(true /* show */, showInputToken, reason,
                         statsToken);
@@ -102,7 +104,7 @@
 
     @GuardedBy("ImfLock.class")
     @Override
-    public void performHideIme(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken,
+    public void performHideIme(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken,
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         final IInputMethodInvoker curMethod = mService.getCurMethodLocked();
         if (curMethod != null) {
@@ -118,11 +120,12 @@
             // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
             if (curMethod.hideSoftInput(hideInputToken, statsToken, 0, resultReceiver)) {
                 if (DEBUG_IME_VISIBILITY) {
-                    EventLog.writeEvent(IMF_HIDE_IME, statsToken.getTag(),
-                            Objects.toString(mService.mCurFocusedWindow),
+                    EventLog.writeEvent(IMF_HIDE_IME,
+                            statsToken != null ? statsToken.getTag() : ImeTracker.TOKEN_NONE,
+                            Objects.toString(mService.mImeBindingState.mFocusedWindow),
                             InputMethodDebug.softInputDisplayReasonToString(reason),
                             InputMethodDebug.softInputModeToString(
-                                    mService.mCurFocusedWindowSoftInputMode));
+                                    mService.mImeBindingState.mFocusedWindowSoftInputMode));
                 }
                 mService.onShowHideSoftInputRequested(false /* show */, hideInputToken, reason,
                         statsToken);
@@ -132,14 +135,16 @@
 
     @GuardedBy("ImfLock.class")
     @Override
-    public void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
+    public void applyImeVisibility(IBinder windowToken, @NonNull ImeTracker.Token statsToken,
             @ImeVisibilityStateComputer.VisibilityState int state) {
-        applyImeVisibility(windowToken, statsToken, state, -1 /* ignore reason */);
+        applyImeVisibility(windowToken, statsToken, state,
+                SoftInputShowHideReason.NOT_SET /* ignore reason */);
     }
 
     @GuardedBy("ImfLock.class")
     void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
-            @ImeVisibilityStateComputer.VisibilityState int state, int reason) {
+            @ImeVisibilityStateComputer.VisibilityState int state,
+            @SoftInputShowHideReason int reason) {
         switch (state) {
             case STATE_SHOW_IME:
                 ImeTracker.forLogging().onProgress(statsToken,
@@ -164,18 +169,20 @@
                 }
                 break;
             case STATE_HIDE_IME_EXPLICIT:
-                mService.hideCurrentInputLocked(windowToken, statsToken, 0, null, reason);
+                mService.hideCurrentInputLocked(windowToken, statsToken,
+                        0 /* flags */, null /* resultReceiver */, reason);
                 break;
             case STATE_HIDE_IME_NOT_ALWAYS:
                 mService.hideCurrentInputLocked(windowToken, statsToken,
-                        InputMethodManager.HIDE_NOT_ALWAYS, null, reason);
+                        InputMethodManager.HIDE_NOT_ALWAYS, null /* resultReceiver */, reason);
                 break;
             case STATE_SHOW_IME_IMPLICIT:
                 mService.showCurrentInputLocked(windowToken, statsToken,
-                        InputMethodManager.SHOW_IMPLICIT, null, reason);
+                        InputMethodManager.SHOW_IMPLICIT, MotionEvent.TOOL_TYPE_UNKNOWN,
+                        null /* resultReceiver */, reason);
                 break;
             case STATE_SHOW_IME_SNAPSHOT:
-                showImeScreenshot(windowToken, mService.getDisplayIdToShowImeLocked(), null);
+                showImeScreenshot(windowToken, mService.getDisplayIdToShowImeLocked());
                 break;
             case STATE_REMOVE_IME_SNAPSHOT:
                 removeImeScreenshot(mService.getDisplayIdToShowImeLocked());
@@ -187,11 +194,10 @@
 
     @GuardedBy("ImfLock.class")
     @Override
-    public boolean showImeScreenshot(@NonNull IBinder imeTarget, int displayId,
-            @Nullable ImeTracker.Token statsToken) {
+    public boolean showImeScreenshot(@NonNull IBinder imeTarget, int displayId) {
         if (mImeTargetVisibilityPolicy.showImeScreenshot(imeTarget, displayId)) {
             mService.onShowHideSoftInputRequested(false /* show */, imeTarget,
-                    SHOW_IME_SCREENSHOT_FROM_IMMS, statsToken);
+                    SHOW_IME_SCREENSHOT_FROM_IMMS, null /* statsToken */);
             return true;
         }
         return false;
@@ -201,8 +207,9 @@
     @Override
     public boolean removeImeScreenshot(int displayId) {
         if (mImeTargetVisibilityPolicy.removeImeScreenshot(displayId)) {
-            mService.onShowHideSoftInputRequested(false /* show */, mService.mCurFocusedWindow,
-                    REMOVE_IME_SCREENSHOT_FROM_IMMS, null);
+            mService.onShowHideSoftInputRequested(false /* show */,
+                    mService.mImeBindingState.mFocusedWindow,
+                    REMOVE_IME_SCREENSHOT_FROM_IMMS, null /* statsToken */);
             return true;
         }
         return false;
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index 776184f..a380bc1 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -201,7 +201,7 @@
 
     // TODO(b/192412909): Convert this back to void method
     @AnyThread
-    boolean showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
+    boolean showSoftInput(IBinder showInputToken, @NonNull ImeTracker.Token statsToken,
             @InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) {
         try {
             mTarget.showSoftInput(showInputToken, statsToken, flags, resultReceiver);
@@ -214,8 +214,8 @@
 
     // TODO(b/192412909): Convert this back to void method
     @AnyThread
-    boolean hideSoftInput(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken, int flags,
-            ResultReceiver resultReceiver) {
+    boolean hideSoftInput(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken,
+            int flags, ResultReceiver resultReceiver) {
         try {
             mTarget.hideSoftInput(hideInputToken, statsToken, flags, resultReceiver);
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/inputmethod/ImeBindingState.java b/services/core/java/com/android/server/inputmethod/ImeBindingState.java
new file mode 100644
index 0000000..4c20c3b
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/ImeBindingState.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_NAME;
+import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
+
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.util.Printer;
+import android.util.proto.ProtoOutputStream;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.server.wm.WindowManagerInternal;
+
+/**
+ * Stores information related to one active IME client on one display.
+ */
+final class ImeBindingState {
+
+    /**
+     * The last window token that we confirmed to be focused.  This is always updated upon
+     * reports from the input method client. If the window state is already changed before the
+     * report is handled, this field just keeps the last value.
+     */
+    @Nullable
+    final IBinder mFocusedWindow;
+
+    /**
+     * {@link WindowManager.LayoutParams#softInputMode} of {@link #mFocusedWindow}.
+     *
+     * @see #mFocusedWindow
+     */
+    @SoftInputModeFlags
+    final int mFocusedWindowSoftInputMode;
+
+    /**
+     * The client by which {@link #mFocusedWindow} was reported. This gets updated whenever
+     * an
+     * IME-focusable window gained focus (without necessarily starting an input connection),
+     * while {@link InputMethodManagerService#mClient} only gets updated when we actually start an
+     * input connection.
+     *
+     * @see #mFocusedWindow
+     */
+    @Nullable
+    final ClientState mFocusedWindowClient;
+
+    /**
+     * The editor info by which {@link #mFocusedWindow} was reported. This differs from
+     * {@link InputMethodManagerService#mCurEditorInfo} the same way {@link #mFocusedWindowClient}
+     * differs from {@link InputMethodManagerService#mCurClient}.
+     *
+     * @see #mFocusedWindow
+     */
+    @Nullable
+    final EditorInfo mFocusedWindowEditorInfo;
+
+    void dumpDebug(ProtoOutputStream proto, WindowManagerInternal windowManagerInternal) {
+        proto.write(CUR_FOCUSED_WINDOW_NAME,
+                windowManagerInternal.getWindowName(mFocusedWindow));
+        proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE,
+                InputMethodDebug.softInputModeToString(mFocusedWindowSoftInputMode));
+    }
+
+    void dump(String prefix, Printer p) {
+        p.println(prefix + "mFocusedWindow()=" + mFocusedWindow);
+        p.println(prefix + "softInputMode=" + InputMethodDebug.softInputModeToString(
+                mFocusedWindowSoftInputMode));
+        p.println(prefix + "mFocusedWindowClient=" + mFocusedWindowClient);
+    }
+
+    static ImeBindingState newEmptyState() {
+        return new ImeBindingState(
+                /*focusedWindow=*/ null,
+                /*focusedWindowSoftInputMode=*/ SOFT_INPUT_STATE_UNSPECIFIED,
+                /*focusedWindowClient=*/ null,
+                /*focusedWindowEditorInfo=*/ null
+        );
+    }
+
+    ImeBindingState(@Nullable IBinder focusedWindow,
+            @SoftInputModeFlags int focusedWindowSoftInputMode,
+            @Nullable ClientState focusedWindowClient,
+            @Nullable EditorInfo focusedWindowEditorInfo) {
+        mFocusedWindow = focusedWindow;
+        mFocusedWindowSoftInputMode = focusedWindowSoftInputMode;
+        mFocusedWindowClient = focusedWindowClient;
+        mFocusedWindowEditorInfo = focusedWindowEditorInfo;
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
index d06c31c..85ab773 100644
--- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
+++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
@@ -75,34 +75,12 @@
 
     @NonNull
     @Override
-    public ImeTracker.Token onRequestShow(@NonNull String tag, int uid,
+    public ImeTracker.Token onStart(@NonNull String tag, int uid, @ImeTracker.Type int type,
             @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason, boolean fromUser) {
         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, fromUser);
-        synchronized (mLock) {
-            mHistory.addEntry(binder, entry);
-
-            // 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 ImeTracker.Token onRequestHide(@NonNull String tag, int uid,
-            @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason, boolean fromUser) {
-        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, fromUser);
+        final var entry = new History.Entry(tag, uid, type, ImeTracker.STATUS_RUN, origin, reason,
+                fromUser);
         synchronized (mLock) {
             mHistory.addEntry(binder, entry);
 
@@ -158,7 +136,7 @@
     /**
      * Updates the IME request tracking token with new information available in IMMS.
      *
-     * @param statsToken the token corresponding to the current IME request.
+     * @param statsToken the token tracking the current IME request.
      * @param requestWindowName the name of the window that created the IME request.
      */
     public void onImmsUpdate(@NonNull ImeTracker.Token statsToken,
@@ -223,7 +201,7 @@
          * Sets the live entry corresponding to the tracking token, if it exists, as finished,
          * and uploads the data for metrics.
          *
-         * @param statsToken the token corresponding to the current IME request.
+         * @param statsToken the token tracking the current IME request.
          * @param status the finish status of the IME request.
          * @param phase the phase the IME request finished at, if it exists
          *              (or {@link ImeTracker#PHASE_NOT_SET} otherwise).
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java
index 29fa369..9f2b84d 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java
@@ -17,7 +17,6 @@
 package com.android.server.inputmethod;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.os.IBinder;
 import android.os.ResultReceiver;
 import android.view.inputmethod.ImeTracker;
@@ -34,12 +33,12 @@
      * Performs showing IME on top of the given window.
      *
      * @param showInputToken A token that represents the requester to show IME.
-     * @param statsToken     A token that tracks the progress of an IME request.
+     * @param statsToken     The token tracking the current IME request.
      * @param resultReceiver If non-null, this will be called back to the caller when
      *                       it has processed request to tell what it has done.
      * @param reason         The reason for requesting to show IME.
      */
-    default void performShowIme(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
+    default void performShowIme(IBinder showInputToken, @NonNull ImeTracker.Token statsToken,
             @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver,
             @SoftInputShowHideReason int reason) {}
 
@@ -47,12 +46,12 @@
      * Performs hiding IME to the given window
      *
      * @param hideInputToken A token that represents the requester to hide IME.
-     * @param statsToken     A token that tracks the progress of an IME request.
+     * @param statsToken     The token tracking the current IME request.
      * @param resultReceiver If non-null, this will be called back to the caller when
      *                       it has processed request to tell what it has done.
      * @param reason         The reason for requesting to hide IME.
      */
-    default void performHideIme(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken,
+    default void performHideIme(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken,
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {}
 
     /**
@@ -60,10 +59,10 @@
      * according to the given visibility state.
      *
      * @param windowToken The token of a window for applying the IME visibility
-     * @param statsToken  A token that tracks the progress of an IME request.
+     * @param statsToken  The token tracking the current IME request.
      * @param state       The new IME visibility state for the applier to handle
      */
-    default void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
+    default void applyImeVisibility(IBinder windowToken, @NonNull ImeTracker.Token statsToken,
             @ImeVisibilityStateComputer.VisibilityState int state) {}
 
     /**
@@ -84,11 +83,9 @@
      *
      * @param windowToken The token of a window to show the IME screenshot.
      * @param displayId The unique id to identify the display
-     * @param statsToken  A token that tracks the progress of an IME request.
      * @return {@code true} if success, {@code false} otherwise.
      */
-    default boolean showImeScreenshot(@NonNull IBinder windowToken, int displayId,
-            @Nullable ImeTracker.Token statsToken) {
+    default boolean showImeScreenshot(@NonNull IBinder windowToken, int displayId) {
         return false;
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index 0dd48ae..cdfde87 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -212,9 +212,11 @@
                     boolean visibleRequested, boolean removed) {
                 if (mCurVisibleImeInputTarget == imeInputTarget && (!visibleRequested || removed)
                         && mCurVisibleImeLayeringOverlay != null) {
-                    mService.onApplyImeVisibilityFromComputer(imeInputTarget,
-                            new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT,
-                                    SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE));
+                    final int reason = SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE;
+                    final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
+                            ImeTracker.ORIGIN_SERVER, reason, false /* fromUser */);
+                    mService.onApplyImeVisibilityFromComputer(imeInputTarget, statsToken,
+                            new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, reason));
                 }
                 mCurVisibleImeInputTarget = (visibleRequested && !removed) ? imeInputTarget : null;
             }
@@ -224,7 +226,7 @@
     /**
      * Called when {@link InputMethodManagerService} is processing the show IME request.
      *
-     * @param statsToken The token for tracking this show request.
+     * @param statsToken The token tracking the current IME request.
      * @return {@code true} when the show request can proceed.
      */
     boolean onImeShowFlags(@NonNull ImeTracker.Token statsToken,
@@ -250,7 +252,7 @@
     /**
      * Called when {@link InputMethodManagerService} is processing the hide IME request.
      *
-     * @param statsToken The token for tracking this hide request.
+     * @param statsToken The token tracking the current IME request.
      * @return {@code true} when the hide request can proceed.
      */
     boolean canHideIme(@NonNull ImeTracker.Token statsToken,
@@ -546,7 +548,7 @@
             }
         }
         // Fallback to the focused window for some edge cases (e.g. relaunching the activity)
-        return mService.mCurFocusedWindow;
+        return mService.mImeBindingState.mFocusedWindow;
     }
 
     IBinder getWindowTokenFrom(ImeTargetWindowState windowState) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 307b70a..fef5661 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -28,7 +28,6 @@
 import static android.server.inputmethod.InputMethodManagerServiceProto.BOUND_TO_METHOD;
 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ATTRIBUTE;
 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_CLIENT;
-import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_NAME;
 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE;
 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ID;
 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_METHOD_ID;
@@ -40,7 +39,6 @@
 import static android.server.inputmethod.InputMethodManagerServiceProto.IN_FULLSCREEN_MODE;
 import static android.server.inputmethod.InputMethodManagerServiceProto.IS_INTERACTIVE;
 import static android.server.inputmethod.InputMethodManagerServiceProto.LAST_IME_TARGET_WINDOW_NAME;
-import static android.server.inputmethod.InputMethodManagerServiceProto.LAST_SWITCH_USER_ID;
 import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_IME_WITH_HARD_KEYBOARD;
 import static android.server.inputmethod.InputMethodManagerServiceProto.SYSTEM_READY;
 import static android.view.Display.DEFAULT_DISPLAY;
@@ -69,7 +67,6 @@
 import android.annotation.Nullable;
 import android.annotation.UiThread;
 import android.annotation.UserIdInt;
-import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -194,23 +191,20 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
 import java.security.InvalidParameterException;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.Locale;
 import java.util.Objects;
 import java.util.OptionalInt;
 import java.util.WeakHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 import java.util.function.IntConsumer;
 
@@ -232,6 +226,16 @@
         int FAILURE = -1;
     }
 
+    /**
+     * Indicates that the annotated field is not yet ready for concurrent multi-user support.
+     *
+     * <p>See b/305849394 for details.</p>
+     */
+    @Retention(SOURCE)
+    @Target({ElementType.FIELD})
+    private @interface MultiUserUnawareField {
+    }
+
     private static final int MSG_SHOW_IM_SUBTYPE_PICKER = 1;
 
     private static final int MSG_HIDE_ALL_INPUT_METHODS = 1035;
@@ -277,14 +281,13 @@
     @NonNull
     private final String[] mNonPreemptibleInputMethods;
 
-    @UserIdInt
-    private int mLastSwitchUserId;
-
     final Context mContext;
     final Resources mRes;
     private final Handler mHandler;
     @NonNull
+    @MultiUserUnawareField
     private InputMethodSettings mSettings;
+    @MultiUserUnawareField
     final SettingsObserver mSettingsObserver;
     final WindowManagerInternal mWindowManagerInternal;
     private final ActivityManagerInternal mActivityManagerInternal;
@@ -293,15 +296,16 @@
     final ImePlatformCompatUtils mImePlatformCompatUtils;
     final InputMethodDeviceConfigs mInputMethodDeviceConfigs;
 
-    @GuardedBy("ImfLock.class")
-    @NonNull
-    private AdditionalSubtypeMap mAdditionalSubtypeMap = AdditionalSubtypeMap.EMPTY_MAP;
     private final UserManagerInternal mUserManagerInternal;
+    @MultiUserUnawareField
     private final InputMethodMenuController mMenuController;
+    @MultiUserUnawareField
     @NonNull private final InputMethodBindingController mBindingController;
+    @MultiUserUnawareField
     @NonNull private final AutofillSuggestionsController mAutofillController;
 
     @GuardedBy("ImfLock.class")
+    @MultiUserUnawareField
     @NonNull private final ImeVisibilityStateComputer mVisibilityStateComputer;
 
     @GuardedBy("ImfLock.class")
@@ -320,13 +324,16 @@
 
     // Mapping from deviceId to the device-specific imeId for that device.
     @GuardedBy("ImfLock.class")
+    @MultiUserUnawareField
     private final SparseArray<String> mVirtualDeviceMethodMap = new SparseArray<>();
 
     // TODO: Instantiate mSwitchingController for each user.
     @NonNull
+    @MultiUserUnawareField
     private InputMethodSubtypeSwitchingController mSwitchingController;
     // TODO: Instantiate mHardwareKeyboardShortcutController for each user.
     @NonNull
+    @MultiUserUnawareField
     private HardwareKeyboardShortcutController mHardwareKeyboardShortcutController;
 
     /**
@@ -344,23 +351,29 @@
     }
 
     @GuardedBy("ImfLock.class")
+    @MultiUserUnawareField
     private int mDisplayIdToShowIme = INVALID_DISPLAY;
 
     @GuardedBy("ImfLock.class")
+    @MultiUserUnawareField
     private int mDeviceIdToShowIme = DEVICE_ID_DEFAULT;
 
     @Nullable private StatusBarManagerInternal mStatusBarManagerInternal;
     private boolean mShowOngoingImeSwitcherForPhones;
     @GuardedBy("ImfLock.class")
+    @MultiUserUnawareField
     private final HandwritingModeController mHwController;
     @GuardedBy("ImfLock.class")
+    @MultiUserUnawareField
     private IntArray mStylusIds;
 
     @GuardedBy("ImfLock.class")
     @Nullable
+    @MultiUserUnawareField
     private OverlayableSystemBooleanResourceWrapper mImeDrawsImeNavBarRes;
     @GuardedBy("ImfLock.class")
     @Nullable
+    @MultiUserUnawareField
     Future<?> mImeDrawsImeNavBarResLazyInitFuture;
 
     static class SessionState {
@@ -423,6 +436,12 @@
     private final ClientController mClientController;
 
     /**
+     * Holds the current IME binding state info.
+     */
+    @MultiUserUnawareField
+    ImeBindingState mImeBindingState;
+
+    /**
      * Set once the system is ready to run third party code.
      */
     boolean mSystemReady;
@@ -482,66 +501,36 @@
     private ClientState mCurClient;
 
     /**
-     * The last window token that we confirmed to be focused.  This is always updated upon reports
-     * from the input method client.  If the window state is already changed before the report is
-     * handled, this field just keeps the last value.
-     */
-    IBinder mCurFocusedWindow;
-
-    /**
      * The last window token that we confirmed that IME started talking to.  This is always updated
      * upon reports from the input method.  If the window state is already changed before the report
      * is handled, this field just keeps the last value.
      */
+    @MultiUserUnawareField
     IBinder mLastImeTargetWindow;
 
     /**
-     * {@link LayoutParams#softInputMode} of {@link #mCurFocusedWindow}.
-     *
-     * @see #mCurFocusedWindow
-     */
-    @SoftInputModeFlags
-    int mCurFocusedWindowSoftInputMode;
-
-    /**
-     * The client by which {@link #mCurFocusedWindow} was reported. This gets updated whenever an
-     * IME-focusable window gained focus (without necessarily starting an input connection),
-     * while {@link #mCurClient} only gets updated when we actually start an input connection.
-     *
-     * @see #mCurFocusedWindow
-     */
-    @Nullable
-    ClientState mCurFocusedWindowClient;
-
-    /**
-     * The editor info by which {@link #mCurFocusedWindow} was reported. This differs from
-     * {@link #mCurEditorInfo} the same way {@link #mCurFocusedWindowClient} differs
-     * from {@link #mCurClient}.
-     *
-     * @see #mCurFocusedWindow
-     */
-    @Nullable
-    EditorInfo mCurFocusedWindowEditorInfo;
-
-    /**
      * The {@link IRemoteInputConnection} last provided by the current client.
      */
+    @MultiUserUnawareField
     IRemoteInputConnection mCurInputConnection;
 
     /**
      * The {@link ImeOnBackInvokedDispatcher} last provided by the current client to
      * receive {@link android.window.OnBackInvokedCallback}s forwarded from IME.
      */
+    @MultiUserUnawareField
     ImeOnBackInvokedDispatcher mCurImeDispatcher;
 
     /**
      * The {@link IRemoteAccessibilityInputConnection} last provided by the current client.
      */
+    @MultiUserUnawareField
     @Nullable IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection;
 
     /**
      * The {@link EditorInfo} last provided by the current client.
      */
+    @MultiUserUnawareField
     @Nullable
     EditorInfo mCurEditorInfo;
 
@@ -562,11 +551,13 @@
     /**
      * The current subtype of the current input method.
      */
+    @MultiUserUnawareField
     private InputMethodSubtype mCurrentSubtype;
 
     /**
      * {@code true} if the IME has not been mostly hidden via {@link android.view.InsetsController}
      */
+    @MultiUserUnawareField
     private boolean mCurPerceptible;
 
     /**
@@ -578,13 +569,18 @@
         return mBindingController.hasMainConnection();
     }
 
-    /** The token tracking the current IME request or {@code null} otherwise. */
+    /**
+     * The token tracking the current IME show request that is waiting for a connection to an IME,
+     * otherwise {@code null}.
+     */
     @Nullable
+    @MultiUserUnawareField
     private ImeTracker.Token mCurStatsToken;
 
     /**
      * {@code true} if the current input method is in fullscreen mode.
      */
+    @MultiUserUnawareField
     boolean mInFullscreenMode;
 
     /**
@@ -620,6 +616,7 @@
     }
 
     @GuardedBy("ImfLock.class")
+    @MultiUserUnawareField
     private int mCurTokenDisplayId = INVALID_DISPLAY;
 
     /**
@@ -627,6 +624,7 @@
      */
     @GuardedBy("ImfLock.class")
     @Nullable
+    @MultiUserUnawareField
     private IBinder mCurHostInputToken;
 
     /**
@@ -665,25 +663,31 @@
     /**
      * Have we called mCurMethod.bindInput()?
      */
+    @MultiUserUnawareField
     boolean mBoundToMethod;
 
     /**
      * Have we called bindInput() for accessibility services?
      */
+    @MultiUserUnawareField
     boolean mBoundToAccessibility;
 
     /**
      * Currently enabled session.
      */
     @GuardedBy("ImfLock.class")
+    @MultiUserUnawareField
     SessionState mEnabledSession;
+    @MultiUserUnawareField
     SparseArray<AccessibilitySessionState> mEnabledAccessibilitySessions = new SparseArray<>();
 
     /**
      * True if the device is currently interactive with user.  The value is true initially.
      */
+    @MultiUserUnawareField
     boolean mIsInteractive = true;
 
+    @MultiUserUnawareField
     int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
 
     /**
@@ -707,6 +711,7 @@
      * <em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and
      * {@link InputMethodBindingController#unbindCurrentMethod()}.</em>
      */
+    @MultiUserUnawareField
     int mImeWindowVis;
 
     private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
@@ -719,344 +724,9 @@
     private final CopyOnWriteArrayList<InputMethodListListener> mInputMethodListListeners =
             new CopyOnWriteArrayList<>();
 
-    /**
-     * Internal state snapshot when
-     * {@link IInputMethod#startInput(IInputMethod.StartInputParams)} is about to be called.
-     *
-     * <p>Calling that IPC endpoint basically means that
-     * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
-     * back in the current IME process shortly, which will also affect what the current IME starts
-     * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this
-     * snapshot will be taken every time when {@link InputMethodManagerService} is initiating a new
-     * logical input session between the client application and the current IME.</p>
-     *
-     * <p>Be careful to not keep strong references to this object forever, which can prevent
-     * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed.
-     * </p>
-     */
-    private static class StartInputInfo {
-        private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
-
-        final int mSequenceNumber;
-        final long mTimestamp;
-        final long mWallTime;
-        @UserIdInt
-        final int mImeUserId;
-        @NonNull
-        final IBinder mImeToken;
-        final int mImeDisplayId;
-        @NonNull
-        final String mImeId;
-        @StartInputReason
-        final int mStartInputReason;
-        final boolean mRestarting;
-        @UserIdInt
-        final int mTargetUserId;
-        final int mTargetDisplayId;
-        @Nullable
-        final IBinder mTargetWindow;
-        @NonNull
-        final EditorInfo mEditorInfo;
-        @SoftInputModeFlags
-        final int mTargetWindowSoftInputMode;
-        final int mClientBindSequenceNumber;
-
-        StartInputInfo(@UserIdInt int imeUserId, @NonNull IBinder imeToken, int imeDisplayId,
-                @NonNull String imeId, @StartInputReason int startInputReason, boolean restarting,
-                @UserIdInt int targetUserId, int targetDisplayId, @Nullable IBinder targetWindow,
-                @NonNull EditorInfo editorInfo, @SoftInputModeFlags int targetWindowSoftInputMode,
-                int clientBindSequenceNumber) {
-            mSequenceNumber = sSequenceNumber.getAndIncrement();
-            mTimestamp = SystemClock.uptimeMillis();
-            mWallTime = System.currentTimeMillis();
-            mImeUserId = imeUserId;
-            mImeToken = imeToken;
-            mImeDisplayId = imeDisplayId;
-            mImeId = imeId;
-            mStartInputReason = startInputReason;
-            mRestarting = restarting;
-            mTargetUserId = targetUserId;
-            mTargetDisplayId = targetDisplayId;
-            mTargetWindow = targetWindow;
-            mEditorInfo = editorInfo;
-            mTargetWindowSoftInputMode = targetWindowSoftInputMode;
-            mClientBindSequenceNumber = clientBindSequenceNumber;
-        }
-    }
-
     @GuardedBy("ImfLock.class")
     private final WeakHashMap<IBinder, IBinder> mImeTargetWindowMap = new WeakHashMap<>();
 
-    @VisibleForTesting
-    static final class SoftInputShowHideHistory {
-        private final Entry[] mEntries = new Entry[16];
-        private int mNextIndex = 0;
-        private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
-
-        static final class Entry {
-            final int mSequenceNumber = sSequenceNumber.getAndIncrement();
-            @Nullable
-            final ClientState mClientState;
-            @SoftInputModeFlags
-            final int mFocusedWindowSoftInputMode;
-            @SoftInputShowHideReason
-            final int mReason;
-            // The timing of handling showCurrentInputLocked() or hideCurrentInputLocked().
-            final long mTimestamp;
-            final long mWallTime;
-            final boolean mInFullscreenMode;
-            @NonNull
-            final String mFocusedWindowName;
-            @Nullable
-            final EditorInfo mEditorInfo;
-            @NonNull
-            final String mRequestWindowName;
-            @Nullable
-            final String mImeControlTargetName;
-            @Nullable
-            final String mImeTargetNameFromWm;
-            @Nullable
-            final String mImeSurfaceParentName;
-
-            Entry(ClientState client, EditorInfo editorInfo,
-                    String focusedWindowName, @SoftInputModeFlags int softInputMode,
-                    @SoftInputShowHideReason int reason,
-                    boolean inFullscreenMode, String requestWindowName,
-                    @Nullable String imeControlTargetName, @Nullable String imeTargetName,
-                    @Nullable String imeSurfaceParentName) {
-                mClientState = client;
-                mEditorInfo = editorInfo;
-                mFocusedWindowName = focusedWindowName;
-                mFocusedWindowSoftInputMode = softInputMode;
-                mReason = reason;
-                mTimestamp = SystemClock.uptimeMillis();
-                mWallTime = System.currentTimeMillis();
-                mInFullscreenMode = inFullscreenMode;
-                mRequestWindowName = requestWindowName;
-                mImeControlTargetName = imeControlTargetName;
-                mImeTargetNameFromWm = imeTargetName;
-                mImeSurfaceParentName = imeSurfaceParentName;
-            }
-        }
-
-        void addEntry(@NonNull Entry entry) {
-            final int index = mNextIndex;
-            mEntries[index] = entry;
-            mNextIndex = (mNextIndex + 1) % mEntries.length;
-        }
-
-        void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
-            final DateTimeFormatter formatter =
-                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
-                            .withZone(ZoneId.systemDefault());
-
-            for (int i = 0; i < mEntries.length; ++i) {
-                final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
-                if (entry == null) {
-                    continue;
-                }
-                pw.print(prefix);
-                pw.println("SoftInputShowHide #" + entry.mSequenceNumber + ":");
-
-                pw.print(prefix);
-                pw.println("  time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
-                        + " (timestamp=" + entry.mTimestamp + ")");
-
-                pw.print(prefix);
-                pw.print("  reason=" + InputMethodDebug.softInputDisplayReasonToString(
-                        entry.mReason));
-                pw.println(" inFullscreenMode=" + entry.mInFullscreenMode);
-
-                pw.print(prefix);
-                pw.println("  requestClient=" + entry.mClientState);
-
-                pw.print(prefix);
-                pw.println("  focusedWindowName=" + entry.mFocusedWindowName);
-
-                pw.print(prefix);
-                pw.println("  requestWindowName=" + entry.mRequestWindowName);
-
-                pw.print(prefix);
-                pw.println("  imeControlTargetName=" + entry.mImeControlTargetName);
-
-                pw.print(prefix);
-                pw.println("  imeTargetNameFromWm=" + entry.mImeTargetNameFromWm);
-
-                pw.print(prefix);
-                pw.println("  imeSurfaceParentName=" + entry.mImeSurfaceParentName);
-
-                pw.print(prefix);
-                pw.print("  editorInfo:");
-                if (entry.mEditorInfo != null) {
-                    pw.print(" inputType=" + entry.mEditorInfo.inputType);
-                    pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
-                    pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
-                } else {
-                    pw.println(" null");
-                }
-
-                pw.print(prefix);
-                pw.println("  focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString(
-                        entry.mFocusedWindowSoftInputMode));
-            }
-        }
-    }
-
-    /**
-     * A ring buffer to store the history of {@link StartInputInfo}.
-     */
-    private static final class StartInputHistory {
-        /**
-         * Entry size for non low-RAM devices.
-         *
-         * <p>TODO: Consider to follow what other system services have been doing to manage
-         * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
-         */
-        private static final int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 32;
-
-        /**
-         * Entry size for low-RAM devices.
-         *
-         * <p>TODO: Consider to follow what other system services have been doing to manage
-         * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
-         */
-        private static final int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5;
-
-        private static int getEntrySize() {
-            if (ActivityManager.isLowRamDeviceStatic()) {
-                return ENTRY_SIZE_FOR_LOW_RAM_DEVICE;
-            } else {
-                return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE;
-            }
-        }
-
-        /**
-         * Backing store for the ring buffer.
-         */
-        private final Entry[] mEntries = new Entry[getEntrySize()];
-
-        /**
-         * An index of {@link #mEntries}, to which next {@link #addEntry(StartInputInfo)} should
-         * write.
-         */
-        private int mNextIndex = 0;
-
-        /**
-         * Recyclable entry to store the information in {@link StartInputInfo}.
-         */
-        private static final class Entry {
-            int mSequenceNumber;
-            long mTimestamp;
-            long mWallTime;
-            @UserIdInt
-            int mImeUserId;
-            @NonNull
-            String mImeTokenString;
-            int mImeDisplayId;
-            @NonNull
-            String mImeId;
-            @StartInputReason
-            int mStartInputReason;
-            boolean mRestarting;
-            @UserIdInt
-            int mTargetUserId;
-            int mTargetDisplayId;
-            @NonNull
-            String mTargetWindowString;
-            @NonNull
-            EditorInfo mEditorInfo;
-            @SoftInputModeFlags
-            int mTargetWindowSoftInputMode;
-            int mClientBindSequenceNumber;
-
-            Entry(@NonNull StartInputInfo original) {
-                set(original);
-            }
-
-            void set(@NonNull StartInputInfo original) {
-                mSequenceNumber = original.mSequenceNumber;
-                mTimestamp = original.mTimestamp;
-                mWallTime = original.mWallTime;
-                mImeUserId = original.mImeUserId;
-                // Intentionally convert to String so as not to keep a strong reference to a Binder
-                // object.
-                mImeTokenString = String.valueOf(original.mImeToken);
-                mImeDisplayId = original.mImeDisplayId;
-                mImeId = original.mImeId;
-                mStartInputReason = original.mStartInputReason;
-                mRestarting = original.mRestarting;
-                mTargetUserId = original.mTargetUserId;
-                mTargetDisplayId = original.mTargetDisplayId;
-                // Intentionally convert to String so as not to keep a strong reference to a Binder
-                // object.
-                mTargetWindowString = String.valueOf(original.mTargetWindow);
-                mEditorInfo = original.mEditorInfo;
-                mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode;
-                mClientBindSequenceNumber = original.mClientBindSequenceNumber;
-            }
-        }
-
-        /**
-         * Add a new entry and discard the oldest entry as needed.
-         * @param info {@link StartInputInfo} to be added.
-         */
-        void addEntry(@NonNull StartInputInfo info) {
-            final int index = mNextIndex;
-            if (mEntries[index] == null) {
-                mEntries[index] = new Entry(info);
-            } else {
-                mEntries[index].set(info);
-            }
-            mNextIndex = (mNextIndex + 1) % mEntries.length;
-        }
-
-        void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
-            final DateTimeFormatter formatter =
-                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
-                            .withZone(ZoneId.systemDefault());
-
-            for (int i = 0; i < mEntries.length; ++i) {
-                final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
-                if (entry == null) {
-                    continue;
-                }
-                pw.print(prefix);
-                pw.println("StartInput #" + entry.mSequenceNumber + ":");
-
-                pw.print(prefix);
-                pw.println("  time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
-                        + " (timestamp=" + entry.mTimestamp + ")"
-                        + " reason="
-                        + InputMethodDebug.startInputReasonToString(entry.mStartInputReason)
-                        + " restarting=" + entry.mRestarting);
-
-                pw.print(prefix);
-                pw.print("  imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]");
-                pw.print(" imeUserId=" + entry.mImeUserId);
-                pw.println(" imeDisplayId=" + entry.mImeDisplayId);
-
-                pw.print(prefix);
-                pw.println("  targetWin=" + entry.mTargetWindowString
-                        + " [" + entry.mEditorInfo.packageName + "]"
-                        + " targetUserId=" + entry.mTargetUserId
-                        + " targetDisplayId=" + entry.mTargetDisplayId
-                        + " clientBindSeq=" + entry.mClientBindSequenceNumber);
-
-                pw.print(prefix);
-                pw.println("  softInputMode=" + InputMethodDebug.softInputModeToString(
-                        entry.mTargetWindowSoftInputMode));
-
-                pw.print(prefix);
-                pw.println("  inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType)
-                        + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions)
-                        + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId)
-                        + " fieldName=" + entry.mEditorInfo.fieldName
-                        + " actionId=" + entry.mEditorInfo.actionId
-                        + " actionLabel=" + entry.mEditorInfo.actionLabel);
-            }
-        }
-    }
-
     @GuardedBy("ImfLock.class")
     @NonNull
     private final StartInputHistory mStartInputHistory = new StartInputHistory();
@@ -1128,11 +798,11 @@
                     mVisibilityStateComputer.getImePolicy().setA11yRequestNoSoftKeyboard(
                             accessibilitySoftKeyboardSetting);
                     if (mVisibilityStateComputer.getImePolicy().isA11yRequestNoSoftKeyboard()) {
-                        hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
-                                0 /* flags */, null /* resultReceiver */,
+                        hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
                                 SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE);
                     } else if (isShowRequestedForCurrentWindow()) {
-                        showCurrentInputImplicitLocked(mCurFocusedWindow,
+                        showCurrentInputLocked(mImeBindingState.mFocusedWindow,
+                                InputMethodManager.SHOW_IMPLICIT,
                                 SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE);
                     }
                 } else if (stylusHandwritingEnabledUri.equals(uri)) {
@@ -1236,6 +906,14 @@
          */
         private boolean mImePackageAppeared = false;
 
+        /**
+         * Remembers package names passed to {@link #onPackageDataCleared(String, int)}.
+         *
+         * <p>This field must be accessed only from callback methods in {@link PackageMonitor},
+         * which should be bound to {@link #getRegisteredHandler()}.</p>
+         */
+        private ArrayList<String> mDataClearedPackages = new ArrayList<>();
+
         @GuardedBy("ImfLock.class")
         void clearKnownImePackageNamesLocked() {
             mKnownImePackageNames.clear();
@@ -1339,25 +1017,8 @@
 
         @Override
         public void onPackageDataCleared(String packageName, int uid) {
-            synchronized (ImfLock.class) {
-                // Note that one package may implement multiple IMEs.
-                final ArrayList<String> changedImes = new ArrayList<>();
-                for (InputMethodInfo imi : mSettings.getMethodList()) {
-                    if (imi.getPackageName().equals(packageName)) {
-                        changedImes.add(imi.getId());
-                    }
-                }
-                final AdditionalSubtypeMap newMap =
-                        mAdditionalSubtypeMap.cloneWithRemoveOrSelf(changedImes);
-                if (newMap != mAdditionalSubtypeMap) {
-                    mAdditionalSubtypeMap = newMap;
-                    AdditionalSubtypeUtils.save(
-                            mAdditionalSubtypeMap, mSettings.getMethodMap(), mSettings.getUserId());
-                }
-                if (!changedImes.isEmpty()) {
-                    mChangedPackages.add(packageName);
-                }
-            }
+            mChangedPackages.add(packageName);
+            mDataClearedPackages.add(packageName);
         }
 
         @Override
@@ -1369,6 +1030,7 @@
         private void clearPackageChangeState() {
             // No need to lock them because we access these fields only on getRegisteredHandler().
             mChangedPackages.clear();
+            mDataClearedPackages.clear();
             mImePackageAppeared = false;
         }
 
@@ -1397,41 +1059,64 @@
 
         private void onFinishPackageChangesInternal() {
             synchronized (ImfLock.class) {
-                if (!isChangingPackagesOfCurrentUserLocked()) {
-                    return;
-                }
-                if (!shouldRebuildInputMethodListLocked()) {
-                    return;
+                final int userId = getChangingUserId();
+                final boolean isCurrentUser = (userId == mSettings.getUserId());
+                final AdditionalSubtypeMap additionalSubtypeMap =
+                        AdditionalSubtypeMapRepository.get(userId);
+                final InputMethodSettings settings;
+                if (isCurrentUser) {
+                    settings = mSettings;
+                } else {
+                    settings = queryInputMethodServicesInternal(mContext, userId,
+                            additionalSubtypeMap, DirectBootAwareness.AUTO);
                 }
 
                 InputMethodInfo curIm = null;
-                String curInputMethodId = mSettings.getSelectedInputMethod();
-                final List<InputMethodInfo> methodList = mSettings.getMethodList();
-                final int numImes = methodList.size();
-                if (curInputMethodId != null) {
-                    for (int i = 0; i < numImes; i++) {
-                        InputMethodInfo imi = methodList.get(i);
-                        final String imiId = imi.getId();
-                        if (imiId.equals(curInputMethodId)) {
-                            curIm = imi;
-                        }
+                String curInputMethodId = settings.getSelectedInputMethod();
+                final List<InputMethodInfo> methodList = settings.getMethodList();
 
-                        int change = isPackageDisappearing(imi.getPackageName());
-                        if (change == PACKAGE_TEMPORARY_CHANGE
-                                || change == PACKAGE_PERMANENT_CHANGE) {
-                            Slog.i(TAG, "Input method uninstalled, disabling: "
-                                    + imi.getComponent());
-                            setInputMethodEnabledLocked(imi.getId(), false);
-                        } else if (change == PACKAGE_UPDATING) {
-                            Slog.i(TAG,
-                                    "Input method reinstalling, clearing additional subtypes: "
-                                            + imi.getComponent());
-                            mAdditionalSubtypeMap =
-                                    mAdditionalSubtypeMap.cloneWithRemoveOrSelf(imi.getId());
-                            AdditionalSubtypeUtils.save(mAdditionalSubtypeMap,
-                                    mSettings.getMethodMap(), mSettings.getUserId());
-                        }
+                final ArrayList<String> imesToClearAdditionalSubtypes = new ArrayList<>();
+                final int numImes = methodList.size();
+                for (int i = 0; i < numImes; i++) {
+                    InputMethodInfo imi = methodList.get(i);
+                    final String imiId = imi.getId();
+                    if (imiId.equals(curInputMethodId)) {
+                        curIm = imi;
                     }
+                    if (mDataClearedPackages.contains(imi.getPackageName())) {
+                        imesToClearAdditionalSubtypes.add(imiId);
+                    }
+                    int change = isPackageDisappearing(imi.getPackageName());
+                    if (change == PACKAGE_TEMPORARY_CHANGE || change == PACKAGE_PERMANENT_CHANGE) {
+                        Slog.i(TAG, "Input method uninstalled, disabling: " + imi.getComponent());
+                        if (isCurrentUser) {
+                            setInputMethodEnabledLocked(imi.getId(), false);
+                        } else {
+                            settings.buildAndPutEnabledInputMethodsStrRemovingId(
+                                    new StringBuilder(),
+                                    settings.getEnabledInputMethodsAndSubtypeList(),
+                                    imi.getId());
+                        }
+                    } else if (change == PACKAGE_UPDATING) {
+                        Slog.i(TAG, "Input method reinstalling, clearing additional subtypes: "
+                                + imi.getComponent());
+                        imesToClearAdditionalSubtypes.add(imiId);
+                    }
+                }
+
+                // Clear additional subtypes as a batch operation.
+                final AdditionalSubtypeMap newAdditionalSubtypeMap =
+                        additionalSubtypeMap.cloneWithRemoveOrSelf(imesToClearAdditionalSubtypes);
+                final boolean additionalSubtypeChanged =
+                        (newAdditionalSubtypeMap != additionalSubtypeMap);
+                if (additionalSubtypeChanged) {
+                    AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
+                            settings.getMethodMap());
+                }
+
+                if (!isCurrentUser
+                        || !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
+                    return;
                 }
 
                 buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
@@ -1443,7 +1128,7 @@
                     if (change == PACKAGE_TEMPORARY_CHANGE
                             || change == PACKAGE_PERMANENT_CHANGE) {
                         final PackageManager userAwarePackageManager =
-                                getPackageManagerForUser(mContext, mSettings.getUserId());
+                                getPackageManagerForUser(mContext, userId);
                         ServiceInfo si = null;
                         try {
                             si = userAwarePackageManager.getServiceInfo(curIm.getComponent(),
@@ -1518,6 +1203,7 @@
      */
     @Nullable
     @GuardedBy("ImfLock.class")
+    @MultiUserUnawareField
     private UserSwitchHandlerTask mUserSwitchHandlerTask;
 
     /**
@@ -1613,8 +1299,8 @@
         }
         // Hide soft input before user switch task since switch task may block main handler a while
         // and delayed the hideCurrentInputLocked().
-        hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
-                null /* resultReceiver */, SoftInputShowHideReason.HIDE_SWITCH_USER);
+        hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
+                SoftInputShowHideReason.HIDE_SWITCH_USER);
         final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId,
                 clientToBeReset);
         mUserSwitchHandlerTask = task;
@@ -1661,14 +1347,13 @@
 
         mShowOngoingImeSwitcherForPhones = false;
 
-        final int userId = mActivityManagerInternal.getCurrentUserId();
+        AdditionalSubtypeMapRepository.initialize(mHandler);
 
-        mLastSwitchUserId = userId;
+        final int userId = mActivityManagerInternal.getCurrentUserId();
 
         // mSettings should be created before buildInputMethodListLocked
         mSettings = InputMethodSettings.createEmptyMap(userId);
 
-        mAdditionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
         mSwitchingController =
                 InputMethodSubtypeSwitchingController.createInstanceLocked(context,
                         mSettings.getMethodMap(), userId);
@@ -1688,6 +1373,7 @@
         mClientController = new ClientController(mPackageManagerInternal);
         synchronized (ImfLock.class) {
             mClientController.addClientControllerCallback(c -> onClientRemoved(c));
+            mImeBindingState = ImeBindingState.newEmptyState();
         }
 
         mPreventImeStartupUnlessTextEditor = mRes.getBoolean(
@@ -1812,8 +1498,6 @@
         mSettingsObserver.registerContentObserverLocked(newUserId);
 
         mSettings = InputMethodSettings.createEmptyMap(newUserId);
-        // Additional subtypes should be reset when the user is changed
-        mAdditionalSubtypeMap = AdditionalSubtypeUtils.load(newUserId);
         final String defaultImiId = mSettings.getSelectedInputMethod();
 
         if (DEBUG) {
@@ -1850,7 +1534,6 @@
                     + " selectedIme=" + mSettings.getSelectedInputMethod());
         }
 
-        mLastSwitchUserId = newUserId;
         if (mIsInteractive && clientToBeReset != null) {
             final ClientState cs = mClientController.getClient(clientToBeReset.asBinder());
             if (cs == null) {
@@ -1902,7 +1585,7 @@
                     }
                 }, "Lazily initialize IMMS#mImeDrawsImeNavBarRes");
 
-                mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true);
+                mMyPackageMonitor.register(mContext, UserHandle.ALL, mHandler);
                 mSettingsObserver.registerContentObserverLocked(currentUserId);
 
                 final IntentFilter broadcastFilterForAllUsers = new IntentFilter();
@@ -2022,7 +1705,7 @@
             }
             //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList.
             //TODO(b/210039666): use cache.
-            final InputMethodSettings settings = queryMethodMapForUser(userId);
+            final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
             final InputMethodInfo imi = settings.getMethodMap().get(
                     settings.getSelectedInputMethod());
             return imi != null && imi.supportsStylusHandwriting()
@@ -2050,7 +1733,8 @@
                 && directBootAwareness == DirectBootAwareness.AUTO) {
             settings = mSettings;
         } else {
-            final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
+            final AdditionalSubtypeMap additionalSubtypeMap =
+                    AdditionalSubtypeMapRepository.get(userId);
             settings = queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
                     directBootAwareness);
         }
@@ -2071,7 +1755,7 @@
             methodList = mSettings.getEnabledInputMethodList();
             settings = mSettings;
         } else {
-            settings = queryMethodMapForUser(userId);
+            settings = queryMethodMapForUserLocked(userId);
             methodList = settings.getEnabledInputMethodList();
         }
         // filter caller's access to input methods
@@ -2146,7 +1830,7 @@
             return mSettings.getEnabledInputMethodSubtypeList(
                     imi, allowsImplicitlyEnabledSubtypes);
         }
-        final InputMethodSettings settings = queryMethodMapForUser(userId);
+        final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
         final InputMethodInfo imi = settings.getMethodMap().get(imiId);
         if (imi == null) {
             return Collections.emptyList();
@@ -2202,8 +1886,8 @@
         clearClientSessionLocked(client);
         clearClientSessionForAccessibilityLocked(client);
         if (mCurClient == client) {
-            hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
-                    null /* resultReceiver */, SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
+            hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
+                    SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
             if (mBoundToMethod) {
                 mBoundToMethod = false;
                 IInputMethodInvoker curMethod = getCurMethodLocked();
@@ -2216,9 +1900,8 @@
             }
             mBoundToAccessibility = false;
             mCurClient = null;
-            if (mCurFocusedWindowClient == client) {
-                mCurFocusedWindowClient = null;
-                mCurFocusedWindowEditorInfo = null;
+            if (mImeBindingState.mFocusedWindowClient == client) {
+                mImeBindingState = ImeBindingState.newEmptyState();
             }
         }
     }
@@ -2267,7 +1950,7 @@
     @GuardedBy("ImfLock.class")
     void onUnbindCurrentMethodByReset() {
         final ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull(
-                mCurFocusedWindow);
+                mImeBindingState.mFocusedWindow);
         if (winState != null && !winState.isRequestedImeVisible()
                 && !mVisibilityStateComputer.isInputShown()) {
             // Normally, the focus window will apply the IME visibility state to
@@ -2277,7 +1960,9 @@
             // service, that wouldn't make the attached IME token validity check in time)
             // As a result, we have to notify WM to apply IME visibility before clearing the
             // binding states in the first place.
-            mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, mCurStatsToken,
+            final var statsToken = createStatsTokenForFocusedClient(false /* show */,
+                    SoftInputShowHideReason.UNBIND_CURRENT_METHOD);
+            mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow, statsToken,
                     STATE_HIDE_IME);
         }
     }
@@ -2311,7 +1996,7 @@
     @GuardedBy("ImfLock.class")
     private boolean isShowRequestedForCurrentWindow() {
         final ImeTargetWindowState state = mVisibilityStateComputer.getWindowStateOrNull(
-                mCurFocusedWindow);
+                mImeBindingState.mFocusedWindow);
         return state != null && state.isRequestedImeVisible();
     }
 
@@ -2329,10 +2014,9 @@
                 getCurTokenLocked(),
                 mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting,
                 UserHandle.getUserId(mCurClient.mUid),
-                mCurClient.mSelfReportedDisplayId,
-                mCurFocusedWindow, mCurEditorInfo, mCurFocusedWindowSoftInputMode,
-                getSequenceNumberLocked());
-        mImeTargetWindowMap.put(startInputToken, mCurFocusedWindow);
+                mCurClient.mSelfReportedDisplayId, mImeBindingState.mFocusedWindow, mCurEditorInfo,
+                mImeBindingState.mFocusedWindowSoftInputMode, getSequenceNumberLocked());
+        mImeTargetWindowMap.put(startInputToken, mImeBindingState.mFocusedWindow);
         mStartInputHistory.addEntry(info);
 
         // Seems that PackageManagerInternal#grantImplicitAccess() doesn't handle cross-user
@@ -2355,10 +2039,12 @@
         if (isShowRequestedForCurrentWindow()) {
             if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
             // Re-use current statsToken, if it exists.
-            final ImeTracker.Token statsToken = mCurStatsToken;
+            final var statsToken = mCurStatsToken != null ? mCurStatsToken
+                    : createStatsTokenForFocusedClient(true /* show */,
+                            SoftInputShowHideReason.ATTACH_NEW_INPUT);
             mCurStatsToken = null;
-            showCurrentInputLocked(mCurFocusedWindow, statsToken,
-                    mVisibilityStateComputer.getShowFlags(),
+            showCurrentInputLocked(mImeBindingState.mFocusedWindow, statsToken,
+                    mVisibilityStateComputer.getShowFlags(), MotionEvent.TOOL_TYPE_UNKNOWN,
                     null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT);
         }
 
@@ -2431,7 +2117,7 @@
         // Compute the final shown display ID with validated cs.selfReportedDisplayId for this
         // session & other conditions.
         ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull(
-                mCurFocusedWindow);
+                mImeBindingState.mFocusedWindow);
         if (winState == null) {
             return InputBindResult.NOT_IME_TARGET_WINDOW;
         }
@@ -2450,8 +2136,7 @@
         }
 
         if (mVisibilityStateComputer.getImePolicy().isImeHiddenByDisplayPolicy()) {
-            hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
-                    null /* resultReceiver */,
+            hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
                     SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE);
             return InputBindResult.NO_IME;
         }
@@ -3371,8 +3056,8 @@
 
     @Override
     public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
-            @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
-            int lastClickTooType, ResultReceiver resultReceiver,
+            @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+            int lastClickToolType, ResultReceiver resultReceiver,
             @SoftInputShowHideReason int reason) {
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput");
         int uid = Binder.getCallingUid();
@@ -3388,7 +3073,7 @@
             final long ident = Binder.clearCallingIdentity();
             try {
                 if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
-                return showCurrentInputLocked(windowToken, statsToken, flags, lastClickTooType,
+                return showCurrentInputLocked(windowToken, statsToken, flags, lastClickToolType,
                         resultReceiver, reason);
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -3620,7 +3305,8 @@
         Binder.withCleanCallingIdentity(() -> {
             Objects.requireNonNull(windowToken, "windowToken must not be null");
             synchronized (ImfLock.class) {
-                if (mCurFocusedWindow != windowToken || mCurPerceptible == perceptible) {
+                if (mImeBindingState.mFocusedWindow != windowToken
+                        || mCurPerceptible == perceptible) {
                     return;
                 }
                 mCurPerceptible = perceptible;
@@ -3630,24 +3316,18 @@
     }
 
     @GuardedBy("ImfLock.class")
-    boolean showCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
-            @InputMethodManager.ShowFlags int flags, ResultReceiver resultReceiver,
-            @SoftInputShowHideReason int reason) {
+    private boolean showCurrentInputLocked(IBinder windowToken,
+            @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason) {
+        final var statsToken = createStatsTokenForFocusedClient(true /* show */, reason);
         return showCurrentInputLocked(windowToken, statsToken, flags,
-                MotionEvent.TOOL_TYPE_UNKNOWN, resultReceiver, reason);
+                MotionEvent.TOOL_TYPE_UNKNOWN, null /* resultReceiver */, reason);
     }
 
     @GuardedBy("ImfLock.class")
-    private boolean showCurrentInputLocked(IBinder windowToken,
-            @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
-            int lastClickToolType, ResultReceiver resultReceiver,
+    boolean showCurrentInputLocked(IBinder windowToken,
+            @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+            int lastClickToolType, @Nullable ResultReceiver resultReceiver,
             @SoftInputShowHideReason int reason) {
-        // Create statsToken is none exists.
-        if (statsToken == null) {
-            statsToken = createStatsTokenForFocusedClient(true /* show */,
-                    ImeTracker.ORIGIN_SERVER_START_INPUT, reason, false /* fromUser */);
-        }
-
         if (!mVisibilityStateComputer.onImeShowFlags(statsToken, flags)) {
             return false;
         }
@@ -3685,7 +3365,7 @@
 
     @Override
     public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
-            @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
+            @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         int uid = Binder.getCallingUid();
         ImeTracing.getInstance().triggerManagerServiceDump(
@@ -3714,17 +3394,29 @@
         }
     }
 
-    @GuardedBy("ImfLock.class")
-    boolean hideCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
-            @InputMethodManager.HideFlags int flags, ResultReceiver resultReceiver,
-            @SoftInputShowHideReason int reason) {
-        // Create statsToken is none exists.
-        if (statsToken == null) {
-            final boolean fromUser = reason == SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_BACK_KEY;
-            statsToken = createStatsTokenForFocusedClient(false /* show */,
-                    ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason, fromUser);
-        }
+    @Override
+    @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+    public void hideSoftInputFromServerForTest() {
+        super.hideSoftInputFromServerForTest_enforcePermission();
 
+        synchronized (ImfLock.class) {
+            hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
+                    SoftInputShowHideReason.HIDE_SOFT_INPUT);
+        }
+    }
+
+    @GuardedBy("ImfLock.class")
+    private boolean hideCurrentInputLocked(IBinder windowToken,
+            @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason) {
+        final var statsToken = createStatsTokenForFocusedClient(false /* show */, reason);
+        return hideCurrentInputLocked(windowToken, statsToken, flags, null /* resultReceiver */,
+                reason);
+    }
+
+    @GuardedBy("ImfLock.class")
+    boolean hideCurrentInputLocked(IBinder windowToken, @NonNull ImeTracker.Token statsToken,
+            @InputMethodManager.HideFlags int flags, @Nullable ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
         if (!mVisibilityStateComputer.canHideIme(statsToken, flags)) {
             return false;
         }
@@ -3883,7 +3575,8 @@
                     final boolean shouldClearFlag =
                             mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid);
                     final boolean showForced = mVisibilityStateComputer.mShowForced;
-                    if (mCurFocusedWindow != windowToken && showForced && shouldClearFlag) {
+                    if (mImeBindingState.mFocusedWindow != windowToken
+                            && showForced && shouldClearFlag) {
                         mVisibilityStateComputer.mShowForced = false;
                     }
 
@@ -3901,9 +3594,7 @@
                         Slog.w(TAG, "If you need to impersonate a foreground user/profile from"
                                 + " a background user, use EditorInfo.targetInputMethodUser with"
                                 + " INTERACT_ACROSS_USERS_FULL permission.");
-                        hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
-                                0 /* flags */,
-                                null /* resultReceiver */,
+                        hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
                                 SoftInputShowHideReason.HIDE_INVALID_USER);
                         return InputBindResult.INVALID_USER;
                     }
@@ -3964,7 +3655,7 @@
                     + " cs=" + cs);
         }
 
-        final boolean sameWindowFocused = mCurFocusedWindow == windowToken;
+        final boolean sameWindowFocused = mImeBindingState.mFocusedWindow == windowToken;
         final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0;
         final boolean startInputByWinGainedFocus =
                 (startInputFlags & StartInputFlags.WINDOW_GAINED_FOCUS) != 0;
@@ -3995,10 +3686,7 @@
                     null, null, null, null, -1, false);
         }
 
-        mCurFocusedWindow = windowToken;
-        mCurFocusedWindowSoftInputMode = softInputMode;
-        mCurFocusedWindowClient = cs;
-        mCurFocusedWindowEditorInfo = editorInfo;
+        mImeBindingState = new ImeBindingState(windowToken, softInputMode, cs, editorInfo);
         mCurPerceptible = true;
 
         // We want to start input before showing the IME, but after closing
@@ -4011,11 +3699,14 @@
         final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.computeState(windowState,
                 isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags));
         if (imeVisRes != null) {
+            boolean isShow = false;
             switch (imeVisRes.getReason()) {
                 case SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY:
                 case SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV:
                 case SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV:
                 case SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE:
+                    isShow = true;
+
                     if (editorInfo != null) {
                         res = startInputUncheckedLocked(cs, inputContext,
                                 remoteAccessibilityInputConnection, editorInfo, startInputFlags,
@@ -4025,10 +3716,9 @@
                     }
                     break;
             }
-
-            mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, null /* statsToken */,
+            final var statsToken = createStatsTokenForFocusedClient(isShow, imeVisRes.getReason());
+            mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow, statsToken,
                     imeVisRes.getState(), imeVisRes.getReason());
-
             if (imeVisRes.getReason() == SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW) {
                 // If focused display changed, we should unbind current method
                 // to make app window in previous display relayout after Ime
@@ -4054,13 +3744,6 @@
     }
 
     @GuardedBy("ImfLock.class")
-    private void showCurrentInputImplicitLocked(@NonNull IBinder windowToken,
-            @SoftInputShowHideReason int reason) {
-        showCurrentInputLocked(windowToken, null /* statsToken */, InputMethodManager.SHOW_IMPLICIT,
-                null /* resultReceiver */, reason);
-    }
-
-    @GuardedBy("ImfLock.class")
     private boolean canInteractWithImeLocked(int uid, IInputMethodClient client, String methodName,
             @Nullable ImeTracker.Token statsToken) {
         if (mCurClient == null || client == null
@@ -4074,7 +3757,7 @@
                 throw new IllegalArgumentException("unknown client " + client.asBinder());
             }
             ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
-            if (!isImeClientFocused(mCurFocusedWindow, cs)) {
+            if (!isImeClientFocused(mImeBindingState.mFocusedWindow, cs)) {
                 Slog.w(TAG, String.format("Ignoring %s of uid %d : %s", methodName, uid, client));
                 return false;
             }
@@ -4086,8 +3769,8 @@
     @GuardedBy("ImfLock.class")
     private boolean canShowInputMethodPickerLocked(IInputMethodClient client) {
         final int uid = Binder.getCallingUid();
-        if (mCurFocusedWindowClient != null && client != null
-                && mCurFocusedWindowClient.mClient.asBinder() == client.asBinder()) {
+        if (mImeBindingState.mFocusedWindowClient != null && client != null
+                && mImeBindingState.mFocusedWindowClient.mClient.asBinder() == client.asBinder()) {
             return true;
         }
         if (mSettings.getUserId() != UserHandle.getUserId(uid)) {
@@ -4313,7 +3996,7 @@
                 return mSettings.getLastInputMethodSubtype();
             }
 
-            final InputMethodSettings settings = queryMethodMapForUser(userId);
+            final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
             return settings.getLastInputMethodSubtype();
         }
     }
@@ -4344,33 +4027,25 @@
                 return;
             }
 
-            if (mSettings.getUserId() == userId) {
-                final var newAdditionalSubtypeMap = mSettings.getNewAdditionalSubtypeMap(
-                        imiId, toBeAdded, mAdditionalSubtypeMap, mPackageManagerInternal,
-                        callingUid);
-                if (mAdditionalSubtypeMap == newAdditionalSubtypeMap) {
-                    return;
-                }
-                AdditionalSubtypeUtils.save(newAdditionalSubtypeMap, mSettings.getMethodMap(),
-                        mSettings.getUserId());
-                mAdditionalSubtypeMap = newAdditionalSubtypeMap;
-                final long ident = Binder.clearCallingIdentity();
-                try {
-                    buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
-                }
-                return;
-            }
-
-            final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
-            final InputMethodSettings settings = queryInputMethodServicesInternal(mContext, userId,
-                    additionalSubtypeMap, DirectBootAwareness.AUTO);
+            final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
+            final boolean isCurrentUser = (mSettings.getUserId() == userId);
+            final InputMethodSettings settings = isCurrentUser
+                    ? mSettings
+                    : queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
+                            DirectBootAwareness.AUTO);
             final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap(
                     imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid);
             if (additionalSubtypeMap != newAdditionalSubtypeMap) {
-                AdditionalSubtypeUtils.save(newAdditionalSubtypeMap, settings.getMethodMap(),
-                        settings.getUserId());
+                AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
+                        settings.getMethodMap());
+                if (isCurrentUser) {
+                    final long ident = Binder.clearCallingIdentity();
+                    try {
+                        buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+                    } finally {
+                        Binder.restoreCallingIdentity(ident);
+                    }
+                }
             }
         }
     }
@@ -4397,7 +4072,7 @@
             synchronized (ImfLock.class) {
                 final boolean currentUser = (mSettings.getUserId() == userId);
                 final InputMethodSettings settings = currentUser
-                        ? mSettings : queryMethodMapForUser(userId);
+                        ? mSettings : queryMethodMapForUserLocked(userId);
                 if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) {
                     return;
                 }
@@ -4710,12 +4385,11 @@
             proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked());
             proto.write(CUR_SEQ, getSequenceNumberLocked());
             proto.write(CUR_CLIENT, Objects.toString(mCurClient));
-            proto.write(CUR_FOCUSED_WINDOW_NAME,
-                    mWindowManagerInternal.getWindowName(mCurFocusedWindow));
+            mImeBindingState.dumpDebug(proto, mWindowManagerInternal);
             proto.write(LAST_IME_TARGET_WINDOW_NAME,
                     mWindowManagerInternal.getWindowName(mLastImeTargetWindow));
-            proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE,
-                    InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode));
+            proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE, InputMethodDebug.softInputModeToString(
+                    mImeBindingState.mFocusedWindowSoftInputMode));
             if (mCurEditorInfo != null) {
                 mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE);
             }
@@ -4725,7 +4399,6 @@
             proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked()));
             proto.write(CUR_TOKEN_DISPLAY_ID, mCurTokenDisplayId);
             proto.write(SYSTEM_READY, mSystemReady);
-            proto.write(LAST_SWITCH_USER_ID, mLastSwitchUserId);
             proto.write(HAVE_CONNECTION, hasConnectionLocked());
             proto.write(BOUND_TO_METHOD, mBoundToMethod);
             proto.write(IS_INTERACTIVE, mIsInteractive);
@@ -4762,15 +4435,17 @@
 
     @BinderThread
     private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible,
-            @Nullable ImeTracker.Token statsToken) {
+            @NonNull ImeTracker.Token statsToken) {
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility");
             synchronized (ImfLock.class) {
                 if (!calledWithValidTokenLocked(token)) {
                     ImeTracker.forLogging().onFailed(statsToken,
-                            ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
+                            ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
                     return;
                 }
+                ImeTracker.forLogging().onProgress(statsToken,
+                        ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
                 final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(
                         windowToken);
                 mVisibilityApplier.applyImeVisibility(requestToken, statsToken,
@@ -4835,12 +4510,12 @@
         final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken);
         final WindowManagerInternal.ImeTargetInfo info =
                 mWindowManagerInternal.onToggleImeRequested(
-                        show, mCurFocusedWindow, requestToken, mCurTokenDisplayId);
+                        show, mImeBindingState.mFocusedWindow, requestToken, mCurTokenDisplayId);
         mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
-                mCurFocusedWindowClient, mCurFocusedWindowEditorInfo, info.focusedWindowName,
-                mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode,
-                info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName,
-                info.imeSurfaceParentName));
+                mImeBindingState.mFocusedWindowClient, mImeBindingState.mFocusedWindowEditorInfo,
+                info.focusedWindowName, mImeBindingState.mFocusedWindowSoftInputMode, reason,
+                mInFullscreenMode, info.requestWindowName, info.imeControlTargetName,
+                info.imeLayerTargetName, info.imeSurfaceParentName));
 
         if (statsToken != null) {
             mImeTrackerService.onImmsUpdate(statsToken, info.requestWindowName);
@@ -4848,17 +4523,21 @@
     }
 
     @BinderThread
-    private void hideMySoftInput(@NonNull IBinder token, @InputMethodManager.HideFlags int flags,
-            @SoftInputShowHideReason int reason) {
+    private void hideMySoftInput(@NonNull IBinder token, @NonNull ImeTracker.Token statsToken,
+            @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason) {
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput");
             synchronized (ImfLock.class) {
                 if (!calledWithValidTokenLocked(token)) {
+                    ImeTracker.forLogging().onFailed(statsToken,
+                            ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
                     return;
                 }
+                ImeTracker.forLogging().onProgress(statsToken,
+                        ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    hideCurrentInputLocked(mLastImeTargetWindow, null /* statsToken */, flags,
+                    hideCurrentInputLocked(mLastImeTargetWindow, statsToken, flags,
                             null /* resultReceiver */, reason);
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -4870,18 +4549,22 @@
     }
 
     @BinderThread
-    private void showMySoftInput(@NonNull IBinder token, @InputMethodManager.ShowFlags int flags) {
+    private void showMySoftInput(@NonNull IBinder token, @NonNull ImeTracker.Token statsToken,
+            @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason) {
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showMySoftInput");
             synchronized (ImfLock.class) {
                 if (!calledWithValidTokenLocked(token)) {
+                    ImeTracker.forLogging().onFailed(statsToken,
+                            ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
                     return;
                 }
+                ImeTracker.forLogging().onProgress(statsToken,
+                        ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    showCurrentInputLocked(mLastImeTargetWindow, null /* statsToken */, flags,
-                            null /* resultReceiver */,
-                            SoftInputShowHideReason.SHOW_SOFT_INPUT_FROM_IME);
+                    showCurrentInputLocked(mLastImeTargetWindow, statsToken, flags,
+                            MotionEvent.TOOL_TYPE_UNKNOWN, null /* resultReceiver */, reason);
                 } finally {
                     Binder.restoreCallingIdentity(ident);
                 }
@@ -4898,10 +4581,10 @@
         }
     }
 
-    void onApplyImeVisibilityFromComputer(IBinder windowToken,
+    void onApplyImeVisibilityFromComputer(IBinder windowToken, @NonNull ImeTracker.Token statsToken,
             @NonNull ImeVisibilityResult result) {
         synchronized (ImfLock.class) {
-            mVisibilityApplier.applyImeVisibility(windowToken, null, result.getState(),
+            mVisibilityApplier.applyImeVisibility(windowToken, statsToken, result.getState(),
                     result.getReason());
         }
     }
@@ -4993,6 +4676,14 @@
                             .getSortedInputMethodAndSubtypeList(
                                     showAuxSubtypes, isScreenLocked, true /* forImeMenu */,
                                     mContext, mSettings.getMethodMap(), mSettings.getUserId());
+                    if (imList.isEmpty()) {
+                        Slog.w(TAG, "Show switching menu failed, imList is empty,"
+                                + " showAuxSubtypes: " + showAuxSubtypes
+                                + " isScreenLocked: " + isScreenLocked
+                                + " userId: " + mSettings.getUserId());
+                        return false;
+                    }
+
                     mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId,
                             lastInputMethodId, lastInputMethodSubtypeId, imList);
                 }
@@ -5002,10 +4693,8 @@
 
             case MSG_HIDE_ALL_INPUT_METHODS:
                 synchronized (ImfLock.class) {
-                    final @SoftInputShowHideReason int reason = (int) msg.obj;
-                    hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
-                            null /* resultReceiver */, reason);
-
+                    @SoftInputShowHideReason final int reason = (int) msg.obj;
+                    hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, reason);
                 }
                 return true;
             case MSG_REMOVE_IME_SURFACE: {
@@ -5024,7 +4713,7 @@
                 IBinder windowToken = (IBinder) msg.obj;
                 synchronized (ImfLock.class) {
                     try {
-                        if (windowToken == mCurFocusedWindow
+                        if (windowToken == mImeBindingState.mFocusedWindow
                                 && mEnabledSession != null && mEnabledSession.mSession != null) {
                             mEnabledSession.mSession.removeImeSurface();
                         }
@@ -5099,7 +4788,7 @@
             case MSG_START_HANDWRITING:
                 synchronized (ImfLock.class) {
                     IInputMethodInvoker curMethod = getCurMethodLocked();
-                    if (curMethod == null || mCurFocusedWindow == null) {
+                    if (curMethod == null || mImeBindingState.mFocusedWindow == null) {
                         return true;
                     }
                     final HandwritingModeController.HandwritingSession session =
@@ -5107,7 +4796,7 @@
                                     msg.arg1 /*requestId*/,
                                     msg.arg2 /*pid*/,
                                     mBindingController.getCurMethodUid(),
-                                    mCurFocusedWindow);
+                                    mImeBindingState.mFocusedWindow);
                     if (session == null) {
                         Slog.e(TAG,
                                 "Failed to start handwriting session for requestId: " + msg.arg1);
@@ -5160,10 +4849,11 @@
                 // Handle IME visibility when interactive changed before finishing the input to
                 // ensure we preserve the last state as possible.
                 final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.onInteractiveChanged(
-                        mCurFocusedWindow, interactive);
+                        mImeBindingState.mFocusedWindow, interactive);
                 if (imeVisRes != null) {
-                    mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, null,
-                            imeVisRes.getState(), imeVisRes.getReason());
+                    // Pass in a null statsToken as the IME snapshot is not tracked by ImeTracker.
+                    mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow,
+                            null /* statsToken */, imeVisRes.getState(), imeVisRes.getReason());
                 }
                 // Eligible IME processes use new "setInteractive" protocol.
                 mCurClient.mClient.setInteractive(mIsInteractive, mInFullscreenMode);
@@ -5292,7 +4982,8 @@
         mMyPackageMonitor.clearKnownImePackageNamesLocked();
 
         mSettings = queryInputMethodServicesInternal(mContext, mSettings.getUserId(),
-                mAdditionalSubtypeMap, DirectBootAwareness.AUTO);
+                AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
+                DirectBootAwareness.AUTO);
 
         // Construct the set of possible IME packages for onPackageChanged() to avoid false
         // negatives when the package state remains to be the same but only the component state is
@@ -5564,7 +5255,7 @@
                 return getCurrentInputMethodSubtypeLocked();
             }
 
-            final InputMethodSettings settings = queryMethodMapForUser(userId);
+            final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
             return settings.getCurrentInputMethodSubtypeForNonCurrentUsers();
         }
     }
@@ -5631,15 +5322,18 @@
         if (userId == mSettings.getUserId()) {
             settings = mSettings;
         } else {
-            final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
+            final AdditionalSubtypeMap additionalSubtypeMap =
+                    AdditionalSubtypeMapRepository.get(userId);
             settings = queryInputMethodServicesInternal(mContext, userId,
                     additionalSubtypeMap, DirectBootAwareness.AUTO);
         }
         return settings.getMethodMap().get(settings.getSelectedInputMethod());
     }
 
-    private InputMethodSettings queryMethodMapForUser(@UserIdInt int userId) {
-        final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
+    @GuardedBy("ImfLock.class")
+    private InputMethodSettings queryMethodMapForUserLocked(@UserIdInt int userId) {
+        final AdditionalSubtypeMap additionalSubtypeMap =
+                AdditionalSubtypeMapRepository.get(userId);
         return queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
                 DirectBootAwareness.AUTO);
     }
@@ -5655,7 +5349,7 @@
             setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
             return true;
         }
-        final InputMethodSettings settings = queryMethodMapForUser(userId);
+        final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
         if (!settings.getMethodMap().containsKey(imeId)
                 || !settings.getEnabledInputMethodList().contains(
                         settings.getMethodMap().get(imeId))) {
@@ -5795,7 +5489,7 @@
                     setInputMethodEnabledLocked(imeId, enabled);
                     return true;
                 }
-                final InputMethodSettings settings = queryMethodMapForUser(userId);
+                final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
                 if (!settings.getMethodMap().containsKey(imeId)) {
                     return false; // IME is not found.
                 }
@@ -5853,7 +5547,7 @@
         @Override
         public void reportImeControl(@Nullable IBinder windowToken) {
             synchronized (ImfLock.class) {
-                if (mCurFocusedWindow != windowToken) {
+                if (mImeBindingState.mFocusedWindow != windowToken) {
                     // mCurPerceptible was set by the focused window, but it is no longer in
                     // control, so we reset mCurPerceptible.
                     mCurPerceptible = true;
@@ -5867,7 +5561,7 @@
                 // Hide the IME method menu only when the IME surface parent is changed by the
                 // input target changed, in case seeing the dialog dismiss flickering during
                 // the next focused window starting the input connection.
-                if (mLastImeTargetWindow != mCurFocusedWindow) {
+                if (mLastImeTargetWindow != mImeBindingState.mFocusedWindow) {
                     mMenuController.hideInputMethodMenuLocked();
                 }
             }
@@ -6161,11 +5855,7 @@
             client = mCurClient;
             p.println("  mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked());
             p.println("  mCurPerceptible=" + mCurPerceptible);
-            p.println("  mCurFocusedWindow=" + mCurFocusedWindow
-                    + " softInputMode="
-                    + InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode)
-                    + " client=" + mCurFocusedWindowClient);
-            focusedWindowClient = mCurFocusedWindowClient;
+            mImeBindingState.dump("  ", p);
             p.println("  mCurId=" + getCurIdLocked() + " mHaveConnection=" + hasConnectionLocked()
                     + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound="
                     + mBindingController.isVisibleBound());
@@ -6216,7 +5906,8 @@
             p.println("No input method client.");
         }
 
-        if (focusedWindowClient != null && client != focusedWindowClient) {
+        if (mImeBindingState.mFocusedWindowClient != null
+                && client != mImeBindingState.mFocusedWindowClient) {
             p.println(" ");
             p.println("Warning: Current input method client doesn't match the last focused. "
                     + "window.");
@@ -6224,7 +5915,8 @@
             p.println(" ");
             pw.flush();
             try {
-                TransferPipe.dumpAsync(focusedWindowClient.mClient.asBinder(), fd, args);
+                TransferPipe.dumpAsync(
+                        mImeBindingState.mFocusedWindowClient.mClient.asBinder(), fd, args);
             } catch (IOException | RemoteException e) {
                 p.println("Failed to dump input method client in focused window: " + e);
             }
@@ -6296,8 +5988,6 @@
         @ShellCommandResult
         private int onCommandWithSystemIdentity(@Nullable String cmd) {
             switch (TextUtils.emptyIfNull(cmd)) {
-                case "get-last-switch-user-id":
-                    return mService.getLastSwitchUserId(this);
                 case "tracing":
                     return mService.handleShellCommandTraceInputMethod(this);
                 case "ime": {  // For "adb shell ime <command>".
@@ -6411,15 +6101,6 @@
     // ----------------------------------------------------------------------
     // Shell command handlers:
 
-    @BinderThread
-    @ShellCommandResult
-    private int getLastSwitchUserId(@NonNull ShellCommand shellCommand) {
-        synchronized (ImfLock.class) {
-            shellCommand.getOutPrintWriter().println(mLastSwitchUserId);
-            return ShellCommandResult.SUCCESS;
-        }
-    }
-
     /**
      * Handles {@code adb shell ime list}.
      * @param shellCommand {@link ShellCommand} object that is handling this command.
@@ -6562,7 +6243,7 @@
                 previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled);
             }
         } else {
-            final InputMethodSettings settings = queryMethodMapForUser(userId);
+            final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
             if (enabled) {
                 if (!settings.getMethodMap().containsKey(imeId)) {
                     failedToEnableUnknownIme = true;
@@ -6670,8 +6351,7 @@
                     final String nextIme;
                     final List<InputMethodInfo> nextEnabledImes;
                     if (userId == mSettings.getUserId()) {
-                        hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
-                                0 /* flags */, null /* resultReceiver */,
+                        hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
                                 SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
                         mBindingController.unbindCurrentMethod();
 
@@ -6698,7 +6378,7 @@
                         nextEnabledImes = mSettings.getEnabledInputMethodList();
                     } else {
                         final AdditionalSubtypeMap additionalSubtypeMap =
-                                AdditionalSubtypeUtils.load(userId);
+                                AdditionalSubtypeMapRepository.get(userId);
                         final InputMethodSettings settings = queryInputMethodServicesInternal(
                                 mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO);
 
@@ -6800,27 +6480,21 @@
      * Creates an IME request tracking token for the current focused client.
      *
      * @param show whether this is a show or a hide request.
-     * @param origin the origin of the IME request.
      * @param reason the reason why the IME request was created.
-     * @param fromUser whether this request was created directly from user interaction.
      */
     @NonNull
     private ImeTracker.Token createStatsTokenForFocusedClient(boolean show,
-            @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason, boolean fromUser) {
-        final int uid = mCurFocusedWindowClient != null
-                ? mCurFocusedWindowClient.mUid
+            @SoftInputShowHideReason int reason) {
+        final int uid = mImeBindingState.mFocusedWindowClient != null
+                ? mImeBindingState.mFocusedWindowClient.mUid
                 : -1;
-        final var packageName = mCurFocusedWindowEditorInfo != null
-                ? mCurFocusedWindowEditorInfo.packageName
+        final var packageName = mImeBindingState.mFocusedWindowEditorInfo != null
+                ? mImeBindingState.mFocusedWindowEditorInfo.packageName
                 : "uid(" + uid + ")";
 
-        if (show) {
-            return ImeTracker.forLogging()
-                    .onRequestShow(packageName, uid, origin, reason, fromUser);
-        } else {
-            return ImeTracker.forLogging()
-                    .onRequestHide(packageName, uid, origin, reason, fromUser);
-        }
+        return ImeTracker.forLogging().onStart(packageName, uid,
+                show ? ImeTracker.TYPE_SHOW : ImeTracker.TYPE_HIDE, ImeTracker.ORIGIN_SERVER,
+                reason, false /* fromUser */);
     }
 
     private static final class InputMethodPrivilegedOperationsImpl
@@ -6895,12 +6569,13 @@
 
         @BinderThread
         @Override
-        public void hideMySoftInput(@InputMethodManager.HideFlags int flags,
-                @SoftInputShowHideReason int reason, AndroidFuture future /* T=Void */) {
+        public void hideMySoftInput(@NonNull ImeTracker.Token statsToken,
+                @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason,
+                AndroidFuture future /* T=Void */) {
             @SuppressWarnings("unchecked")
             final AndroidFuture<Void> typedFuture = future;
             try {
-                mImms.hideMySoftInput(mToken, flags, reason);
+                mImms.hideMySoftInput(mToken, statsToken, flags, reason);
                 typedFuture.complete(null);
             } catch (Throwable e) {
                 typedFuture.completeExceptionally(e);
@@ -6909,12 +6584,13 @@
 
         @BinderThread
         @Override
-        public void showMySoftInput(@InputMethodManager.ShowFlags int flags,
+        public void showMySoftInput(@NonNull ImeTracker.Token statsToken,
+                @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason,
                 AndroidFuture future /* T=Void */) {
             @SuppressWarnings("unchecked")
             final AndroidFuture<Void> typedFuture = future;
             try {
-                mImms.showMySoftInput(mToken, flags);
+                mImms.showMySoftInput(mToken, statsToken, flags, reason);
                 typedFuture.complete(null);
             } catch (Throwable e) {
                 typedFuture.completeExceptionally(e);
@@ -6973,7 +6649,7 @@
         @BinderThread
         @Override
         public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible,
-                @Nullable ImeTracker.Token statsToken) {
+                @NonNull ImeTracker.Token statsToken) {
             mImms.applyImeVisibility(mToken, windowToken, setVisible, statsToken);
         }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 6ed4848..3bd0a9f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -80,10 +80,6 @@
 
         final int userId = mService.getCurrentImeUserIdLocked();
 
-        if (imList.isEmpty()) {
-            return;
-        }
-
         hideInputMethodMenuLocked();
 
         if (preferredInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index 1379d16..1c958a9 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -167,6 +167,7 @@
 
         final ArrayList<InputMethodInfo> imis = settings.getEnabledInputMethodList();
         if (imis.isEmpty()) {
+            Slog.w(TAG, "Enabled input method list is empty.");
             return new ArrayList<>();
         }
         if (isScreenLocked && includeAuxiliarySubtypes) {
diff --git a/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java b/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java
new file mode 100644
index 0000000..3023603
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.SystemClock;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+
+import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicInteger;
+
+final class SoftInputShowHideHistory {
+    private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
+
+    private final Entry[] mEntries = new Entry[16];
+    private int mNextIndex = 0;
+
+    static final class Entry {
+        final int mSequenceNumber = sSequenceNumber.getAndIncrement();
+        @Nullable
+        final ClientState mClientState;
+        @WindowManager.LayoutParams.SoftInputModeFlags
+        final int mFocusedWindowSoftInputMode;
+        @SoftInputShowHideReason
+        final int mReason;
+        // The timing of handling showCurrentInputLocked() or hideCurrentInputLocked().
+        final long mTimestamp;
+        final long mWallTime;
+        final boolean mInFullscreenMode;
+        @NonNull
+        final String mFocusedWindowName;
+        @Nullable
+        final EditorInfo mEditorInfo;
+        @NonNull
+        final String mRequestWindowName;
+        @Nullable
+        final String mImeControlTargetName;
+        @Nullable
+        final String mImeTargetNameFromWm;
+        @Nullable
+        final String mImeSurfaceParentName;
+
+        Entry(ClientState client, EditorInfo editorInfo,
+                String focusedWindowName,
+                @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+                @SoftInputShowHideReason int reason,
+                boolean inFullscreenMode, String requestWindowName,
+                @Nullable String imeControlTargetName, @Nullable String imeTargetName,
+                @Nullable String imeSurfaceParentName) {
+            mClientState = client;
+            mEditorInfo = editorInfo;
+            mFocusedWindowName = focusedWindowName;
+            mFocusedWindowSoftInputMode = softInputMode;
+            mReason = reason;
+            mTimestamp = SystemClock.uptimeMillis();
+            mWallTime = System.currentTimeMillis();
+            mInFullscreenMode = inFullscreenMode;
+            mRequestWindowName = requestWindowName;
+            mImeControlTargetName = imeControlTargetName;
+            mImeTargetNameFromWm = imeTargetName;
+            mImeSurfaceParentName = imeSurfaceParentName;
+        }
+    }
+
+    void addEntry(@NonNull Entry entry) {
+        final int index = mNextIndex;
+        mEntries[index] = entry;
+        mNextIndex = (mNextIndex + 1) % mEntries.length;
+    }
+
+    void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        final DateTimeFormatter formatter =
+                DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+                        .withZone(ZoneId.systemDefault());
+
+        for (int i = 0; i < mEntries.length; ++i) {
+            final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
+            if (entry == null) {
+                continue;
+            }
+            pw.print(prefix);
+            pw.println("SoftInputShowHide #" + entry.mSequenceNumber + ":");
+
+            pw.print(prefix);
+            pw.println("  time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
+                    + " (timestamp=" + entry.mTimestamp + ")");
+
+            pw.print(prefix);
+            pw.print("  reason=" + InputMethodDebug.softInputDisplayReasonToString(
+                    entry.mReason));
+            pw.println(" inFullscreenMode=" + entry.mInFullscreenMode);
+
+            pw.print(prefix);
+            pw.println("  requestClient=" + entry.mClientState);
+
+            pw.print(prefix);
+            pw.println("  focusedWindowName=" + entry.mFocusedWindowName);
+
+            pw.print(prefix);
+            pw.println("  requestWindowName=" + entry.mRequestWindowName);
+
+            pw.print(prefix);
+            pw.println("  imeControlTargetName=" + entry.mImeControlTargetName);
+
+            pw.print(prefix);
+            pw.println("  imeTargetNameFromWm=" + entry.mImeTargetNameFromWm);
+
+            pw.print(prefix);
+            pw.println("  imeSurfaceParentName=" + entry.mImeSurfaceParentName);
+
+            pw.print(prefix);
+            pw.print("  editorInfo:");
+            if (entry.mEditorInfo != null) {
+                pw.print(" inputType=" + entry.mEditorInfo.inputType);
+                pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
+                pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
+            } else {
+                pw.println(" null");
+            }
+
+            pw.print(prefix);
+            pw.println("  focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString(
+                    entry.mFocusedWindowSoftInputMode));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/StartInputHistory.java b/services/core/java/com/android/server/inputmethod/StartInputHistory.java
new file mode 100644
index 0000000..3a39434
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/StartInputHistory.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.StartInputReason;
+
+import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+/**
+ * A ring buffer to store the history of {@link StartInputInfo}.
+ */
+final class StartInputHistory {
+    /**
+     * Entry size for non low-RAM devices.
+     *
+     * <p>TODO: Consider to follow what other system services have been doing to manage
+     * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
+     */
+    private static final int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 32;
+
+    /**
+     * Entry size for low-RAM devices.
+     *
+     * <p>TODO: Consider to follow what other system services have been doing to manage
+     * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
+     */
+    private static final int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5;
+
+    private static int getEntrySize() {
+        if (ActivityManager.isLowRamDeviceStatic()) {
+            return ENTRY_SIZE_FOR_LOW_RAM_DEVICE;
+        } else {
+            return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE;
+        }
+    }
+
+    /**
+     * Backing store for the ring buffer.
+     */
+    private final Entry[] mEntries = new Entry[getEntrySize()];
+
+    /**
+     * An index of {@link #mEntries}, to which next
+     * {@link #addEntry(StartInputInfo)} should
+     * write.
+     */
+    private int mNextIndex = 0;
+
+    /**
+     * Recyclable entry to store the information in {@link StartInputInfo}.
+     */
+    private static final class Entry {
+        int mSequenceNumber;
+        long mTimestamp;
+        long mWallTime;
+        @UserIdInt
+        int mImeUserId;
+        @NonNull
+        String mImeTokenString;
+        int mImeDisplayId;
+        @NonNull
+        String mImeId;
+        @StartInputReason
+        int mStartInputReason;
+        boolean mRestarting;
+        @UserIdInt
+        int mTargetUserId;
+        int mTargetDisplayId;
+        @NonNull
+        String mTargetWindowString;
+        @NonNull
+        EditorInfo mEditorInfo;
+        @WindowManager.LayoutParams.SoftInputModeFlags
+        int mTargetWindowSoftInputMode;
+        int mClientBindSequenceNumber;
+
+        Entry(@NonNull StartInputInfo original) {
+            set(original);
+        }
+
+        void set(@NonNull StartInputInfo original) {
+            mSequenceNumber = original.mSequenceNumber;
+            mTimestamp = original.mTimestamp;
+            mWallTime = original.mWallTime;
+            mImeUserId = original.mImeUserId;
+            // Intentionally convert to String so as not to keep a strong reference to a Binder
+            // object.
+            mImeTokenString = String.valueOf(original.mImeToken);
+            mImeDisplayId = original.mImeDisplayId;
+            mImeId = original.mImeId;
+            mStartInputReason = original.mStartInputReason;
+            mRestarting = original.mRestarting;
+            mTargetUserId = original.mTargetUserId;
+            mTargetDisplayId = original.mTargetDisplayId;
+            // Intentionally convert to String so as not to keep a strong reference to a Binder
+            // object.
+            mTargetWindowString = String.valueOf(original.mTargetWindow);
+            mEditorInfo = original.mEditorInfo;
+            mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode;
+            mClientBindSequenceNumber = original.mClientBindSequenceNumber;
+        }
+    }
+
+    /**
+     * Add a new entry and discard the oldest entry as needed.
+     *
+     * @param info {@link StartInputInfo} to be added.
+     */
+    void addEntry(@NonNull StartInputInfo info) {
+        final int index = mNextIndex;
+        if (mEntries[index] == null) {
+            mEntries[index] = new Entry(info);
+        } else {
+            mEntries[index].set(info);
+        }
+        mNextIndex = (mNextIndex + 1) % mEntries.length;
+    }
+
+    void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        final DateTimeFormatter formatter =
+                DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+                        .withZone(ZoneId.systemDefault());
+
+        for (int i = 0; i < mEntries.length; ++i) {
+            final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
+            if (entry == null) {
+                continue;
+            }
+            pw.print(prefix);
+            pw.println("StartInput #" + entry.mSequenceNumber + ":");
+
+            pw.print(prefix);
+            pw.println("  time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
+                    + " (timestamp=" + entry.mTimestamp + ")"
+                    + " reason="
+                    + InputMethodDebug.startInputReasonToString(entry.mStartInputReason)
+                    + " restarting=" + entry.mRestarting);
+
+            pw.print(prefix);
+            pw.print("  imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]");
+            pw.print(" imeUserId=" + entry.mImeUserId);
+            pw.println(" imeDisplayId=" + entry.mImeDisplayId);
+
+            pw.print(prefix);
+            pw.println("  targetWin=" + entry.mTargetWindowString
+                    + " [" + entry.mEditorInfo.packageName + "]"
+                    + " targetUserId=" + entry.mTargetUserId
+                    + " targetDisplayId=" + entry.mTargetDisplayId
+                    + " clientBindSeq=" + entry.mClientBindSequenceNumber);
+
+            pw.print(prefix);
+            pw.println("  softInputMode=" + InputMethodDebug.softInputModeToString(
+                    entry.mTargetWindowSoftInputMode));
+
+            pw.print(prefix);
+            pw.println("  inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType)
+                    + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions)
+                    + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId)
+                    + " fieldName=" + entry.mEditorInfo.fieldName
+                    + " actionId=" + entry.mEditorInfo.actionId
+                    + " actionLabel=" + entry.mEditorInfo.actionLabel);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/StartInputInfo.java b/services/core/java/com/android/server/inputmethod/StartInputInfo.java
new file mode 100644
index 0000000..1cff737
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/StartInputInfo.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.inputmethodservice.InputMethodService;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+
+import com.android.internal.inputmethod.IInputMethod;
+import com.android.internal.inputmethod.StartInputReason;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Internal state snapshot when
+ * {@link IInputMethod#startInput(IInputMethod.StartInputParams)} is about to be called.
+ *
+ * <p>Calling that IPC endpoint basically means that
+ * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
+ * back in the current IME process shortly, which will also affect what the current IME starts
+ * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this
+ * snapshot will be taken every time when {@link InputMethodManagerService} is initiating a new
+ * logical input session between the client application and the current IME.</p>
+ *
+ * <p>Be careful to not keep strong references to this object forever, which can prevent
+ * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed.
+ * </p>
+ */
+final class StartInputInfo {
+    private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
+
+    final int mSequenceNumber;
+    final long mTimestamp;
+    final long mWallTime;
+    @UserIdInt
+    final int mImeUserId;
+    @NonNull
+    final IBinder mImeToken;
+    final int mImeDisplayId;
+    @NonNull
+    final String mImeId;
+    @StartInputReason
+    final int mStartInputReason;
+    final boolean mRestarting;
+    @UserIdInt
+    final int mTargetUserId;
+    final int mTargetDisplayId;
+    @Nullable
+    final IBinder mTargetWindow;
+    @NonNull
+    final EditorInfo mEditorInfo;
+    @WindowManager.LayoutParams.SoftInputModeFlags
+    final int mTargetWindowSoftInputMode;
+    final int mClientBindSequenceNumber;
+
+    StartInputInfo(@UserIdInt int imeUserId, @NonNull IBinder imeToken, int imeDisplayId,
+            @NonNull String imeId, @StartInputReason int startInputReason, boolean restarting,
+            @UserIdInt int targetUserId, int targetDisplayId, @Nullable IBinder targetWindow,
+            @NonNull EditorInfo editorInfo,
+            @WindowManager.LayoutParams.SoftInputModeFlags int targetWindowSoftInputMode,
+            int clientBindSequenceNumber) {
+        mSequenceNumber = sSequenceNumber.getAndIncrement();
+        mTimestamp = SystemClock.uptimeMillis();
+        mWallTime = System.currentTimeMillis();
+        mImeUserId = imeUserId;
+        mImeToken = imeToken;
+        mImeDisplayId = imeDisplayId;
+        mImeId = imeId;
+        mStartInputReason = startInputReason;
+        mRestarting = restarting;
+        mTargetUserId = targetUserId;
+        mTargetDisplayId = targetDisplayId;
+        mTargetWindow = targetWindow;
+        mEditorInfo = editorInfo;
+        mTargetWindowSoftInputMode = targetWindowSoftInputMode;
+        mClientBindSequenceNumber = clientBindSequenceNumber;
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
index 62d4455..396192e 100644
--- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -72,6 +72,7 @@
 import com.android.internal.view.IInputMethodManager;
 
 import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
@@ -100,7 +101,6 @@
     }
 
     private void offloadInner(Runnable r) {
-        boolean useThrowingRunnable = r instanceof ThrowingRunnable;
         final long identity = Binder.clearCallingIdentity();
         try {
             mExecutor.execute(() -> {
@@ -109,14 +109,9 @@
                 Binder.restoreCallingIdentity(identity);
                 try {
                     try {
-                        if (useThrowingRunnable) {
-                            ((ThrowingRunnable) r).runOrThrow();
-                        } else {
-                            r.run();
-                        }
+                        r.run();
                     } catch (Exception e) {
-                        Slog.e(TAG, "Error in async call", e);
-                        throw ExceptionUtils.propagate(e);
+                        Slog.e(TAG, "Error in async IMMS call", e);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(inner);
@@ -183,6 +178,13 @@
         return true;
     }
 
+    @Override
+    @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+    public void hideSoftInputFromServerForTest() throws RemoteException {
+        super.hideSoftInputFromServerForTest_enforcePermission();
+        mInner.hideSoftInputFromServerForTest();
+    }
+
     @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
     @Override
     public void startInputOrWindowGainedFocusAsync(
@@ -404,6 +406,13 @@
                 in, out, err, args, callback, resultReceiver);
     }
 
+    @Override
+    protected void dump(@NonNull FileDescriptor fd,
+            @NonNull PrintWriter fout,
+            @Nullable String[] args) {
+        ((InputMethodManagerService) mInner).dump(fd, fout, args);
+    }
+
     private void sendOnStartInputResult(
             IInputMethodClient client, InputBindResult res, int startInputSeq) {
         InputMethodManagerService service = (InputMethodManagerService) mInner;
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index fc99471..0ca4808 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -449,7 +449,7 @@
     }
 
     /**
-     * Sends a reliable message rom this client to a nanoapp.
+     * Sends a reliable message from this client to a nanoapp.
      *
      * @param message the message to send
      * @param transactionCallback The callback to use to confirm the delivery of the message for
@@ -473,6 +473,12 @@
             @Nullable IContextHubTransactionCallback transactionCallback) {
         ContextHubServiceUtil.checkPermissions(mContext);
 
+        // Clear the isReliable and messageSequenceNumber fields.
+        // These will be set to true and a real value if the message
+        // is reliable.
+        message.setIsReliable(false);
+        message.setMessageSequenceNumber(0);
+
         @ContextHubTransaction.Result int result;
         if (isRegistered()) {
             int authState = mMessageChannelNanoappIdMap.getOrDefault(
@@ -485,7 +491,9 @@
                 // Return a bland error code for apps targeting old SDKs since they wouldn't be able
                 // to use an error code added in S.
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
-            } else if (authState == AUTHORIZATION_UNKNOWN) {
+            }
+
+            if (authState == AUTHORIZATION_UNKNOWN) {
                 // Only check permissions the first time a nanoapp is queried since nanoapp
                 // permissions don't currently change at runtime. If the host permission changes
                 // later, that'll be checked by onOpChanged.
diff --git a/services/core/java/com/android/server/location/contexthub/OWNERS b/services/core/java/com/android/server/location/contexthub/OWNERS
index 90c2330..c62e323 100644
--- a/services/core/java/com/android/server/location/contexthub/OWNERS
+++ b/services/core/java/com/android/server/location/contexthub/OWNERS
@@ -1,3 +1,3 @@
-arthuri@google.com
 bduddie@google.com
+matthewsedam@google.com
 stange@google.com
diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
index 2522f7b..470cc04 100644
--- a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
@@ -109,20 +109,15 @@
 
         synchronized (mLock) {
             mDeviceIdleHelper.addListener(this);
-            mDeviceIdle = mDeviceIdleHelper.isDeviceIdle();
-            mDeviceStationaryHelper.addListener(this);
-            mDeviceStationary = false;
-            mDeviceStationaryRealtimeMs = Long.MIN_VALUE;
-
-            onThrottlingChangedLocked(false);
+            onDeviceIdleChanged(mDeviceIdleHelper.isDeviceIdle());
         }
     }
 
     @Override
     protected void onStop() {
         synchronized (mLock) {
-            mDeviceStationaryHelper.removeListener(this);
             mDeviceIdleHelper.removeListener(this);
+            onDeviceIdleChanged(false);
 
             mIncomingRequest = ProviderRequest.EMPTY_REQUEST;
             mOutgoingRequest = ProviderRequest.EMPTY_REQUEST;
@@ -155,13 +150,26 @@
             }
 
             mDeviceIdle = deviceIdle;
-            onThrottlingChangedLocked(false);
+            if (deviceIdle) {
+                // device stationary helper will deliver an immediate listener update
+                mDeviceStationaryHelper.addListener(this);
+            } else {
+                mDeviceStationaryHelper.removeListener(this);
+                mDeviceStationary = false;
+                mDeviceStationaryRealtimeMs = Long.MIN_VALUE;
+                onThrottlingChangedLocked(false);
+            }
         }
     }
 
     @Override
     public void onDeviceStationaryChanged(boolean deviceStationary) {
         synchronized (mLock) {
+            if (!mDeviceIdle) {
+                // stationary detection is only registered while idle - ignore late notifications
+                return;
+            }
+
             if (mDeviceStationary == deviceStationary) {
                 return;
             }
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
index a597edd..8fdc22b 100644
--- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
@@ -59,7 +59,7 @@
     private static final String EXTRA_LOCATION_TAGS = "android:location_allow_listed_tags";
     private static final String LOCATION_TAGS_SEPARATOR = ";";
 
-    private static final long RESET_DELAY_MS = 1000;
+    private static final long RESET_DELAY_MS = 10000;
 
     /**
      * Creates and registers this proxy. If no suitable service is available for the proxy, returns
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 9a76ebd..c80f988 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -108,7 +108,6 @@
 import android.provider.Settings;
 import android.security.AndroidKeyStoreMaintenance;
 import android.security.Authorization;
-import android.security.KeyStore;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
 import android.security.keystore.recovery.KeyChainProtectionParams;
@@ -169,6 +168,7 @@
 import java.security.GeneralSecurityException;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
+import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
@@ -292,7 +292,7 @@
     private final IActivityManager mActivityManager;
     private final SyntheticPasswordManager mSpManager;
 
-    private final java.security.KeyStore mJavaKeyStore;
+    private final KeyStore mKeyStore;
     private final RecoverableKeyStoreManager mRecoverableKeyStoreManager;
     private final UnifiedProfilePasswordCache mUnifiedProfilePasswordCache;
 
@@ -564,10 +564,6 @@
             return DeviceStateCache.getInstance();
         }
 
-        public KeyStore getKeyStore() {
-            return KeyStore.getInstance();
-        }
-
         public RecoverableKeyStoreManager getRecoverableKeyStoreManager() {
             return RecoverableKeyStoreManager.getInstance(mContext);
         }
@@ -619,9 +615,9 @@
             return (BiometricManager) mContext.getSystemService(Context.BIOMETRIC_SERVICE);
         }
 
-        public java.security.KeyStore getJavaKeyStore() {
+        public KeyStore getKeyStore() {
             try {
-                java.security.KeyStore ks = java.security.KeyStore.getInstance(
+                KeyStore ks = KeyStore.getInstance(
                         SyntheticPasswordCrypto.androidKeystoreProviderName());
                 ks.load(new AndroidKeyStoreLoadStoreParameter(
                         SyntheticPasswordCrypto.keyNamespace()));
@@ -631,8 +627,7 @@
             }
         }
 
-        public @NonNull UnifiedProfilePasswordCache getUnifiedProfilePasswordCache(
-                java.security.KeyStore ks) {
+        public @NonNull UnifiedProfilePasswordCache getUnifiedProfilePasswordCache(KeyStore ks) {
             return new UnifiedProfilePasswordCache(ks);
         }
 
@@ -654,7 +649,7 @@
     protected LockSettingsService(Injector injector) {
         mInjector = injector;
         mContext = injector.getContext();
-        mJavaKeyStore = injector.getJavaKeyStore();
+        mKeyStore = injector.getKeyStore();
         mRecoverableKeyStoreManager = injector.getRecoverableKeyStoreManager();
         mHandler = injector.getHandler(injector.getServiceThread());
         mStrongAuth = injector.getStrongAuth();
@@ -676,7 +671,7 @@
         mGatekeeperPasswords = new LongSparseArray<>();
 
         mSpManager = injector.getSyntheticPasswordManager(mStorage);
-        mUnifiedProfilePasswordCache = injector.getUnifiedProfilePasswordCache(mJavaKeyStore);
+        mUnifiedProfilePasswordCache = injector.getUnifiedProfilePasswordCache(mKeyStore);
         mBiometricDeferredQueue = new BiometricDeferredQueue(mSpManager, mHandler);
 
         mRebootEscrowManager = injector.getRebootEscrowManager(new RebootEscrowCallbacks(),
@@ -819,7 +814,8 @@
         // storage is locked, instead of when the user is stopped.  This would ensure the flags get
         // reset if CE storage is locked later for a user that allows delayed locking.
         if (android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
+                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
             UserProperties userProperties = mUserManager.getUserProperties(UserHandle.of(userId));
             if (userProperties != null && userProperties.getAllowStoppingUserWithDelayedLocking()) {
                 return;
@@ -1482,7 +1478,7 @@
         byte[] encryptedPassword = Arrays.copyOfRange(storedData, PROFILE_KEY_IV_SIZE,
                 storedData.length);
         byte[] decryptionResult;
-        SecretKey decryptionKey = (SecretKey) mJavaKeyStore.getKey(
+        SecretKey decryptionKey = (SecretKey) mKeyStore.getKey(
                 PROFILE_KEY_NAME_DECRYPT + userId, null);
 
         Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
@@ -1875,9 +1871,10 @@
         }
     }
 
-    private void onPostPasswordChanged(LockscreenCredential newCredential, int userHandle) {
-        updatePasswordHistory(newCredential, userHandle);
-        mContext.getSystemService(TrustManager.class).reportEnabledTrustAgentsChanged(userHandle);
+    private void onPostPasswordChanged(LockscreenCredential newCredential, int userId) {
+        updatePasswordHistory(newCredential, userId);
+        mContext.getSystemService(TrustManager.class).reportEnabledTrustAgentsChanged(userId);
+        sendMainUserCredentialChangedNotificationIfNeeded(userId);
     }
 
     /**
@@ -2076,16 +2073,16 @@
             keyGenerator.init(new SecureRandom());
             SecretKey secretKey = keyGenerator.generateKey();
             try {
-                mJavaKeyStore.setEntry(
+                mKeyStore.setEntry(
                         PROFILE_KEY_NAME_ENCRYPT + profileUserId,
-                        new java.security.KeyStore.SecretKeyEntry(secretKey),
+                        new KeyStore.SecretKeyEntry(secretKey),
                         new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
                                 .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                                 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                                 .build());
-                mJavaKeyStore.setEntry(
+                mKeyStore.setEntry(
                         PROFILE_KEY_NAME_DECRYPT + profileUserId,
-                        new java.security.KeyStore.SecretKeyEntry(secretKey),
+                        new KeyStore.SecretKeyEntry(secretKey),
                         new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
                                 .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                                 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
@@ -2094,7 +2091,7 @@
                                 .setUserAuthenticationValidityDurationSeconds(30)
                                 .build());
                 // Key imported, obtain a reference to it.
-                SecretKey keyStoreEncryptionKey = (SecretKey) mJavaKeyStore.getKey(
+                SecretKey keyStoreEncryptionKey = (SecretKey) mKeyStore.getKey(
                         PROFILE_KEY_NAME_ENCRYPT + profileUserId, null);
                 Cipher cipher = Cipher.getInstance(
                         KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
@@ -2104,7 +2101,7 @@
                 iv = cipher.getIV();
             } finally {
                 // The original key can now be discarded.
-                mJavaKeyStore.deleteEntry(PROFILE_KEY_NAME_ENCRYPT + profileUserId);
+                mKeyStore.deleteEntry(PROFILE_KEY_NAME_ENCRYPT + profileUserId);
             }
         } catch (UnrecoverableKeyException
                 | BadPaddingException | IllegalBlockSizeException | KeyStoreException
@@ -2556,11 +2553,10 @@
         final String encryptAlias = PROFILE_KEY_NAME_ENCRYPT + targetUserId;
         final String decryptAlias = PROFILE_KEY_NAME_DECRYPT + targetUserId;
         try {
-            if (mJavaKeyStore.containsAlias(encryptAlias) ||
-                    mJavaKeyStore.containsAlias(decryptAlias)) {
+            if (mKeyStore.containsAlias(encryptAlias) || mKeyStore.containsAlias(decryptAlias)) {
                 Slogf.i(TAG, "Removing keystore profile key for user %d", targetUserId);
-                mJavaKeyStore.deleteEntry(encryptAlias);
-                mJavaKeyStore.deleteEntry(decryptAlias);
+                mKeyStore.deleteEntry(encryptAlias);
+                mKeyStore.deleteEntry(decryptAlias);
             }
         } catch (KeyStoreException e) {
             // We have tried our best to remove the key.
@@ -3062,7 +3058,6 @@
         setCurrentLskfBasedProtectorId(newProtectorId, userId);
         LockPatternUtils.invalidateCredentialTypeCache();
         synchronizeUnifiedChallengeForProfiles(userId, profilePasswords);
-        sendMainUserCredentialChangedNotificationIfNeeded(userId);
 
         setUserPasswordMetrics(credential, userId);
         mUnifiedProfilePasswordCache.removePassword(userId);
@@ -3457,7 +3452,7 @@
 
     private void dumpKeystoreKeys(IndentingPrintWriter pw) {
         try {
-            final Enumeration<String> aliases = mJavaKeyStore.aliases();
+            final Enumeration<String> aliases = mKeyStore.aliases();
             while (aliases.hasMoreElements()) {
                 pw.println(aliases.nextElement());
             }
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 1f7d549..2a48785 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -851,7 +851,6 @@
         }
     }
 
-    @RequiresPermission(value = Manifest.permission.MEDIA_ROUTING_CONTROL, conditional = true)
     private boolean checkMediaRoutingControlPermission(
             int callerUid, int callerPid, @Nullable String callerPackageName) {
         return PermissionChecker.checkPermissionForDataDelivery(
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index a110e56..1dc86f2 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -35,12 +35,11 @@
  * Keeps the record of {@link Session2Token} to help send command to the corresponding session.
  */
 // TODO(jaewan): Do not call service method directly -- introduce listener instead.
-public class MediaSession2Record implements MediaSessionRecordImpl {
+public class MediaSession2Record extends MediaSessionRecordImpl {
     private static final String TAG = "MediaSession2Record";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     private final Object mLock = new Object();
 
-    private final int mUniqueId;
     @GuardedBy("mLock")
     private final Session2Token mSessionToken;
     @GuardedBy("mLock")
@@ -57,20 +56,18 @@
     private boolean mIsClosed;
 
     private final int mPid;
-    private final ForegroundServiceDelegationOptions mForegroundServiceDelegationOptions;
 
     public MediaSession2Record(
             Session2Token sessionToken,
             MediaSessionService service,
             Looper handlerLooper,
             int pid,
-            int policies,
-            int uniqueId) {
+            int policies) {
         // The lock is required to prevent `Controller2Callback` from using partially initialized
         // `MediaSession2Record.this`.
         synchronized (mLock) {
+            mUniqueId = sNextMediaSessionRecordId.getAndIncrement();
             mSessionToken = sessionToken;
-            mUniqueId = uniqueId;
             mService = service;
             mHandlerExecutor = new HandlerExecutor(new Handler(handlerLooper));
             mController = new MediaController2.Builder(service.getContext(), sessionToken)
@@ -78,32 +75,6 @@
                     .build();
             mPid = pid;
             mPolicies = policies;
-            mForegroundServiceDelegationOptions =
-                    new ForegroundServiceDelegationOptions.Builder()
-                            .setClientPid(mPid)
-                            .setClientUid(getUid())
-                            .setClientPackageName(getPackageName())
-                            .setClientAppThread(null)
-                            .setSticky(false)
-                            .setClientInstanceName(
-                                    "MediaSessionFgsDelegate_"
-                                            + getUid()
-                                            + "_"
-                                            + mPid
-                                            + "_"
-                                            + getPackageName())
-                            .setForegroundServiceTypes(0)
-                            .setDelegationService(
-                                    ForegroundServiceDelegationOptions
-                                            .DELEGATION_SERVICE_MEDIA_PLAYBACK)
-                            .build();
-        }
-    }
-
-    @Override
-    public int getUniqueId() {
-        synchronized (mLock) {
-            return mUniqueId;
         }
     }
 
@@ -128,7 +99,10 @@
 
     @Override
     public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
-        return mForegroundServiceDelegationOptions;
+        // For an app to be eligible for FGS delegation, it needs a media session liked to a media
+        // notification. Currently, notifications cannot be linked to MediaSession2 so it is not
+        // supported.
+        return null;
     }
 
     @Override
@@ -210,7 +184,7 @@
 
     @Override
     public void dump(PrintWriter pw, String prefix) {
-        pw.println(prefix + "uniqueId=" + mUniqueId);
+        pw.println(prefix + "uniqueId=" + getUniqueId());
         pw.println(prefix + "token=" + mSessionToken);
         pw.println(prefix + "controller=" + mController);
 
@@ -220,7 +194,7 @@
 
     @Override
     public String toString() {
-        return getPackageName() + "/" + mUniqueId + " (userId=" + getUserId() + ")";
+        return getPackageName() + "/" + getUniqueId() + " (userId=" + getUserId() + ")";
     }
 
     private class Controller2Callback extends MediaController2.ControllerCallback {
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 1552704..a9a8272 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -96,7 +96,7 @@
  * MediaSession wrapper class instead.
  */
 // TODO(jaewan): Do not call service method directly -- introduce listener instead.
-public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionRecordImpl {
+public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinder.DeathRecipient {
 
     /**
      * {@link android.media.session.MediaSession#setMediaButtonBroadcastReceiver(
@@ -173,7 +173,6 @@
     private final int mUserId;
     private final String mPackageName;
     private final String mTag;
-    private final int mUniqueId;
     private final Bundle mSessionInfo;
     private final ControllerStub mController;
     private final MediaSession.Token mSessionToken;
@@ -231,18 +230,17 @@
             String ownerPackageName,
             ISessionCallback cb,
             String tag,
-            int uniqueId,
             Bundle sessionInfo,
             MediaSessionService service,
             Looper handlerLooper,
             int policies)
             throws RemoteException {
+        mUniqueId = sNextMediaSessionRecordId.getAndIncrement();
         mOwnerPid = ownerPid;
         mOwnerUid = ownerUid;
         mUserId = userId;
         mPackageName = ownerPackageName;
         mTag = tag;
-        mUniqueId = uniqueId;
         mSessionInfo = sessionInfo;
         mController = new ControllerStub();
         mSessionToken = new MediaSession.Token(ownerUid, mController);
@@ -303,16 +301,6 @@
     }
 
     /**
-     * Get the unique id of this session record.
-     *
-     * @return a unique id of this session record.
-     */
-    @Override
-    public int getUniqueId() {
-        return mUniqueId;
-    }
-
-    /**
      * Get the info for this session.
      *
      * @return Info that identifies this session.
@@ -724,7 +712,7 @@
 
     @Override
     public String toString() {
-        return mPackageName + "/" + mTag + "/" + mUniqueId + " (userId=" + mUserId + ")";
+        return mPackageName + "/" + mTag + "/" + getUniqueId() + " (userId=" + mUserId + ")";
     }
 
     @Override
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index e53a2db..e4b2fad 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -25,39 +25,37 @@
 import com.android.server.media.MediaSessionPolicyProvider.SessionPolicy;
 
 import java.io.PrintWriter;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Common interfaces between {@link MediaSessionRecord} and {@link MediaSession2Record}.
  */
-public interface MediaSessionRecordImpl extends AutoCloseable {
+public abstract class MediaSessionRecordImpl {
 
-    /**
-     * Get the unique id of this session record.
-     *
-     * @return a unique id of this session record.
-     */
-    int getUniqueId();
+    static final AtomicInteger sNextMediaSessionRecordId = new AtomicInteger(1);
+    int mUniqueId;
 
     /**
      * Get the info for this session.
      *
      * @return Info that identifies this session.
      */
-    String getPackageName();
+    public abstract String getPackageName();
 
     /**
      * Get the UID this session was created for.
      *
      * @return The UID for this session.
      */
-    int getUid();
+    public abstract int getUid();
 
     /**
      * Get the user id this session was created for.
      *
      * @return The user id for this session.
      */
-    int getUserId();
+    public abstract int getUserId();
 
     /**
      * Get the {@link ForegroundServiceDelegationOptions} needed for notifying activity manager
@@ -66,7 +64,7 @@
      * @return the {@link ForegroundServiceDelegationOptions} needed for notifying the activity
      *     manager service with changes in the {@link PlaybackState} for this session.
      */
-    ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions();
+    public abstract ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions();
 
     /**
      * Check if this session has system priority and should receive media buttons before any other
@@ -74,7 +72,7 @@
      *
      * @return True if this is a system priority session, false otherwise
      */
-    boolean isSystemPriority();
+    public abstract boolean isSystemPriority();
 
     /**
      * Send a volume adjustment to the session owner. Direction must be one of
@@ -95,7 +93,7 @@
      * @param useSuggested True to use adjustSuggestedStreamVolumeForUid instead of
      *          adjustStreamVolumeForUid
      */
-    void adjustVolume(String packageName, String opPackageName, int pid, int uid,
+    public abstract void adjustVolume(String packageName, String opPackageName, int pid, int uid,
             boolean asSystemService, int direction, int flags, boolean useSuggested);
 
     /**
@@ -105,7 +103,7 @@
      * @return True if the session is active, false otherwise.
      */
     // TODO(jaewan): Find better naming, or remove this from the MediaSessionRecordImpl.
-    boolean isActive();
+    public abstract boolean isActive();
 
     /**
      * Check if the session's playback active state matches with the expectation. This always
@@ -115,7 +113,7 @@
      * @param expected True if playback is expected to be active. False otherwise.
      * @return True if the session's playback matches with the expectation. False otherwise.
      */
-    boolean checkPlaybackActiveState(boolean expected);
+    public abstract boolean checkPlaybackActiveState(boolean expected);
 
     /**
      * Check whether the playback type is local or remote.
@@ -128,7 +126,7 @@
      *
      * @return {@code true} if the playback is local. {@code false} if the playback is remote.
      */
-    boolean isPlaybackTypeLocal();
+    public abstract boolean isPlaybackTypeLocal();
 
     /**
      * Sends media button.
@@ -145,27 +143,27 @@
      * @return {@code true} if the attempt to send media button was successfully.
      *         {@code false} otherwise.
      */
-    boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
-            KeyEvent ke, int sequenceId, ResultReceiver cb);
+    public abstract boolean sendMediaButton(String packageName, int pid, int uid,
+            boolean asSystemService, KeyEvent ke, int sequenceId, ResultReceiver cb);
 
     /**
      * Returns whether the media session can handle volume key events.
      *
      * @return True if this media session can handle volume key events, false otherwise.
      */
-    boolean canHandleVolumeKey();
+    public abstract boolean canHandleVolumeKey();
 
     /**
      * Get session policies from custom policy provider set when MediaSessionRecord is instantiated.
      * If custom policy does not exist, will return null.
      */
     @SessionPolicy
-    int getSessionPolicies();
+    public abstract int getSessionPolicies();
 
     /**
      * Overwrite session policies that have been set when MediaSessionRecord is instantiated.
      */
-    void setSessionPolicies(@SessionPolicy int policies);
+    public abstract void setSessionPolicies(@SessionPolicy int policies);
 
     /**
      * Dumps internal state
@@ -173,16 +171,37 @@
      * @param pw print writer
      * @param prefix prefix
      */
-    void dump(PrintWriter pw, String prefix);
+    public abstract void dump(PrintWriter pw, String prefix);
 
     /**
-     * Override {@link AutoCloseable#close} to tell it not to throw exception.
+     * Similar to {@link AutoCloseable#close} without throwing an exception.
      */
-    @Override
-    void close();
+    public abstract void close();
+
+    /**
+     * Get the unique id of this session record.
+     *
+     * @return a unique id of this session record.
+     */
+    public int getUniqueId() {
+        return mUniqueId;
+    }
 
     /**
      * Returns whether {@link #close()} is called before.
      */
-    boolean isClosed();
+    public abstract boolean isClosed();
+
+    @Override
+    public final boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || !(o instanceof MediaSessionRecordImpl)) return false;
+        MediaSessionRecordImpl that = (MediaSessionRecordImpl) o;
+        return mUniqueId == that.mUniqueId;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(mUniqueId);
+    }
 }
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 9e98a58..e2163c5 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -106,7 +106,6 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * System implementation of MediaSessionManager
@@ -128,6 +127,22 @@
      */
     private static final String MEDIA_BUTTON_RECEIVER = "media_button_receiver";
 
+    /**
+     * Action reported to UsageStatsManager when a media session becomes active and user engaged
+     * for a given app. App is expected to show an ongoing notification after this.
+     */
+    private static final String USAGE_STATS_ACTION_START = "start";
+
+    /**
+     * Action reported to UsageStatsManager when a media session is no longer active and user
+     * engaged for a given app. If media session only pauses for a brief time the event will not
+     * necessarily be reported in case user is still "engaging" and will restart it momentarily.
+     * In such case, action may be reported after a short delay to ensure user is truly no longer
+     * engaging. Afterwards, the app is no longer expected to show an ongoing notification.
+     */
+    private static final String USAGE_STATS_ACTION_STOP = "stop";
+    private static final String USAGE_STATS_CATEGORY = "android.media";
+
     private final Context mContext;
     private final SessionManagerImpl mSessionManagerImpl;
     private final MessageHandler mHandler = new MessageHandler();
@@ -154,9 +169,8 @@
     private UsageStatsManagerInternal mUsageStatsManagerInternal;
 
     /* Maps uid with all user engaging session tokens associated to it */
-    private final SparseArray<Set<MediaSession.Token>> mUserEngagingSessions = new SparseArray<>();
-
-    private final AtomicInteger mNextMediaSessionRecordId = new AtomicInteger(1);
+    private final SparseArray<Set<MediaSessionRecordImpl>> mUserEngagingSessions =
+            new SparseArray<>();
 
     // The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
     // It's always not null after the MediaSessionService is started.
@@ -196,8 +210,7 @@
                                     MediaSessionService.this,
                                     mRecordThread.getLooper(),
                                     pid,
-                                    /* policies= */ 0,
-                                    /* uniqueId= */ mNextMediaSessionRecordId.getAndIncrement());
+                                    /* policies= */ 0);
                     synchronized (mLock) {
                         FullUserRecord user = getFullUserRecordLocked(record.getUserId());
                         if (user != null) {
@@ -613,9 +626,7 @@
         }
         ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
                 record.getForegroundServiceDelegationOptions();
-        if (foregroundServiceDelegationOptions == null
-                || foregroundServiceDelegationOptions.mClientPid == Process.INVALID_PID) {
-            // This record doesn't support FGS delegation. In practice, this is MediaSession2.
+        if (foregroundServiceDelegationOptions == null) {
             return;
         }
         if (allowRunningInForeground) {
@@ -628,24 +639,24 @@
     }
 
     private void reportMediaInteractionEvent(MediaSessionRecordImpl record, boolean userEngaged) {
-        if (!android.app.usage.Flags.userInteractionTypeApi()
-                || !(record instanceof MediaSessionRecord)) {
+        if (!android.app.usage.Flags.userInteractionTypeApi()) {
             return;
         }
 
         String packageName = record.getPackageName();
         int sessionUid = record.getUid();
-        MediaSession.Token token = ((MediaSessionRecord) record).getSessionToken();
         if (userEngaged) {
             if (!mUserEngagingSessions.contains(sessionUid)) {
                 mUserEngagingSessions.put(sessionUid, new HashSet<>());
-                reportUserInteractionEvent(/* action= */ "start", record.getUserId(), packageName);
+                reportUserInteractionEvent(
+                    USAGE_STATS_ACTION_START, record.getUserId(), packageName);
             }
-            mUserEngagingSessions.get(sessionUid).add(token);
+            mUserEngagingSessions.get(sessionUid).add(record);
         } else if (mUserEngagingSessions.contains(sessionUid)) {
-            mUserEngagingSessions.get(sessionUid).remove(token);
+            mUserEngagingSessions.get(sessionUid).remove(record);
             if (mUserEngagingSessions.get(sessionUid).isEmpty()) {
-                reportUserInteractionEvent(/* action= */ "stop", record.getUserId(), packageName);
+                reportUserInteractionEvent(
+                    USAGE_STATS_ACTION_STOP, record.getUserId(), packageName);
                 mUserEngagingSessions.remove(sessionUid);
             }
         }
@@ -653,7 +664,7 @@
 
     private void reportUserInteractionEvent(String action, int userId, String packageName) {
         PersistableBundle extras = new PersistableBundle();
-        extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, "android.media");
+        extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, USAGE_STATS_CATEGORY);
         extras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, action);
         mUsageStatsManagerInternal.reportUserInteractionEvent(packageName, userId, extras);
     }
@@ -806,7 +817,6 @@
                                 callerPackageName,
                                 cb,
                                 tag,
-                                /* uniqueId= */ mNextMediaSessionRecordId.getAndIncrement(),
                                 sessionInfo,
                                 this,
                                 mRecordThread.getLooper(),
diff --git a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
index f60f55c..7acc3ef 100644
--- a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
+++ b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
@@ -374,6 +374,7 @@
                     event.getInputMediaItemInfos().isEmpty()
                             ? EMPTY_MEDIA_ITEM_INFO
                             : event.getInputMediaItemInfos().get(0);
+            @MediaItemInfo.DataType long inputDataTypes = inputMediaItemInfo.getDataTypes();
             String inputAudioSampleMimeType =
                     getFilteredFirstMimeType(
                             inputMediaItemInfo.getSampleMimeTypes(), AUDIO_MIME_TYPE_PREFIX);
@@ -396,6 +397,7 @@
                     event.getOutputMediaItemInfo() == null
                             ? EMPTY_MEDIA_ITEM_INFO
                             : event.getOutputMediaItemInfo();
+            @MediaItemInfo.DataType long outputDataTypes = outputMediaItemInfo.getDataTypes();
             String outputAudioSampleMimeType =
                     getFilteredFirstMimeType(
                             outputMediaItemInfo.getSampleMimeTypes(), AUDIO_MIME_TYPE_PREFIX);
@@ -415,6 +417,7 @@
                     !outputCodecNames.isEmpty() ? outputCodecNames.get(0) : "";
             String outputSecondCodecName =
                     outputCodecNames.size() > 1 ? outputCodecNames.get(1) : "";
+            @EditingEndedEvent.OperationType long operationTypes = event.getOperationTypes();
             StatsEvent statsEvent =
                     StatsEvent.newBuilder()
                             .setAtomId(798)
@@ -423,11 +426,63 @@
                             .writeFloat(event.getFinalProgressPercent())
                             .writeInt(event.getErrorCode())
                             .writeLong(event.getTimeSinceCreatedMillis())
+                            .writeBoolean(
+                                    (operationTypes
+                                                    & EditingEndedEvent
+                                                            .OPERATION_TYPE_VIDEO_TRANSCODE)
+                                            != 0)
+                            .writeBoolean(
+                                    (operationTypes
+                                                    & EditingEndedEvent
+                                                            .OPERATION_TYPE_AUDIO_TRANSCODE)
+                                            != 0)
+                            .writeBoolean(
+                                    (operationTypes & EditingEndedEvent.OPERATION_TYPE_VIDEO_EDIT)
+                                            != 0)
+                            .writeBoolean(
+                                    (operationTypes & EditingEndedEvent.OPERATION_TYPE_AUDIO_EDIT)
+                                            != 0)
+                            .writeBoolean(
+                                    (operationTypes
+                                                    & EditingEndedEvent
+                                                            .OPERATION_TYPE_VIDEO_TRANSMUX)
+                                            != 0)
+                            .writeBoolean(
+                                    (operationTypes
+                                                    & EditingEndedEvent
+                                                            .OPERATION_TYPE_AUDIO_TRANSMUX)
+                                            != 0)
+                            .writeBoolean(
+                                    (operationTypes & EditingEndedEvent.OPERATION_TYPE_PAUSED) != 0)
+                            .writeBoolean(
+                                    (operationTypes & EditingEndedEvent.OPERATION_TYPE_RESUMED)
+                                            != 0)
                             .writeString(getFilteredLibraryName(event.getExporterName()))
                             .writeString(getFilteredLibraryName(event.getMuxerName()))
                             .writeInt(getThroughputFps(event))
                             .writeInt(event.getInputMediaItemInfos().size())
                             .writeInt(inputMediaItemInfo.getSourceType())
+                            .writeBoolean((inputDataTypes & MediaItemInfo.DATA_TYPE_IMAGE) != 0)
+                            .writeBoolean((inputDataTypes & MediaItemInfo.DATA_TYPE_VIDEO) != 0)
+                            .writeBoolean((inputDataTypes & MediaItemInfo.DATA_TYPE_AUDIO) != 0)
+                            .writeBoolean((inputDataTypes & MediaItemInfo.DATA_TYPE_METADATA) != 0)
+                            .writeBoolean((inputDataTypes & MediaItemInfo.DATA_TYPE_DEPTH) != 0)
+                            .writeBoolean((inputDataTypes & MediaItemInfo.DATA_TYPE_GAIN_MAP) != 0)
+                            .writeBoolean(
+                                    (inputDataTypes & MediaItemInfo.DATA_TYPE_HIGH_FRAME_RATE) != 0)
+                            .writeBoolean(
+                                    (inputDataTypes
+                                                    & MediaItemInfo
+                                                            .DATA_TYPE_SPEED_SETTING_CUE_POINTS)
+                                            != 0)
+                            .writeBoolean((inputDataTypes & MediaItemInfo.DATA_TYPE_GAPLESS) != 0)
+                            .writeBoolean(
+                                    (inputDataTypes & MediaItemInfo.DATA_TYPE_SPATIAL_AUDIO) != 0)
+                            .writeBoolean(
+                                    (inputDataTypes
+                                                    & MediaItemInfo
+                                                            .DATA_TYPE_HIGH_DYNAMIC_RANGE_VIDEO)
+                                            != 0)
                             .writeLong(
                                     getBucketedDurationMillis(
                                             inputMediaItemInfo.getDurationMillis()))
@@ -443,6 +498,7 @@
                                     getFilteredAudioSampleRateHz(
                                             inputMediaItemInfo.getAudioSampleRateHz()))
                             .writeInt(inputMediaItemInfo.getAudioChannelCount())
+                            .writeLong(inputMediaItemInfo.getAudioSampleCount())
                             .writeInt(inputVideoSize.getWidth())
                             .writeInt(inputVideoSize.getHeight())
                             .writeInt(inputVideoResolution)
@@ -456,6 +512,28 @@
                             .writeInt(getVideoFrameRateEnum(inputMediaItemInfo.getVideoFrameRate()))
                             .writeString(inputFirstCodecName)
                             .writeString(inputSecondCodecName)
+                            .writeBoolean((outputDataTypes & MediaItemInfo.DATA_TYPE_IMAGE) != 0)
+                            .writeBoolean((outputDataTypes & MediaItemInfo.DATA_TYPE_VIDEO) != 0)
+                            .writeBoolean((outputDataTypes & MediaItemInfo.DATA_TYPE_AUDIO) != 0)
+                            .writeBoolean((outputDataTypes & MediaItemInfo.DATA_TYPE_METADATA) != 0)
+                            .writeBoolean((outputDataTypes & MediaItemInfo.DATA_TYPE_DEPTH) != 0)
+                            .writeBoolean((outputDataTypes & MediaItemInfo.DATA_TYPE_GAIN_MAP) != 0)
+                            .writeBoolean(
+                                    (outputDataTypes & MediaItemInfo.DATA_TYPE_HIGH_FRAME_RATE)
+                                            != 0)
+                            .writeBoolean(
+                                    (outputDataTypes
+                                                    & MediaItemInfo
+                                                            .DATA_TYPE_SPEED_SETTING_CUE_POINTS)
+                                            != 0)
+                            .writeBoolean((outputDataTypes & MediaItemInfo.DATA_TYPE_GAPLESS) != 0)
+                            .writeBoolean(
+                                    (outputDataTypes & MediaItemInfo.DATA_TYPE_SPATIAL_AUDIO) != 0)
+                            .writeBoolean(
+                                    (outputDataTypes
+                                                    & MediaItemInfo
+                                                            .DATA_TYPE_HIGH_DYNAMIC_RANGE_VIDEO)
+                                            != 0)
                             .writeLong(
                                     getBucketedDurationMillis(
                                             outputMediaItemInfo.getDurationMillis()))
@@ -471,6 +549,7 @@
                                     getFilteredAudioSampleRateHz(
                                             outputMediaItemInfo.getAudioSampleRateHz()))
                             .writeInt(outputMediaItemInfo.getAudioChannelCount())
+                            .writeLong(outputMediaItemInfo.getAudioSampleCount())
                             .writeInt(outputVideoSize.getWidth())
                             .writeInt(outputVideoSize.getHeight())
                             .writeInt(outputVideoResolution)
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 582058d..97ce77c 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -1255,7 +1255,7 @@
                 if (isUidStateChangeRelevant(callbackInfo, procState, procStateSeq, capability)) {
                     callbackInfo.update(uid, procState, procStateSeq, capability);
                     if (!callbackInfo.isPending) {
-                        mUidEventHandler.obtainMessage(UID_MSG_STATE_CHANGED, callbackInfo)
+                        mUidEventHandler.obtainMessage(UID_MSG_STATE_CHANGED, uid, 0)
                                 .sendToTarget();
                         callbackInfo.isPending = true;
                     }
@@ -1267,7 +1267,6 @@
             synchronized (mUidStateCallbackInfos) {
                 mUidStateCallbackInfos.remove(uid);
             }
-            // TODO: b/327058756 - Remove any pending UID_MSG_STATE_CHANGED on the handler.
             mUidEventHandler.obtainMessage(UID_MSG_GONE, uid, 0).sendToTarget();
         }
     };
@@ -5873,13 +5872,13 @@
     private final Handler.Callback mUidEventHandlerCallback = new Handler.Callback() {
         @Override
         public boolean handleMessage(Message msg) {
+            final int uid = msg.arg1;
             switch (msg.what) {
                 case UID_MSG_STATE_CHANGED: {
-                    handleUidChanged((UidStateCallbackInfo) msg.obj);
+                    handleUidChanged(uid);
                     return true;
                 }
                 case UID_MSG_GONE: {
-                    final int uid = msg.arg1;
                     handleUidGone(uid);
                     return true;
                 }
@@ -5890,23 +5889,27 @@
         }
     };
 
-    void handleUidChanged(@NonNull UidStateCallbackInfo uidStateCallbackInfo) {
+    void handleUidChanged(int uid) {
         Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "onUidStateChanged");
         try {
-            boolean updated;
-            final int uid;
             final int procState;
             final long procStateSeq;
             final int capability;
-            synchronized (mUidRulesFirstLock) {
-                synchronized (mUidStateCallbackInfos) {
-                    uid = uidStateCallbackInfo.uid;
-                    procState = uidStateCallbackInfo.procState;
-                    procStateSeq = uidStateCallbackInfo.procStateSeq;
-                    capability = uidStateCallbackInfo.capability;
-                    uidStateCallbackInfo.isPending = false;
+            synchronized (mUidStateCallbackInfos) {
+                final UidStateCallbackInfo uidStateCallbackInfo = mUidStateCallbackInfos.get(uid);
+                if (uidStateCallbackInfo == null) {
+                    // This can happen if UidObserver#onUidGone gets called before we reach
+                    // here. In this case, there is no point in processing this change as this
+                    // will immediately be followed by a call to handleUidGone anyway.
+                    return;
                 }
-
+                procState = uidStateCallbackInfo.procState;
+                procStateSeq = uidStateCallbackInfo.procStateSeq;
+                capability = uidStateCallbackInfo.capability;
+                uidStateCallbackInfo.isPending = false;
+            }
+            final boolean updated;
+            synchronized (mUidRulesFirstLock) {
                 // We received a uid state change callback, add it to the history so that it
                 // will be useful for debugging.
                 mLogger.uidStateChanged(uid, procState, procStateSeq, capability);
@@ -5929,6 +5932,14 @@
     void handleUidGone(int uid) {
         Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "onUidGone");
         try {
+            synchronized (mUidStateCallbackInfos) {
+                if (mUidStateCallbackInfos.contains(uid)) {
+                    // This can happen if UidObserver#onUidStateChanged gets called before we
+                    // reach here. In this case, there is no point in processing this change as this
+                    // will immediately be followed by a call to handleUidChanged anyway.
+                    return;
+                }
+            }
             final boolean updated;
             synchronized (mUidRulesFirstLock) {
                 updated = removeUidStateUL(uid);
diff --git a/services/core/java/com/android/server/net/TEST_MAPPING b/services/core/java/com/android/server/net/TEST_MAPPING
index e0376ed..4fc1a17 100644
--- a/services/core/java/com/android/server/net/TEST_MAPPING
+++ b/services/core/java/com/android/server/net/TEST_MAPPING
@@ -27,6 +27,15 @@
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
+    },
+    {
+      "name": "FrameworksVpnTests",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        }
+      ],
+      "file_patterns": ["VpnManagerService\\.java"]
     }
   ]
 }
diff --git a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
index ab650af..27b8574 100644
--- a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
+++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
@@ -65,7 +65,9 @@
         mColorDisplayManager = context.getSystemService(ColorDisplayManager.class);
         mPowerManager = context.getSystemService(PowerManager.class);
         mUiModeManager = context.getSystemService(UiModeManager.class);
-        mWallpaperManager = context.getSystemService(WallpaperManager.class);
+        WallpaperManager wallpaperManager = context.getSystemService(WallpaperManager.class);
+        mWallpaperManager = wallpaperManager != null && wallpaperManager.isWallpaperSupported()
+                ? wallpaperManager : null;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index f645eaa..3ecc58e 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -942,6 +942,23 @@
         return false;
     }
 
+    protected boolean isPackageOrComponentAllowedWithPermission(ComponentName component,
+            int userId) {
+        if (!(isPackageOrComponentAllowed(component.flattenToString(), userId)
+                || isPackageOrComponentAllowed(component.getPackageName(), userId))) {
+            return false;
+        }
+        return componentHasBindPermission(component, userId);
+    }
+
+    private boolean componentHasBindPermission(ComponentName component, int userId) {
+        ServiceInfo info = getServiceInfo(component, userId);
+        if (info == null) {
+            return false;
+        }
+        return mConfig.bindPermission.equals(info.permission);
+    }
+
     boolean isPackageOrComponentUserSet(String pkgOrComponent, int userId) {
         synchronized (mApproved) {
             ArraySet<String> services = mUserSetServices.get(userId);
@@ -1003,6 +1020,7 @@
                     for (int uid : uidList) {
                         if (isPackageAllowed(pkgName, UserHandle.getUserId(uid))) {
                             anyServicesInvolved = true;
+                            trimApprovedListsForInvalidServices(pkgName, UserHandle.getUserId(uid));
                         }
                     }
                 }
@@ -1135,8 +1153,7 @@
 
         synchronized (mMutex) {
             if (enabled) {
-                if (isPackageOrComponentAllowed(component.flattenToString(), userId)
-                        || isPackageOrComponentAllowed(component.getPackageName(), userId)) {
+                if (isPackageOrComponentAllowedWithPermission(component, userId)) {
                     registerServiceLocked(component, userId);
                 } else {
                     Slog.d(TAG, component + " no longer has permission to be bound");
@@ -1270,6 +1287,33 @@
         return removed;
     }
 
+    private void trimApprovedListsForInvalidServices(String packageName, int userId) {
+        synchronized (mApproved) {
+            final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.get(userId);
+            if (approvedByType == null) {
+                return;
+            }
+            for (int i = 0; i < approvedByType.size(); i++) {
+                final ArraySet<String> approved = approvedByType.valueAt(i);
+                for (int j = approved.size() - 1; j >= 0; j--) {
+                    final String approvedPackageOrComponent = approved.valueAt(j);
+                    if (TextUtils.equals(getPackageName(approvedPackageOrComponent), packageName)) {
+                        final ComponentName component = ComponentName.unflattenFromString(
+                                approvedPackageOrComponent);
+                        if (component != null && !componentHasBindPermission(component, userId)) {
+                            approved.removeAt(j);
+                            if (DEBUG) {
+                                Slog.v(TAG, "Removing " + approvedPackageOrComponent
+                                        + " from approved list; no bind permission found "
+                                        + mConfig.bindPermission);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     protected String getPackageName(String packageOrComponent) {
         final ComponentName component = ComponentName.unflattenFromString(packageOrComponent);
         if (component != null) {
@@ -1519,8 +1563,7 @@
     void reregisterService(final ComponentName cn, final int userId) {
         // If rebinding a package that died, ensure it still has permission
         // after the rebind delay
-        if (isPackageOrComponentAllowed(cn.getPackageName(), userId)
-                || isPackageOrComponentAllowed(cn.flattenToString(), userId)) {
+        if (isPackageOrComponentAllowedWithPermission(cn, userId)) {
             registerService(cn, userId);
         }
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 3a7ac0b..c38fbda 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1204,6 +1204,10 @@
         }
     }
 
+    private static boolean privateSpaceFlagsEnabled() {
+        return allowPrivateProfile() && android.multiuser.Flags.enablePrivateSpaceFeatures();
+    }
+
     private final class SavePolicyFileRunnable implements Runnable {
         @Override
         public void run() {
@@ -2142,7 +2146,7 @@
         }
 
         private boolean isProfileUnavailable(String action) {
-            return allowPrivateProfile() ?
+            return privateSpaceFlagsEnabled() ?
                     action.equals(Intent.ACTION_PROFILE_UNAVAILABLE) :
                     action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
         }
@@ -2744,7 +2748,7 @@
         filter.addAction(Intent.ACTION_USER_REMOVED);
         filter.addAction(Intent.ACTION_USER_UNLOCKED);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
-        if (allowPrivateProfile()){
+        if (privateSpaceFlagsEnabled()){
             filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE);
         }
         getContext().registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null);
@@ -8206,7 +8210,7 @@
                 try {
                     return mTelecomManager.isInManagedCall()
                             || mTelecomManager.isInSelfManagedCall(pkg,
-                            /* hasCrossUserAccess */ true);
+                            UserHandle.ALL);
                 } catch (IllegalStateException ise) {
                     // Telecom is not ready (this is likely early boot), so there are no calls.
                     return false;
@@ -12052,9 +12056,17 @@
             if (lifetimeExtensionRefactor()) {
                 // Generally, only System or System UI should have the permissions to call
                 // registerSystemService.
-                // isCallerSystemorPhone tells us whether the caller is System. Then, if it's not
-                // the system, we know it's system UI.
-                info.isSystemUi = !isCallerSystemOrPhone();
+                // isCallerSystemOrPhone tells us whether the caller is System. We negate this,
+                // to eliminate cases where the service was added by the system. This leaves
+                // services registered by system server.
+                // To identify system UI, we explicitly check the status bar permission for the
+                // uid in the info object.
+                // We can't use the calling uid here because it belongs to system server.
+                // Note that this will also return true for the shell, but we deem this
+                // acceptable, for the purposes of testing.
+                info.isSystemUi = !isCallerSystemOrPhone() && getContext().checkPermission(
+                        android.Manifest.permission.STATUS_BAR_SERVICE, -1, info.uid)
+                        == PERMISSION_GRANTED;
             }
             final INotificationListener listener = (INotificationListener) info.service;
             final NotificationRankingUpdate update;
diff --git a/services/core/java/com/android/server/notification/ZenAdapters.java b/services/core/java/com/android/server/notification/ZenAdapters.java
deleted file mode 100644
index 37b263c..0000000
--- a/services/core/java/com/android/server/notification/ZenAdapters.java
+++ /dev/null
@@ -1,83 +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.notification;
-
-import android.app.Flags;
-import android.app.NotificationManager.Policy;
-import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenPolicy;
-
-/**
- * Converters between different Zen representations.
- */
-class ZenAdapters {
-
-    static ZenPolicy notificationPolicyToZenPolicy(Policy policy) {
-        ZenPolicy.Builder zenPolicyBuilder = new ZenPolicy.Builder()
-                .allowAlarms(policy.allowAlarms())
-                .allowCalls(
-                        policy.allowCalls()
-                                ? ZenModeConfig.getZenPolicySenders(policy.allowCallsFrom())
-                                : ZenPolicy.PEOPLE_TYPE_NONE)
-                .allowConversations(
-                        policy.allowConversations()
-                                ? notificationPolicyConversationSendersToZenPolicy(
-                                        policy.allowConversationsFrom())
-                                : ZenPolicy.CONVERSATION_SENDERS_NONE)
-                .allowEvents(policy.allowEvents())
-                .allowMedia(policy.allowMedia())
-                .allowMessages(
-                        policy.allowMessages()
-                                ? ZenModeConfig.getZenPolicySenders(policy.allowMessagesFrom())
-                                : ZenPolicy.PEOPLE_TYPE_NONE)
-                .allowReminders(policy.allowReminders())
-                .allowRepeatCallers(policy.allowRepeatCallers())
-                .allowSystem(policy.allowSystem());
-
-        if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) {
-            zenPolicyBuilder.showBadges(policy.showBadges())
-                    .showFullScreenIntent(policy.showFullScreenIntents())
-                    .showInAmbientDisplay(policy.showAmbient())
-                    .showInNotificationList(policy.showInNotificationList())
-                    .showLights(policy.showLights())
-                    .showPeeking(policy.showPeeking())
-                    .showStatusBarIcons(policy.showStatusBarIcons());
-        }
-
-        if (Flags.modesApi()) {
-            zenPolicyBuilder.allowPriorityChannels(policy.allowPriorityChannels());
-        }
-
-        return zenPolicyBuilder.build();
-    }
-
-    @ZenPolicy.ConversationSenders
-    private static int notificationPolicyConversationSendersToZenPolicy(
-            int npPriorityConversationSenders) {
-        switch (npPriorityConversationSenders) {
-            case Policy.CONVERSATION_SENDERS_ANYONE:
-                return ZenPolicy.CONVERSATION_SENDERS_ANYONE;
-            case Policy.CONVERSATION_SENDERS_IMPORTANT:
-                return ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
-            case Policy.CONVERSATION_SENDERS_NONE:
-                return ZenPolicy.CONVERSATION_SENDERS_NONE;
-            case Policy.CONVERSATION_SENDERS_UNSET:
-            default:
-                return ZenPolicy.CONVERSATION_SENDERS_UNSET;
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
index b9a267f..8e37b4f 100644
--- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java
+++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
@@ -32,6 +32,7 @@
 import android.content.pm.PackageManager;
 import android.os.Process;
 import android.service.notification.DNDPolicyProto;
+import android.service.notification.ZenAdapters;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
 import android.service.notification.ZenModeConfig.ZenRule;
@@ -591,9 +592,11 @@
                 // This applies to both call and message senders, but not conversation senders,
                 // where they use the same enum values.
                 proto.write(DNDPolicyProto.ALLOW_CALLS_FROM,
-                        ZenModeConfig.getZenPolicySenders(mNewPolicy.allowCallsFrom()));
+                        ZenAdapters.notificationPolicySendersToZenPolicyPeopleType(
+                                mNewPolicy.allowCallsFrom()));
                 proto.write(DNDPolicyProto.ALLOW_MESSAGES_FROM,
-                        ZenModeConfig.getZenPolicySenders(mNewPolicy.allowMessagesFrom()));
+                        ZenAdapters.notificationPolicySendersToZenPolicyPeopleType(
+                                mNewPolicy.allowMessagesFrom()));
                 proto.write(DNDPolicyProto.ALLOW_CONVERSATIONS_FROM,
                         mNewPolicy.allowConversationsFrom());
 
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 6857869..289faf4 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -35,6 +35,8 @@
 import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
 import static com.android.internal.util.Preconditions.checkArgument;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -84,6 +86,7 @@
 import android.service.notification.Condition;
 import android.service.notification.ConditionProviderService;
 import android.service.notification.DeviceEffectsApplier;
+import android.service.notification.ZenAdapters;
 import android.service.notification.ZenDeviceEffects;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
@@ -535,36 +538,40 @@
     public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule,
             @ConfigChangeOrigin int origin, String reason, int callingUid) {
         requirePublicOrigin("updateAutomaticZenRule", origin);
-        ZenModeConfig newConfig;
+        if (ruleId == null) {
+            throw new IllegalArgumentException("ruleId cannot be null");
+        }
         synchronized (mConfigLock) {
             if (mConfig == null) return false;
             if (DEBUG) {
                 Log.d(TAG, "updateAutomaticZenRule zenRule=" + automaticZenRule
                         + " reason=" + reason);
             }
-            newConfig = mConfig.copy();
-            ZenModeConfig.ZenRule rule;
-            if (ruleId == null) {
-                throw new IllegalArgumentException("Rule doesn't exist");
-            } else {
-                rule = newConfig.automaticRules.get(ruleId);
-                if (rule == null || !canManageAutomaticZenRule(rule)) {
-                    throw new SecurityException(
-                            "Cannot update rules not owned by your condition provider");
-                }
+            ZenModeConfig.ZenRule oldRule = mConfig.automaticRules.get(ruleId);
+            if (oldRule == null || !canManageAutomaticZenRule(oldRule)) {
+                throw new SecurityException(
+                        "Cannot update rules not owned by your condition provider");
             }
+            ZenModeConfig newConfig = mConfig.copy();
+            ZenModeConfig.ZenRule newRule = requireNonNull(newConfig.automaticRules.get(ruleId));
             if (!Flags.modesApi()) {
-                if (rule.enabled != automaticZenRule.isEnabled()) {
-                    dispatchOnAutomaticRuleStatusChanged(mConfig.user, rule.getPkg(), ruleId,
+                if (newRule.enabled != automaticZenRule.isEnabled()) {
+                    dispatchOnAutomaticRuleStatusChanged(mConfig.user, newRule.getPkg(), ruleId,
                             automaticZenRule.isEnabled()
                                     ? AUTOMATIC_RULE_STATUS_ENABLED
                                     : AUTOMATIC_RULE_STATUS_DISABLED);
                 }
             }
 
-            populateZenRule(rule.pkg, automaticZenRule, rule, origin, /* isNew= */ false);
+            boolean updated = populateZenRule(newRule.pkg, automaticZenRule, newRule,
+                    origin, /* isNew= */ false);
+            if (Flags.modesApi() && !updated) {
+                // Bail out so we don't have the side effects of updating a rule (i.e. dropping
+                // condition) when no changes happen.
+                return true;
+            }
             return setConfigLocked(newConfig, origin, reason,
-                    rule.component, true, callingUid);
+                    newRule.component, true, callingUid);
         }
     }
 
@@ -1072,31 +1079,67 @@
         return null;
     }
 
-    private void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule,
+    /**
+     * Populates a {@code ZenRule} with the content of the {@link AutomaticZenRule}. Can be used for
+     * both rule creation or update (distinguished by the {@code isNew} parameter. The change is
+     * applied differently depending on the origin; for example app-provided changes might be
+     * ignored (if the rule was previously customized by the user), while user-provided changes
+     * update the user-modified bitmasks for any modifications.
+     *
+     * <p>Returns {@code true} if the rule was modified. Note that this is not equivalent to
+     * {@link ZenRule#equals} or {@link AutomaticZenRule#equals}, for various reasons:
+     * <ul>
+     *     <li>some metadata-related fields are not considered
+     *     <li>some fields (like {@code condition} are always reset, and ignored for this result
+     *     <li>an app may provide changes that are not actually applied, as described above
+     * </ul>
+     */
+    private boolean populateZenRule(String pkg, AutomaticZenRule azr, ZenRule rule,
                          @ConfigChangeOrigin int origin, boolean isNew) {
         if (Flags.modesApi()) {
+            boolean modified = false;
             // These values can always be edited by the app, so we apply changes immediately.
             if (isNew) {
                 rule.id = ZenModeConfig.newRuleId();
                 rule.creationTime = mClock.millis();
-                rule.component = automaticZenRule.getOwner();
+                rule.component = azr.getOwner();
                 rule.pkg = pkg;
+                modified = true;
             }
 
             rule.condition = null;
-            rule.conditionId = automaticZenRule.getConditionId();
-            if (rule.enabled != automaticZenRule.isEnabled()) {
-                rule.snoozing = false;
+            if (!Objects.equals(rule.conditionId, azr.getConditionId())) {
+                rule.conditionId = azr.getConditionId();
+                modified = true;
             }
-            rule.enabled = automaticZenRule.isEnabled();
-            rule.configurationActivity = automaticZenRule.getConfigurationActivity();
-            rule.allowManualInvocation = automaticZenRule.isManualInvocationAllowed();
-            rule.iconResName =
-                    drawableResIdToResName(rule.pkg, automaticZenRule.getIconResId());
-            rule.triggerDescription = automaticZenRule.getTriggerDescription();
-            rule.type = automaticZenRule.getType();
+            if (rule.enabled != azr.isEnabled()) {
+                rule.enabled = azr.isEnabled();
+                rule.snoozing = false;
+                modified = true;
+            }
+            if (!Objects.equals(rule.configurationActivity, azr.getConfigurationActivity())) {
+                rule.configurationActivity = azr.getConfigurationActivity();
+                modified = true;
+            }
+            if (rule.allowManualInvocation != azr.isManualInvocationAllowed()) {
+                rule.allowManualInvocation = azr.isManualInvocationAllowed();
+                modified = true;
+            }
+            String iconResName = drawableResIdToResName(rule.pkg, azr.getIconResId());
+            if (!Objects.equals(rule.iconResName, iconResName)) {
+                rule.iconResName = iconResName;
+                modified = true;
+            }
+            if (!Objects.equals(rule.triggerDescription, azr.getTriggerDescription())) {
+                rule.triggerDescription = azr.getTriggerDescription();
+                modified = true;
+            }
+            if (rule.type != azr.getType()) {
+                rule.type = azr.getType();
+                modified = true;
+            }
             // TODO: b/310620812 - Remove this once FLAG_MODES_API is inlined.
-            rule.modified = automaticZenRule.isModified();
+            rule.modified = azr.isModified();
 
             // Name is treated differently than other values:
             // App is allowed to update name if the name was not modified by the user (even if
@@ -1106,7 +1149,8 @@
             String previousName = rule.name;
             if (isNew || doesOriginAlwaysUpdateValues(origin)
                     || (rule.userModifiedFields & AutomaticZenRule.FIELD_NAME) == 0) {
-                rule.name = automaticZenRule.getName();
+                rule.name = azr.getName();
+                modified |= !Objects.equals(rule.name, previousName);
             }
 
             // For the remaining values, rules can always have all values updated if:
@@ -1118,50 +1162,56 @@
 
             // For all other values, if updates are not allowed, we discard the update.
             if (!updateValues) {
-                return;
+                return modified;
             }
 
             // Updates the bitmasks if the origin of the change is the user.
             boolean updateBitmask = (origin == UPDATE_ORIGIN_USER);
 
-            if (updateBitmask && !TextUtils.equals(previousName, automaticZenRule.getName())) {
+            if (updateBitmask && !TextUtils.equals(previousName, azr.getName())) {
                 rule.userModifiedFields |= AutomaticZenRule.FIELD_NAME;
             }
             int newZenMode = NotificationManager.zenModeFromInterruptionFilter(
-                    automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF);
-            if (updateBitmask && rule.zenMode != newZenMode) {
-                rule.userModifiedFields |= AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
+                    azr.getInterruptionFilter(), Global.ZEN_MODE_OFF);
+            if (rule.zenMode != newZenMode) {
+                rule.zenMode = newZenMode;
+                if (updateBitmask) {
+                    rule.userModifiedFields |= AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
+                }
+                modified = true;
             }
 
-            // Updates the values in the ZenRule itself.
-            rule.zenMode = newZenMode;
-
             // Updates the bitmask and values for all policy fields, based on the origin.
-            updatePolicy(rule, automaticZenRule.getZenPolicy(), updateBitmask, isNew);
+            modified |= updatePolicy(rule, azr.getZenPolicy(), updateBitmask, isNew);
 
             // Updates the bitmask and values for all device effect fields, based on the origin.
-            updateZenDeviceEffects(rule, automaticZenRule.getDeviceEffects(),
+            modified |= updateZenDeviceEffects(rule, azr.getDeviceEffects(),
                     origin == UPDATE_ORIGIN_APP, updateBitmask);
+
+            return modified;
         } else {
-            if (rule.enabled != automaticZenRule.isEnabled()) {
+            if (rule.enabled != azr.isEnabled()) {
                 rule.snoozing = false;
             }
-            rule.name = automaticZenRule.getName();
+            rule.name = azr.getName();
             rule.condition = null;
-            rule.conditionId = automaticZenRule.getConditionId();
-            rule.enabled = automaticZenRule.isEnabled();
-            rule.modified = automaticZenRule.isModified();
-            rule.zenPolicy = automaticZenRule.getZenPolicy();
+            rule.conditionId = azr.getConditionId();
+            rule.enabled = azr.isEnabled();
+            rule.modified = azr.isModified();
+            rule.zenPolicy = azr.getZenPolicy();
             rule.zenMode = NotificationManager.zenModeFromInterruptionFilter(
-                    automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF);
-            rule.configurationActivity = automaticZenRule.getConfigurationActivity();
+                    azr.getInterruptionFilter(), Global.ZEN_MODE_OFF);
+            rule.configurationActivity = azr.getConfigurationActivity();
 
             if (isNew) {
                 rule.id = ZenModeConfig.newRuleId();
                 rule.creationTime = System.currentTimeMillis();
-                rule.component = automaticZenRule.getOwner();
+                rule.component = azr.getOwner();
                 rule.pkg = pkg;
             }
+
+            // Only the MODES_API path cares about the result, so just return whatever here.
+            return true;
         }
     }
 
@@ -1181,16 +1231,19 @@
      * provided {@code ZenRule}, keeping any pre-existing settings from {@code zenRule.zenPolicy}
      * for any unset policy fields in {@code newPolicy}. The user-modified bitmask is updated to
      * reflect the changes being applied (if applicable, i.e. if the update is from the user).
+     *
+     * <p>Returns {@code true} if the policy of the rule was modified.
      */
-    private void updatePolicy(ZenRule zenRule, @Nullable ZenPolicy newPolicy,
+    private boolean updatePolicy(ZenRule zenRule, @Nullable ZenPolicy newPolicy,
             boolean updateBitmask, boolean isNew) {
         if (newPolicy == null) {
             if (isNew) {
                 // Newly created rule with no provided policy; fill in with the default.
                 zenRule.zenPolicy = mDefaultConfig.toZenPolicy();
+                return true;
             }
             // Otherwise, a null policy means no policy changes, so we can stop here.
-            return;
+            return false;
         }
 
         // If oldPolicy is null, we compare against the default policy when determining which
@@ -1271,6 +1324,8 @@
             }
             zenRule.zenPolicyUserModifiedFields = userModifiedFields;
         }
+
+        return !newPolicy.equals(oldPolicy);
     }
 
     /**
@@ -1282,12 +1337,14 @@
      * <p>Apps cannot turn on hidden effects (those tagged as {@code @hide}), so those fields are
      * treated especially: for a new rule, they are blanked out; for an updated rule, previous
      * values are preserved.
+     *
+     * <p>Returns {@code true} if the device effects of the rule were modified.
      */
-    private static void updateZenDeviceEffects(ZenRule zenRule,
+    private static boolean updateZenDeviceEffects(ZenRule zenRule,
             @Nullable ZenDeviceEffects newEffects, boolean isFromApp, boolean updateBitmask) {
         // Same as with ZenPolicy, supplying null effects means keeping the previous ones.
         if (newEffects == null) {
-            return;
+            return false;
         }
 
         ZenDeviceEffects oldEffects = zenRule.zenDeviceEffects != null
@@ -1348,6 +1405,8 @@
             }
             zenRule.zenDeviceEffectsUserModifiedFields = userModifiedFields;
         }
+
+        return !newEffects.equals(oldEffects);
     }
 
     private AutomaticZenRule zenRuleToAutomaticZenRule(ZenRule rule) {
@@ -2504,7 +2563,7 @@
         if (resId == 0) {
             return null;
         }
-        Objects.requireNonNull(packageName);
+        requireNonNull(packageName);
         try {
             final Resources res = mPm.getResourcesForApplication(packageName);
             return res.getResourceName(resId);
diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java
index 15cfca5..1b22154 100644
--- a/services/core/java/com/android/server/om/IdmapDaemon.java
+++ b/services/core/java/com/android/server/om/IdmapDaemon.java
@@ -51,8 +51,15 @@
     // before stopping the service.
     private static final int SERVICE_TIMEOUT_MS = 10000;
 
-    // The amount of time in milliseconds to wait when attempting to connect to idmap service.
-    private static final int SERVICE_CONNECT_TIMEOUT_MS = 5000;
+    // The device may enter CPU sleep while waiting for the service startup, and in that mode
+    // the uptime doesn't increment. Thus, we need to have two timeouts: a smaller one for the
+    // uptime and a longer one for the wall time in case when the device never advances the uptime,
+    // so the watchdog won't get triggered.
+
+    // The amount of uptime in milliseconds to wait when attempting to connect to idmap service.
+    private static final int SERVICE_CONNECT_UPTIME_TIMEOUT_MS = 5000;
+    // The amount of wall time in milliseconds to wait.
+    private static final int SERVICE_CONNECT_WALLTIME_TIMEOUT_MS = 30000;
     private static final int SERVICE_CONNECT_INTERVAL_SLEEP_MS = 5;
 
     private static final String IDMAP_DAEMON = "idmap2d";
@@ -274,20 +281,29 @@
             }
         }
 
-        final long endMillis = SystemClock.uptimeMillis() + SERVICE_CONNECT_TIMEOUT_MS;
+        long uptimeMillis = SystemClock.uptimeMillis();
+        final long endUptimeMillis = uptimeMillis + SERVICE_CONNECT_UPTIME_TIMEOUT_MS;
+        long walltimeMillis = SystemClock.elapsedRealtime();
+        final long endWalltimeMillis = walltimeMillis + SERVICE_CONNECT_WALLTIME_TIMEOUT_MS;
+
         do {
             final IBinder binder = ServiceManager.getService(IDMAP_SERVICE);
             if (binder != null) {
                 binder.linkToDeath(
-                        () -> Slog.w(TAG, String.format("service '%s' died", IDMAP_SERVICE)), 0);
+                        () -> Slog.w(TAG,
+                                TextUtils.formatSimple("service '%s' died", IDMAP_SERVICE)), 0);
                 return binder;
             }
             SystemClock.sleep(SERVICE_CONNECT_INTERVAL_SLEEP_MS);
-        } while (SystemClock.uptimeMillis() <= endMillis);
+        } while ((uptimeMillis = SystemClock.uptimeMillis()) <= endUptimeMillis
+                && (walltimeMillis = SystemClock.elapsedRealtime()) <= endWalltimeMillis);
 
         throw new TimeoutException(
-            String.format("Failed to connect to '%s' in %d milliseconds", IDMAP_SERVICE,
-                    SERVICE_CONNECT_TIMEOUT_MS));
+                TextUtils.formatSimple("Failed to connect to '%s' in %d/%d ms (spent %d/%d ms)",
+                        IDMAP_SERVICE, SERVICE_CONNECT_UPTIME_TIMEOUT_MS,
+                        SERVICE_CONNECT_WALLTIME_TIMEOUT_MS,
+                        uptimeMillis - endUptimeMillis + SERVICE_CONNECT_UPTIME_TIMEOUT_MS,
+                        walltimeMillis - endWalltimeMillis + SERVICE_CONNECT_WALLTIME_TIMEOUT_MS));
     }
 
     private static void stopIdmapService() {
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index a4c4347..71800ef 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.ondeviceintelligence;
 
+import static android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException.PROCESSING_UPDATE_STATUS_CONNECTION_FAILED;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.app.AppGlobals;
@@ -30,12 +32,14 @@
 import android.app.ondeviceintelligence.IProcessingSignal;
 import android.app.ondeviceintelligence.IResponseCallback;
 import android.app.ondeviceintelligence.IStreamingResponseCallback;
-import android.app.ondeviceintelligence.ITokenCountCallback;
+import android.app.ondeviceintelligence.ITokenInfoCallback;
 import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
+import android.os.Binder;
+import android.os.Bundle;
 import android.os.ICancellationSignal;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
@@ -43,8 +47,13 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
-import android.service.ondeviceintelligence.IOnDeviceTrustedInferenceService;
+import android.service.ondeviceintelligence.IOnDeviceIntelligenceService;
+import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
+import android.service.ondeviceintelligence.IRemoteProcessingService;
 import android.service.ondeviceintelligence.IRemoteStorageService;
+import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
+import android.service.ondeviceintelligence.OnDeviceIntelligenceService;
+import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
 import android.text.TextUtils;
 import android.util.Slog;
 
@@ -59,10 +68,11 @@
 import java.util.Set;
 
 /**
- * This is the system service for handling calls on the {@link OnDeviceIntelligenceManager}. This
+ * This is the system service for handling calls on the
+ * {@link android.app.ondeviceintelligence.OnDeviceIntelligenceManager}. This
  * service holds connection references to the underlying remote services i.e. the isolated service
- * {@link android.service.ondeviceintelligence.OnDeviceTrustedInferenceService} and a regular
- * service counter part {@link android.service.ondeviceintelligence.OnDeviceIntelligenceService}.
+ * {@link OnDeviceSandboxedInferenceService} and a regular
+ * service counter part {@link OnDeviceIntelligenceService}.
  *
  * Note: Both the remote services run under the SYSTEM user, as we cannot have separate instance of
  * the Inference service for each user, due to possible high memory footprint.
@@ -82,7 +92,7 @@
     protected final Object mLock = new Object();
 
 
-    private RemoteOnDeviceTrustedInferenceService mRemoteInferenceService;
+    private RemoteOnDeviceSandboxedInferenceService mRemoteInferenceService;
     private RemoteOnDeviceIntelligenceService mRemoteOnDeviceIntelligenceService;
     volatile boolean mIsServiceEnabled;
 
@@ -157,7 +167,7 @@
             }
             ensureRemoteIntelligenceServiceInitialized();
             mRemoteOnDeviceIntelligenceService.post(
-                    service -> service.getFeature(id, featureCallback));
+                    service -> service.getFeature(Binder.getCallingUid(), id, featureCallback));
         }
 
         @Override
@@ -177,7 +187,7 @@
             }
             ensureRemoteIntelligenceServiceInitialized();
             mRemoteOnDeviceIntelligenceService.post(
-                    service -> service.listFeatures(listFeaturesCallback));
+                    service -> service.listFeatures(Binder.getCallingUid(), listFeaturesCallback));
         }
 
         @Override
@@ -199,7 +209,8 @@
             }
             ensureRemoteIntelligenceServiceInitialized();
             mRemoteOnDeviceIntelligenceService.post(
-                    service -> service.getFeatureDetails(feature, featureDetailsCallback));
+                    service -> service.getFeatureDetails(Binder.getCallingUid(), feature,
+                            featureDetailsCallback));
         }
 
         @Override
@@ -219,33 +230,35 @@
             }
             ensureRemoteIntelligenceServiceInitialized();
             mRemoteOnDeviceIntelligenceService.post(
-                    service -> service.requestFeatureDownload(feature, cancellationSignal,
+                    service -> service.requestFeatureDownload(Binder.getCallingUid(), feature,
+                            cancellationSignal,
                             downloadCallback));
         }
 
 
         @Override
-        public void requestTokenCount(Feature feature,
+        public void requestTokenInfo(Feature feature,
                 Content request, ICancellationSignal cancellationSignal,
-                ITokenCountCallback tokenCountcallback) throws RemoteException {
+                ITokenInfoCallback tokenInfoCallback) throws RemoteException {
             Slog.i(TAG, "OnDeviceIntelligenceManagerInternal prepareFeatureProcessing");
             Objects.requireNonNull(feature);
             Objects.requireNonNull(request);
-            Objects.requireNonNull(tokenCountcallback);
+            Objects.requireNonNull(tokenInfoCallback);
 
             mContext.enforceCallingOrSelfPermission(
                     Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
             if (!mIsServiceEnabled) {
                 Slog.w(TAG, "Service not available");
-                tokenCountcallback.onFailure(
+                tokenInfoCallback.onFailure(
                         OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
                         "OnDeviceIntelligenceManagerService is unavailable",
                         new PersistableBundle());
             }
-            ensureRemoteTrustedInferenceServiceInitialized();
+            ensureRemoteInferenceServiceInitialized();
             mRemoteInferenceService.post(
-                    service -> service.requestTokenCount(feature, request, cancellationSignal,
-                            tokenCountcallback));
+                    service -> service.requestTokenInfo(Binder.getCallingUid(), feature, request,
+                            cancellationSignal,
+                            tokenInfoCallback));
         }
 
         @Override
@@ -259,7 +272,6 @@
             Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequest");
             Objects.requireNonNull(feature);
             Objects.requireNonNull(responseCallback);
-            Objects.requireNonNull(request);
             mContext.enforceCallingOrSelfPermission(
                     Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
             if (!mIsServiceEnabled) {
@@ -269,9 +281,10 @@
                         "OnDeviceIntelligenceManagerService is unavailable",
                         new PersistableBundle());
             }
-            ensureRemoteTrustedInferenceServiceInitialized();
+            ensureRemoteInferenceServiceInitialized();
             mRemoteInferenceService.post(
-                    service -> service.processRequest(feature, request, requestType,
+                    service -> service.processRequest(Binder.getCallingUid(), feature, request,
+                            requestType,
                             cancellationSignal, processingSignal,
                             responseCallback));
         }
@@ -285,7 +298,6 @@
                 IStreamingResponseCallback streamingCallback) throws RemoteException {
             Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequestStreaming");
             Objects.requireNonNull(feature);
-            Objects.requireNonNull(request);
             Objects.requireNonNull(streamingCallback);
             mContext.enforceCallingOrSelfPermission(
                     Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
@@ -296,9 +308,10 @@
                         "OnDeviceIntelligenceManagerService is unavailable",
                         new PersistableBundle());
             }
-            ensureRemoteTrustedInferenceServiceInitialized();
+            ensureRemoteInferenceServiceInitialized();
             mRemoteInferenceService.post(
-                    service -> service.processRequestStreaming(feature, request, requestType,
+                    service -> service.processRequestStreaming(Binder.getCallingUid(), feature,
+                            request, requestType,
                             cancellationSignal, processingSignal,
                             streamingCallback));
         }
@@ -313,25 +326,66 @@
                 mRemoteOnDeviceIntelligenceService = new RemoteOnDeviceIntelligenceService(mContext,
                         ComponentName.unflattenFromString(serviceName),
                         UserHandle.SYSTEM.getIdentifier());
+                mRemoteOnDeviceIntelligenceService.setServiceLifecycleCallbacks(
+                        new ServiceConnector.ServiceLifecycleCallbacks<>() {
+                            @Override
+                            public void onConnected(
+                                    @NonNull IOnDeviceIntelligenceService service) {
+                                try {
+                                    service.registerRemoteServices(
+                                            getRemoteProcessingService());
+                                } catch (RemoteException ex) {
+                                    Slog.w(TAG, "Failed to send connected event", ex);
+                                }
+                            }
+                        });
             }
         }
     }
 
-    private void ensureRemoteTrustedInferenceServiceInitialized() throws RemoteException {
+    @NonNull
+    private IRemoteProcessingService.Stub getRemoteProcessingService() {
+        return new IRemoteProcessingService.Stub() {
+            @Override
+            public void updateProcessingState(
+                    Bundle processingState,
+                    IProcessingUpdateStatusCallback callback) {
+                try {
+                    ensureRemoteInferenceServiceInitialized();
+                    mRemoteInferenceService.post(
+                            service -> service.updateProcessingState(
+                                    processingState, callback));
+                } catch (RemoteException unused) {
+                    try {
+                        callback.onFailure(
+                                PROCESSING_UPDATE_STATUS_CONNECTION_FAILED,
+                                "Received failure invoking the remote processing service.");
+                    } catch (RemoteException ex) {
+                        Slog.w(TAG, "Failed to send failure status.", ex);
+                    }
+                }
+            }
+        };
+    }
+
+    private void ensureRemoteInferenceServiceInitialized() throws RemoteException {
         synchronized (mLock) {
             if (mRemoteInferenceService == null) {
                 String serviceName = mContext.getResources().getString(
-                        R.string.config_defaultOnDeviceTrustedInferenceService);
+                        R.string.config_defaultOnDeviceSandboxedInferenceService);
                 validateService(serviceName, true);
-                mRemoteInferenceService = new RemoteOnDeviceTrustedInferenceService(mContext,
+                mRemoteInferenceService = new RemoteOnDeviceSandboxedInferenceService(mContext,
                         ComponentName.unflattenFromString(serviceName),
                         UserHandle.SYSTEM.getIdentifier());
                 mRemoteInferenceService.setServiceLifecycleCallbacks(
                         new ServiceConnector.ServiceLifecycleCallbacks<>() {
                             @Override
                             public void onConnected(
-                                    @NonNull IOnDeviceTrustedInferenceService service) {
+                                    @NonNull IOnDeviceSandboxedInferenceService service) {
                                 try {
+                                    ensureRemoteIntelligenceServiceInitialized();
+                                    mRemoteOnDeviceIntelligenceService.post(
+                                            intelligenceService -> intelligenceService.notifyInferenceServiceConnected());
                                     service.registerRemoteStorageService(
                                             getIRemoteStorageService());
                                 } catch (RemoteException ex) {
@@ -358,8 +412,7 @@
             @Override
             public void getReadOnlyFeatureFileDescriptorMap(
                     Feature feature,
-                    RemoteCallback remoteCallback)
-                    throws RemoteException {
+                    RemoteCallback remoteCallback) {
                 mRemoteOnDeviceIntelligenceService.post(
                         service -> service.getReadOnlyFeatureFileDescriptorMap(
                                 feature, remoteCallback));
@@ -387,7 +440,7 @@
             }
 
             checkServiceRequiresPermission(serviceInfo,
-                    Manifest.permission.BIND_ON_DEVICE_TRUSTED_SERVICE);
+                    Manifest.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE);
             if (!isIsolatedService(serviceInfo)) {
                 throw new SecurityException(
                         "Call required an isolated service, but the configured service: "
diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
similarity index 73%
rename from services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java
rename to services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
index cc8e788..69ba1d2 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
@@ -22,18 +22,18 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.service.ondeviceintelligence.IOnDeviceTrustedInferenceService;
-import android.service.ondeviceintelligence.OnDeviceTrustedInferenceService;
+import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
+import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
 
 import com.android.internal.infra.ServiceConnector;
 
 
 /**
- * Manages the connection to the remote on-device trusted inference service. Also, handles unbinding
+ * Manages the connection to the remote on-device sand boxed inference service. Also, handles unbinding
  * logic set by the service implementation via a SecureSettings flag.
  */
-public class RemoteOnDeviceTrustedInferenceService extends
-        ServiceConnector.Impl<IOnDeviceTrustedInferenceService> {
+public class RemoteOnDeviceSandboxedInferenceService extends
+        ServiceConnector.Impl<IOnDeviceSandboxedInferenceService> {
     /**
      * Creates an instance of {@link ServiceConnector}
      *
@@ -43,12 +43,12 @@
      *                {@link Context#unbindService unbinding}
      * @param userId  to be used for {@link Context#bindServiceAsUser binding}
      */
-    RemoteOnDeviceTrustedInferenceService(Context context, ComponentName serviceName,
+    RemoteOnDeviceSandboxedInferenceService(Context context, ComponentName serviceName,
             int userId) {
         super(context, new Intent(
-                        OnDeviceTrustedInferenceService.SERVICE_INTERFACE).setComponent(serviceName),
+                        OnDeviceSandboxedInferenceService.SERVICE_INTERFACE).setComponent(serviceName),
                 BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId,
-                IOnDeviceTrustedInferenceService.Stub::asInterface);
+                IOnDeviceSandboxedInferenceService.Stub::asInterface);
 
         // Bind right away
         connect();
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 8452c0e..4eb8b2b 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -21,7 +21,6 @@
 import android.Manifest;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
-import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.role.RoleManager;
@@ -39,6 +38,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -96,6 +96,7 @@
     private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
 
     private final Object mLock = new Object();
+    private final Injector mInjector;
     private final Context mContext;
     private final AppOpsManager mAppOps;
     private final TelephonyManager mTelephonyManager;
@@ -345,6 +346,18 @@
         AtomicFile getMappingFile() {
             return mMappingFile;
         }
+
+        UserManager getUserManager() {
+            return mContext.getSystemService(UserManager.class);
+        }
+
+        DevicePolicyManager getDevicePolicyManager() {
+            return mContext.getSystemService(DevicePolicyManager.class);
+        }
+
+        void setSystemProperty(String key, String value) {
+            SystemProperties.set(key, value);
+        }
     }
 
     BugreportManagerServiceImpl(Context context) {
@@ -356,6 +369,7 @@
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     BugreportManagerServiceImpl(Injector injector) {
+        mInjector = injector;
         mContext = injector.getContext();
         mAppOps = mContext.getSystemService(AppOpsManager.class);
         mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
@@ -388,12 +402,7 @@
         int callingUid = Binder.getCallingUid();
         enforcePermission(callingPackage, callingUid, bugreportMode
                 == BugreportParams.BUGREPORT_MODE_TELEPHONY /* checkCarrierPrivileges */);
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            ensureUserCanTakeBugReport(bugreportMode);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+        ensureUserCanTakeBugReport(bugreportMode);
 
         Slogf.i(TAG, "Starting bugreport for %s / %d", callingPackage, callingUid);
         synchronized (mLock) {
@@ -432,7 +441,6 @@
     @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);
@@ -564,54 +572,59 @@
     }
 
     /**
-     * Validates that the current user is an admin user or, when bugreport is requested remotely
-     * that the current user is an affiliated user.
+     * Validates that the calling user is an admin user or, when bugreport is requested remotely
+     * that the user is an affiliated user.
      *
-     * @throws IllegalArgumentException if the current user is not an admin user
+     * @throws IllegalArgumentException if the calling user or the parent of the calling profile
+     *                                  user is not an admin user.
      */
     private void ensureUserCanTakeBugReport(int bugreportMode) {
-        UserInfo currentUser = null;
+        // Get the calling userId before clearing the caller identity.
+        int effectiveCallingUserId = UserHandle.getUserId(Binder.getCallingUid());
+        boolean isAdminUser = false;
+        final long identity = Binder.clearCallingIdentity();
         try {
-            currentUser = ActivityManager.getService().getCurrentUser();
-        } catch (RemoteException e) {
-            // Impossible to get RemoteException for an in-process call.
+            UserInfo profileParent =
+                    mInjector.getUserManager().getProfileParent(effectiveCallingUserId);
+            if (profileParent == null) {
+                isAdminUser = mInjector.getUserManager().isUserAdmin(effectiveCallingUserId);
+            } else {
+                // If the caller is a profile, we need to check its parent user instead.
+                // Therefore setting the profile parent user as the effective calling user.
+                effectiveCallingUserId = profileParent.id;
+                isAdminUser = profileParent.isAdmin();
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
         }
-
-        if (currentUser == null) {
-            logAndThrow("There is no current user, so no bugreport can be requested.");
-        }
-
-        if (!currentUser.isAdmin()) {
+        if (!isAdminUser) {
             if (bugreportMode == BugreportParams.BUGREPORT_MODE_REMOTE
-                    && isCurrentUserAffiliated(currentUser.id)) {
+                    && isUserAffiliated(effectiveCallingUserId)) {
                 return;
             }
-            logAndThrow(TextUtils.formatSimple("Current user %s is not an admin user."
-                    + " Only admin users are allowed to take bugreport.", currentUser.id));
+            logAndThrow(TextUtils.formatSimple("Calling user %s is not an admin user."
+                    + " Only admin users and their profiles are allowed to take bugreport.",
+                    effectiveCallingUserId));
         }
     }
 
     /**
-     * Returns {@code true} if the device has device owner and the current user is affiliated
+     * Returns {@code true} if the device has device owner and the specified user is affiliated
      * with the device owner.
      */
-    private boolean isCurrentUserAffiliated(int currentUserId) {
-        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+    private boolean isUserAffiliated(int userId) {
+        DevicePolicyManager dpm = mInjector.getDevicePolicyManager();
         int deviceOwnerUid = dpm.getDeviceOwnerUserId();
         if (deviceOwnerUid == UserHandle.USER_NULL) {
             return false;
         }
 
-        int callingUserId = UserHandle.getUserId(Binder.getCallingUid());
-
-        Slog.i(TAG, "callingUid: " + callingUserId + " deviceOwnerUid: " + deviceOwnerUid
-                + " currentUserId: " + currentUserId);
-
-        if (callingUserId != deviceOwnerUid) {
-            logAndThrow("Caller is not device owner on provisioned device.");
+        if (DEBUG) {
+            Slog.d(TAG, "callingUid: " + userId + " deviceOwnerUid: " + deviceOwnerUid);
         }
-        if (!dpm.isAffiliatedUser(currentUserId)) {
-            logAndThrow("Current user is not affiliated to the device owner.");
+
+        if (userId != deviceOwnerUid && !dpm.isAffiliatedUser(userId)) {
+            logAndThrow("User " + userId + " is not affiliated to the device owner.");
         }
         return true;
     }
@@ -728,7 +741,7 @@
     @GuardedBy("mLock")
     private IDumpstate startAndGetDumpstateBinderServiceLocked() {
         // Start bugreport service.
-        SystemProperties.set("ctl.start", BUGREPORT_SERVICE);
+        mInjector.setSystemProperty("ctl.start", BUGREPORT_SERVICE);
 
         IDumpstate ds = null;
         boolean timedOut = false;
@@ -760,7 +773,7 @@
         // This tells init to cancel bugreportd service. Note that this is achieved through
         // setting a system property which is not thread-safe. So the lock here offers
         // thread-safety only among callers of the API.
-        SystemProperties.set("ctl.stop", BUGREPORT_SERVICE);
+        mInjector.setSystemProperty("ctl.stop", BUGREPORT_SERVICE);
     }
 
     @RequiresPermission(android.Manifest.permission.DUMP)
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 23d48e8..278deb8 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -207,18 +207,8 @@
                     + intent.toShortString(false, true, false, false)
                     + " " + intent.getExtras(), here);
         }
-        final boolean ordered;
-        if (mAmInternal.isModernQueueEnabled()) {
-            // When the modern broadcast stack is enabled, deliver all our
-            // broadcasts as unordered, since the modern stack has better
-            // support for sequencing cold-starts, and it supports
-            // delivering resultTo for non-ordered broadcasts
-            ordered = false;
-        } else {
-            ordered = (finishedReceiver != null);
-        }
-        mAmInternal.broadcastIntent(
-                intent, finishedReceiver, requiredPermissions, ordered, userId,
+        mAmInternal.broadcastIntentWithCallback(
+                intent, finishedReceiver, requiredPermissions, userId,
                 broadcastAllowList == null ? null : broadcastAllowList.get(userId),
                 filterExtrasForReceiver, bOptions);
     }
@@ -389,7 +379,8 @@
      */
     boolean canLauncherAccessProfile(ComponentName launcherComponent, int userId) {
         if (android.os.Flags.allowPrivateProfile()
-                && Flags.enablePermissionToAccessHiddenProfiles()) {
+                && Flags.enablePermissionToAccessHiddenProfiles()
+                && Flags.enablePrivateSpaceFeatures()) {
             if (mUmInternal.getUserProperties(userId).getProfileApiVisibility()
                     != UserProperties.PROFILE_API_VISIBILITY_HIDDEN) {
                 return true;
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 9afdde5..b5476fd 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -760,13 +760,18 @@
         if (pkgName == null) {
             if (!mCrossProfileIntentResolverEngine.shouldSkipCurrentProfile(this, intent,
                     resolvedType, userId)) {
-                /*
-                 Check for results in the current profile only if there is no
-                 {@link CrossProfileIntentFilter} for user with flag
-                 {@link PackageManager.SKIP_CURRENT_PROFILE} set.
-                 */
-                result.addAll(filterIfNotSystemUser(mComponentResolver.queryActivities(this,
-                        intent, resolvedType, flags, userId), userId));
+
+                final List<ResolveInfo> queryResult = mComponentResolver.queryActivities(this,
+                        intent, resolvedType, flags, userId);
+                // If the user doesn't exist, the queryResult is null
+                if (queryResult != null) {
+                    /*
+                     Check for results in the current profile only if there is no
+                     {@link CrossProfileIntentFilter} for user with flag
+                     {@link PackageManager.SKIP_CURRENT_PROFILE} set.
+                     */
+                    result.addAll(filterIfNotSystemUser(queryResult, userId));
+                }
             }
             addInstant = isInstantAppResolutionAllowed(intent, result, userId,
                     false /*skipPackageCheck*/, flags);
@@ -788,9 +793,13 @@
 
             if (setting != null && setting.getAndroidPackage() != null && (resolveForStart
                     || !shouldFilterApplication(setting, filterCallingUid, userId))) {
-                result.addAll(filterIfNotSystemUser(mComponentResolver.queryActivities(this,
+                final List<ResolveInfo> queryResult = mComponentResolver.queryActivities(this,
                         intent, resolvedType, flags, setting.getAndroidPackage().getActivities(),
-                        userId), userId));
+                        userId);
+                // If the user doesn't exist, the queryResult is null
+                if (queryResult != null) {
+                    result.addAll(filterIfNotSystemUser(queryResult, userId));
+                }
             }
             if (result == null || result.size() == 0) {
                 // the caller wants to resolve for a particular package; however, there
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 3abf3a5..ecfc768 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -50,13 +50,17 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ApexStagedEvent;
 import android.content.pm.Flags;
+import android.content.pm.IPackageManagerNative;
+import android.content.pm.IStagedApexObserver;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.dex.ArtManager;
 import android.os.Binder;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.Trace;
@@ -1054,6 +1058,10 @@
                 artManager.scheduleBackgroundDexoptJob();
             }
         }, new IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED));
+
+        if (Flags.useArtServiceV2()) {
+            StagedApexObserver.registerForStagedApexUpdates(artManager);
+        }
     }
 
     /**
@@ -1168,4 +1176,32 @@
                 && dexoptOptions.isCompilationEnabled()
                 && !isApex;
     }
+
+    private static class StagedApexObserver extends IStagedApexObserver.Stub {
+        private final @NonNull ArtManagerLocal mArtManager;
+
+        static void registerForStagedApexUpdates(@NonNull ArtManagerLocal artManager) {
+            IPackageManagerNative packageNative = IPackageManagerNative.Stub.asInterface(
+                    ServiceManager.getService("package_native"));
+            if (packageNative == null) {
+                Log.e(TAG, "No IPackageManagerNative");
+                return;
+            }
+
+            try {
+                packageNative.registerStagedApexObserver(new StagedApexObserver(artManager));
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to register staged apex observer", e);
+            }
+        }
+
+        private StagedApexObserver(@NonNull ArtManagerLocal artManager) {
+            mArtManager = artManager;
+        }
+
+        @Override
+        public void onApexStaged(@NonNull ApexStagedEvent event) {
+            mArtManager.onApexStaged(event.stagedApexModuleNames);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 8cc242c..4bfd077 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -17,6 +17,8 @@
 package com.android.server.pm;
 
 import static android.content.pm.Flags.disallowSdkLibsToBeApps;
+import static android.content.pm.PackageManager.APP_METADATA_SOURCE_APK;
+import static android.content.pm.PackageManager.APP_METADATA_SOURCE_INSTALLER;
 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;
@@ -43,11 +45,11 @@
 import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
 
+import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.APP_METADATA_FILE_NAME;
 import static com.android.server.pm.DexOptHelper.useArtService;
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
 import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
-import static com.android.server.pm.PackageManagerService.APP_METADATA_FILE_NAME;
 import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
 import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
 import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
@@ -1078,7 +1080,7 @@
                     reconciledPackages = ReconcilePackageUtils.reconcilePackages(
                             requests, Collections.unmodifiableMap(mPm.mPackages),
                             versionInfos, mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
-                            mPm.mSettings);
+                            mPm.mSettings, mPm.mInjector.getSystemConfig());
                 } catch (ReconcileFailure e) {
                     for (InstallRequest request : requests) {
                         request.setError("Reconciliation failed...", e);
@@ -1168,6 +1170,7 @@
 
     @GuardedBy("mPm.mInstallLock")
     private void preparePackageLI(InstallRequest request) throws PrepareFailure {
+        final int[] allUsers =  mPm.mUserManager.getUserIds();
         final int installFlags = request.getInstallFlags();
         final boolean onExternal = request.getVolumeUuid() != null;
         final boolean instantApp = ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0);
@@ -1443,7 +1446,7 @@
 
                 systemApp = ps.isSystem();
                 request.setOriginUsers(ps.queryUsersInstalledOrHasData(
-                        mPm.mUserManager.getUserIds()));
+                        allUsers));
             }
 
             final int numGroups = ArrayUtils.size(parsedPackage.getPermissionGroups());
@@ -1701,7 +1704,6 @@
 
                 final boolean isInstantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
 
-                final int[] allUsers;
                 final int[] installedUsers;
                 final int[] uninstalledUsers;
 
@@ -1794,7 +1796,6 @@
                     }
 
                     // In case of rollback, remember per-user/profile install state
-                    allUsers = mPm.mUserManager.getUserIds();
                     installedUsers = ps.queryInstalledUsers(allUsers, true);
                     uninstalledUsers = ps.queryInstalledUsers(allUsers, false);
 
@@ -2218,17 +2219,23 @@
             final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
             if (ps != null) {
                 installRequest.setNewUsers(
-                        ps.queryInstalledUsers(mPm.mUserManager.getUserIds(), true));
+                        ps.queryInstalledUsers(allUsers, true));
                 ps.setUpdateAvailable(false /*updateAvailable*/);
 
                 File appMetadataFile = new File(ps.getPath(), APP_METADATA_FILE_NAME);
                 if (appMetadataFile.exists()) {
                     ps.setAppMetadataFilePath(appMetadataFile.getAbsolutePath());
                     if (Flags.aslInApkAppMetadataSource()) {
-                        ps.setAppMetadataSource(PackageManager.APP_METADATA_SOURCE_INSTALLER);
+                        ps.setAppMetadataSource(APP_METADATA_SOURCE_INSTALLER);
                     }
                 } else {
-                    ps.setAppMetadataFilePath(null);
+                    if (Flags.aslInApkAppMetadataSource()
+                            && parsedPackage.isAppMetadataFileInApk()) {
+                        ps.setAppMetadataFilePath(appMetadataFile.getAbsolutePath());
+                        ps.setAppMetadataSource(APP_METADATA_SOURCE_APK);
+                    } else {
+                        ps.setAppMetadataFilePath(null);
+                    }
                 }
             }
             if (installRequest.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) {
@@ -2319,7 +2326,7 @@
                 // Retrieve the overlays for shared libraries of the package.
                 if (!ps.getPkgState().getUsesLibraryInfos().isEmpty()) {
                     for (SharedLibraryWrapper sharedLib : ps.getPkgState().getUsesLibraryInfos()) {
-                        for (int currentUserId : UserManagerService.getInstance().getUserIds()) {
+                        for (int currentUserId : allUsers) {
                             if (sharedLib.getType() != SharedLibraryInfo.TYPE_DYNAMIC) {
                                 // TODO(146804378): Support overlaying static shared libraries
                                 continue;
@@ -2408,9 +2415,8 @@
                 }
 
                 // Set install reason for users that are having the package newly installed.
-                final int[] allUsersList = mPm.mUserManager.getUserIds();
                 if (userId == UserHandle.USER_ALL) {
-                    for (int currentUserId : allUsersList) {
+                    for (int currentUserId : allUsers) {
                         if (!previousUserIds.contains(currentUserId)
                                 && ps.getInstalled(currentUserId)) {
                             ps.setInstallReason(installReason, currentUserId);
@@ -2430,7 +2436,7 @@
                 }
 
                 // Ensure that the uninstall reason is UNKNOWN for users with the package installed.
-                for (int currentUserId : allUsersList) {
+                for (int currentUserId : allUsers) {
                     if (ps.getInstalled(currentUserId)) {
                         ps.setUninstallReason(UNINSTALL_REASON_UNKNOWN, currentUserId);
                     }
@@ -3804,7 +3810,7 @@
                                 mPm.mPackages, Collections.singletonMap(pkgName,
                                         mPm.getSettingsVersionForPackage(parsedPackage)),
                                 mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
-                                mPm.mSettings);
+                                mPm.mSettings, mPm.mInjector.getSystemConfig());
                 if ((scanFlags & SCAN_AS_APEX) == 0) {
                     appIdCreated = optimisticallyRegisterAppId(installRequest);
                 } else {
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 3f9e989..c6bb99e 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -22,8 +22,8 @@
 import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.app.AppOpsManager.OP_ARCHIVE_ICON_OVERLAY;
 import static android.app.AppOpsManager.OP_UNARCHIVAL_CONFIRMATION;
-import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
-import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 import static android.app.PendingIntent.FLAG_MUTABLE;
 import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
@@ -161,6 +161,9 @@
 public class LauncherAppsService extends SystemService {
     private static final String WM_TRACE_DIR = "/data/misc/wmtrace/";
     private static final String VC_FILE_SUFFIX = ".vc";
+    // TODO(b/310027945): Update the intent name.
+    private static final String PS_SETTINGS_INTENT =
+            "com.android.settings.action.PRIVATE_SPACE_SETUP_FLOW";
 
     private static final Set<PosixFilePermission> WM_TRACE_FILE_PERMISSIONS = Set.of(
             PosixFilePermission.OWNER_WRITE,
@@ -581,7 +584,8 @@
             return android.os.Flags.allowPrivateProfile()
                     && Flags.enableHidingProfiles()
                     && Flags.enableLauncherAppsHiddenProfileChecks()
-                    && Flags.enablePermissionToAccessHiddenProfiles();
+                    && Flags.enablePermissionToAccessHiddenProfiles()
+                    && Flags.enablePrivateSpaceFeatures();
         }
 
         @VisibleForTesting // We override it in unit tests
@@ -1777,6 +1781,27 @@
             }
         }
 
+        @Override
+        public @Nullable IntentSender getPrivateSpaceSettingsIntent() {
+            if (!canAccessHiddenProfile(getCallingUid(), getCallingPid())) {
+                Slog.e(TAG, "Caller cannot access hidden profiles");
+                return null;
+            }
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                Intent psSettingsIntent = new Intent(PS_SETTINGS_INTENT);
+                psSettingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                        | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+                final PendingIntent pi = PendingIntent.getActivity(mContext,
+                        /* requestCode */ 0,
+                        psSettingsIntent,
+                        PendingIntent.FLAG_IMMUTABLE | FLAG_UPDATE_CURRENT);
+                return pi == null ? null : pi.getIntentSender();
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
         @Nullable
         private IntentSender buildAppMarketIntentSenderForUser(@NonNull UserHandle user) {
             Intent appMarketIntent = new Intent(Intent.ACTION_MAIN);
@@ -2091,6 +2116,18 @@
 
         @RequiresPermission(READ_FRAME_BUFFER)
         @Override
+        public void saveViewCaptureData() {
+            int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER);
+            if (PERMISSION_GRANTED == status) {
+                forEachViewCaptureWindow(this::dumpViewCaptureDataToWmTrace);
+            } else {
+                Log.w(TAG, "caller lacks permissions to save view capture data");
+            }
+        }
+
+
+        @RequiresPermission(READ_FRAME_BUFFER)
+        @Override
         public void registerDumpCallback(@NonNull IDumpCallback cb) {
             int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER);
             if (PERMISSION_GRANTED == status) {
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 2b20bfd..ec98fff 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -21,7 +21,7 @@
 import static android.app.ActivityManager.START_PERMISSION_DENIED;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_IGNORED;
-import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
 import static android.content.pm.ArchivedActivityInfo.bytesFromBitmap;
 import static android.content.pm.ArchivedActivityInfo.drawableToBitmap;
 import static android.content.pm.PackageInstaller.EXTRA_UNARCHIVE_STATUS;
@@ -31,6 +31,7 @@
 import static android.content.pm.PackageManager.DELETE_ALL_USERS;
 import static android.content.pm.PackageManager.DELETE_ARCHIVE;
 import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
+import static android.content.pm.PackageManager.INSTALL_UNARCHIVE;
 import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT;
 import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;
 import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE;
@@ -556,6 +557,28 @@
     }
 
     /**
+     * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
+     * This allows the badging to be done based on the actual bitmap size rather than
+     * the scaled bitmap size.
+     */
+    private static class FixedSizeBitmapDrawable extends BitmapDrawable {
+
+        FixedSizeBitmapDrawable(@Nullable final Bitmap bitmap) {
+            super(null, bitmap);
+        }
+
+        @Override
+        public int getIntrinsicHeight() {
+            return getBitmap().getWidth();
+        }
+
+        @Override
+        public int getIntrinsicWidth() {
+            return getBitmap().getWidth();
+        }
+    }
+
+    /**
      * Create an <a
      * href="https://developer.android.com/develop/ui/views/launch/icon_design_adaptive">
      * adaptive icon</a> from an icon.
@@ -568,6 +591,11 @@
         }
 
         // see BaseIconFactory#createShapedIconBitmap
+        if (iconDrawable instanceof BitmapDrawable) {
+            var icon = ((BitmapDrawable) iconDrawable).getBitmap();
+            iconDrawable = new FixedSizeBitmapDrawable(icon);
+        }
+
         float inset = getExtraInsetFraction();
         inset = inset / (1 + 2 * inset);
         Drawable d = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK),
@@ -754,8 +782,9 @@
 
         int draftSessionId;
         try {
-            draftSessionId = Binder.withCleanCallingIdentity(() ->
-                    createDraftSession(packageName, installerPackage, statusReceiver, userId));
+            draftSessionId = Binder.withCleanCallingIdentity(
+                    () -> createDraftSession(packageName, installerPackage, callerPackageName,
+                            statusReceiver, userId));
         } catch (RuntimeException e) {
             if (e.getCause() instanceof IOException) {
                 throw ExceptionUtils.wrap((IOException) e.getCause());
@@ -795,11 +824,18 @@
     }
 
     private int createDraftSession(String packageName, String installerPackage,
+            String callerPackageName,
             IntentSender statusReceiver, int userId) throws IOException {
         PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
                 PackageInstaller.SessionParams.MODE_FULL_INSTALL);
         sessionParams.setAppPackageName(packageName);
-        sessionParams.installFlags = INSTALL_UNARCHIVE_DRAFT;
+        sessionParams.setAppLabel(
+                mContext.getString(com.android.internal.R.string.unarchival_session_app_label));
+        sessionParams.setAppIcon(
+                getArchivedAppIcon(packageName, UserHandle.of(userId), callerPackageName));
+        // To make sure SessionInfo::isUnarchival returns true for draft sessions,
+        // INSTALL_UNARCHIVE is also set.
+        sessionParams.installFlags = (INSTALL_UNARCHIVE_DRAFT | INSTALL_UNARCHIVE);
 
         int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId);
         // Handles case of repeated unarchival calls for the same package.
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 5792d86..ba90378 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -358,7 +358,7 @@
     /** Default byte size limit for app metadata */
     private static final long DEFAULT_APP_METADATA_BYTE_SIZE_LIMIT = 32000;
 
-    private static final int APP_METADATA_FILE_ACCESS_MODE = 0640;
+    static final int APP_METADATA_FILE_ACCESS_MODE = 0640;
 
     /**
      * Throws IllegalArgumentException if the {@link IntentSender} from an immutable
@@ -964,7 +964,7 @@
 
     private boolean isEmergencyInstallerEnabled(String packageName, Computer snapshot) {
         final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName);
-        if (ps == null || ps.getPkg() == null || !ps.isSystem()) {
+        if (ps == null || ps.getPkg() == null) {
             return false;
         }
         String emergencyInstaller = ps.getPkg().getEmergencyInstaller();
@@ -1782,8 +1782,7 @@
     }
 
     private File getStagedAppMetadataFile() {
-        File file = new File(stageDir, APP_METADATA_FILE_NAME);
-        return file.exists() ? file : null;
+        return new File(stageDir, APP_METADATA_FILE_NAME);
     }
 
     private static boolean isAppMetadata(String name) {
@@ -1799,7 +1798,7 @@
         assertCallerIsOwnerOrRoot();
         synchronized (mLock) {
             assertPreparedAndNotCommittedOrDestroyedLocked("getAppMetadataFd");
-            if (getStagedAppMetadataFile() == null) {
+            if (!getStagedAppMetadataFile().exists()) {
                 return null;
             }
             try {
@@ -1813,12 +1812,12 @@
     @Override
     public void removeAppMetadata() {
         File file = getStagedAppMetadataFile();
-        if (file != null) {
+        if (file.exists()) {
             file.delete();
         }
     }
 
-    private static long getAppMetadataSizeLimit() {
+    static long getAppMetadataSizeLimit() {
         final long token = Binder.clearCallingIdentity();
         try {
             return DeviceConfig.getLong(NAMESPACE_PACKAGE_MANAGER_SERVICE,
@@ -2131,7 +2130,7 @@
         }
 
         File appMetadataFile = getStagedAppMetadataFile();
-        if (appMetadataFile != null) {
+        if (appMetadataFile.exists()) {
             long sizeLimit = getAppMetadataSizeLimit();
             if (appMetadataFile.length() > sizeLimit) {
                 appMetadataFile.delete();
@@ -3419,7 +3418,7 @@
 
         final List<ApkLite> addedFiles = getAddedApkLitesLocked();
         if (addedFiles.isEmpty()
-                && (removeSplitList.size() == 0 || getStagedAppMetadataFile() != null)) {
+                && (removeSplitList.size() == 0 || getStagedAppMetadataFile().exists())) {
             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                     TextUtils.formatSimple("Session: %d. No packages staged in %s", sessionId,
                           stageDir.getAbsolutePath()));
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 3c256b1..7c51707 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -18,6 +18,8 @@
 import static android.Manifest.permission.MANAGE_DEVICE_ADMINS;
 import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS;
 import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.content.pm.PackageManager.APP_METADATA_SOURCE_APK;
+import static android.content.pm.PackageManager.APP_METADATA_SOURCE_UNKNOWN;
 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_DISABLED_UNTIL_USED;
@@ -36,7 +38,6 @@
 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;
@@ -166,7 +167,6 @@
 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;
@@ -596,7 +596,8 @@
     static final String RANDOM_DIR_PREFIX = "~~";
     static final char RANDOM_CODEPATH_PREFIX = '-';
 
-    static final String APP_METADATA_FILE_NAME = "app.metadata";
+    public static final String APP_METADATA_FILE_NAME = "app.metadata";
+    public static final String APP_METADATA_FILE_IN_APK_PATH = "assets/" + APP_METADATA_FILE_NAME;
 
     static final int DEFAULT_FILE_ACCESS_MODE = 0644;
 
@@ -1538,6 +1539,19 @@
             return;
         }
 
+        // Initialize all necessary settings for archival installation.
+        pkgSetting
+                // No package.
+                .setPkg(null)
+                // Mark for later restore.
+                .setPendingRestore(true);
+        for (int userId : userIds) {
+            // Unmark "installed" for all users.
+            pkgSetting
+                    .modifyUserState(userId)
+                    .setInstalled(false);
+        }
+
         String responsibleInstallerPackage = PackageArchiver.getResponsibleInstallerPackage(
                 pkgSetting);
         // TODO(b/278553670) Check if responsibleInstallerPackage supports unarchival.
@@ -1548,16 +1562,11 @@
         for (int userId : userIds) {
             var archiveState = mInstallerService.mPackageArchiver.createArchiveState(
                     archivePackage, userId, responsibleInstallerPackage);
-            if (archiveState == null) {
-                continue;
-            }
-            pkgSetting
-                    .setPkg(null)
-                    // This package was installed as archived. Need to mark it for later restore.
-                    .setPendingRestore(true)
+            if (archiveState != null) {
+                pkgSetting
                     .modifyUserState(userId)
-                    .setInstalled(false)
                     .setArchiveState(archiveState);
+            }
         }
     }
 
@@ -3197,15 +3206,8 @@
         }
 
         if (quarantined) {
-            final boolean hasQuarantineAppsPerm = mContext.checkCallingOrSelfPermission(
-                    android.Manifest.permission.QUARANTINE_APPS) == PERMISSION_GRANTED;
-            // TODO: b/305256093 - In order to facilitate testing, temporarily allowing apps
-            // with SUSPEND_APPS permission to quarantine apps. Remove this once the testing
-            // is done and this is no longer needed.
-            if (!hasQuarantineAppsPerm) {
-                mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SUSPEND_APPS,
-                        callingMethod);
-            }
+            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.QUARANTINE_APPS,
+                    callingMethod);
         } else {
             mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SUSPEND_APPS,
                     callingMethod);
@@ -5227,15 +5229,30 @@
                         new PackageManager.NameNotFoundException(packageName));
             }
             String filePath = ps.getAppMetadataFilePath();
-            if (filePath != null) {
-                File file = new File(filePath);
-                try {
-                    return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
-                } catch (FileNotFoundException e) {
+            if (filePath == null) {
+                return null;
+            }
+            File file = new File(filePath);
+            if (Flags.aslInApkAppMetadataSource() && !file.exists()
+                    && ps.getAppMetadataSource() == APP_METADATA_SOURCE_APK) {
+                String apkPath = ps.getPkg().getSplits().get(0).getPath();
+                if (!PackageManagerServiceUtils.extractAppMetadataFromApk(apkPath, file)) {
+                    if (file.exists()) {
+                        file.delete();
+                    }
+                    synchronized (mLock) {
+                        PackageSetting pkgSetting = mSettings.getPackageLPr(packageName);
+                        pkgSetting.setAppMetadataFilePath(null);
+                        pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_UNKNOWN);
+                    }
                     return null;
                 }
             }
-            return null;
+            try {
+                return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+            } catch (FileNotFoundException e) {
+                return null;
+            }
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.GET_APP_METADATA)
@@ -6261,16 +6278,8 @@
                 SuspendDialogInfo dialogInfo, int flags, String suspendingPackage,
                 int suspendingUserId, int targetUserId) {
             final int callingUid = Binder.getCallingUid();
-            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 = suspendingPackage.equals(wellbeingPkg);
-                }
-            }
+            final boolean quarantined = ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0)
+                    && Flags.quarantinedEnabled();
             final Computer snapshot = snapshotComputer();
             final UserPackage suspender = UserPackage.of(targetUserId, suspendingPackage);
             enforceCanSetPackagesSuspendedAsUser(snapshot, quarantined, suspender, callingUid,
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 0a3dfc0..189a138 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -27,6 +27,9 @@
 import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
 import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__EXPLICIT_INTENT_FILTER_UNMATCH;
 import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
+import static com.android.server.pm.PackageInstallerSession.APP_METADATA_FILE_ACCESS_MODE;
+import static com.android.server.pm.PackageInstallerSession.getAppMetadataSizeLimit;
+import static com.android.server.pm.PackageManagerService.APP_METADATA_FILE_IN_APK_PATH;
 import static com.android.server.pm.PackageManagerService.COMPRESSED_EXTENSION;
 import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
 import static com.android.server.pm.PackageManagerService.DEBUG_INTENT_MATCHING;
@@ -142,6 +145,8 @@
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.zip.GZIPInputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
 
 /**
  * Class containing helper methods for the PackageManagerService.
@@ -1559,6 +1564,35 @@
         return initiatingPackageName == null || SHELL_PACKAGE_NAME.equals(initiatingPackageName);
     }
 
+    /**
+     * Extract the app.metadata file from apk.
+     */
+    public static boolean extractAppMetadataFromApk(String apkPath, File appMetadataFile) {
+        boolean found = false;
+        try (ZipInputStream zipInputStream =
+                     new ZipInputStream(new FileInputStream(new File(apkPath)))) {
+            ZipEntry zipEntry = null;
+            while ((zipEntry = zipInputStream.getNextEntry()) != null) {
+                if (zipEntry.getName().equals(APP_METADATA_FILE_IN_APK_PATH)) {
+                    found = true;
+                    try (FileOutputStream out = new FileOutputStream(appMetadataFile)) {
+                        FileUtils.copy(zipInputStream, out);
+                    }
+                    if (appMetadataFile.length() > getAppMetadataSizeLimit()) {
+                        appMetadataFile.delete();
+                        return false;
+                    }
+                    Os.chmod(appMetadataFile.getAbsolutePath(), APP_METADATA_FILE_ACCESS_MODE);
+                    break;
+                }
+            }
+        } catch (Exception e) {
+            Slog.e(TAG, e.getMessage());
+            return false;
+        }
+        return found;
+    }
+
     public static void linkFilesToOldDirs(@NonNull Installer installer,
                                            @NonNull String packageName,
                                            @NonNull File newPath,
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
index 9a7916a..90d6adc 100644
--- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -17,6 +17,7 @@
 package com.android.server.pm;
 
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
 import static android.content.pm.SigningDetails.CapabilityMergeRule.MERGE_RESTRICTED_CAPABILITY;
 
@@ -25,6 +26,7 @@
 import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP;
 import static com.android.server.pm.PackageManagerService.TAG;
 
+import android.content.pm.Flags;
 import android.content.pm.PackageManager;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.SigningDetails;
@@ -36,6 +38,7 @@
 
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.SystemConfig;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.utils.WatchedLongSparseArray;
 
@@ -53,14 +56,17 @@
  * as install) led to the request.
  */
 final class ReconcilePackageUtils {
-    private static final boolean ALLOW_NON_PRELOADS_SYSTEM_SIGNATURE = Build.IS_DEBUGGABLE || true;
+    // TODO(b/308573259): with allow-list, we should be able to disallow such installs even in
+    // debuggable builds.
+    private static final boolean ALLOW_NON_PRELOADS_SYSTEM_SHAREDUIDS = Build.IS_DEBUGGABLE
+            || !Flags.restrictNonpreloadsSystemShareduids();
 
     public static List<ReconciledPackage> reconcilePackages(
             List<InstallRequest> installRequests,
             Map<String, AndroidPackage> allPackages,
             Map<String, Settings.VersionInfo> versionInfos,
             SharedLibrariesImpl sharedLibraries,
-            KeySetManagerService ksms, Settings settings)
+            KeySetManagerService ksms, Settings settings, SystemConfig systemConfig)
             throws ReconcileFailure {
         final List<ReconciledPackage> result = new ArrayList<>(installRequests.size());
 
@@ -187,11 +193,19 @@
                                     SigningDetails.CertCapabilities.PERMISSION)) {
                         Slog.d(TAG, "Non-preload app associated with system signature: "
                                 + signatureCheckPs.getPackageName());
-                        if (!ALLOW_NON_PRELOADS_SYSTEM_SIGNATURE) {
-                            throw new ReconcileFailure(
-                                    INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
-                                    "Non-preload app associated with system signature: "
-                                            + signatureCheckPs.getPackageName());
+                        if (sharedUserSetting != null && !ALLOW_NON_PRELOADS_SYSTEM_SHAREDUIDS) {
+                            // Check the allow-list.
+                            var allowList = systemConfig.getPackageToSharedUidAllowList();
+                            var sharedUidName = allowList.get(signatureCheckPs.getPackageName());
+                            if (sharedUidName == null
+                                    || !sharedUserSetting.name.equals(sharedUidName)) {
+                                var msg = "Non-preload app " + signatureCheckPs.getPackageName()
+                                        + " signed with platform signature and joining shared uid: "
+                                        + sharedUserSetting.name;
+                                Slog.e(TAG, msg + ", allowList: " + allowList);
+                                throw new ReconcileFailure(
+                                        INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID, msg);
+                            }
                         }
                     }
 
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index fe65010..e35a169 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2710,7 +2710,8 @@
 
         } catch (java.io.IOException e) {
             mReadMessages.append("Error reading: " + e.toString());
-            PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e);
+            PackageManagerService.reportSettingsProblem(Log.ERROR,
+                    "Error reading stopped packages: " + e);
             Slog.wtf(PackageManagerService.TAG, "Error reading package manager stopped packages",
                     e);
 
@@ -3386,12 +3387,7 @@
 
                     } else if (tagName.equals("verifier")) {
                         final String deviceIdentity = parser.getAttributeValue(null, "device");
-                        try {
-                            mVerifierDeviceIdentity = VerifierDeviceIdentity.parse(deviceIdentity);
-                        } catch (IllegalArgumentException e) {
-                            Slog.w(PackageManagerService.TAG, "Discard invalid verifier device id: "
-                                    + e.getMessage());
-                        }
+                        mVerifierDeviceIdentity = VerifierDeviceIdentity.parse(deviceIdentity);
                     } else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) {
                         // No longer used.
                     } else if (tagName.equals("keyset-settings")) {
@@ -3419,7 +3415,8 @@
                 }
 
                 str.close();
-            } catch (IOException | XmlPullParserException | ArrayIndexOutOfBoundsException e) {
+            } catch (IOException | XmlPullParserException | ArrayIndexOutOfBoundsException
+                     | IllegalArgumentException e) {
                 // Remove corrupted file and retry.
                 atomicFile.failRead(str, e);
 
@@ -4558,6 +4555,10 @@
             for (int i = 0; i < size; i++) {
                 final PackageSetting ps = mPackages.valueAt(i);
                 if (ps.getPkg() == null) {
+                    // This would force-create correct per-user state.
+                    ps.setInstalled(false, userHandle);
+                    // Make sure the app is excluded from storage mapping for this user.
+                    writeKernelMappingLPr(ps);
                     continue;
                 }
                 final boolean shouldMaybeInstall = ps.isSystem() &&
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index a600eea..4c653f6 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -520,7 +520,7 @@
         mShortcutRequestPinProcessor = new ShortcutRequestPinProcessor(this, mLock);
         mShortcutDumpFiles = new ShortcutDumpFiles(this);
         mIsAppSearchEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.SHORTCUT_APPSEARCH_INTEGRATION, true)
+                SystemUiDeviceConfigFlags.SHORTCUT_APPSEARCH_INTEGRATION, false)
                 && !injectIsLowRamDevice();
 
         if (onlyForPackageManagerApis) {
@@ -2833,7 +2833,8 @@
 
     @VisibleForTesting
     boolean areShortcutsSupportedOnHomeScreen(@UserIdInt int userId) {
-        if (!android.os.Flags.allowPrivateProfile() || !Flags.disablePrivateSpaceItemsOnHome()) {
+        if (!android.os.Flags.allowPrivateProfile() || !Flags.disablePrivateSpaceItemsOnHome()
+                || !android.multiuser.Flags.enablePrivateSpaceFeatures()) {
             return true;
         }
         final long start = getStatStartTime();
@@ -3795,6 +3796,7 @@
                 }
 
                 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+                final boolean archival = intent.getBooleanExtra(Intent.EXTRA_ARCHIVAL, false);
 
                 switch (action) {
                     case Intent.ACTION_PACKAGE_ADDED:
@@ -3805,7 +3807,7 @@
                         }
                         break;
                     case Intent.ACTION_PACKAGE_REMOVED:
-                        if (!replacing) {
+                        if (!replacing || (replacing && archival)) {
                             handlePackageRemoved(packageName, userId);
                         }
                         break;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7349755..88e7596 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -21,10 +21,15 @@
 import static android.content.Intent.EXTRA_USER_ID;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
+import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
+import static android.content.pm.PackageManager.FEATURE_LEANBACK;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
 import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
 import static android.os.UserManager.DISALLOW_USER_SWITCH;
 import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
 import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN;
+import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
 
 import static com.android.internal.app.SetScreenLockDialogActivity.EXTRA_ORIGIN_USER_ID;
 import static com.android.internal.app.SetScreenLockDialogActivity.LAUNCH_REASON_DISABLE_QUIET_MODE;
@@ -1006,9 +1011,17 @@
         emulateSystemUserModeIfNeeded();
     }
 
+    private boolean doesDeviceHardwareSupportPrivateSpace() {
+        return !mPm.hasSystemFeature(FEATURE_EMBEDDED, 0)
+                && !mPm.hasSystemFeature(FEATURE_WATCH, 0)
+                && !mPm.hasSystemFeature(FEATURE_LEANBACK, 0)
+                && !mPm.hasSystemFeature(FEATURE_AUTOMOTIVE, 0);
+    }
+
     private static boolean isAutoLockForPrivateSpaceEnabled() {
         return android.os.Flags.allowPrivateProfile()
-                && Flags.supportAutolockForPrivateSpace();
+                && Flags.supportAutolockForPrivateSpace()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures();
     }
 
     void systemReady() {
@@ -1052,7 +1065,8 @@
 
     private boolean isAutoLockingPrivateSpaceOnRestartsEnabled() {
         return android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.enablePrivateSpaceAutolockOnRestarts();
+                && android.multiuser.Flags.enablePrivateSpaceAutolockOnRestarts()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures();
     }
 
     /**
@@ -1493,7 +1507,8 @@
     private boolean isProfileHidden(int userId) {
         UserProperties userProperties = getUserPropertiesCopy(userId);
         if (android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.enableHidingProfiles()) {
+                && android.multiuser.Flags.enableHidingProfiles()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
             return userProperties.getProfileApiVisibility()
                     == UserProperties.PROFILE_API_VISIBILITY_HIDDEN;
         }
@@ -1693,7 +1708,8 @@
                 setQuietModeEnabled(userId, true /* enableQuietMode */, target, callingPackage);
                 return true;
             }
-            if (android.os.Flags.allowPrivateProfile()) {
+            if (android.os.Flags.allowPrivateProfile()
+                    && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
                 final UserProperties userProperties = getUserPropertiesInternal(userId);
                 if (userProperties != null
                         && userProperties.isAuthAlwaysRequiredToDisableQuietMode()) {
@@ -1839,7 +1855,8 @@
         logQuietModeEnabled(userId, enableQuietMode, callingPackage);
 
         // Broadcast generic intents for all profiles
-        if (android.os.Flags.allowPrivateProfile()) {
+        if (android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
             broadcastProfileAvailabilityChanges(profile, parent.getUserHandle(),
                     enableQuietMode, false);
         }
@@ -1852,7 +1869,8 @@
 
     private void stopUserForQuietMode(int userId) throws RemoteException {
         if (android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
+                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
             // Allow delayed locking since some profile types want to be able to unlock again via
             // biometrics.
             ActivityManager.getService()
@@ -2751,6 +2769,18 @@
     }
 
     @Override
+    public boolean canAddPrivateProfile(@UserIdInt int userId) {
+        checkCreateUsersPermission("canHaveRestrictedProfile");
+        UserInfo parentUserInfo = getUserInfo(userId);
+        return isUserTypeEnabled(USER_TYPE_PROFILE_PRIVATE)
+                && canAddMoreProfilesToUser(USER_TYPE_PROFILE_PRIVATE,
+                    userId, /* allowedToRemoveOne */ false)
+                && (parentUserInfo != null && parentUserInfo.isMain())
+                && doesDeviceHardwareSupportPrivateSpace()
+                && !hasUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, userId);
+    }
+
+    @Override
     public boolean hasRestrictedProfiles(@UserIdInt int userId) {
         checkManageUsersPermission("hasRestrictedProfiles");
         synchronized (mUsersLock) {
@@ -5308,7 +5338,7 @@
         if (!isUserTypeEnabled(userTypeDetails)) {
             throwCheckedUserOperationException(
                     "Cannot add a user of disabled type " + userType + ".",
-                    UserManager.USER_OPERATION_ERROR_MAX_USERS);
+                    UserManager.USER_OPERATION_ERROR_DISABLED_USER);
         }
 
         synchronized (mUsersLock) {
@@ -5341,6 +5371,7 @@
         final boolean isDemo = UserManager.isUserTypeDemo(userType);
         final boolean isManagedProfile = UserManager.isUserTypeManagedProfile(userType);
         final boolean isCommunalProfile = UserManager.isUserTypeCommunalProfile(userType);
+        final boolean isPrivateProfile = UserManager.isUserTypePrivateProfile(userType);
 
         final long ident = Binder.clearCallingIdentity();
         UserInfo userInfo;
@@ -5387,6 +5418,12 @@
                                     + " for user " + parentId,
                             UserManager.USER_OPERATION_ERROR_MAX_USERS);
                 }
+                if (android.multiuser.Flags.blockPrivateSpaceCreation()
+                        && isPrivateProfile && !canAddPrivateProfile(parentId)) {
+                    throwCheckedUserOperationException(
+                            "Cannot add profile of type " + userType + " for user " + parentId,
+                            UserManager.USER_OPERATION_ERROR_PRIVATE_PROFILE);
+                }
                 if (isRestricted && (parentId != UserHandle.USER_SYSTEM)
                         && !isCreationOverrideEnabled()) {
                     throwCheckedUserOperationException(
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index c2f74a8..c9fd261 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -155,7 +155,8 @@
             UserManager.DISALLOW_CONFIG_DEFAULT_APPS,
             UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
             UserManager.DISALLOW_SIM_GLOBALLY,
-            UserManager.DISALLOW_ASSIST_CONTENT
+            UserManager.DISALLOW_ASSIST_CONTENT,
+            UserManager.DISALLOW_THREAD_NETWORK
     });
 
     public static final Set<String> DEPRECATED_USER_RESTRICTIONS = Sets.newArraySet(
@@ -206,7 +207,8 @@
             UserManager.DISALLOW_ADD_WIFI_CONFIG,
             UserManager.DISALLOW_CELLULAR_2G,
             UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO,
-            UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO
+            UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
+            UserManager.DISALLOW_THREAD_NETWORK
     );
 
     /**
@@ -252,7 +254,8 @@
                     UserManager.DISALLOW_ADD_WIFI_CONFIG,
                     UserManager.DISALLOW_CELLULAR_2G,
                     UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO,
-                    UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO
+                    UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
+                    UserManager.DISALLOW_THREAD_NETWORK
             );
 
     /**
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 114daaa..7f9c1cf 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -292,6 +292,7 @@
                 .setName(USER_TYPE_PROFILE_PRIVATE)
                 .setBaseType(FLAG_PROFILE)
                 .setMaxAllowedPerParent(1)
+                .setEnabled(UserManager.isPrivateProfileEnabled() ? 1 : 0)
                 .setLabels(R.string.profile_label_private)
                 .setIconBadge(com.android.internal.R.drawable.ic_private_profile_icon_badge)
                 .setBadgePlain(com.android.internal.R.drawable.ic_private_profile_badge)
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
index 47032ea..754b141 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
@@ -408,6 +408,9 @@
 
     /**
      * Gets the permission states for requested package, persistent device and user.
+     * <p>
+     * <strong>Note: </strong>Default device permissions are not inherited in this API. Returns the
+     * exact permission states for the requested device.
      *
      * @param packageName name of the package you are checking against
      * @param deviceId id of the persistent device you are checking against
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index 305b087..5c8215e 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -16,6 +16,10 @@
 
 package com.android.server.pm.verify.domain;
 
+import static android.content.IntentFilter.WILDCARD;
+
+import static com.android.server.pm.verify.domain.DomainVerificationUtils.isValidDomain;
+
 import static java.util.Collections.emptyList;
 import static java.util.Collections.emptySet;
 
@@ -253,9 +257,18 @@
             Map<String, List<UriRelativeFilterGroup>> domainToGroupsMap =
                     pkgState.getUriRelativeFilterGroupMap();
             for (String domain : bundle.keySet()) {
+                if (!isValidDomain(domain)) {
+                    continue;
+                }
                 ArrayList<UriRelativeFilterGroupParcel> parcels =
                         bundle.getParcelableArrayList(domain, UriRelativeFilterGroupParcel.class);
-                domainToGroupsMap.put(domain, UriRelativeFilterGroup.parcelsToGroups(parcels));
+                List<UriRelativeFilterGroup> groups =
+                        UriRelativeFilterGroup.parcelsToGroups(parcels);
+                if (groups == null || groups.isEmpty()) {
+                    domainToGroupsMap.remove(domain);
+                } else {
+                    domainToGroupsMap.put(domain, groups);
+                }
             }
         }
     }
@@ -273,9 +286,11 @@
                 Map<String, List<UriRelativeFilterGroup>> map =
                         pkgState.getUriRelativeFilterGroupMap();
                 for (int i = 0; i < domains.size(); i++) {
-                    List<UriRelativeFilterGroup> groups = map.get(domains.get(i));
-                    bundle.putParcelableList(domains.get(i),
-                            UriRelativeFilterGroup.groupsToParcels(groups));
+                    if (map.containsKey(domains.get(i))) {
+                        List<UriRelativeFilterGroup> groups = map.get(domains.get(i));
+                        bundle.putParcelableList(domains.get(i),
+                                UriRelativeFilterGroup.groupsToParcels(groups));
+                    }
                 }
             }
         }
@@ -285,15 +300,29 @@
     @NonNull
     private List<UriRelativeFilterGroup> getUriRelativeFilterGroups(@NonNull String packageName,
             @NonNull String domain) {
-        List<UriRelativeFilterGroup> groups = Collections.emptyList();
+        List<UriRelativeFilterGroup> groups;
         synchronized (mLock) {
             DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
             if (pkgState != null) {
-                groups = pkgState.getUriRelativeFilterGroupMap().getOrDefault(domain,
-                        Collections.emptyList());
+                Map<String, List<UriRelativeFilterGroup>> groupMap =
+                        pkgState.getUriRelativeFilterGroupMap();
+                groups = groupMap.get(domain);
+                if (groups != null) {
+                    return groups;
+                }
+                int first = domain.indexOf(".");
+                int second = domain.indexOf('.', first + 1);
+                while (first > 0 && second > 0) {
+                    groups = groupMap.get(WILDCARD + domain.substring(first));
+                    if (groups != null) {
+                        return groups;
+                    }
+                    first = second;
+                    second = domain.indexOf('.', second + 1);
+                }
             }
         }
-        return groups;
+        return Collections.emptyList();
     }
 
     @NonNull
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
index 3fd00c6..b8c4d22 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
@@ -35,6 +35,9 @@
 
 public final class DomainVerificationUtils {
 
+    public static final int MAX_DOMAIN_LENGTH = 254;
+    public static final int MAX_DOMAIN_LABEL_LENGTH = 63;
+
     private static final ThreadLocal<Matcher> sCachedMatcher = ThreadLocal.withInitial(
             () -> Patterns.DOMAIN_NAME.matcher(""));
 
@@ -108,4 +111,41 @@
         appInfo.targetSdkVersion = pkg.getTargetSdkVersion();
         return appInfo;
     }
+
+    static boolean isValidDomain(String domain) {
+        if (domain.length() > MAX_DOMAIN_LENGTH || domain.equals("*")) {
+            return false;
+        }
+        if (domain.charAt(0) == '*') {
+            if (domain.charAt(1) != '.') {
+                return false;
+            }
+            domain = domain.substring(2);
+        }
+        int labels = 1;
+        int labelStart = -1;
+        for (int i = 0; i < domain.length(); i++) {
+            char c = domain.charAt(i);
+            if (c == '.') {
+                int labelLength = i - labelStart - 1;
+                if (labelLength == 0 || labelLength > MAX_DOMAIN_LABEL_LENGTH) {
+                    return false;
+                }
+                labelStart = i;
+                labels += 1;
+            } else if (!isValidDomainChar(c)) {
+                return false;
+            }
+        }
+        int lastLabelLength = domain.length() - labelStart - 1;
+        if (lastLabelLength == 0 || lastLabelLength > 63) {
+            return false;
+        }
+        return labels > 1;
+    }
+
+    private static boolean isValidDomainChar(char c) {
+        return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
+                || (c >= '0' && c <= '9') || c == '-';
+    }
 }
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index 1b220a0..453c6ef 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -16,7 +16,7 @@
 
 package com.android.server.policy;
 
-import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
 import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE_IDENTIFIER;
 
 import android.annotation.NonNull;
@@ -45,9 +45,9 @@
 import com.android.server.input.InputManagerInternal;
 import com.android.server.policy.devicestate.config.Conditions;
 import com.android.server.policy.devicestate.config.DeviceStateConfig;
-import com.android.server.policy.devicestate.config.Flags;
 import com.android.server.policy.devicestate.config.LidSwitchCondition;
 import com.android.server.policy.devicestate.config.NumericRange;
+import com.android.server.policy.devicestate.config.Properties;
 import com.android.server.policy.devicestate.config.SensorCondition;
 import com.android.server.policy.devicestate.config.XmlParser;
 
@@ -63,8 +63,10 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.function.BooleanSupplier;
 
 import javax.xml.datatype.DatatypeConfigurationException;
@@ -94,21 +96,49 @@
     private static final BooleanSupplier FALSE_BOOLEAN_SUPPLIER = () -> false;
 
     @VisibleForTesting
-    static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(MINIMUM_DEVICE_STATE_IDENTIFIER,
-            "DEFAULT", 0 /* flags */);
+    static final DeviceState DEFAULT_DEVICE_STATE =
+            new DeviceState(new DeviceState.Configuration.Builder(MINIMUM_DEVICE_STATE_IDENTIFIER,
+                    "DEFAULT").build());
 
     private static final String VENDOR_CONFIG_FILE_PATH = "etc/devicestate/";
     private static final String DATA_CONFIG_FILE_PATH = "system/devicestate/";
     private static final String CONFIG_FILE_NAME = "device_state_configuration.xml";
-    private static final String FLAG_CANCEL_OVERRIDE_REQUESTS = "FLAG_CANCEL_OVERRIDE_REQUESTS";
-    private static final String FLAG_APP_INACCESSIBLE = "FLAG_APP_INACCESSIBLE";
-    private static final String FLAG_EMULATED_ONLY = "FLAG_EMULATED_ONLY";
-    private static final String FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
-            "FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP";
-    private static final String FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL =
-            "FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL";
-    private static final String FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE =
-            "FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE";
+    private static final String PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED =
+            "com.android.server.policy.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED";
+    private static final String PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN =
+            "com.android.server.policy.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN";
+    private static final String PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN =
+            "com.android.server.policy.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN";
+    private static final String PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS =
+            "com.android.server.policy.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS";
+    private static final String PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
+            "com.android.server.policy.PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP";
+    private static final String PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL =
+            "com.android.server.policy.PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL";
+    private static final String PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE =
+            "com.android.server.policy.PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE";
+    private static final String PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST =
+            "com.android.server.policy.PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST";
+    private static final String PROPERTY_APP_INACCESSIBLE =
+            "com.android.server.policy.PROPERTY_APP_INACCESSIBLE";
+    private static final String PROPERTY_EMULATED_ONLY =
+            "com.android.server.policy.PROPERTY_EMULATED_ONLY";
+    private static final String PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY =
+            "com.android.server.policy.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY";
+    private static final String PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY =
+            "com.android.server.policy.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY";
+    private static final String PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP =
+            "com.android.server.policy.PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP";
+    private static final String PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE =
+            "com.android.server.policy.PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE";
+    private static final String PROPERTY_EXTENDED_DEVICE_STATE_EXTERNAL_DISPLAY =
+            "com.android.server.policy.PROPERTY_EXTENDED_DEVICE_STATE_EXTERNAL_DISPLAY";
+    private static final String PROPERTY_FEATURE_REAR_DISPLAY =
+            "com.android.server.policy.PROPERTY_FEATURE_REAR_DISPLAY";
+    private static final String PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT =
+            "com.android.server.policy.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT";
+
+
 
     /** Interface that allows reading the device state configuration. */
     interface ReadableConfig {
@@ -149,40 +179,25 @@
                     final int state = stateConfig.getIdentifier().intValue();
                     final String name = stateConfig.getName() == null ? "" : stateConfig.getName();
 
-                    int flags = 0;
-                    final Flags configFlags = stateConfig.getFlags();
+                    Set<@DeviceState.DeviceStateProperties Integer> systemProperties =
+                            new HashSet<>();
+                    Set<@DeviceState.DeviceStateProperties Integer> physicalProperties =
+                            new HashSet<>();
+                    final Properties configFlags = stateConfig.getProperties();
                     if (configFlags != null) {
-                        List<String> configFlagStrings = configFlags.getFlag();
-                        for (int i = 0; i < configFlagStrings.size(); i++) {
-                            final String configFlagString = configFlagStrings.get(i);
-                            switch (configFlagString) {
-                                case FLAG_CANCEL_OVERRIDE_REQUESTS:
-                                    flags |= DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
-                                    break;
-                                case FLAG_APP_INACCESSIBLE:
-                                    flags |= DeviceState.FLAG_APP_INACCESSIBLE;
-                                    break;
-                                case FLAG_EMULATED_ONLY:
-                                    flags |= DeviceState.FLAG_EMULATED_ONLY;
-                                    break;
-                                case FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP:
-                                    flags |= DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
-                                    break;
-                                case FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL:
-                                    flags |= DeviceState
-                                            .FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL;
-                                    break;
-                                case FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE:
-                                    flags |= DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE;
-                                default:
-                                    Slog.w(TAG, "Parsed unknown flag with name: "
-                                            + configFlagString);
-                                    break;
-                            }
+                        List<String> configPropertyStrings = configFlags.getProperty();
+                        for (int i = 0; i < configPropertyStrings.size(); i++) {
+                            final String configPropertyString = configPropertyStrings.get(i);
+                            addPropertyByString(configPropertyString, systemProperties,
+                                    physicalProperties);
                         }
                     }
-
-                    deviceStateList.add(new DeviceState(state, name, flags));
+                    DeviceState.Configuration deviceStateConfiguration =
+                            new DeviceState.Configuration.Builder(state, name)
+                                    .setSystemProperties(systemProperties)
+                                    .setPhysicalProperties(physicalProperties)
+                                    .build();
+                    deviceStateList.add(new DeviceState(deviceStateConfiguration));
 
                     final Conditions condition = stateConfig.getConditions();
                     conditionsList.add(condition);
@@ -190,13 +205,88 @@
             }
         }
 
-        if (deviceStateList.size() == 0) {
+        if (deviceStateList.isEmpty()) {
             deviceStateList.add(DEFAULT_DEVICE_STATE);
             conditionsList.add(null);
         }
         return new DeviceStateProviderImpl(context, deviceStateList, conditionsList);
     }
 
+    private static void addPropertyByString(String propertyString,
+            Set<@DeviceState.SystemDeviceStateProperties Integer> systemProperties,
+            Set<@DeviceState.PhysicalDeviceStateProperties Integer> physicalProperties) {
+        switch (propertyString) {
+            // Look for the physical hardware properties first
+            case PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED:
+                physicalProperties.add(
+                        DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED);
+                break;
+            case PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN:
+                physicalProperties.add(
+                        DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN);
+                break;
+            case PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN:
+                physicalProperties.add(
+                        DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN);
+                break;
+            case PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS:
+                systemProperties.add(
+                        DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS);
+                break;
+            case PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP:
+                systemProperties.add(
+                        DeviceState.PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
+                break;
+            case PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL:
+                systemProperties.add(
+                        DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL);
+                break;
+            case PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE:
+                systemProperties.add(
+                        DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE);
+                break;
+            case PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST:
+                systemProperties.add(
+                        DeviceState.PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST);
+                break;
+            case PROPERTY_APP_INACCESSIBLE:
+                systemProperties.add(DeviceState.PROPERTY_APP_INACCESSIBLE);
+                break;
+            case PROPERTY_EMULATED_ONLY:
+                systemProperties.add(DeviceState.PROPERTY_EMULATED_ONLY);
+                break;
+            case PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY:
+                systemProperties.add(
+                        DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY);
+                break;
+            case PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY:
+                systemProperties.add(
+                        DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY);
+                break;
+            case PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP:
+                systemProperties.add(
+                        DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP);
+                break;
+            case PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE:
+                systemProperties.add(
+                        DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE);
+                break;
+            case PROPERTY_EXTENDED_DEVICE_STATE_EXTERNAL_DISPLAY:
+                systemProperties.add(
+                        DeviceState.PROPERTY_EXTENDED_DEVICE_STATE_EXTERNAL_DISPLAY);
+                break;
+            case PROPERTY_FEATURE_REAR_DISPLAY:
+                systemProperties.add(DeviceState.PROPERTY_FEATURE_REAR_DISPLAY);
+                break;
+            case PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT:
+                systemProperties.add(DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT);
+                break;
+            default:
+                Slog.w(TAG, "Parsed unknown flag with name: " + propertyString);
+                break;
+        }
+    }
+
     // Lock for internal state.
     private final Object mLock = new Object();
     private final Context mContext;
@@ -210,7 +300,7 @@
     @GuardedBy("mLock")
     private Listener mListener = null;
     @GuardedBy("mLock")
-    private int mLastReportedState = INVALID_DEVICE_STATE;
+    private int mLastReportedState = INVALID_DEVICE_STATE_IDENTIFIER;
 
     @GuardedBy("mLock")
     private Boolean mIsLidOpen;
@@ -285,7 +375,7 @@
             if (conditions == null) {
                 // If this state has the FLAG_EMULATED_ONLY flag on it, it should never be triggered
                 // by a physical hardware change, and should always return false for it's conditions
-                if (deviceStates.get(i).hasFlag(DeviceState.FLAG_EMULATED_ONLY)) {
+                if (deviceStates.get(i).hasProperty(DeviceState.PROPERTY_EMULATED_ONLY)) {
                     mStateConditions.put(state, FALSE_BOOLEAN_SUPPLIER);
                 } else {
                     mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER);
@@ -410,13 +500,13 @@
             }
             listener = mListener;
             for (DeviceState deviceState : mOrderedStates) {
-                if (isThermalStatusCriticalOrAbove(mThermalStatus)
-                        && deviceState.hasFlag(
-                                DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
+                if (isThermalStatusCriticalOrAbove(mThermalStatus) && deviceState.hasProperty(
+                        DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
+                )) {
                     continue;
                 }
-                if (mPowerSaveModeEnabled && deviceState.hasFlag(
-                        DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
+                if (mPowerSaveModeEnabled && deviceState.hasProperty(
+                        DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
                     continue;
                 }
                 supportedStates.add(deviceState);
@@ -424,18 +514,19 @@
         }
 
         listener.onSupportedDeviceStatesChanged(
-                supportedStates.toArray(new DeviceState[supportedStates.size()]), reason);
+                supportedStates.toArray(new DeviceState[supportedStates.size()]),
+                reason);
     }
 
     /** Computes the current device state and notifies the listener of a change, if needed. */
     void notifyDeviceStateChangedIfNeeded() {
-        int stateToReport = INVALID_DEVICE_STATE;
+        int stateToReport = INVALID_DEVICE_STATE_IDENTIFIER;
         synchronized (mLock) {
             if (mListener == null) {
                 return;
             }
 
-            int newState = INVALID_DEVICE_STATE;
+            int newState = INVALID_DEVICE_STATE_IDENTIFIER;
             for (int i = 0; i < mOrderedStates.length; i++) {
                 int state = mOrderedStates[i].getIdentifier();
                 if (DEBUG) {
@@ -464,18 +555,18 @@
                     break;
                 }
             }
-            if (newState == INVALID_DEVICE_STATE) {
+            if (newState == INVALID_DEVICE_STATE_IDENTIFIER) {
                 Slog.e(TAG, "No declared device states match any of the required conditions.");
                 dumpSensorValues();
             }
 
-            if (newState != INVALID_DEVICE_STATE && newState != mLastReportedState) {
+            if (newState != INVALID_DEVICE_STATE_IDENTIFIER && newState != mLastReportedState) {
                 mLastReportedState = newState;
                 stateToReport = newState;
             }
         }
 
-        if (stateToReport != INVALID_DEVICE_STATE) {
+        if (stateToReport != INVALID_DEVICE_STATE_IDENTIFIER) {
             mListener.onStateChanged(stateToReport);
         }
     }
@@ -774,8 +865,9 @@
     }
 
     private static boolean hasThermalSensitiveState(List<DeviceState> deviceStates) {
-        for (DeviceState state : deviceStates) {
-            if (state.hasFlag(DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
+        for (int i = 0; i < deviceStates.size(); i++) {
+            if (deviceStates.get(i).hasProperty(
+                    DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
                 return true;
             }
         }
@@ -784,7 +876,8 @@
 
     private static boolean hasPowerSaveSensitiveState(List<DeviceState> deviceStates) {
         for (int i = 0; i < deviceStates.size(); i++) {
-            if (deviceStates.get(i).hasFlag(DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
+            if (deviceStates.get(i).hasProperty(
+                    DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
                 return true;
             }
         }
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index 539bb6b..3a79d0d 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -17,6 +17,7 @@
 package com.android.server.policy;
 
 import android.annotation.SuppressLint;
+import android.app.role.RoleManager;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
@@ -45,6 +46,8 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * Manages quick launch shortcuts by:
@@ -52,8 +55,8 @@
  * <li> Returning a shortcut-matching intent to clients
  * <li> Returning particular kind of application intent by special key.
  */
-class ModifierShortcutManager {
-    private static final String TAG = "WindowManager";
+public class ModifierShortcutManager {
+    private static final String TAG = "ModifierShortcutManager";
 
     private static final String TAG_BOOKMARKS = "bookmarks";
     private static final String TAG_BOOKMARK = "bookmark";
@@ -63,9 +66,13 @@
     private static final String ATTRIBUTE_SHORTCUT = "shortcut";
     private static final String ATTRIBUTE_CATEGORY = "category";
     private static final String ATTRIBUTE_SHIFT = "shift";
+    private static final String ATTRIBUTE_ROLE = "role";
 
     private final SparseArray<Intent> mIntentShortcuts = new SparseArray<>();
     private final SparseArray<Intent> mShiftShortcuts = new SparseArray<>();
+    private final SparseArray<String> mRoleShortcuts = new SparseArray<String>();
+    private final SparseArray<String> mShiftRoleShortcuts = new SparseArray<String>();
+    private final Map<String, Intent> mRoleIntents = new HashMap<String, Intent>();
 
     private LongSparseArray<IShortcutService> mShortcutKeyServices = new LongSparseArray<>();
 
@@ -76,10 +83,12 @@
      * usage page.  We don't support quite that many yet...
      */
     static SparseArray<String> sApplicationLaunchKeyCategories;
+    static SparseArray<String> sApplicationLaunchKeyRoles;
     static {
+        sApplicationLaunchKeyRoles = new SparseArray<String>();
         sApplicationLaunchKeyCategories = new SparseArray<String>();
-        sApplicationLaunchKeyCategories.append(
-                KeyEvent.KEYCODE_EXPLORER, Intent.CATEGORY_APP_BROWSER);
+        sApplicationLaunchKeyRoles.append(
+                KeyEvent.KEYCODE_EXPLORER, RoleManager.ROLE_BROWSER);
         sApplicationLaunchKeyCategories.append(
                 KeyEvent.KEYCODE_ENVELOPE, Intent.CATEGORY_APP_EMAIL);
         sApplicationLaunchKeyCategories.append(
@@ -92,14 +101,25 @@
                 KeyEvent.KEYCODE_CALCULATOR, Intent.CATEGORY_APP_CALCULATOR);
     }
 
+    public static final String EXTRA_ROLE =
+            "com.android.server.policy.ModifierShortcutManager.EXTRA_ROLE";
+
     private final Context mContext;
     private final Handler mHandler;
+    private final RoleManager mRoleManager;
+    private final PackageManager mPackageManager;
     private boolean mSearchKeyShortcutPending = false;
     private boolean mConsumeSearchKeyUp = true;
 
     ModifierShortcutManager(Context context, Handler handler) {
         mContext = context;
         mHandler = handler;
+        mPackageManager = mContext.getPackageManager();
+        mRoleManager = mContext.getSystemService(RoleManager.class);
+        mRoleManager.addOnRoleHoldersChangedListenerAsUser(mContext.getMainExecutor(),
+                (String roleName, UserHandle user) -> {
+                    mRoleIntents.remove(roleName);
+                }, UserHandle.ALL);
         loadShortcuts();
     }
 
@@ -141,14 +161,42 @@
             shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
             if (shortcutChar != 0) {
                 shortcutIntent = shortcutMap.get(shortcutChar);
+
+                if (shortcutIntent == null) {
+                    // Check for role based shortcut
+                    String role = isShiftOn ? mShiftRoleShortcuts.get(shortcutChar)
+                            : mRoleShortcuts.get(shortcutChar);
+                    if (role != null) {
+                        shortcutIntent = getRoleLaunchIntent(role);
+                    }
+                }
             }
         }
 
         return shortcutIntent;
     }
 
+    private Intent getRoleLaunchIntent(String role) {
+        Intent intent = mRoleIntents.get(role);
+        if (intent == null) {
+            if (mRoleManager.isRoleAvailable(role)) {
+                String rolePackage = mRoleManager.getDefaultApplication(role);
+                if (rolePackage != null) {
+                    intent = mPackageManager.getLaunchIntentForPackage(rolePackage);
+                    intent.putExtra(EXTRA_ROLE, role);
+                    mRoleIntents.put(role, intent);
+                } else {
+                    Log.w(TAG, "No default application for role " + role);
+                }
+            } else {
+                Log.w(TAG, "Role " + role + " is not available.");
+            }
+        }
+        return intent;
+    }
+
     private void loadShortcuts() {
-        PackageManager packageManager = mContext.getPackageManager();
+
         try {
             XmlResourceParser parser = mContext.getResources().getXml(
                     com.android.internal.R.xml.bookmarks);
@@ -170,29 +218,37 @@
                 String shortcutName = parser.getAttributeValue(null, ATTRIBUTE_SHORTCUT);
                 String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY);
                 String shiftName = parser.getAttributeValue(null, ATTRIBUTE_SHIFT);
+                String roleName = parser.getAttributeValue(null, ATTRIBUTE_ROLE);
 
                 if (TextUtils.isEmpty(shortcutName)) {
-                    Log.w(TAG, "Unable to get shortcut for: " + packageName + "/" + className);
+                    Log.w(TAG, "Shortcut required for bookmark with category=" + categoryName
+                            + " packageName=" + packageName + " className=" + className
+                            + " role=" + roleName + "shiftName=" + shiftName);
                     continue;
                 }
 
                 final int shortcutChar = shortcutName.charAt(0);
                 final boolean isShiftShortcut = (shiftName != null && shiftName.equals("true"));
-
                 final Intent intent;
                 if (packageName != null && className != null) {
+                    if (roleName != null || categoryName != null) {
+                        Log.w(TAG, "Cannot specify role or category when package and class"
+                                + " are present for bookmark packageName=" + packageName
+                                + " className=" + className + " shortcutChar=" + shortcutChar);
+                        continue;
+                    }
                     ComponentName componentName = new ComponentName(packageName, className);
                     try {
-                        packageManager.getActivityInfo(componentName,
+                        mPackageManager.getActivityInfo(componentName,
                                 PackageManager.MATCH_DIRECT_BOOT_AWARE
                                         | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                                         | PackageManager.MATCH_UNINSTALLED_PACKAGES);
                     } catch (PackageManager.NameNotFoundException e) {
-                        String[] packages = packageManager.canonicalToCurrentPackageNames(
+                        String[] packages = mPackageManager.canonicalToCurrentPackageNames(
                                 new String[] { packageName });
                         componentName = new ComponentName(packages[0], className);
                         try {
-                            packageManager.getActivityInfo(componentName,
+                            mPackageManager.getActivityInfo(componentName,
                                     PackageManager.MATCH_DIRECT_BOOT_AWARE
                                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                                             | PackageManager.MATCH_UNINSTALLED_PACKAGES);
@@ -207,10 +263,25 @@
                     intent.addCategory(Intent.CATEGORY_LAUNCHER);
                     intent.setComponent(componentName);
                 } else if (categoryName != null) {
+                    if (roleName != null) {
+                        Log.w(TAG, "Cannot specify role bookmark when category is present for"
+                                + " bookmark shortcutChar=" + shortcutChar
+                                + " category= " + categoryName);
+                        continue;
+                    }
                     intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, categoryName);
+                } else if (roleName != null) {
+                    // We can't resolve the role at the time of this file being parsed as the
+                    // device hasn't finished booting, so we will look it up lazily.
+                    if (isShiftShortcut) {
+                        mShiftRoleShortcuts.put(shortcutChar, roleName);
+                    } else {
+                        mRoleShortcuts.put(shortcutChar, roleName);
+                    }
+                    continue;
                 } else {
                     Log.w(TAG, "Unable to add bookmark for shortcut " + shortcutName
-                            + ": missing package/class or category attributes");
+                            + ": missing package/class, category or role attributes");
                     continue;
                 }
 
@@ -298,10 +369,17 @@
             // Invoke shortcuts using Meta.
             metaState &= ~KeyEvent.META_META_MASK;
         } else {
+            Intent intent = null;
             // Handle application launch keys.
+            String role = sApplicationLaunchKeyRoles.get(keyCode);
             String category = sApplicationLaunchKeyCategories.get(keyCode);
-            if (category != null) {
-                Intent intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, category);
+            if (role != null) {
+                intent = getRoleLaunchIntent(role);
+            } else if (category != null) {
+                intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, category);
+            }
+
+            if (intent != null) {
                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                 try {
                     mContext.startActivityAsUser(intent, UserHandle.CURRENT);
@@ -309,7 +387,7 @@
                     Slog.w(TAG, "Dropping application launch key because "
                             + "the activity to which it is registered was not found: "
                             + "keyCode=" + KeyEvent.keyCodeToString(keyCode) + ","
-                            + " category=" + category);
+                            + " category=" + category + " role=" + role);
                 }
                 logKeyboardShortcut(keyEvent, KeyboardLogEvent.getLogEventFromIntent(intent));
                 return true;
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index f15646f..98499417 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -237,7 +237,7 @@
         mAppOpsCallback = new IAppOpsCallback.Stub() {
             public void opChanged(int op, int uid, @Nullable String packageName,
                     String persistentDeviceId) {
-                if (Objects.equals(persistentDeviceId,
+                if (!Objects.equals(persistentDeviceId,
                         VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)) {
                     return;
                 }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 8781bf1..266418f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3504,8 +3504,8 @@
                 if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
                     if (statusbar != null) {
-                        statusbar.goToFullscreenFromSplit();
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
+                        statusbar.moveFocusedTaskToFullscreen(getTargetDisplayIdForKeyEvent(event));
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.MULTI_WINDOW_NAVIGATION);
                         return true;
                     }
                 }
@@ -3514,7 +3514,7 @@
                 if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
                     if (statusbar != null) {
-                        statusbar.enterDesktop(event.getDisplayId());
+                        statusbar.enterDesktop(getTargetDisplayIdForKeyEvent(event));
                         logKeyboardSystemsEvent(event, KeyboardLogEvent.DESKTOP_MODE);
                         return true;
                     }
@@ -3523,7 +3523,8 @@
             case KeyEvent.KEYCODE_DPAD_LEFT:
                 if (firstDown && event.isMetaPressed()) {
                     if (event.isCtrlPressed()) {
-                        enterStageSplitFromRunningApp(true /* leftOrTop */);
+                        moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event),
+                                true /* leftOrTop */);
                         logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
                     } else {
                         logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK);
@@ -3534,7 +3535,8 @@
                 break;
             case KeyEvent.KEYCODE_DPAD_RIGHT:
                 if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
-                    enterStageSplitFromRunningApp(false /* leftOrTop */);
+                    moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event),
+                            false /* leftOrTop */);
                     logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
                     return true;
                 }
@@ -4313,6 +4315,7 @@
             boolean allowDuringSetup) {
         if (allowDuringSetup || isUserSetupComplete()) {
             mContext.startActivityAsUser(intent, bundle, handle);
+            dismissKeyboardShortcutsMenu();
         } else {
             Slog.i(TAG, "Not starting activity because user setup is in progress: " + intent);
         }
@@ -4363,6 +4366,7 @@
         if (statusbar != null) {
             statusbar.showRecentApps(triggeredFromAltTab);
         }
+        dismissKeyboardShortcutsMenu();
     }
 
     private void toggleKeyboardShortcutsMenu(int deviceId) {
@@ -4387,10 +4391,10 @@
         }
     }
 
-    private void enterStageSplitFromRunningApp(boolean leftOrTop) {
+    private void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) {
         StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
         if (statusbar != null) {
-            statusbar.enterStageSplitFromRunningApp(leftOrTop);
+            statusbar.moveFocusedTaskToStageSplit(displayId, leftOrTop);
         }
     }
 
@@ -6410,7 +6414,7 @@
 
     private boolean performHapticFeedback(int effectId, boolean always, String reason) {
         return performHapticFeedback(Process.myUid(), mContext.getOpPackageName(),
-            effectId, always, reason);
+            effectId, always, reason, false /* fromIme */);
     }
 
     @Override
@@ -6420,7 +6424,7 @@
 
     @Override
     public boolean performHapticFeedback(int uid, String packageName, int effectId,
-            boolean always, String reason) {
+            boolean always, String reason, boolean fromIme) {
         if (!mVibrator.hasVibrator()) {
             return false;
         }
@@ -6431,7 +6435,7 @@
         }
         VibrationAttributes attrs =
                 mHapticFeedbackVibrationProvider.getVibrationAttributesForHapticFeedback(
-                        effectId, /* bypassVibrationIntensitySetting= */ always);
+                        effectId, /* bypassVibrationIntensitySetting= */ always, fromIme);
         VibratorFrameworkStatsLogger.logPerformHapticsFeedbackIfKeyboard(uid, effectId);
         mVibrator.vibrate(uid, packageName, effect, reason, attrs);
         return true;
@@ -6951,4 +6955,18 @@
                     == PERMISSION_GRANTED;
         }
     }
+
+    private int getTargetDisplayIdForKeyEvent(KeyEvent event) {
+        int displayId = event.getDisplayId();
+
+        if (displayId == INVALID_DISPLAY) {
+            displayId = mTopFocusedDisplayId;
+        }
+
+        if (displayId == INVALID_DISPLAY) {
+            return DEFAULT_DISPLAY;
+        } else {
+            return displayId;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
index 32a21c5..cebf7fb 100644
--- a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
+++ b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
@@ -21,7 +21,6 @@
 import static android.Manifest.permission.WRITE_MEDIA_STORAGE;
 import static android.app.AppOpsManager.OP_LEGACY_STORAGE;
 import static android.app.AppOpsManager.OP_NONE;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
@@ -148,7 +147,7 @@
                             pkg.hasPreserveLegacyExternalStorage();
                     targetSDK = getMinimumTargetSDK(context, appInfo, user);
 
-                    shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
+                    shouldApplyRestriction = !isWhiteListed;
                     isForcedScopedStorage = sForcedScopedStorageAppWhitelist
                             .contains(appInfo.packageName);
                 } else {
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 2174fd6..5956594 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -1072,7 +1072,7 @@
      * Call from application to perform haptic feedback on its window.
      */
     public boolean performHapticFeedback(int uid, String packageName, int effectId,
-            boolean always, String reason);
+            boolean always, String reason, boolean fromIme);
 
     /**
      * Called when we have started keeping the screen on because a window
diff --git a/services/core/java/com/android/server/power/Android.bp b/services/core/java/com/android/server/power/Android.bp
index 607d435..863ff76 100644
--- a/services/core/java/com/android/server/power/Android.bp
+++ b/services/core/java/com/android/server/power/Android.bp
@@ -9,4 +9,5 @@
 java_aconfig_library {
     name: "backstage_power_flags_lib",
     aconfig_declarations: "backstage_power_flags",
+    sdk_version: "system_current",
 }
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 652cf18..fde49d2 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -902,9 +902,9 @@
         }
 
         if (mActivityManagerInternal.isSystemReady()) {
-            final boolean ordered = !mActivityManagerInternal.isModernQueueEnabled();
-            mActivityManagerInternal.broadcastIntent(mScreenOnIntent, mWakeUpBroadcastDone,
-                    null, ordered, UserHandle.USER_ALL, null, null, mScreenOnOffOptions);
+            mActivityManagerInternal.broadcastIntentWithCallback(mScreenOnIntent,
+                    mWakeUpBroadcastDone, null, UserHandle.USER_ALL,
+                    null, null, mScreenOnOffOptions);
         } else {
             EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 2, 1);
             sendNextBroadcast();
@@ -927,9 +927,9 @@
         }
 
         if (mActivityManagerInternal.isSystemReady()) {
-            final boolean ordered = !mActivityManagerInternal.isModernQueueEnabled();
-            mActivityManagerInternal.broadcastIntent(mScreenOffIntent, mGoToSleepBroadcastDone,
-                    null, ordered, UserHandle.USER_ALL, null, null, mScreenOnOffOptions);
+            mActivityManagerInternal.broadcastIntentWithCallback(mScreenOffIntent,
+                    mGoToSleepBroadcastDone, null, UserHandle.USER_ALL,
+                    null, null, mScreenOnOffOptions);
         } else {
             EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 3, 1);
             sendNextBroadcast();
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index a172de0..b50e2bf 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -7149,7 +7149,7 @@
      * Any changes to the device state are treated as user interactions.
      */
     class DeviceStateListener implements DeviceStateManager.DeviceStateCallback {
-        private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
+        private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
 
         @Override
         public void onStateChanged(int deviceState) {
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 24d7acd..5360788 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -700,6 +700,8 @@
                     return runOverrideStatus();
                 case "reset":
                     return runReset();
+                case "headroom":
+                    return runHeadroom();
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -862,6 +864,36 @@
             }
         }
 
+        private int runHeadroom() {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                final PrintWriter pw = getOutPrintWriter();
+                int forecastSecs;
+                try {
+                    forecastSecs = Integer.parseInt(getNextArgRequired());
+                } catch (RuntimeException ex) {
+                    pw.println("Error: " + ex);
+                    return -1;
+                }
+                if (!mHalReady.get()) {
+                    pw.println("Error: thermal HAL is not ready");
+                    return -1;
+                }
+
+                if (forecastSecs < MIN_FORECAST_SEC || forecastSecs > MAX_FORECAST_SEC) {
+                    pw.println(
+                            "Error: forecast second input should be in range [" + MIN_FORECAST_SEC
+                                    + "," + MAX_FORECAST_SEC + "]");
+                    return -1;
+                }
+                float headroom = mTemperatureWatcher.getForecast(forecastSecs);
+                pw.println("Headroom in " + forecastSecs + " seconds: " + headroom);
+                return 0;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
         @Override
         public void onHelp() {
             final PrintWriter pw = getOutPrintWriter();
@@ -877,6 +909,9 @@
             pw.println("    status code is defined in android.os.Temperature.");
             pw.println("  reset");
             pw.println("    unlocks the thermal status of the device.");
+            pw.println("  headroom FORECAST_SECONDS");
+            pw.println("    gets the thermal headroom forecast in specified seconds, from ["
+                    + MIN_FORECAST_SEC + "," + MAX_FORECAST_SEC + "].");
             pw.println();
         }
     }
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index f8c678a..52ef87c 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -45,11 +45,9 @@
 import static android.hardware.SensorPrivacyManager.Sources.QS_TILE;
 import static android.hardware.SensorPrivacyManager.Sources.SETTINGS;
 import static android.hardware.SensorPrivacyManager.Sources.SHELL;
-import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
-import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
-import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
 import static android.hardware.SensorPrivacyManager.StateTypes.DISABLED;
 import static android.hardware.SensorPrivacyManager.StateTypes.ENABLED;
+import static android.hardware.SensorPrivacyManager.StateTypes.ENABLED_EXCEPT_ALLOWLISTED_APPS;
 import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_HARDWARE;
 import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE;
 import static android.os.UserHandle.USER_NULL;
@@ -57,11 +55,9 @@
 
 import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION;
 import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__ACTION_UNKNOWN;
-import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
-import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
-import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
 import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF;
 import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON;
+import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON_EXCEPT_ALLOWLISTED_APPS;
 import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__CAMERA;
 import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__MICROPHONE;
 import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__SENSOR_UNKNOWN;
@@ -98,7 +94,6 @@
 import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.graphics.drawable.Icon;
-import android.hardware.CameraPrivacyAllowlistEntry;
 import android.hardware.ISensorPrivacyListener;
 import android.hardware.ISensorPrivacyManager;
 import android.hardware.SensorPrivacyManager;
@@ -153,7 +148,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Objects;
 
@@ -170,18 +164,12 @@
 
     public static final int REMINDER_DIALOG_DELAY_MILLIS = 500;
     @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
-    private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS =
-            PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
-    @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
-    private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS =
-            PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
-    @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
-    private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS =
-            PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
-    @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
     private static final int ACTION__TOGGLE_ON =
             PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON;
     @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
+    private static final int ACTION__TOGGLE_ON_EXCEPT_ALLOWLISTED_APPS =
+            PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON_EXCEPT_ALLOWLISTED_APPS;
+    @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
     private static final int ACTION__TOGGLE_OFF =
             PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF;
     @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
@@ -208,8 +196,7 @@
     private CallStateHelper mCallStateHelper;
     private KeyguardManager mKeyguardManager;
 
-    List<CameraPrivacyAllowlistEntry> mCameraPrivacyAllowlist =
-            new ArrayList<CameraPrivacyAllowlistEntry>();
+    List<String> mCameraPrivacyAllowlist = new ArrayList<String>();
 
     private int mCurrentUser = USER_NULL;
 
@@ -227,14 +214,8 @@
         mPackageManagerInternal = getLocalService(PackageManagerInternal.class);
         mNotificationManager = mContext.getSystemService(NotificationManager.class);
         mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl();
-        ArrayMap<String, Boolean> cameraPrivacyAllowlist =
-                SystemConfig.getInstance().getCameraPrivacyAllowlist();
-
-        for (Map.Entry<String, Boolean> entry : cameraPrivacyAllowlist.entrySet()) {
-            CameraPrivacyAllowlistEntry ent = new CameraPrivacyAllowlistEntry();
-            ent.packageName = entry.getKey();
-            ent.isMandatory =  entry.getValue();
-            mCameraPrivacyAllowlist.add(ent);
+        for (String entry : SystemConfig.getInstance().getCameraPrivacyAllowlist()) {
+            mCameraPrivacyAllowlist.add(entry);
         }
     }
 
@@ -908,14 +889,8 @@
                     case DISABLED :
                         logAction = ACTION__TOGGLE_ON;
                         break;
-                    case AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS :
-                        logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
-                        break;
-                    case AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS :
-                        logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
-                        break;
-                    case AUTOMOTIVE_DRIVER_ASSISTANCE_APPS :
-                        logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
+                    case ENABLED_EXCEPT_ALLOWLISTED_APPS :
+                        logAction = ACTION__TOGGLE_ON_EXCEPT_ALLOWLISTED_APPS;
                         break;
                     default :
                         logAction = ACTION__ACTION_UNKNOWN;
@@ -981,11 +956,23 @@
         @Override
         @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
         @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
-        public List<CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist() {
+        public List<String> getCameraPrivacyAllowlist() {
             enforceObserveSensorPrivacyPermission();
             return mCameraPrivacyAllowlist;
         }
 
+        /**
+         * Sets camera privacy allowlist.
+         * @param allowlist List of automotive driver assistance packages for
+         * privacy allowlisting.
+         * @hide
+         */
+        @Override
+        public void setCameraPrivacyAllowlist(List<String> allowlist) {
+            enforceManageSensorPrivacyPermission();
+            mCameraPrivacyAllowlist =  new ArrayList<>(allowlist);
+        }
+
         @Override
         @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
         @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
@@ -1005,23 +992,9 @@
                 return true;
             } else if (state == DISABLED) {
                 return false;
-            } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS) {
-                for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) {
-                    if ((packageName.equals(entry.packageName)) && !entry.isMandatory) {
-                        return false;
-                    }
-                }
-                return true;
-            } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS) {
-                for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) {
-                    if ((packageName.equals(entry.packageName)) && entry.isMandatory) {
-                        return false;
-                    }
-                }
-                return true;
-            } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_APPS) {
-                for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) {
-                    if (packageName.equals(entry.packageName)) {
+            } else if (state == ENABLED_EXCEPT_ALLOWLISTED_APPS) {
+                for (String entry : mCameraPrivacyAllowlist) {
+                    if (packageName.equals(entry)) {
                         return false;
                     }
                 }
@@ -1616,7 +1589,7 @@
                             setToggleSensorPrivacy(userId, SHELL, sensor, false);
                         }
                         break;
-                        case "automotive_driver_assistance_apps" : {
+                        case "enable_except_allowlisted_apps" : {
                             if (Flags.cameraPrivacyAllowlist()) {
                                 int sensor = sensorStrToId(getNextArgRequired());
                                 if ((!isAutomotive(mContext)) || (sensor != CAMERA)) {
@@ -1625,33 +1598,7 @@
                                 }
 
                                 setToggleSensorPrivacyState(userId, SHELL, sensor,
-                                        AUTOMOTIVE_DRIVER_ASSISTANCE_APPS);
-                            }
-                        }
-                        break;
-                        case "automotive_driver_assistance_helpful_apps" : {
-                            if (Flags.cameraPrivacyAllowlist()) {
-                                int sensor = sensorStrToId(getNextArgRequired());
-                                if ((!isAutomotive(mContext)) || (sensor != CAMERA)) {
-                                    pw.println("Command not valid for this sensor");
-                                    return -1;
-                                }
-
-                                setToggleSensorPrivacyState(userId, SHELL, sensor,
-                                        AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS);
-                            }
-                        }
-                        break;
-                        case "automotive_driver_assistance_required_apps" : {
-                            if (Flags.cameraPrivacyAllowlist()) {
-                                int sensor = sensorStrToId(getNextArgRequired());
-                                if ((!isAutomotive(mContext)) || (sensor != CAMERA)) {
-                                    pw.println("Command not valid for this sensor");
-                                    return -1;
-                                }
-
-                                setToggleSensorPrivacyState(userId, SHELL, sensor,
-                                        AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS);
+                                        ENABLED_EXCEPT_ALLOWLISTED_APPS);
                             }
                         }
                         break;
@@ -1679,18 +1626,9 @@
                     pw.println("");
                     if (Flags.cameraPrivacyAllowlist()) {
                         if (isAutomotive(mContext)) {
-                            pw.println("  automotive_driver_assistance_apps USER_ID SENSOR");
-                            pw.println("    Disable privacy for automotive apps which help you"
-                                    + " drive and apps which are required by OEM");
-                            pw.println("");
-                            pw.println("  automotive_driver_assistance_helpful_apps "
+                            pw.println("  enable_except_allowlisted_apps "
                                     + "USER_ID SENSOR");
-                            pw.println("    Disable privacy for automotive apps which "
-                                    + "help you drive.");
-                            pw.println("");
-                            pw.println("  automotive_driver_assistance_required_apps "
-                                    + "USER_ID SENSOR");
-                            pw.println("    Disable privacy for automotive apps which are "
+                            pw.println("    Enable privacy except for automotive apps which are "
                                     + "required by OEM.");
                             pw.println("");
                         }
diff --git a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
index b0dcf95..881583a 100644
--- a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
+++ b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
@@ -282,10 +282,7 @@
             Slog.d(TAG,
                     "pullDataBytesTransferLocked() done. results count " + pulledData.size());
         }
-        if (!pulledData.isEmpty()) {
-            return StatsManager.PULL_SUCCESS;
-        }
-        return StatsManager.PULL_SKIP;
+        return StatsManager.PULL_SUCCESS;
     }
 
     private static boolean isEmpty(NetworkStats stats) {
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 3c6baa8..c73f89c 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -224,16 +224,18 @@
     void showRearDisplayDialog(int currentBaseState);
 
     /**
-     * Called when requested to go to fullscreen from the active split app.
+     * Called when requested to go to fullscreen from the focused app.
+     *
+     * @param displayId of the current display.
      */
-    void goToFullscreenFromSplit();
+    void moveFocusedTaskToFullscreen(int displayId);
 
     /**
      * Enters stage split from a current running app.
      *
-     * @see com.android.internal.statusbar.IStatusBar#enterStageSplitFromRunningApp
+     * @see com.android.internal.statusbar.IStatusBar#moveFocusedTaskToStageSplit
      */
-    void enterStageSplitFromRunningApp(boolean leftOrTop);
+    void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop);
 
     /**
      * Shows the media output switcher dialog.
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 14c38bd..214dbe0 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -810,21 +810,21 @@
         }
 
         @Override
-        public void goToFullscreenFromSplit() {
+        public void moveFocusedTaskToFullscreen(int displayId) {
             IStatusBar bar = mBar;
             if (bar != null) {
                 try {
-                    bar.goToFullscreenFromSplit();
+                    bar.moveFocusedTaskToFullscreen(displayId);
                 } catch (RemoteException ex) { }
             }
         }
 
         @Override
-        public void enterStageSplitFromRunningApp(boolean leftOrTop) {
+        public void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) {
             IStatusBar bar = mBar;
             if (bar != null) {
                 try {
-                    bar.enterStageSplitFromRunningApp(leftOrTop);
+                    bar.moveFocusedTaskToStageSplit(displayId, leftOrTop);
                 } catch (RemoteException ex) { }
             }
         }
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index ffce50e..34c90f1 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -349,7 +349,7 @@
             }
         }
 
-        userState.mIAppMap.clear();
+        userState.mAdServiceMap.clear();
         userState.mAdServiceMap = adServiceMap;
     }
 
@@ -989,7 +989,7 @@
                         return;
                     }
                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
-                    TvAdServiceState adState = userState.mAdMap.get(serviceId);
+                    TvAdServiceState adState = userState.mAdServiceMap.get(serviceId);
                     if (adState == null) {
                         Slogf.w(TAG, "Failed to find state for serviceId=" + serviceId);
                         sendAdSessionTokenToClientLocked(client, serviceId, null, null, seq);
@@ -3032,6 +3032,7 @@
             ITvAdService service, IBinder sessionToken, int userId) {
         UserState userState = getOrCreateUserStateLocked(userId);
         AdSessionState sessionState = userState.mAdSessionStateMap.get(sessionToken);
+
         if (DEBUG) {
             Slogf.d(TAG, "createAdSessionInternalLocked(iAppServiceId="
                     + sessionState.mAdServiceId + ")");
@@ -3301,8 +3302,6 @@
 
     private static final class UserState {
         private final int mUserId;
-        // A mapping from the TV AD service ID to its TvAdServiceState.
-        private Map<String, TvAdServiceState> mAdMap = new HashMap<>();
         // A mapping from the name of a TV Interactive App service to its state.
         private final Map<ComponentName, AdServiceState> mAdServiceStateMap = new HashMap<>();
         // A mapping from the token of a TV Interactive App session to its state.
diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java
index 743005a..b7d8cfc 100644
--- a/services/core/java/com/android/server/utils/AnrTimer.java
+++ b/services/core/java/com/android/server/utils/AnrTimer.java
@@ -767,7 +767,7 @@
      * Return true if the native timers are supported.  Native timers are supported if the method
      * nativeAnrTimerSupported() can be executed and it returns true.
      */
-    private static boolean nativeTimersSupported() {
+    public static boolean nativeTimersSupported() {
         try {
             return nativeAnrTimerSupported();
         } catch (java.lang.UnsatisfiedLinkError e) {
diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
index 17f5e95..7206c03 100644
--- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
+++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
@@ -164,7 +164,6 @@
         registerCarrierPrivilegesCallbacks();
     }
 
-    // TODO(b/221306368): Refactor with the new onCarrierServiceChange in the new CPCallback
     private void registerCarrierPrivilegesCallbacks() {
         final HandlerExecutor executor = new HandlerExecutor(mHandler);
         final int modemCount = mTelephonyManager.getActiveModemCount();
diff --git a/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java b/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
index fb5140d..48dd992 100644
--- a/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
@@ -51,8 +51,8 @@
     public List<Step> cancel() {
         if (mCancelled) {
             // Double cancelling will just turn off the vibrator right away.
-            return Arrays.asList(
-                    new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(), controller));
+            return Arrays.asList(new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(),
+                    controller, /* isCleanUp= */ true));
         }
         return super.cancel();
     }
@@ -92,8 +92,8 @@
                 } else {
                     // Vibration is completing normally, turn off after the deadline in case we
                     // don't receive the callback in time (callback also triggers it right away).
-                    return Arrays.asList(new TurnOffVibratorStep(
-                            conductor, mPendingVibratorOffDeadline, controller));
+                    return Arrays.asList(new TurnOffVibratorStep(conductor,
+                            mPendingVibratorOffDeadline, controller, /* isCleanUp= */ false));
                 }
             }
 
diff --git a/services/core/java/com/android/server/vibrator/GroupedAggregatedLogRecords.java b/services/core/java/com/android/server/vibrator/GroupedAggregatedLogRecords.java
new file mode 100644
index 0000000..7ee2901
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/GroupedAggregatedLogRecords.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.SystemClock;
+import android.util.IndentingPrintWriter;
+import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
+
+import java.util.ArrayDeque;
+
+/**
+ * A generic grouped list of aggregated log records to be printed in dumpsys.
+ *
+ * <p>This can be used to dump history of operations or requests to the vibrator services, e.g.
+ * vibration requests grouped by usage or vibration parameters sent to the vibrator control service.
+ *
+ * @param <T> The type of log entries aggregated in this record.
+ */
+abstract class GroupedAggregatedLogRecords<T extends GroupedAggregatedLogRecords.SingleLogRecord> {
+    private final SparseArray<ArrayDeque<AggregatedLogRecord<T>>> mGroupedRecords;
+    private final int mSizeLimit;
+    private final int mAggregationTimeLimitMs;
+
+    GroupedAggregatedLogRecords(int sizeLimit, int aggregationTimeLimitMs) {
+        mGroupedRecords = new SparseArray<>();
+        mSizeLimit = sizeLimit;
+        mAggregationTimeLimitMs = aggregationTimeLimitMs;
+    }
+
+    /** Prints a header to identify the group to be logged. */
+    abstract void dumpGroupHeader(IndentingPrintWriter pw, int groupKey);
+
+    /** Returns the {@link ProtoOutputStream} repeated field id to log records of this group. */
+    abstract long findGroupKeyProtoFieldId(int groupKey);
+
+    /**
+     * Adds given entry to this record list, dropping the oldest record if size limit was reached
+     * for its group.
+     *
+     * @param record The new {@link SingleLogRecord} to be recorded.
+     * @return The oldest {@link AggregatedLogRecord} entry being dropped from the group list if
+     * it's full, null otherwise.
+     */
+    final synchronized AggregatedLogRecord<T> add(T record) {
+        int groupKey = record.getGroupKey();
+        if (!mGroupedRecords.contains(groupKey)) {
+            mGroupedRecords.put(groupKey, new ArrayDeque<>(mSizeLimit));
+        }
+        ArrayDeque<AggregatedLogRecord<T>> records = mGroupedRecords.get(groupKey);
+        if (mAggregationTimeLimitMs > 0 && !records.isEmpty()) {
+            AggregatedLogRecord<T> lastAggregatedRecord = records.getLast();
+            if (lastAggregatedRecord.mayAggregate(record, mAggregationTimeLimitMs)) {
+                lastAggregatedRecord.record(record);
+                return null;
+            }
+        }
+        AggregatedLogRecord<T> removedRecord = null;
+        if (records.size() >= mSizeLimit) {
+            removedRecord = records.removeFirst();
+        }
+        records.addLast(new AggregatedLogRecord<>(record));
+        return removedRecord;
+    }
+
+    final synchronized void dump(IndentingPrintWriter pw) {
+        for (int i = 0; i < mGroupedRecords.size(); i++) {
+            dumpGroupHeader(pw, mGroupedRecords.keyAt(i));
+            pw.increaseIndent();
+            for (AggregatedLogRecord<T> records : mGroupedRecords.valueAt(i)) {
+                records.dump(pw);
+            }
+            pw.decreaseIndent();
+            pw.println();
+        }
+    }
+
+    final synchronized void dump(ProtoOutputStream proto) {
+        for (int i = 0; i < mGroupedRecords.size(); i++) {
+            long fieldId = findGroupKeyProtoFieldId(mGroupedRecords.keyAt(i));
+            for (AggregatedLogRecord<T> records : mGroupedRecords.valueAt(i)) {
+                records.dump(proto, fieldId);
+            }
+        }
+    }
+
+    /**
+     * Represents an aggregation of log record entries that can be printed in a compact manner.
+     *
+     * <p>The aggregation is controlled by a time limit on the difference between the creation time
+     * of two consecutive entries that {@link SingleLogRecord#mayAggregate}.
+     *
+     * @param <T> The type of log entries aggregated in this record.
+     */
+    static final class AggregatedLogRecord<T extends SingleLogRecord> {
+        private final T mFirst;
+        private T mLatest;
+        private int mCount;
+
+        AggregatedLogRecord(T record) {
+            mLatest = mFirst = record;
+            mCount = 1;
+        }
+
+        T getLatest() {
+            return mLatest;
+        }
+
+        synchronized boolean mayAggregate(T record, long timeLimitMs) {
+            long timeDeltaMs = Math.abs(mLatest.getCreateUptimeMs() - record.getCreateUptimeMs());
+            return mLatest.mayAggregate(record) && timeDeltaMs < timeLimitMs;
+        }
+
+        synchronized void record(T record) {
+            mLatest = record;
+            mCount++;
+        }
+
+        synchronized void dump(IndentingPrintWriter pw) {
+            mFirst.dump(pw);
+            if (mCount == 1) {
+                return;
+            }
+            if (mCount > 2) {
+                pw.println("-> Skipping " + (mCount - 2) + " aggregated entries, latest:");
+            }
+            mLatest.dump(pw);
+        }
+
+        synchronized void dump(ProtoOutputStream proto, long fieldId) {
+            mFirst.dump(proto, fieldId);
+            if (mCount > 1) {
+                mLatest.dump(proto, fieldId);
+            }
+        }
+    }
+
+    /**
+     * Represents a single log entry that can be grouped and aggregated for compact logging.
+     *
+     * <p>Entries are first grouped by an integer group key, and then aggregated with consecutive
+     * entries of same group within a limited timespan.
+     */
+    interface SingleLogRecord {
+
+        /** The group identifier for this record (e.g. vibration usage). */
+        int getGroupKey();
+
+        /**
+         * The timestamp in millis that should be used for aggregation of close entries.
+         *
+         * <p>Should be {@link SystemClock#uptimeMillis()} to be used for calculations.
+         */
+        long getCreateUptimeMs();
+
+        /**
+         * Returns true if this record can be aggregated with the given one (e.g. the represent the
+         * same vibration request from the same process client).
+         */
+        boolean mayAggregate(SingleLogRecord record);
+
+        /** Writes this record into given {@link IndentingPrintWriter}. */
+        void dump(IndentingPrintWriter pw);
+
+        /** Writes this record into given {@link ProtoOutputStream} field. */
+        void dump(ProtoOutputStream proto, long fieldId);
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/HalVibration.java b/services/core/java/com/android/server/vibrator/HalVibration.java
index 70e2e27..8f755f4 100644
--- a/services/core/java/com/android/server/vibrator/HalVibration.java
+++ b/services/core/java/com/android/server/vibrator/HalVibration.java
@@ -54,12 +54,18 @@
     /** Vibration status. */
     private Vibration.Status mStatus;
 
+    /** Reported scale values applied to the vibration effects. */
+    private int mScaleLevel;
+    private float mAdaptiveScale;
+
     HalVibration(@NonNull IBinder token, @NonNull CombinedVibration effect,
             @NonNull CallerInfo callerInfo) {
         super(token, callerInfo);
         mOriginalEffect = effect;
         mEffectToPlay = effect;
         mStatus = Vibration.Status.RUNNING;
+        mScaleLevel = VibrationScaler.SCALE_NONE;
+        mAdaptiveScale = VibrationScaler.ADAPTIVE_SCALE_NONE;
     }
 
     /**
@@ -119,20 +125,24 @@
     }
 
     /**
-     * Scales the {@link #getEffectToPlay()} and each fallback effect with a scaling transformation.
-     *
-     * @param scaler A {@link VibrationEffect.Transformation<Integer>} that takes one of the
-     *               {@code VibrationAttributes.USAGE_*} as the modifier to scale the effect
-     *               based on the user settings.
+     * Scales the {@link #getEffectToPlay()} and each fallback effect based on the vibration usage.
      */
-    public void scaleEffects(VibrationEffect.Transformation<Integer> scaler) {
+    public void scaleEffects(VibrationScaler scaler) {
         int vibrationUsage = callerInfo.attrs.getUsage();
-        CombinedVibration newEffect = mEffectToPlay.transform(scaler, vibrationUsage);
+
+        // Save scale values for debugging purposes.
+        mScaleLevel = scaler.getScaleLevel(vibrationUsage);
+        mAdaptiveScale = scaler.getAdaptiveHapticsScale(vibrationUsage);
+
+        // Scale all VibrationEffect instances in given CombinedVibration.
+        CombinedVibration newEffect = mEffectToPlay.transform(scaler::scale, vibrationUsage);
         if (!Objects.equals(mEffectToPlay, newEffect)) {
             mEffectToPlay = newEffect;
         }
+
+        // Scale all fallback VibrationEffect instances that can be used by VibrationThread.
         for (int i = 0; i < mFallbacks.size(); i++) {
-            mFallbacks.setValueAt(i, scaler.transform(mFallbacks.valueAt(i), vibrationUsage));
+            mFallbacks.setValueAt(i, scaler.scale(mFallbacks.valueAt(i), vibrationUsage));
         }
     }
 
@@ -171,7 +181,7 @@
         CombinedVibration originalEffect =
                 Objects.equals(mOriginalEffect, mEffectToPlay) ? null : mOriginalEffect;
         return new Vibration.DebugInfo(mStatus, stats, mEffectToPlay, originalEffect,
-                /* scale= */ 0, callerInfo);
+                mScaleLevel, mAdaptiveScale, callerInfo);
     }
 
     /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
index a346216..9756094 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
@@ -19,8 +19,8 @@
 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.Flags;
 import android.os.vibrator.persistence.ParsedVibration;
 import android.os.vibrator.persistence.VibrationXmlParser;
 import android.text.TextUtils;
@@ -73,8 +73,6 @@
  *
  * <p>After a successful parsing of the customization XML file, it returns a {@link SparseArray}
  * that maps each customized haptic feedback effect ID to its respective {@link VibrationEffect}.
- *
- * @hide
  */
 final class HapticFeedbackCustomization {
     private static final String TAG = "HapticFeedbackCustomization";
@@ -104,8 +102,6 @@
      * @throws {@link IOException} if an IO error occurs while parsing the customization XML.
      * @throws {@link CustomizationParserException} for any non-IO error that occurs when parsing
      *      the XML, like an invalid XML content or an invalid haptic feedback constant.
-     *
-     * @hide
      */
     @Nullable
     static SparseArray<VibrationEffect> loadVibrations(Resources res, VibratorInfo vibratorInfo)
@@ -202,8 +198,6 @@
 
     /**
      * Represents an error while parsing a haptic feedback customization XML.
-     *
-     * @hide
      */
     static final class CustomizationParserException extends Exception {
         private CustomizationParserException(String message) {
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index 519acec..96f045d 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -34,8 +34,6 @@
 
 /**
  * Provides the {@link VibrationEffect} and {@link VibrationAttributes} for haptic feedback.
- *
- * @hide
  */
 public final class HapticFeedbackVibrationProvider {
     private static final String TAG = "HapticFeedbackVibrationProvider";
@@ -58,17 +56,14 @@
 
     private float mKeyboardVibrationFixedAmplitude;
 
-    /** @hide */
     public HapticFeedbackVibrationProvider(Resources res, Vibrator vibrator) {
         this(res, vibrator.getInfo());
     }
 
-    /** @hide */
     public HapticFeedbackVibrationProvider(Resources res, VibratorInfo vibratorInfo) {
         this(res, vibratorInfo, loadHapticCustomizations(res, vibratorInfo));
     }
 
-    /** @hide */
     @VisibleForTesting HapticFeedbackVibrationProvider(
             Resources res,
             VibratorInfo vibratorInfo,
@@ -190,10 +185,11 @@
      *      to get.
      * @param bypassVibrationIntensitySetting {@code true} if the returned attribute should bypass
      *      vibration intensity settings. {@code false} otherwise.
+     * @param fromIme the haptic feedback is performed from an IME.
      * @return the {@link VibrationAttributes} that should be used for the provided haptic feedback.
      */
     public VibrationAttributes getVibrationAttributesForHapticFeedback(
-            int effectId, boolean bypassVibrationIntensitySetting) {
+            int effectId, boolean bypassVibrationIntensitySetting, boolean fromIme) {
         VibrationAttributes attrs;
         switch (effectId) {
             case HapticFeedbackConstants.EDGE_SQUEEZE:
@@ -209,7 +205,7 @@
                 break;
             case HapticFeedbackConstants.KEYBOARD_TAP:
             case HapticFeedbackConstants.KEYBOARD_RELEASE:
-                attrs = createKeyboardVibrationAttributes();
+                attrs = createKeyboardVibrationAttributes(fromIme);
                 break;
             default:
                 attrs = TOUCH_VIBRATION_ATTRIBUTES;
@@ -222,7 +218,7 @@
         if (shouldBypassInterruptionPolicy(effectId)) {
             flags |= VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
         }
-        if (shouldBypassIntensityScale(effectId)) {
+        if (shouldBypassIntensityScale(effectId, fromIme)) {
             flags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE;
         }
 
@@ -337,9 +333,9 @@
                 /* fallbackForPredefinedEffect= */ predefinedEffectFallback);
     }
 
-    private boolean shouldBypassIntensityScale(int effectId) {
-        if (!Flags.keyboardCategoryEnabled() || mKeyboardVibrationFixedAmplitude < 0) {
-            // shouldn't bypass if not support keyboard category or no fixed amplitude
+    private boolean shouldBypassIntensityScale(int effectId, boolean isIme) {
+        if (!Flags.keyboardCategoryEnabled() || mKeyboardVibrationFixedAmplitude < 0 || !isIme) {
+            // Shouldn't bypass if not support keyboard category, no fixed amplitude or not an IME.
             return false;
         }
         switch (effectId) {
@@ -353,8 +349,9 @@
         return false;
     }
 
-    private static VibrationAttributes createKeyboardVibrationAttributes() {
-        if (!Flags.keyboardCategoryEnabled()) {
+    private VibrationAttributes createKeyboardVibrationAttributes(boolean fromIme) {
+        // Use touch attribute when the keyboard category is disable or it's not from an IME.
+        if (!Flags.keyboardCategoryEnabled() || !fromIme) {
             return TOUCH_VIBRATION_ATTRIBUTES;
         }
 
diff --git a/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java b/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
index 84da9f2..f40c994 100644
--- a/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
@@ -44,8 +44,8 @@
 
     @Override
     public List<Step> cancel() {
-        return Arrays.asList(
-                new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(), controller));
+        return Arrays.asList(new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(),
+                controller, /* isCleanUp= */ true));
     }
 
     @Override
@@ -71,8 +71,8 @@
                 // Vibrator amplitude cannot go further down, just turn it off with the configured
                 // deadline that has been adjusted for the scenario when this was triggered by a
                 // cancelled vibration.
-                return Arrays.asList(new TurnOffVibratorStep(
-                        conductor, mPendingVibratorOffDeadline, controller));
+                return Arrays.asList(new TurnOffVibratorStep(conductor, mPendingVibratorOffDeadline,
+                        controller, /* isCleanUp= */ true));
             }
             return Arrays.asList(new RampOffVibratorStep(
                     conductor,
diff --git a/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java b/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java
index 297ef56..065ce11 100644
--- a/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java
@@ -32,20 +32,23 @@
  */
 final class TurnOffVibratorStep extends AbstractVibratorStep {
 
+    private final boolean mIsCleanUp;
+
     TurnOffVibratorStep(VibrationStepConductor conductor, long startTime,
-            VibratorController controller) {
+            VibratorController controller, boolean isCleanUp) {
         super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1, startTime);
+        mIsCleanUp = isCleanUp;
     }
 
     @Override
     public boolean isCleanUp() {
-        return true;
+        return mIsCleanUp;
     }
 
     @Override
     public List<Step> cancel() {
-        return Arrays.asList(
-                new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(), controller));
+        return Arrays.asList(new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(),
+                controller, /* isCleanUp= */ true));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index b2e808a..6fc9d9a 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -208,6 +208,7 @@
      * potentially expensive or resource-linked objects, such as {@link IBinder}.
      */
     static final class DebugInfo {
+        final Status mStatus;
         final long mCreateTime;
         final CallerInfo mCallerInfo;
         @Nullable
@@ -218,12 +219,12 @@
         private final long mDurationMs;
         @Nullable
         private final CombinedVibration mOriginalEffect;
-        private final float mScale;
-        private final Status mStatus;
+        private final int mScaleLevel;
+        private final float mAdaptiveScale;
 
         DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration playedEffect,
-                @Nullable CombinedVibration originalEffect, float scale,
-                @NonNull CallerInfo callerInfo) {
+                @Nullable CombinedVibration originalEffect, int scaleLevel,
+                float adaptiveScale, @NonNull CallerInfo callerInfo) {
             Objects.requireNonNull(callerInfo);
             mCreateTime = stats.getCreateTimeDebug();
             mStartTime = stats.getStartTimeDebug();
@@ -231,7 +232,8 @@
             mDurationMs = stats.getDurationDebug();
             mPlayedEffect = playedEffect;
             mOriginalEffect = originalEffect;
-            mScale = scale;
+            mScaleLevel = scaleLevel;
+            mAdaptiveScale = adaptiveScale;
             mCallerInfo = callerInfo;
             mStatus = status;
         }
@@ -246,10 +248,15 @@
                     + ", status: " + mStatus.name().toLowerCase(Locale.ROOT)
                     + ", playedEffect: " + mPlayedEffect
                     + ", originalEffect: " + mOriginalEffect
-                    + ", scale: " + String.format(Locale.ROOT, "%.2f", mScale)
+                    + ", scaleLevel: " + VibrationScaler.scaleLevelToString(mScaleLevel)
+                    + ", adaptiveScale: " + String.format(Locale.ROOT, "%.2f", mAdaptiveScale)
                     + ", callerInfo: " + mCallerInfo;
         }
 
+        void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
+            statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale);
+        }
+
         /**
          * Write this info in a compact way into given {@link PrintWriter}.
          *
@@ -259,26 +266,39 @@
         void dumpCompact(IndentingPrintWriter pw) {
             boolean isExternalVibration = mPlayedEffect == null;
             String timingsStr = String.format(Locale.ROOT,
-                    "%s | %8s | %20s | duration: %5dms | start: %12s | end: %10s",
+                    "%s | %8s | %20s | duration: %5dms | start: %12s | end: %12s",
                     DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime)),
                     isExternalVibration ? "external" : "effect",
                     mStatus.name().toLowerCase(Locale.ROOT),
                     mDurationMs,
                     mStartTime == 0 ? "" : DEBUG_TIME_FORMAT.format(new Date(mStartTime)),
                     mEndTime == 0 ? "" : DEBUG_TIME_FORMAT.format(new Date(mEndTime)));
-            String callerInfoStr = String.format(Locale.ROOT,
-                    " | %s (uid=%d, deviceId=%d) | usage: %s (audio=%s) | flags: %s | reason: %s",
-                    mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.deviceId,
-                    mCallerInfo.attrs.usageToString(),
-                    AudioAttributes.usageToString(mCallerInfo.attrs.getAudioUsage()),
+            String paramStr = String.format(Locale.ROOT,
+                    " | scale: %8s (adaptive=%.2f) | flags: %4s | usage: %s",
+                    VibrationScaler.scaleLevelToString(mScaleLevel), mAdaptiveScale,
                     Long.toBinaryString(mCallerInfo.attrs.getFlags()),
-                    mCallerInfo.reason);
+                    mCallerInfo.attrs.usageToString());
+            // Optional, most vibrations have category unknown so skip them to simplify the logs
+            String categoryStr =
+                    mCallerInfo.attrs.getCategory() != VibrationAttributes.CATEGORY_UNKNOWN
+                            ? " | category=" + VibrationAttributes.categoryToString(
+                            mCallerInfo.attrs.getCategory())
+                            : "";
+            // Optional, most vibrations should not be defined via AudioAttributes
+            // so skip them to simplify the logs
+            String audioUsageStr =
+                    mCallerInfo.attrs.getOriginalAudioUsage() != AudioAttributes.USAGE_UNKNOWN
+                            ? " | audioUsage=" + AudioAttributes.usageToString(
+                            mCallerInfo.attrs.getOriginalAudioUsage())
+                            : "";
+            String callerStr = String.format(Locale.ROOT,
+                    " | %s (uid=%d, deviceId=%d) | reason: %s",
+                    mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.deviceId, mCallerInfo.reason);
             String effectStr = String.format(Locale.ROOT,
-                    " | played: %s | original: %s | scale: %.2f",
+                    " | played: %s | original: %s",
                     mPlayedEffect == null ? null : mPlayedEffect.toDebugString(),
-                    mOriginalEffect == null ? null : mOriginalEffect.toDebugString(),
-                    mScale);
-            pw.println(timingsStr + callerInfoStr + effectStr);
+                    mOriginalEffect == null ? null : mOriginalEffect.toDebugString());
+            pw.println(timingsStr + paramStr + categoryStr + audioUsageStr + callerStr + effectStr);
         }
 
         /** Write this info into given {@link PrintWriter}. */
@@ -293,7 +313,8 @@
                     + (mEndTime == 0 ? null : DEBUG_DATE_TIME_FORMAT.format(new Date(mEndTime))));
             pw.println("playedEffect = " + mPlayedEffect);
             pw.println("originalEffect = " + mOriginalEffect);
-            pw.println("scale = " + String.format(Locale.ROOT, "%.2f", mScale));
+            pw.println("scale = " + VibrationScaler.scaleLevelToString(mScaleLevel));
+            pw.println("adaptiveScale = " + String.format(Locale.ROOT, "%.2f", mAdaptiveScale));
             pw.println("callerInfo = " + mCallerInfo);
             pw.decreaseIndent();
         }
@@ -310,6 +331,7 @@
             final VibrationAttributes attrs = mCallerInfo.attrs;
             proto.write(VibrationAttributesProto.USAGE, attrs.getUsage());
             proto.write(VibrationAttributesProto.AUDIO_USAGE, attrs.getAudioUsage());
+            proto.write(VibrationAttributesProto.CATEGORY, attrs.getCategory());
             proto.write(VibrationAttributesProto.FLAGS, attrs.getFlags());
             proto.end(attrsToken);
 
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index 5d17884..d9ca710 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -26,10 +26,14 @@
 import android.os.vibrator.Flags;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.VibrationEffectSegment;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Locale;
 
 /** Controls vibration scaling. */
 final class VibrationScaler {
@@ -37,13 +41,12 @@
 
     // Scale levels. Each level, except MUTE, is defined as the delta between the current setting
     // and the default intensity for that type of vibration (i.e. current - default).
-    private static final int SCALE_VERY_LOW =
-            ExternalVibrationScale.ScaleLevel.SCALE_VERY_LOW; // -2
-    private static final int SCALE_LOW = ExternalVibrationScale.ScaleLevel.SCALE_LOW; // -1
-    private static final int SCALE_NONE = ExternalVibrationScale.ScaleLevel.SCALE_NONE; // 0
-    private static final int SCALE_HIGH = ExternalVibrationScale.ScaleLevel.SCALE_HIGH; // 1
-    private static final int SCALE_VERY_HIGH =
-            ExternalVibrationScale.ScaleLevel.SCALE_VERY_HIGH; // 2
+    static final int SCALE_VERY_LOW = ExternalVibrationScale.ScaleLevel.SCALE_VERY_LOW; // -2
+    static final int SCALE_LOW = ExternalVibrationScale.ScaleLevel.SCALE_LOW; // -1
+    static final int SCALE_NONE = ExternalVibrationScale.ScaleLevel.SCALE_NONE; // 0
+    static final int SCALE_HIGH = ExternalVibrationScale.ScaleLevel.SCALE_HIGH; // 1
+    static final int SCALE_VERY_HIGH = ExternalVibrationScale.ScaleLevel.SCALE_VERY_HIGH; // 2
+    static final float ADAPTIVE_SCALE_NONE = 1f;
 
     // Scale factors for each level.
     private static final float SCALE_FACTOR_VERY_LOW = 0.6f;
@@ -52,6 +55,8 @@
     private static final float SCALE_FACTOR_HIGH = 1.2f;
     private static final float SCALE_FACTOR_VERY_HIGH = 1.4f;
 
+    private static final ScaleLevel SCALE_LEVEL_NONE = new ScaleLevel(SCALE_FACTOR_NONE);
+
     // A mapping from the intensity adjustment to the scaling to apply, where the intensity
     // adjustment is defined as the delta between the default intensity level and the user selected
     // intensity level. It's important that we apply the scaling on the delta between the two so
@@ -69,7 +74,7 @@
         mScaleLevels = new SparseArray<>();
         mScaleLevels.put(SCALE_VERY_LOW, new ScaleLevel(SCALE_FACTOR_VERY_LOW));
         mScaleLevels.put(SCALE_LOW, new ScaleLevel(SCALE_FACTOR_LOW));
-        mScaleLevels.put(SCALE_NONE, new ScaleLevel(SCALE_FACTOR_NONE));
+        mScaleLevels.put(SCALE_NONE, SCALE_LEVEL_NONE);
         mScaleLevels.put(SCALE_HIGH, new ScaleLevel(SCALE_FACTOR_HIGH));
         mScaleLevels.put(SCALE_VERY_HIGH, new ScaleLevel(SCALE_FACTOR_VERY_HIGH));
     }
@@ -87,25 +92,24 @@
      * @param usageHint one of VibrationAttributes.USAGE_*
      * @return one of ExternalVibrationScale.ScaleLevel.SCALE_*
      */
-    public int getExternalVibrationScaleLevel(int usageHint) {
+    public int getScaleLevel(int usageHint) {
         int defaultIntensity = mSettingsController.getDefaultIntensity(usageHint);
         int currentIntensity = mSettingsController.getCurrentIntensity(usageHint);
-
         if (currentIntensity == Vibrator.VIBRATION_INTENSITY_OFF) {
             // Bypassing user settings, or it has changed between checking and scaling. Use default.
             return SCALE_NONE;
         }
 
         int scaleLevel = currentIntensity - defaultIntensity;
-
         if (scaleLevel >= SCALE_VERY_LOW && scaleLevel <= SCALE_VERY_HIGH) {
             return scaleLevel;
-        } else {
-            // Something about our scaling has gone wrong, so just play with no scaling.
-            Slog.w(TAG, "Error in scaling calculations, ended up with invalid scale level "
-                    + scaleLevel + " for vibration with usage " + usageHint);
-            return SCALE_NONE;
         }
+
+        // Something about our scaling has gone wrong, so just play with no scaling.
+        Slog.wtf(TAG, "Error in scaling calculations, ended up with invalid scale level "
+                + scaleLevel + " for vibration with usage " + usageHint);
+
+        return SCALE_NONE;
     }
 
     /**
@@ -117,11 +121,9 @@
      * @return The adaptive haptics scale.
      */
     public float getAdaptiveHapticsScale(int usageHint) {
-        if (shouldApplyAdaptiveHapticsScale(usageHint)) {
-            return mAdaptiveHapticsScales.get(usageHint);
-        }
-
-        return 1f; // no scaling
+        return Flags.adaptiveHapticsEnabled()
+                ? mAdaptiveHapticsScales.get(usageHint, ADAPTIVE_SCALE_NONE)
+                : ADAPTIVE_SCALE_NONE;
     }
 
     /**
@@ -140,21 +142,16 @@
             return effect;
         }
 
-        int defaultIntensity = mSettingsController.getDefaultIntensity(usageHint);
-        int currentIntensity = mSettingsController.getCurrentIntensity(usageHint);
-
-        if (currentIntensity == Vibrator.VIBRATION_INTENSITY_OFF) {
-            // Bypassing user settings, or it has changed between checking and scaling. Use default.
-            currentIntensity = defaultIntensity;
-        }
-
-        int newEffectStrength = intensityToEffectStrength(currentIntensity);
-        ScaleLevel scaleLevel = mScaleLevels.get(currentIntensity - defaultIntensity);
+        int newEffectStrength = getEffectStrength(usageHint);
+        ScaleLevel scaleLevel = mScaleLevels.get(getScaleLevel(usageHint));
+        float adaptiveScale = getAdaptiveHapticsScale(usageHint);
 
         if (scaleLevel == null) {
             // Something about our scaling has gone wrong, so just play with no scaling.
-            Slog.e(TAG, "No configured scaling level!"
-                    + " (current=" + currentIntensity + ", default= " + defaultIntensity + ")");
+            Slog.e(TAG, "No configured scaling level found! (current="
+                    + mSettingsController.getCurrentIntensity(usageHint) + ", default= "
+                    + mSettingsController.getDefaultIntensity(usageHint) + ")");
+            scaleLevel = SCALE_LEVEL_NONE;
         }
 
         VibrationEffect.Composed composedEffect = (VibrationEffect.Composed) effect;
@@ -162,20 +159,11 @@
                 new ArrayList<>(composedEffect.getSegments());
         int segmentCount = segments.size();
         for (int i = 0; i < segmentCount; i++) {
-            VibrationEffectSegment segment = segments.get(i);
-            segment = segment.resolve(mDefaultVibrationAmplitude)
-                    .applyEffectStrength(newEffectStrength);
-            if (scaleLevel != null) {
-                segment = segment.scale(scaleLevel.factor);
-            }
-
-            // If adaptive haptics scaling is available for this usage, apply it to the segment.
-            if (shouldApplyAdaptiveHapticsScale(usageHint)) {
-                float adaptiveScale = mAdaptiveHapticsScales.get(usageHint);
-                segment = segment.scaleLinearly(adaptiveScale);
-            }
-
-            segments.set(i, segment);
+            segments.set(i,
+                    segments.get(i).resolve(mDefaultVibrationAmplitude)
+                            .applyEffectStrength(newEffectStrength)
+                            .scale(scaleLevel.factor)
+                            .scaleLinearly(adaptiveScale));
         }
         if (segments.equals(composedEffect.getSegments())) {
             // No segment was updated, return original effect.
@@ -197,15 +185,7 @@
      * updated effect strength
      */
     public PrebakedSegment scale(PrebakedSegment prebaked, int usageHint) {
-        int currentIntensity = mSettingsController.getCurrentIntensity(usageHint);
-
-        if (currentIntensity == Vibrator.VIBRATION_INTENSITY_OFF) {
-            // Bypassing user settings, or it has changed between checking and scaling. Use default.
-            currentIntensity = mSettingsController.getDefaultIntensity(usageHint);
-        }
-
-        int newEffectStrength = intensityToEffectStrength(currentIntensity);
-        return prebaked.applyEffectStrength(newEffectStrength);
+        return prebaked.applyEffectStrength(getEffectStrength(usageHint));
     }
 
     /**
@@ -213,8 +193,6 @@
      *
      * @param usageHint one of VibrationAttributes.USAGE_*.
      * @param scale The scaling factor that should be applied to vibrations of this usage.
-     *
-     * @hide
      */
     public void updateAdaptiveHapticsScale(@VibrationAttributes.Usage int usageHint, float scale) {
         mAdaptiveHapticsScales.put(usageHint, scale);
@@ -224,24 +202,68 @@
      * Removes the usage from the cached adaptive haptics scales list.
      *
      * @param usageHint one of VibrationAttributes.USAGE_*.
-     *
-     * @hide
      */
     public void removeAdaptiveHapticsScale(@VibrationAttributes.Usage int usageHint) {
         mAdaptiveHapticsScales.remove(usageHint);
     }
 
-    /**
-     * Removes all cached adaptive haptics scales.
-     *
-     * @hide
-     */
+    /** Removes all cached adaptive haptics scales. */
     public void clearAdaptiveHapticsScales() {
         mAdaptiveHapticsScales.clear();
     }
 
-    private boolean shouldApplyAdaptiveHapticsScale(int usageHint) {
-        return Flags.adaptiveHapticsEnabled() && mAdaptiveHapticsScales.contains(usageHint);
+    /** Write current settings into given {@link PrintWriter}. */
+    void dump(IndentingPrintWriter pw) {
+        pw.println("VibrationScaler:");
+        pw.increaseIndent();
+        pw.println("defaultVibrationAmplitude = " + mDefaultVibrationAmplitude);
+
+        pw.println("ScaleLevels:");
+        pw.increaseIndent();
+        for (int i = 0; i < mScaleLevels.size(); i++) {
+            int scaleLevelKey = mScaleLevels.keyAt(i);
+            ScaleLevel scaleLevel = mScaleLevels.valueAt(i);
+            pw.println(scaleLevelToString(scaleLevelKey) + " = " + scaleLevel);
+        }
+        pw.decreaseIndent();
+
+        pw.println("AdaptiveHapticsScales:");
+        pw.increaseIndent();
+        for (int i = 0; i < mAdaptiveHapticsScales.size(); i++) {
+            int usage = mAdaptiveHapticsScales.keyAt(i);
+            float scale = mAdaptiveHapticsScales.valueAt(i);
+            pw.println(VibrationAttributes.usageToString(usage)
+                    + " = " + String.format(Locale.ROOT, "%.2f", scale));
+        }
+        pw.decreaseIndent();
+
+        pw.decreaseIndent();
+    }
+
+    /** Write current settings into given {@link ProtoOutputStream}. */
+    void dump(ProtoOutputStream proto) {
+        proto.write(VibratorManagerServiceDumpProto.DEFAULT_VIBRATION_AMPLITUDE,
+                mDefaultVibrationAmplitude);
+    }
+
+    @Override
+    public String toString() {
+        return "VibrationScaler{"
+                + "mScaleLevels=" + mScaleLevels
+                + ", mDefaultVibrationAmplitude=" + mDefaultVibrationAmplitude
+                + ", mAdaptiveHapticsScales=" + mAdaptiveHapticsScales
+                + '}';
+    }
+
+    private int getEffectStrength(int usageHint) {
+        int currentIntensity = mSettingsController.getCurrentIntensity(usageHint);
+
+        if (currentIntensity == Vibrator.VIBRATION_INTENSITY_OFF) {
+            // Bypassing user settings, or it has changed between checking and scaling. Use default.
+            currentIntensity = mSettingsController.getDefaultIntensity(usageHint);
+        }
+
+        return intensityToEffectStrength(currentIntensity);
     }
 
     /** Mapping of Vibrator.VIBRATION_INTENSITY_* values to {@link EffectStrength}. */
@@ -259,6 +281,17 @@
         }
     }
 
+    static String scaleLevelToString(int scaleLevel) {
+        return switch (scaleLevel) {
+            case SCALE_VERY_LOW -> "VERY_LOW";
+            case SCALE_LOW -> "LOW";
+            case SCALE_NONE -> "NONE";
+            case SCALE_HIGH -> "HIGH";
+            case SCALE_VERY_HIGH -> "VERY_HIGH";
+            default -> String.valueOf(scaleLevel);
+        };
+    }
+
     /** Represents the scale that must be applied to a vibration effect intensity. */
     private static final class ScaleLevel {
         public final float factor;
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 99ce3e2..2fc183d 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -386,7 +386,6 @@
      * Returns the duration, in milliseconds, that the vibrator control service will wait for new
      * vibration params.
      * @return The request vibration params timeout in milliseconds.
-     * @hide
      */
     public int getRequestVibrationParamsTimeoutMs() {
         return mVibrationConfig.getRequestVibrationParamsTimeoutMs();
@@ -533,7 +532,7 @@
             return false;
         }
 
-        if (Flags.keyboardCategoryEnabled()) {
+        if (Flags.keyboardCategoryEnabled() && mVibrationConfig.hasFixedKeyboardAmplitude()) {
             int category = callerInfo.attrs.getCategory();
             if (usage == USAGE_TOUCH && category == CATEGORY_KEYBOARD) {
                 // Keyboard touch has a different user setting.
@@ -645,11 +644,16 @@
                         .append("), ");
             }
             vibrationIntensitiesString.append('}');
+            String keyboardVibrationOnString = mKeyboardVibrationOn
+                    + " (default: " + mVibrationConfig.isDefaultKeyboardVibrationEnabled() + ")";
             return "VibrationSettings{"
                     + "mVibratorConfig=" + mVibrationConfig
+                    + ", mVibrateOn=" + mVibrateOn
+                    + ", mKeyboardVibrationOn=" + keyboardVibrationOnString
                     + ", mVibrateInputDevices=" + mVibrateInputDevices
                     + ", mBatterySaverMode=" + mBatterySaverMode
-                    + ", mVibrateOn=" + mVibrateOn
+                    + ", mRingerMode=" + ringerModeToString(mRingerMode)
+                    + ", mOnWirelessCharger=" + mOnWirelessCharger
                     + ", mVibrationIntensities=" + vibrationIntensitiesString
                     + ", mProcStatesCache=" + mUidObserver.mProcStatesCache
                     + '}';
@@ -658,32 +662,40 @@
 
     /** Write current settings into given {@link PrintWriter}. */
     void dump(IndentingPrintWriter pw) {
-        pw.println("VibrationSettings:");
-        pw.increaseIndent();
-        pw.println("vibrateOn = " + mVibrateOn);
-        pw.println("vibrateInputDevices = " + mVibrateInputDevices);
-        pw.println("batterySaverMode = " + mBatterySaverMode);
-        pw.println("VibrationIntensities:");
+        synchronized (mLock) {
+            pw.println("VibrationSettings:");
+            pw.increaseIndent();
+            pw.println("vibrateOn = " + mVibrateOn);
+            pw.println("keyboardVibrationOn = " + mKeyboardVibrationOn
+                    + ", default: " + mVibrationConfig.isDefaultKeyboardVibrationEnabled());
+            pw.println("vibrateInputDevices = " + mVibrateInputDevices);
+            pw.println("batterySaverMode = " + mBatterySaverMode);
+            pw.println("ringerMode = " + ringerModeToString(mRingerMode));
+            pw.println("onWirelessCharger = " + mOnWirelessCharger);
+            pw.println("processStateCache size = " + mUidObserver.mProcStatesCache.size());
 
-        pw.increaseIndent();
-        for (int i = 0; i < mCurrentVibrationIntensities.size(); i++) {
-            int usage = mCurrentVibrationIntensities.keyAt(i);
-            int intensity = mCurrentVibrationIntensities.valueAt(i);
-            pw.println(VibrationAttributes.usageToString(usage) + " = "
-                    + intensityToString(intensity)
-                    + ", default: " + intensityToString(getDefaultIntensity(usage)));
+            pw.println("VibrationIntensities:");
+            pw.increaseIndent();
+            for (int i = 0; i < mCurrentVibrationIntensities.size(); i++) {
+                int usage = mCurrentVibrationIntensities.keyAt(i);
+                int intensity = mCurrentVibrationIntensities.valueAt(i);
+                pw.println(VibrationAttributes.usageToString(usage) + " = "
+                        + intensityToString(intensity)
+                        + ", default: " + intensityToString(getDefaultIntensity(usage)));
+            }
+            pw.decreaseIndent();
+
+            mVibrationConfig.dumpWithoutDefaultSettings(pw);
+            pw.decreaseIndent();
         }
-        pw.decreaseIndent();
-
-        mVibrationConfig.dumpWithoutDefaultSettings(pw);
-        pw.println("processStateCache = " + mUidObserver.mProcStatesCache);
-        pw.decreaseIndent();
     }
 
     /** Write current settings into given {@link ProtoOutputStream}. */
     void dump(ProtoOutputStream proto) {
         synchronized (mLock) {
             proto.write(VibratorManagerServiceDumpProto.VIBRATE_ON, mVibrateOn);
+            proto.write(VibratorManagerServiceDumpProto.KEYBOARD_VIBRATION_ON,
+                    mKeyboardVibrationOn);
             proto.write(VibratorManagerServiceDumpProto.LOW_POWER_MODE, mBatterySaverMode);
             proto.write(VibratorManagerServiceDumpProto.ALARM_INTENSITY,
                     getCurrentIntensity(USAGE_ALARM));
@@ -723,18 +735,22 @@
     }
 
     private static String intensityToString(int intensity) {
-        switch (intensity) {
-            case Vibrator.VIBRATION_INTENSITY_OFF:
-                return "OFF";
-            case Vibrator.VIBRATION_INTENSITY_LOW:
-                return "LOW";
-            case Vibrator.VIBRATION_INTENSITY_MEDIUM:
-                return "MEDIUM";
-            case Vibrator.VIBRATION_INTENSITY_HIGH:
-                return "HIGH";
-            default:
-                return "UNKNOWN INTENSITY " + intensity;
-        }
+        return switch (intensity) {
+            case Vibrator.VIBRATION_INTENSITY_OFF -> "OFF";
+            case Vibrator.VIBRATION_INTENSITY_LOW -> "LOW";
+            case Vibrator.VIBRATION_INTENSITY_MEDIUM -> "MEDIUM";
+            case Vibrator.VIBRATION_INTENSITY_HIGH -> "HIGH";
+            default -> "UNKNOWN INTENSITY " + intensity;
+        };
+    }
+
+    private static String ringerModeToString(int ringerMode) {
+        return switch (ringerMode) {
+            case AudioManager.RINGER_MODE_SILENT -> "silent";
+            case AudioManager.RINGER_MODE_VIBRATE -> "vibrate";
+            case AudioManager.RINGER_MODE_NORMAL -> "normal";
+            default -> String.valueOf(ringerMode);
+        };
     }
 
     @VibrationIntensity
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index f6af9ad..f3e226e 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -40,8 +40,10 @@
 import java.util.List;
 import java.util.PriorityQueue;
 import java.util.Queue;
+import java.util.concurrent.CancellationException;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Creates and manages a queue of steps for performing a VibrationEffect, as well as coordinating
@@ -70,6 +72,7 @@
 
     private final DeviceAdapter mDeviceAdapter;
     private final VibrationScaler mVibrationScaler;
+    private final VibratorFrameworkStatsLogger mStatsLogger;
 
     // Not guarded by lock because it's mostly used to read immutable fields by this conductor.
     // This is only modified here at the prepareToStart method which always runs at the vibration
@@ -103,14 +106,15 @@
     private int mSuccessfulVibratorOnSteps;
 
     VibrationStepConductor(HalVibration vib, VibrationSettings vibrationSettings,
-            DeviceAdapter deviceAdapter,
-            VibrationScaler vibrationScaler,
+            DeviceAdapter deviceAdapter, VibrationScaler vibrationScaler,
+            VibratorFrameworkStatsLogger statsLogger,
             CompletableFuture<Void> requestVibrationParamsFuture,
             VibrationThread.VibratorManagerHooks vibratorManagerHooks) {
         this.mVibration = vib;
         this.vibrationSettings = vibrationSettings;
         this.mDeviceAdapter = deviceAdapter;
         mVibrationScaler = vibrationScaler;
+        mStatsLogger = statsLogger;
         mRequestVibrationParamsFuture = requestVibrationParamsFuture;
         this.vibratorManagerHooks = vibratorManagerHooks;
         this.mSignalVibratorsComplete =
@@ -161,7 +165,7 @@
                 waitForVibrationParamsIfRequired();
             }
             // Scale resolves the default amplitudes from the effect before scaling them.
-            mVibration.scaleEffects(mVibrationScaler::scale);
+            mVibration.scaleEffects(mVibrationScaler);
         } else {
             mVibration.resolveEffects(mVibrationScaler.getDefaultVibrationAmplitude());
         }
@@ -461,9 +465,19 @@
         }
 
         try {
-            mRequestVibrationParamsFuture.orTimeout(
+            mRequestVibrationParamsFuture.get(
                     vibrationSettings.getRequestVibrationParamsTimeoutMs(),
-                    TimeUnit.MILLISECONDS).get();
+                    TimeUnit.MILLISECONDS);
+        } catch (TimeoutException e) {
+            if (DEBUG) {
+                Slog.d(TAG, "Request for vibration params timed out", e);
+            }
+            mStatsLogger.logVibrationParamRequestTimeout(mVibration.callerInfo.uid);
+        } catch (CancellationException e) {
+            if (DEBUG) {
+                Slog.d(TAG, "Request for vibration params cancelled, maybe superseded or"
+                        + " vibrator controller unregistered. Skipping params...", e);
+            }
         } catch (Throwable e) {
             Slog.w(TAG, "Failed to retrieve vibration params.", e);
         }
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index 17a9e33..10317c9 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -30,6 +30,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
+import android.content.Context;
 import android.frameworks.vibrator.IVibratorControlService;
 import android.frameworks.vibrator.IVibratorController;
 import android.frameworks.vibrator.ScaleParam;
@@ -37,56 +38,76 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.VibrationAttributes;
+import android.os.VibrationEffect;
+import android.util.IndentingPrintWriter;
+import android.util.IntArray;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 
 /**
  * Implementation of {@link IVibratorControlService} which allows the registration of
  * {@link IVibratorController} to set and receive vibration params.
- *
- * @hide
  */
-public final class VibratorControlService extends IVibratorControlService.Stub {
+final class VibratorControlService extends IVibratorControlService.Stub {
     private static final String TAG = "VibratorControlService";
     private static final int UNRECOGNIZED_VIBRATION_TYPE = -1;
     private static final int NO_SCALE = -1;
 
+    private static final SimpleDateFormat DEBUG_DATE_TIME_FORMAT =
+            new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
+
+    private final VibrationParamsRecords mVibrationParamsRecords;
     private final VibratorControllerHolder mVibratorControllerHolder;
     private final VibrationScaler mVibrationScaler;
+    private final VibratorFrameworkStatsLogger mStatsLogger;
     private final Object mLock;
     private final int[] mRequestVibrationParamsForUsages;
 
     @GuardedBy("mLock")
-    private CompletableFuture<Void> mRequestVibrationParamsFuture = null;
-    @GuardedBy("mLock")
-    private IBinder mRequestVibrationParamsToken;
+    @Nullable
+    private VibrationParamRequest mVibrationParamRequest = null;
 
-    public VibratorControlService(VibratorControllerHolder vibratorControllerHolder,
-            VibrationScaler vibrationScaler, VibrationSettings vibrationSettings, Object lock) {
+    VibratorControlService(Context context,
+            VibratorControllerHolder vibratorControllerHolder, VibrationScaler vibrationScaler,
+            VibrationSettings vibrationSettings, VibratorFrameworkStatsLogger statsLogger,
+            Object lock) {
         mVibratorControllerHolder = vibratorControllerHolder;
         mVibrationScaler = vibrationScaler;
+        mStatsLogger = statsLogger;
         mLock = lock;
         mRequestVibrationParamsForUsages = vibrationSettings.getRequestVibrationParamsForUsages();
+
+        int dumpSizeLimit = context.getResources().getInteger(
+                com.android.internal.R.integer.config_previousVibrationsDumpSizeLimit);
+        int dumpAggregationTimeLimit = context.getResources().getInteger(
+                com.android.internal.R.integer
+                        .config_previousVibrationsDumpAggregationTimeMillisLimit);
+        mVibrationParamsRecords =
+                new VibrationParamsRecords(dumpSizeLimit, dumpAggregationTimeLimit);
     }
 
     @Override
-    public void registerVibratorController(IVibratorController controller)
-            throws RemoteException {
+    public void registerVibratorController(IVibratorController controller) {
         synchronized (mLock) {
             mVibratorControllerHolder.setVibratorController(controller);
         }
     }
 
     @Override
-    public void unregisterVibratorController(@NonNull IVibratorController controller)
-            throws RemoteException {
+    public void unregisterVibratorController(@NonNull IVibratorController controller) {
         Objects.requireNonNull(controller);
 
         synchronized (mLock) {
@@ -110,7 +131,7 @@
 
     @Override
     public void setVibrationParams(@SuppressLint("ArrayReturn") VibrationParam[] params,
-            @NonNull IVibratorController token) throws RemoteException {
+            @NonNull IVibratorController token) {
         Objects.requireNonNull(token);
 
         synchronized (mLock) {
@@ -128,12 +149,12 @@
             }
 
             updateAdaptiveHapticsScales(params);
+            recordUpdateVibrationParams(params, /* fromRequest= */ false);
         }
     }
 
     @Override
-    public void clearVibrationParams(int types, @NonNull IVibratorController token)
-            throws RemoteException {
+    public void clearVibrationParams(int types, @NonNull IVibratorController token) {
         Objects.requireNonNull(token);
 
         synchronized (mLock) {
@@ -151,42 +172,48 @@
             }
 
             updateAdaptiveHapticsScales(types, NO_SCALE);
+            recordClearVibrationParams(types);
         }
     }
 
     @Override
     public void onRequestVibrationParamsComplete(
-            @NonNull IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result)
-            throws RemoteException {
+            @NonNull IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result) {
         Objects.requireNonNull(requestToken);
 
         synchronized (mLock) {
-            if (mRequestVibrationParamsToken == null) {
+            if (mVibrationParamRequest == null) {
                 Slog.wtf(TAG,
                         "New vibration params received but no token was cached in the service. "
                                 + "New vibration params ignored.");
+                mStatsLogger.logVibrationParamResponseIgnored();
                 return;
             }
 
-            if (!Objects.equals(requestToken, mRequestVibrationParamsToken)) {
+            if (!Objects.equals(requestToken, mVibrationParamRequest.token)) {
                 Slog.w(TAG,
                         "New vibration params received but the provided token does not match the "
                                 + "cached one. New vibration params ignored.");
+                mStatsLogger.logVibrationParamResponseIgnored();
                 return;
             }
 
+            long latencyMs = SystemClock.uptimeMillis() - mVibrationParamRequest.uptimeMs;
+            mStatsLogger.logVibrationParamRequestLatency(mVibrationParamRequest.uid, latencyMs);
+
             updateAdaptiveHapticsScales(result);
             endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ false);
+            recordUpdateVibrationParams(result, /* fromRequest= */ true);
         }
     }
 
     @Override
-    public int getInterfaceVersion() throws RemoteException {
+    public int getInterfaceVersion() {
         return this.VERSION;
     }
 
     @Override
-    public String getInterfaceHash() throws RemoteException {
+    public String getInterfaceHash() {
         return this.HASH;
     }
 
@@ -202,7 +229,7 @@
      */
     @Nullable
     public CompletableFuture<Void> triggerVibrationParamsRequest(
-            @VibrationAttributes.Usage int usage, int timeoutInMillis) {
+            int uid, @VibrationAttributes.Usage int usage, int timeoutInMillis) {
         synchronized (mLock) {
             IVibratorController vibratorController =
                     mVibratorControllerHolder.getVibratorController();
@@ -221,16 +248,16 @@
 
             try {
                 endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
-                mRequestVibrationParamsFuture = new CompletableFuture<>();
-                mRequestVibrationParamsToken = new Binder();
+                mVibrationParamRequest = new VibrationParamRequest(uid);
                 vibratorController.requestVibrationParams(vibrationType, timeoutInMillis,
-                        mRequestVibrationParamsToken);
+                        mVibrationParamRequest.token);
+                return mVibrationParamRequest.future;
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to request vibration params.", e);
                 endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
             }
 
-            return mRequestVibrationParamsFuture;
+            return null;
         }
     }
 
@@ -256,16 +283,52 @@
     }
 
     /**
-     * Returns the {@link #mRequestVibrationParamsToken} which is used to validate
+     * Returns the binder token which is used to validate
      * {@link #onRequestVibrationParamsComplete(IBinder, VibrationParam[])} calls.
      */
     @VisibleForTesting
     public IBinder getRequestVibrationParamsToken() {
         synchronized (mLock) {
-            return mRequestVibrationParamsToken;
+            return mVibrationParamRequest == null ? null : mVibrationParamRequest.token;
         }
     }
 
+    /** Write current settings into given {@link PrintWriter}. */
+    void dump(IndentingPrintWriter pw) {
+        boolean isVibratorControllerRegistered;
+        boolean hasPendingVibrationParamsRequest;
+        synchronized (mLock) {
+            isVibratorControllerRegistered =
+                    mVibratorControllerHolder.getVibratorController() != null;
+            hasPendingVibrationParamsRequest = mVibrationParamRequest != null;
+        }
+
+        pw.println("VibratorControlService:");
+        pw.increaseIndent();
+        pw.println("isVibratorControllerRegistered = " + isVibratorControllerRegistered);
+        pw.println("hasPendingVibrationParamsRequest = " + hasPendingVibrationParamsRequest);
+
+        pw.println();
+        pw.println("Vibration parameters update history:");
+        pw.increaseIndent();
+        mVibrationParamsRecords.dump(pw);
+        pw.decreaseIndent();
+
+        pw.decreaseIndent();
+    }
+
+    /** Write current settings into given {@link ProtoOutputStream}. */
+    void dump(ProtoOutputStream proto) {
+        boolean isVibratorControllerRegistered;
+        synchronized (mLock) {
+            isVibratorControllerRegistered =
+                    mVibratorControllerHolder.getVibratorController() != null;
+        }
+        proto.write(VibratorManagerServiceDumpProto.IS_VIBRATOR_CONTROLLER_REGISTERED,
+                isVibratorControllerRegistered);
+        mVibrationParamsRecords.dump(proto);
+    }
+
     /**
      * Completes or cancels the vibration params request future and resets the future and token
      * to null.
@@ -273,18 +336,10 @@
      */
     @GuardedBy("mLock")
     private void endOngoingRequestVibrationParamsLocked(boolean wasCancelled) {
-        mRequestVibrationParamsToken = null;
-        if (mRequestVibrationParamsFuture == null) {
-            return;
+        if (mVibrationParamRequest != null) {
+            mVibrationParamRequest.endRequest(wasCancelled);
         }
-
-        if (wasCancelled) {
-            mRequestVibrationParamsFuture.cancel(/* mayInterruptIfRunning= */ true);
-        } else {
-            mRequestVibrationParamsFuture.complete(null);
-        }
-
-        mRequestVibrationParamsFuture = null;
+        mVibrationParamRequest = null;
     }
 
     private static int mapToAdaptiveVibrationType(@VibrationAttributes.Usage int usage) {
@@ -312,6 +367,33 @@
         }
     }
 
+    private static int[] mapFromAdaptiveVibrationTypeToVibrationUsages(int types) {
+        IntArray usages = new IntArray(15);
+        if ((ScaleParam.TYPE_ALARM & types) != 0) {
+            usages.add(USAGE_ALARM);
+        }
+
+        if ((ScaleParam.TYPE_NOTIFICATION & types) != 0) {
+            usages.add(USAGE_NOTIFICATION);
+            usages.add(USAGE_COMMUNICATION_REQUEST);
+        }
+
+        if ((ScaleParam.TYPE_RINGTONE & types) != 0) {
+            usages.add(USAGE_RINGTONE);
+        }
+
+        if ((ScaleParam.TYPE_MEDIA & types) != 0) {
+            usages.add(USAGE_MEDIA);
+            usages.add(USAGE_UNKNOWN);
+        }
+
+        if ((ScaleParam.TYPE_INTERACTIVE & types) != 0) {
+            usages.add(USAGE_TOUCH);
+            usages.add(USAGE_HARDWARE_FEEDBACK);
+        }
+        return usages.toArray();
+    }
+
     /**
      * Updates the adaptive haptics scales cached in {@link VibrationScaler} with the
      * provided params.
@@ -319,7 +401,14 @@
      * @param params the new vibration params.
      */
     private void updateAdaptiveHapticsScales(@Nullable VibrationParam[] params) {
+        if (params == null) {
+            return;
+        }
         for (VibrationParam param : params) {
+            if (param.getTag() != VibrationParam.scale) {
+                Slog.e(TAG, "Unsupported vibration param: " + param);
+                continue;
+            }
             ScaleParam scaleParam = param.getScale();
             updateAdaptiveHapticsScales(scaleParam.typesMask, scaleParam.scale);
         }
@@ -333,27 +422,9 @@
      * @param scale The scaling factor that should be applied to the vibrations.
      */
     private void updateAdaptiveHapticsScales(int types, float scale) {
-        if ((ScaleParam.TYPE_ALARM & types) != 0) {
-            updateOrRemoveAdaptiveHapticsScale(USAGE_ALARM, scale);
-        }
-
-        if ((ScaleParam.TYPE_NOTIFICATION & types) != 0) {
-            updateOrRemoveAdaptiveHapticsScale(USAGE_NOTIFICATION, scale);
-            updateOrRemoveAdaptiveHapticsScale(USAGE_COMMUNICATION_REQUEST, scale);
-        }
-
-        if ((ScaleParam.TYPE_RINGTONE & types) != 0) {
-            updateOrRemoveAdaptiveHapticsScale(USAGE_RINGTONE, scale);
-        }
-
-        if ((ScaleParam.TYPE_MEDIA & types) != 0) {
-            updateOrRemoveAdaptiveHapticsScale(USAGE_MEDIA, scale);
-            updateOrRemoveAdaptiveHapticsScale(USAGE_UNKNOWN, scale);
-        }
-
-        if ((ScaleParam.TYPE_INTERACTIVE & types) != 0) {
-            updateOrRemoveAdaptiveHapticsScale(USAGE_TOUCH, scale);
-            updateOrRemoveAdaptiveHapticsScale(USAGE_HARDWARE_FEEDBACK, scale);
+        mStatsLogger.logVibrationParamScale(scale);
+        for (int usage : mapFromAdaptiveVibrationTypeToVibrationUsages(types)) {
+            updateOrRemoveAdaptiveHapticsScale(usage, scale);
         }
     }
 
@@ -375,4 +446,157 @@
 
         mVibrationScaler.updateAdaptiveHapticsScale(usageHint, scale);
     }
+
+    private void recordUpdateVibrationParams(@Nullable VibrationParam[] params,
+            boolean fromRequest) {
+        if (params == null) {
+            return;
+        }
+        VibrationParamsRecords.Operation operation =
+                fromRequest ? VibrationParamsRecords.Operation.PULL
+                        : VibrationParamsRecords.Operation.PUSH;
+        long createTime = SystemClock.uptimeMillis();
+        for (VibrationParam param : params) {
+            if (param.getTag() != VibrationParam.scale) {
+                Slog.w(TAG, "Unsupported vibration param ignored from dumpsys records: " + param);
+                continue;
+            }
+            ScaleParam scaleParam = param.getScale();
+            mVibrationParamsRecords.add(new VibrationScaleParamRecord(operation, createTime,
+                    scaleParam.typesMask, scaleParam.scale));
+        }
+    }
+
+    private void recordClearVibrationParams(int typesMask) {
+        long createTime = SystemClock.uptimeMillis();
+        mVibrationParamsRecords.add(new VibrationScaleParamRecord(
+                VibrationParamsRecords.Operation.CLEAR, createTime, typesMask, NO_SCALE));
+    }
+
+    /**
+     * Keep records of {@link VibrationParam} values received by this service from a registered
+     * {@link VibratorController} and provide debug information for this service.
+     */
+    private static final class VibrationParamsRecords
+            extends GroupedAggregatedLogRecords<VibrationScaleParamRecord> {
+
+        /** The type of operations on vibration parameters that the service is recording. */
+        enum Operation {
+            PULL, PUSH, CLEAR
+        };
+
+        VibrationParamsRecords(int sizeLimit, int aggregationTimeLimit) {
+            super(sizeLimit, aggregationTimeLimit);
+        }
+
+        @Override
+        synchronized void dumpGroupHeader(IndentingPrintWriter pw, int paramType) {
+            if (paramType == VibrationParam.scale) {
+                pw.println("SCALE:");
+            } else {
+                pw.println("UNKNOWN:");
+            }
+        }
+
+        @Override
+        synchronized long findGroupKeyProtoFieldId(int usage) {
+            return VibratorManagerServiceDumpProto.PREVIOUS_VIBRATION_PARAMS;
+        }
+    }
+
+    /** Represents a request for {@link VibrationParam}. */
+    private static final class VibrationParamRequest {
+        public final CompletableFuture<Void> future = new CompletableFuture<>();
+        public final IBinder token = new Binder();
+        public final int uid;
+        public final long uptimeMs;
+
+        VibrationParamRequest(int uid) {
+            this.uid = uid;
+            uptimeMs = SystemClock.uptimeMillis();
+        }
+
+        public void endRequest(boolean wasCancelled) {
+            if (wasCancelled) {
+                future.cancel(/* mayInterruptIfRunning= */ true);
+            } else {
+                future.complete(null);
+            }
+        }
+    }
+
+    /**
+     * Record for a single {@link Vibration.DebugInfo}, that can be grouped by usage and aggregated
+     * by UID, {@link VibrationAttributes} and {@link VibrationEffect}.
+     */
+    private static final class VibrationScaleParamRecord
+            implements GroupedAggregatedLogRecords.SingleLogRecord {
+
+        private final VibrationParamsRecords.Operation mOperation;
+        private final long mCreateTime;
+        private final int mTypesMask;
+        private final float mScale;
+
+        VibrationScaleParamRecord(VibrationParamsRecords.Operation operation, long createTime,
+                int typesMask, float scale) {
+            mOperation = operation;
+            mCreateTime = createTime;
+            mTypesMask = typesMask;
+            mScale = scale;
+        }
+
+        @Override
+        public int getGroupKey() {
+            return VibrationParam.scale;
+        }
+
+        @Override
+        public long getCreateUptimeMs() {
+            return mCreateTime;
+        }
+
+        @Override
+        public boolean mayAggregate(GroupedAggregatedLogRecords.SingleLogRecord record) {
+            if (!(record instanceof VibrationScaleParamRecord param)) {
+                return false;
+            }
+            return mTypesMask == param.mTypesMask && mOperation == param.mOperation;
+        }
+
+        @Override
+        public void dump(IndentingPrintWriter pw) {
+            String line = String.format(Locale.ROOT,
+                    "%s | %6s | scale: %5s | typesMask: %6s | usages: %s",
+                    DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime)),
+                    mOperation.name().toLowerCase(Locale.ROOT),
+                    (mScale == NO_SCALE) ? "" : String.format(Locale.ROOT, "%.2f", mScale),
+                    Long.toBinaryString(mTypesMask), createVibrationUsagesString());
+            pw.println(line);
+        }
+
+        @Override
+        public void dump(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+            proto.write(VibrationParamProto.CREATE_TIME, mCreateTime);
+            proto.write(VibrationParamProto.IS_FROM_REQUEST,
+                    mOperation == VibrationParamsRecords.Operation.PULL);
+
+            final long scaleToken = proto.start(VibrationParamProto.SCALE);
+            proto.write(VibrationScaleParamProto.TYPES_MASK, mTypesMask);
+            proto.write(VibrationScaleParamProto.SCALE, mScale);
+            proto.end(scaleToken);
+
+            proto.end(token);
+        }
+
+        private String createVibrationUsagesString() {
+            StringBuilder sb = new StringBuilder();
+            int[] usages = mapFromAdaptiveVibrationTypeToVibrationUsages(mTypesMask);
+            for (int i = 0; i < usages.length; i++) {
+                if (i > 0) sb.append(", ");
+                sb.append(VibrationAttributes.usageToString(usages[i]));
+            }
+            return sb.toString();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index f5d4d1e..6710d02 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -17,7 +17,6 @@
 package com.android.server.vibrator;
 
 import android.annotation.Nullable;
-import android.annotation.Nullable;
 import android.hardware.vibrator.IVibrator;
 import android.os.Binder;
 import android.os.IVibratorStateListener;
@@ -354,13 +353,13 @@
     }
 
     void dump(IndentingPrintWriter pw) {
-        pw.println("VibratorController:");
+        pw.println("Vibrator (id=" + mVibratorInfo.getId() + "):");
         pw.increaseIndent();
         pw.println("isVibrating = " + mIsVibrating);
         pw.println("isUnderExternalControl = " + mIsUnderExternalControl);
         pw.println("currentAmplitude = " + mCurrentAmplitude);
         pw.println("vibratorInfoLoadSuccessful = " + mVibratorInfoLoadSuccessful);
-        pw.println("vibratorStateListenerCount = "
+        pw.println("vibratorStateListener size = "
                 + mVibratorStateListeners.getRegisteredCallbackCount());
         mVibratorInfo.dump(pw);
         pw.decreaseIndent();
diff --git a/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java
index 79a99b3..b49fb85 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java
@@ -24,8 +24,6 @@
 
 /**
  * Holder class for {@link IVibratorController}.
- *
- * @hide
  */
 public final class VibratorControllerHolder implements IBinder.DeathRecipient {
     private static final String TAG = "VibratorControllerHolder";
diff --git a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
index 7e601b6..e9c3894 100644
--- a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
@@ -25,6 +25,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.modules.expresslog.Counter;
+import com.android.modules.expresslog.Histogram;
 
 import java.util.ArrayDeque;
 import java.util.Queue;
@@ -40,6 +41,23 @@
     // Warning about dropping entries after this amount of atoms were dropped by the throttle.
     private static final int VIBRATION_REPORTED_WARNING_QUEUE_SIZE = 200;
 
+    // Latency between 0ms and 99ms, with 100 representing overflow latencies >= 100ms.
+    // Underflow not expected.
+    private static final Histogram sVibrationParamRequestLatencyHistogram = new Histogram(
+            "vibrator.value_vibration_param_request_latency",
+            new Histogram.UniformOptions(20, 0, 100));
+
+    // Scales in [0, 2), with 2 representing overflow scales >= 2.
+    // Underflow expected to represent how many times scales were cleared (set to -1).
+    private static final Histogram sVibrationParamScaleHistogram = new Histogram(
+            "vibrator.value_vibration_param_scale", new Histogram.UniformOptions(20, 0, 2));
+
+    // Scales in [0, 2), with 2 representing overflow scales >= 2.
+    // Underflow not expected.
+    private static final Histogram sAdaptiveHapticScaleHistogram = new Histogram(
+            "vibrator.value_vibration_adaptive_haptic_scale",
+            new Histogram.UniformOptions(20, 0, 2));
+
     private final Object mLock = new Object();
     private final Handler mHandler;
     private final long mVibrationReportedLogIntervalMillis;
@@ -140,6 +158,33 @@
         }
     }
 
+    /** Logs adaptive haptic scale value applied to a vibration, only if it's not 1.0. */
+    public void logVibrationAdaptiveHapticScale(int uid, float scale) {
+        if (Float.compare(scale, 1f) != 0) {
+            sAdaptiveHapticScaleHistogram.logSampleWithUid(uid, scale);
+        }
+    }
+
+    /** Logs a vibration param scale value received by the vibrator control service. */
+    public void logVibrationParamScale(float scale) {
+        sVibrationParamScaleHistogram.logSample(scale);
+    }
+
+    /** Logs the latency of a successful vibration params request completed before a vibration. */
+    public void logVibrationParamRequestLatency(int uid, long latencyMs) {
+        sVibrationParamRequestLatencyHistogram.logSampleWithUid(uid, (float) latencyMs);
+    }
+
+    /** Logs a vibration params request timed out before a vibration. */
+    public void logVibrationParamRequestTimeout(int uid) {
+        Counter.logIncrementWithUid("vibrator.value_vibration_param_request_timeout", uid);
+    }
+
+    /** Logs when a response received for a vibration params request is ignored by the service. */
+    public void logVibrationParamResponseIgnored() {
+        Counter.logIncrement("vibrator.value_vibration_param_response_ignored");
+    }
+
     /** Logs only if the haptics feedback effect is one of the KEYBOARD_ constants. */
     public static void logPerformHapticsFeedbackIfKeyboard(int uid, int hapticsFeedbackEffect) {
         boolean isKeyboard;
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 78e0ebb..9e9025e 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -16,7 +16,6 @@
 
 package com.android.server.vibrator;
 
-import static android.os.ExternalVibrationScale.ScaleLevel.SCALE_MUTE;
 import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
 import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
 
@@ -84,7 +83,6 @@
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
@@ -214,12 +212,13 @@
         mContext = context;
         mInjector = injector;
         mHandler = injector.createHandler(Looper.myLooper());
+        mFrameworkStatsLogger = injector.getFrameworkStatsLogger(mHandler);
 
         mVibrationSettings = new VibrationSettings(mContext, mHandler);
         mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
-        mVibratorControlService = new VibratorControlService(
+        mVibratorControlService = new VibratorControlService(mContext,
                 injector.createVibratorControllerHolder(), mVibrationScaler, mVibrationSettings,
-                mLock);
+                mFrameworkStatsLogger, mLock);
         mInputDeviceDelegate = new InputDeviceDelegate(mContext, mHandler);
 
         VibrationCompleteListener listener = new VibrationCompleteListener(this);
@@ -237,7 +236,6 @@
                 recentDumpSizeLimit, dumpSizeLimit, dumpAggregationTimeLimit);
 
         mBatteryStatsService = injector.getBatteryStatsService();
-        mFrameworkStatsLogger = injector.getFrameworkStatsLogger(mHandler);
 
         mAppOps = mContext.getSystemService(AppOpsManager.class);
 
@@ -416,14 +414,14 @@
     }
 
     @Override // Binder call
-    public void performHapticFeedback(
-            int uid, int deviceId, String opPkg, int constant, boolean always, String reason) {
+    public void performHapticFeedback(int uid, int deviceId, String opPkg, int constant,
+            boolean always, String reason, boolean fromIme) {
         // Note that the `performHapticFeedback` method does not take a token argument from the
         // caller, and instead, uses this service as the token. This is to mitigate performance
         // impact that would otherwise be caused due to marshal latency. Haptic feedback effects are
         // short-lived, so we don't need to cancel when the process dies.
         performHapticFeedbackInternal(
-                uid, deviceId, opPkg, constant, always, reason, /* token= */ this);
+                uid, deviceId, opPkg, constant, always, reason, /* token= */ this, fromIme);
     }
 
     /**
@@ -435,7 +433,7 @@
     @Nullable
     HalVibration performHapticFeedbackInternal(
             int uid, int deviceId, String opPkg, int constant, boolean always, String reason,
-            IBinder token) {
+            IBinder token, boolean fromIme) {
         HapticFeedbackVibrationProvider hapticVibrationProvider = getHapticVibrationProvider();
         if (hapticVibrationProvider == null) {
             Slog.w(TAG, "performHapticFeedback; haptic vibration provider not ready.");
@@ -449,7 +447,7 @@
         CombinedVibration combinedVibration = CombinedVibration.createParallel(effect);
         VibrationAttributes attrs =
                 hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
-                        constant, /* bypassVibrationIntensitySetting= */ always);
+                        constant, /* bypassVibrationIntensitySetting= */ always, fromIme);
         VibratorFrameworkStatsLogger.logPerformHapticsFeedbackIfKeyboard(uid, constant);
         return vibrateWithoutPermissionCheck(uid, deviceId, opPkg, combinedVibration, attrs,
                 "performHapticFeedback: " + reason, token);
@@ -639,13 +637,16 @@
         }
         IndentingPrintWriter pw = new IndentingPrintWriter(w, /* singleIndent= */ "  ");
         synchronized (mLock) {
-            pw.println("Vibrator Manager Service:");
+            pw.println("VibratorManagerService:");
             pw.increaseIndent();
 
             mVibrationSettings.dump(pw);
             pw.println();
 
-            pw.println("VibratorControllers:");
+            mVibrationScaler.dump(pw);
+            pw.println();
+
+            pw.println("Vibrators:");
             pw.increaseIndent();
             for (int i = 0; i < mVibrators.size(); i++) {
                 mVibrators.valueAt(i).dump(pw);
@@ -686,6 +687,10 @@
         pw.println();
         pw.println();
         mVibratorManagerRecords.dump(pw);
+
+        pw.println();
+        pw.println();
+        mVibratorControlService.dump(pw);
     }
 
     private void dumpProto(FileDescriptor fd) {
@@ -695,6 +700,7 @@
         }
         synchronized (mLock) {
             mVibrationSettings.dump(proto);
+            mVibrationScaler.dump(proto);
             if (mCurrentVibration != null) {
                 mCurrentVibration.getVibration().getDebugInfo().dump(proto,
                         VibratorManagerServiceDumpProto.CURRENT_VIBRATION);
@@ -716,6 +722,7 @@
                     isUnderExternalControl);
         }
         mVibratorManagerRecords.dump(proto);
+        mVibratorControlService.dump(proto);
         proto.flush();
     }
 
@@ -846,9 +853,7 @@
     private void endVibrationLocked(HalVibration vib, Vibration.EndInfo vibrationEndInfo,
             boolean shouldWriteStats) {
         vib.end(vibrationEndInfo);
-        logVibrationStatus(vib.callerInfo.uid, vib.callerInfo.attrs,
-                vibrationEndInfo.status);
-        mVibratorManagerRecords.record(vib);
+        logAndRecordVibration(vib.getDebugInfo());
         if (shouldWriteStats) {
             mFrameworkStatsLogger.writeVibrationReportedAsync(
                     vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
@@ -859,9 +864,7 @@
     private void endVibrationAndWriteStatsLocked(ExternalVibrationHolder vib,
             Vibration.EndInfo vibrationEndInfo) {
         vib.end(vibrationEndInfo);
-        logVibrationStatus(vib.externalVibration.getUid(),
-                vib.externalVibration.getVibrationAttributes(), vibrationEndInfo.status);
-        mVibratorManagerRecords.record(vib);
+        logAndRecordVibration(vib.getDebugInfo());
         mFrameworkStatsLogger.writeVibrationReportedAsync(
                 vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
     }
@@ -875,19 +878,19 @@
                 vib.callerInfo.attrs.getUsage())) {
             requestVibrationParamsFuture =
                     mVibratorControlService.triggerVibrationParamsRequest(
-                            vib.callerInfo.attrs.getUsage(),
+                            vib.callerInfo.uid, vib.callerInfo.attrs.getUsage(),
                             mVibrationSettings.getRequestVibrationParamsTimeoutMs());
         }
 
         return new VibrationStepConductor(vib, mVibrationSettings, mDeviceAdapter, mVibrationScaler,
-                requestVibrationParamsFuture, mVibrationThreadCallbacks);
+                mFrameworkStatsLogger, requestVibrationParamsFuture, mVibrationThreadCallbacks);
     }
 
     private Vibration.EndInfo startVibrationOnInputDevicesLocked(HalVibration vib) {
         if (!vib.callerInfo.attrs.isFlagSet(
                 VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)) {
             // Scale resolves the default amplitudes from the effect before scaling them.
-            vib.scaleEffects(mVibrationScaler::scale);
+            vib.scaleEffects(mVibrationScaler);
         } else {
             vib.resolveEffects(mVibrationScaler.getDefaultVibrationAmplitude());
         }
@@ -896,6 +899,12 @@
         return new Vibration.EndInfo(Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
     }
 
+    private void logAndRecordVibration(Vibration.DebugInfo info) {
+        info.logMetrics(mFrameworkStatsLogger);
+        logVibrationStatus(info.mCallerInfo.uid, info.mCallerInfo.attrs, info.mStatus);
+        mVibratorManagerRecords.record(info);
+    }
+
     private void logVibrationStatus(int uid, VibrationAttributes attrs,
             Vibration.Status status) {
         switch (status) {
@@ -1663,7 +1672,8 @@
 
         public Vibration.DebugInfo getDebugInfo() {
             return new Vibration.DebugInfo(mStatus, stats, /* playedEffect= */ null,
-                    /* originalEffect= */ null, scale.scaleLevel, callerInfo);
+                    /* originalEffect= */ null, scale.scaleLevel, scale.adaptiveHapticsScale,
+                    callerInfo);
         }
 
         public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
@@ -1739,22 +1749,17 @@
                 int aggregationTimeLimit) {
             mAggregatedVibrationHistory =
                     new VibrationRecords(aggregationSizeLimit, aggregationTimeLimit);
-            mRecentVibrations = new VibrationRecords(
-                    recentVibrationSizeLimit, /* aggregationTimeLimit= */ 0);
+            // Recent vibrations are not aggregated, to help debugging issues that just happened.
+            mRecentVibrations =
+                    new VibrationRecords(recentVibrationSizeLimit, /* aggregationTimeLimit= */ 0);
         }
 
-        synchronized void record(HalVibration vib) {
-            record(vib.getDebugInfo());
-        }
-
-        synchronized void record(ExternalVibrationHolder vib) {
-            record(vib.getDebugInfo());
-        }
-
-        private synchronized void record(Vibration.DebugInfo info) {
-            AggregatedVibrationRecord removedRecord = mRecentVibrations.record(info);
-            if (removedRecord != null) {
-                mAggregatedVibrationHistory.record(removedRecord.mLatestVibration);
+        synchronized void record(Vibration.DebugInfo info) {
+            GroupedAggregatedLogRecords.AggregatedLogRecord<VibrationRecord> droppedRecord =
+                    mRecentVibrations.add(new VibrationRecord(info));
+            if (droppedRecord != null) {
+                // Move dropped record from recent list to aggregated history list.
+                mAggregatedVibrationHistory.add(droppedRecord.getLatest());
             }
         }
 
@@ -1763,9 +1768,9 @@
             pw.increaseIndent();
             mRecentVibrations.dump(pw);
             pw.decreaseIndent();
-            pw.println();
-            pw.println();
 
+            pw.println();
+            pw.println();
             pw.println("Aggregated vibration history:");
             pw.increaseIndent();
             mAggregatedVibrationHistory.dump(pw);
@@ -1778,127 +1783,75 @@
     }
 
     /** Keep records of vibrations played and provide debug information for this service. */
-    private static final class VibrationRecords {
-        private final SparseArray<LinkedList<AggregatedVibrationRecord>> mVibrations =
-                new SparseArray<>();
-        private final int mSizeLimit;
-        private final int mAggregationTimeLimit;
+    private static final class VibrationRecords
+            extends GroupedAggregatedLogRecords<VibrationRecord> {
 
         VibrationRecords(int sizeLimit, int aggregationTimeLimit) {
-            mSizeLimit = sizeLimit;
-            mAggregationTimeLimit = aggregationTimeLimit;
+            super(sizeLimit, aggregationTimeLimit);
         }
 
-        synchronized AggregatedVibrationRecord record(Vibration.DebugInfo info) {
-            int usage = info.mCallerInfo.attrs.getUsage();
-            if (!mVibrations.contains(usage)) {
-                mVibrations.put(usage, new LinkedList<>());
-            }
-            LinkedList<AggregatedVibrationRecord> records = mVibrations.get(usage);
-            if (mAggregationTimeLimit > 0 && !records.isEmpty()) {
-                AggregatedVibrationRecord lastRecord = records.getLast();
-                if (lastRecord.mayAggregate(info, mAggregationTimeLimit)) {
-                    lastRecord.record(info);
-                    return null;
-                }
-            }
-            AggregatedVibrationRecord removedRecord = null;
-            if (records.size() > mSizeLimit) {
-                removedRecord = records.removeFirst();
-            }
-            records.addLast(new AggregatedVibrationRecord(info));
-            return removedRecord;
+        @Override
+        void dumpGroupHeader(IndentingPrintWriter pw, int usage) {
+            pw.println(VibrationAttributes.usageToString(usage) + ":");
         }
 
-        synchronized void dump(IndentingPrintWriter pw) {
-            for (int i = 0; i < mVibrations.size(); i++) {
-                pw.println(VibrationAttributes.usageToString(mVibrations.keyAt(i)) + ":");
-                pw.increaseIndent();
-                for (AggregatedVibrationRecord info : mVibrations.valueAt(i)) {
-                    info.dump(pw);
-                }
-                pw.decreaseIndent();
-                pw.println();
-            }
-        }
-
-        synchronized void dump(ProtoOutputStream proto) {
-            for (int i = 0; i < mVibrations.size(); i++) {
-                long fieldId;
-                switch (mVibrations.keyAt(i)) {
-                    case VibrationAttributes.USAGE_RINGTONE:
-                        fieldId = VibratorManagerServiceDumpProto.PREVIOUS_RING_VIBRATIONS;
-                        break;
-                    case VibrationAttributes.USAGE_NOTIFICATION:
-                        fieldId = VibratorManagerServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS;
-                        break;
-                    case VibrationAttributes.USAGE_ALARM:
-                        fieldId = VibratorManagerServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS;
-                        break;
-                    default:
-                        fieldId = VibratorManagerServiceDumpProto.PREVIOUS_VIBRATIONS;
-                }
-                for (AggregatedVibrationRecord info : mVibrations.valueAt(i)) {
-                    if (info.mLatestVibration.mPlayedEffect == null) {
-                        // External vibrations are reported separately in the dump proto
-                        info.dump(proto,
-                                VibratorManagerServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
-                    } else {
-                        info.dump(proto, fieldId);
-                    }
-                }
-            }
-        }
-
-        synchronized void dumpOnSingleField(ProtoOutputStream proto, long fieldId) {
-            for (int i = 0; i < mVibrations.size(); i++) {
-                for (AggregatedVibrationRecord info : mVibrations.valueAt(i)) {
-                    info.dump(proto, fieldId);
-                }
-            }
+        @Override
+        long findGroupKeyProtoFieldId(int usage) {
+            return switch (usage) {
+                case VibrationAttributes.USAGE_RINGTONE ->
+                    VibratorManagerServiceDumpProto.PREVIOUS_RING_VIBRATIONS;
+                case VibrationAttributes.USAGE_NOTIFICATION ->
+                    VibratorManagerServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS;
+                case VibrationAttributes.USAGE_ALARM ->
+                    VibratorManagerServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS;
+                default ->
+                    VibratorManagerServiceDumpProto.PREVIOUS_VIBRATIONS;
+            };
         }
     }
 
     /**
-     * Record that keeps the last {@link Vibration.DebugInfo} played, aggregating close vibrations
-     * from the same uid that have the same {@link VibrationAttributes} and {@link VibrationEffect}.
+     * Record for a single {@link Vibration.DebugInfo}, that can be grouped by usage and aggregated
+     * by UID, {@link VibrationAttributes} and {@link VibrationEffect}.
      */
-    private static final class AggregatedVibrationRecord {
-        private final Vibration.DebugInfo mFirstVibration;
-        private Vibration.DebugInfo mLatestVibration;
-        private int mVibrationCount;
+    private static final class VibrationRecord
+            implements GroupedAggregatedLogRecords.SingleLogRecord {
+        private final Vibration.DebugInfo mInfo;
 
-        AggregatedVibrationRecord(Vibration.DebugInfo info) {
-            mLatestVibration = mFirstVibration = info;
-            mVibrationCount = 1;
+        VibrationRecord(Vibration.DebugInfo info) {
+            mInfo = info;
         }
 
-        synchronized boolean mayAggregate(Vibration.DebugInfo info, long timeLimit) {
-            return Objects.equals(mLatestVibration.mCallerInfo.uid, info.mCallerInfo.uid)
-                    && Objects.equals(mLatestVibration.mCallerInfo.attrs, info.mCallerInfo.attrs)
-                    && Objects.equals(mLatestVibration.mPlayedEffect, info.mPlayedEffect)
-                    && Math.abs(mLatestVibration.mCreateTime - info.mCreateTime) < timeLimit;
+        @Override
+        public int getGroupKey() {
+            return mInfo.mCallerInfo.attrs.getUsage();
         }
 
-        synchronized void record(Vibration.DebugInfo vib) {
-            mLatestVibration = vib;
-            mVibrationCount++;
+        @Override
+        public long getCreateUptimeMs() {
+            return mInfo.mCreateTime;
         }
 
-        synchronized void dump(IndentingPrintWriter pw) {
-            mFirstVibration.dumpCompact(pw);
-            if (mVibrationCount == 1) {
-                return;
+        @Override
+        public boolean mayAggregate(GroupedAggregatedLogRecords.SingleLogRecord record) {
+            if (!(record instanceof VibrationRecord)) {
+                return false;
             }
-            if (mVibrationCount > 2) {
-                pw.println(
-                        "-> Skipping " + (mVibrationCount - 2) + " aggregated vibrations, latest:");
-            }
-            mLatestVibration.dumpCompact(pw);
+            Vibration.DebugInfo info = ((VibrationRecord) record).mInfo;
+            return mInfo.mCallerInfo.uid == info.mCallerInfo.uid
+                    && Objects.equals(mInfo.mCallerInfo.attrs, info.mCallerInfo.attrs)
+                    && Objects.equals(mInfo.mPlayedEffect, info.mPlayedEffect);
         }
 
-        synchronized void dump(ProtoOutputStream proto, long fieldId) {
-            mLatestVibration.dump(proto, fieldId);
+        @Override
+        public void dump(IndentingPrintWriter pw) {
+            // Prints a compact version of each vibration request for dumpsys.
+            mInfo.dumpCompact(pw);
+        }
+
+        @Override
+        public void dump(ProtoOutputStream proto, long fieldId) {
+            mInfo.dump(proto, fieldId);
         }
     }
 
@@ -2001,7 +1954,6 @@
 
         @Override
         public ExternalVibrationScale onExternalVibrationStart(ExternalVibration vib) {
-
             if (!hasExternalControlCapability()) {
                 return SCALE_MUTE;
             }
@@ -2085,10 +2037,9 @@
                 }
                 mCurrentExternalVibration = vibHolder;
                 vibHolder.linkToDeath();
-                vibHolder.scale.scaleLevel = mVibrationScaler.getExternalVibrationScaleLevel(
-                        attrs.getUsage());
-                vibHolder.scale.adaptiveHapticsScale = mVibrationScaler.getAdaptiveHapticsScale(
-                        attrs.getUsage());
+                vibHolder.scale.scaleLevel = mVibrationScaler.getScaleLevel(attrs.getUsage());
+                vibHolder.scale.adaptiveHapticsScale =
+                        mVibrationScaler.getAdaptiveHapticsScale(attrs.getUsage());
             }
 
             if (waitForCompletion) {
@@ -2146,6 +2097,7 @@
     /** Provide limited functionality from {@link VibratorManagerService} as shell commands. */
     private final class VibratorManagerShellCommand extends ShellCommand {
         public static final String SHELL_PACKAGE_NAME = "com.android.shell";
+        public static final long VIBRATION_END_TIMEOUT_MS = 500; // Clean up shouldn't be too long.
 
         private final class CommonOptions {
             public boolean force = false;
@@ -2300,7 +2252,7 @@
             HalVibration vib = performHapticFeedbackInternal(Binder.getCallingUid(),
                     Context.DEVICE_ID_DEFAULT, SHELL_PACKAGE_NAME, constant,
                     /* always= */ commonOptions.force, /* reason= */ commonOptions.description,
-                    deathBinder);
+                    deathBinder, false /* fromIme */);
             maybeWaitOnVibration(vib, commonOptions);
 
             return 0;
@@ -2521,6 +2473,9 @@
                     // Waits for the client vibration to finish, but the VibrationThread may still
                     // do cleanup after this.
                     vib.waitForEnd();
+                    // Wait for vibration clean up and possible ramp down before ending.
+                    mVibrationThread.waitForThreadIdle(
+                            mVibrationSettings.getRampDownDuration() + VIBRATION_END_TIMEOUT_MS);
                 } catch (InterruptedException e) {
                 }
             }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 37f3825..5175b74 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -39,6 +39,7 @@
 import android.view.DisplayInfo;
 import android.view.View;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.utils.TimingsTraceAndSlog;
 
 import libcore.io.IoUtils;
@@ -65,7 +66,7 @@
      * Maximum acceptable parallax.
      * A value of 1 means "the additional width for parallax is at most 100% of the screen width"
      */
-    private static final float MAX_PARALLAX = 1f;
+    @VisibleForTesting static final float MAX_PARALLAX = 1f;
 
     /**
      * We define three ways to adjust a crop. These modes are used depending on the situation:
@@ -73,10 +74,9 @@
      *   - When going from folded to unfolded, we want to add content
      *   - For a screen rotation, we want to keep the same amount of content
      */
-    private static final int ADD = 1;
-    private static final int REMOVE = 2;
-    private static final int BALANCE = 3;
-
+    @VisibleForTesting static final int ADD = 1;
+    @VisibleForTesting static final int REMOVE = 2;
+    @VisibleForTesting static final int BALANCE = 3;
 
     private final WallpaperDisplayHelper mWallpaperDisplayHelper;
 
@@ -131,19 +131,23 @@
                     (bitmapSize.y - crop.height()) / 2);
             return crop;
         }
+
+        // If any suggested crop is invalid, fallback to case 1
+        for (int i = 0; i < suggestedCrops.size(); i++) {
+            Rect testCrop = suggestedCrops.valueAt(i);
+            if (testCrop == null || testCrop.left < 0 || testCrop.top < 0
+                    || testCrop.right > bitmapSize.x || testCrop.bottom > bitmapSize.y) {
+                Slog.w(TAG, "invalid crop: " + testCrop + " for bitmap size: " + bitmapSize);
+                return getCrop(displaySize, bitmapSize, new SparseArray<>(), rtl);
+            }
+        }
+
         int orientation = getOrientation(displaySize);
 
         // Case 2: if the orientation exists in the suggested crops, adjust the suggested crop
         Rect suggestedCrop = suggestedCrops.get(orientation);
         if (suggestedCrop != null) {
-            if (suggestedCrop.left < 0 || suggestedCrop.top < 0
-                    || suggestedCrop.right > bitmapSize.x || suggestedCrop.bottom > bitmapSize.y) {
-                Slog.w(TAG, "invalid suggested crop: " + suggestedCrop);
-                Rect fullImage = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
-                return getAdjustedCrop(fullImage, bitmapSize, displaySize, true, rtl, ADD);
-            } else {
                 return getAdjustedCrop(suggestedCrop, bitmapSize, displaySize, true, rtl, ADD);
-            }
         }
 
         // Case 3: if we have the 90° rotated orientation in the suggested crops, reuse it and
@@ -209,7 +213,8 @@
      * Given a crop, a displaySize for the orientation of that crop, compute the visible part of the
      * crop. This removes any additional width used for parallax. No-op if displaySize == null.
      */
-    private static Rect noParallax(Rect crop, Point displaySize, Point bitmapSize, boolean rtl) {
+    @VisibleForTesting
+    static Rect noParallax(Rect crop, Point displaySize, Point bitmapSize, boolean rtl) {
         if (displaySize == null) return crop;
         Rect adjustedCrop = getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD);
         // only keep the visible part (without parallax)
@@ -240,12 +245,14 @@
      *     </li>
      * </ul>
      */
-    private static Rect getAdjustedCrop(Rect crop, Point bitmapSize, Point screenSize,
+    @VisibleForTesting
+    static Rect getAdjustedCrop(Rect crop, Point bitmapSize, Point screenSize,
             boolean parallax, boolean rtl, int mode) {
         Rect adjustedCrop = new Rect(crop);
         float cropRatio = ((float) crop.width()) / crop.height();
         float screenRatio = ((float) screenSize.x) / screenSize.y;
-        if (cropRatio >= screenRatio) {
+        if (cropRatio == screenRatio) return crop;
+        if (cropRatio > screenRatio) {
             if (!parallax) {
                 // rotate everything 90 degrees clockwise, compute the result, and rotate back
                 int newLeft = bitmapSize.y - crop.bottom;
@@ -274,6 +281,7 @@
                 }
             }
         } else {
+            // TODO (b/281648899) the third case is not always correct, fix that.
             int widthToAdd = mode == REMOVE ? 0
                     : mode == ADD ? (int) (0.5 + crop.height() * screenRatio - crop.width())
                     : (int) (0.5 + crop.height() - crop.width());
@@ -357,15 +365,30 @@
      * Given some suggested crops, find cropHints for all orientations of the default display.
      */
     SparseArray<Rect> getDefaultCrops(SparseArray<Rect> suggestedCrops, Point bitmapSize) {
-        SparseArray<Rect> result = new SparseArray<>();
-        // add missing cropHints for all orientation of the default display
+
         SparseArray<Point> defaultDisplaySizes = mWallpaperDisplayHelper.getDefaultDisplaySizes();
         boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
                 == View.LAYOUT_DIRECTION_RTL;
+
+        // adjust existing entries for the default display
+        SparseArray<Rect> adjustedSuggestedCrops = new SparseArray<>();
         for (int i = 0; i < defaultDisplaySizes.size(); i++) {
             int orientation = defaultDisplaySizes.keyAt(i);
             Point displaySize = defaultDisplaySizes.valueAt(i);
-            Rect newCrop = getCrop(displaySize, bitmapSize, suggestedCrops, rtl);
+            Rect suggestedCrop = suggestedCrops.get(orientation);
+            if (suggestedCrop != null) {
+                adjustedSuggestedCrops.put(orientation,
+                        getCrop(displaySize, bitmapSize, suggestedCrops, rtl));
+            }
+        }
+
+        // add missing cropHints for all orientation of the default display
+        SparseArray<Rect> result = adjustedSuggestedCrops.clone();
+        for (int i = 0; i < defaultDisplaySizes.size(); i++) {
+            int orientation = defaultDisplaySizes.keyAt(i);
+            if (result.contains(orientation)) continue;
+            Point displaySize = defaultDisplaySizes.valueAt(i);
+            Rect newCrop = getCrop(displaySize, bitmapSize, adjustedSuggestedCrops, rtl);
             result.put(orientation, newCrop);
         }
         return result;
@@ -629,6 +652,9 @@
         if (!success) {
             Slog.e(TAG, "Unable to apply new wallpaper");
             wallpaper.getCropFile().delete();
+            wallpaper.mCropHints.clear();
+            wallpaper.cropHint.set(0, 0, 0, 0);
+            wallpaper.mSampleSize = 1f;
         }
 
         if (wallpaper.getCropFile().exists()) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index 88e9672..0165d65 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -341,6 +341,7 @@
             } else {
                 wallpaper.cropHint.set(totalCropHint);
             }
+            wallpaper.mSampleSize = parser.getAttributeFloat(null, "sampleSize", 1f);
         } else {
             wallpaper.cropHint.set(totalCropHint);
         }
@@ -493,6 +494,7 @@
             out.attributeInt(null, "totalCropTop", wallpaper.cropHint.top);
             out.attributeInt(null, "totalCropRight", wallpaper.cropHint.right);
             out.attributeInt(null, "totalCropBottom", wallpaper.cropHint.bottom);
+            out.attributeFloat(null, "sampleSize", wallpaper.mSampleSize);
         } else if (!multiCrop()) {
             final DisplayData wpdData =
                     mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index c3efcb1..885baf6 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2691,6 +2691,7 @@
                 }
 
                 float maxDimAmount = getHighestDimAmountFromMap(wallpaper.mUidToDimAmount);
+                if (wallpaper.mWallpaperDimAmount == maxDimAmount) return;
                 wallpaper.mWallpaperDimAmount = maxDimAmount;
                 // Also set the dim amount to the lock screen wallpaper if the lock and home screen
                 // do not share the same wallpaper
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index 5f6ffd98..8742ab1 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -21,8 +21,8 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.ActivityOptions;
 import android.app.BroadcastOptions;
-import android.app.ComponentOptions;
 import android.app.PendingIntent;
 import android.app.ambientcontext.AmbientContextEvent;
 import android.app.wearable.IWearableSensingManager;
@@ -353,7 +353,7 @@
                             dataRequest);
                     BroadcastOptions options = BroadcastOptions.makeBasic();
                     options.setPendingIntentBackgroundActivityStartMode(
-                            ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED);
+                            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED);
                     mDataRequestRateLimiter.noteEvent(
                             userId, RATE_LIMITER_PACKAGE_NAME, RATE_LIMITER_TAG);
                     final long previousCallingIdentity = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index e9c4096..043470f 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -30,6 +30,7 @@
 import android.os.Process;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.Slog;
 import android.webkit.IWebViewUpdateService;
@@ -37,6 +38,7 @@
 import android.webkit.WebViewProviderResponse;
 
 import com.android.internal.util.DumpUtils;
+import com.android.modules.expresslog.Histogram;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 
@@ -52,6 +54,14 @@
 
     private static final String TAG = "WebViewUpdateService";
 
+    private static final Histogram sPrepareWebViewInSystemServerLatency = new Histogram(
+            "webview.value_prepare_webview_in_system_server_latency",
+            new Histogram.ScaledRangeOptions(20, 0, 1, 1.5f));
+
+    private static final Histogram sAppWaitingForRelroCompletionDelay = new Histogram(
+            "webview.value_app_waiting_for_relro_completion_delay",
+            new Histogram.ScaledRangeOptions(20, 0, 1, 1.4f));
+
     private BroadcastReceiver mWebViewUpdatedReceiver;
     private WebViewUpdateServiceInterface mImpl;
 
@@ -132,7 +142,10 @@
     }
 
     public void prepareWebViewInSystemServer() {
+        long currentTimeMs = SystemClock.uptimeMillis();
         mImpl.prepareWebViewInSystemServer();
+        sPrepareWebViewInSystemServerLatency.logSample(
+                (float) (SystemClock.uptimeMillis() - currentTimeMs));
     }
 
     private static String packageNameFromIntent(Intent intent) {
@@ -204,8 +217,12 @@
                 throw new IllegalStateException("Cannot create a WebView from the SystemServer");
             }
 
+            long startTimeMs = SystemClock.uptimeMillis();
             final WebViewProviderResponse webViewProviderResponse =
                     WebViewUpdateService.this.mImpl.waitForAndGetProvider();
+            long endTimeMs = SystemClock.uptimeMillis();
+            sAppWaitingForRelroCompletionDelay.logSample((float) (endTimeMs - startTimeMs));
+
             if (webViewProviderResponse.packageInfo != null) {
                 grantVisibilityToCaller(
                         webViewProviderResponse.packageInfo.packageName, Binder.getCallingUid());
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index 1d6ad6d..532ff98 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -23,12 +23,15 @@
 import android.os.AsyncTask;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.util.AndroidRuntimeException;
 import android.util.Slog;
 import android.webkit.UserPackage;
 import android.webkit.WebViewFactory;
 import android.webkit.WebViewProviderInfo;
 import android.webkit.WebViewProviderResponse;
 
+import com.android.modules.expresslog.Counter;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -357,6 +360,12 @@
                 mNumRelroCreationsFinished = 0;
                 mNumRelroCreationsStarted =
                     mSystemInterface.onWebViewProviderChanged(newPackage);
+                Counter.logIncrement("webview.value_on_webview_provider_changed_counter");
+                if (newPackage.packageName.equals(getDefaultWebViewPackage().packageName)) {
+                    Counter.logIncrement(
+                            "webview.value_on_webview_provider_changed_"
+                            + "with_default_package_counter");
+                }
                 // If the relro creations finish before we know the number of started creations
                 // we will have to do any cleanup/notifying here.
                 checkIfRelrosDoneLocked();
@@ -388,9 +397,15 @@
 
     @Override
     public WebViewProviderInfo getDefaultWebViewPackage() {
-        throw new IllegalStateException(
-                "getDefaultWebViewPackage shouldn't be called if update_service_v2 flag is"
-                        + " disabled.");
+        for (WebViewProviderInfo provider : getWebViewPackages()) {
+            if (provider.availableByDefault) {
+                return provider;
+            }
+        }
+
+        // This should be unreachable because the config parser enforces that there is at least
+        // one availableByDefault provider.
+        throw new AndroidRuntimeException("No available by default WebView Provider.");
     }
 
     private static class ProviderAndPackageInfo {
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
index 596de68..fb338ba 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -31,6 +31,8 @@
 import android.webkit.WebViewProviderInfo;
 import android.webkit.WebViewProviderResponse;
 
+import com.android.modules.expresslog.Counter;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -96,6 +98,9 @@
     private boolean mWebViewPackageDirty = false;
     private boolean mAnyWebViewInstalled = false;
 
+    // Keeps track of whether we attempted to repair WebView before.
+    private boolean mAttemptedToRepairBefore = false;
+
     private static final int NUMBER_OF_RELROS_UNKNOWN = Integer.MAX_VALUE;
 
     // The WebView package currently in use (or the one we are preparing).
@@ -136,6 +141,7 @@
                 boolean removedOrChangedOldPackage = false;
                 String oldProviderName = null;
                 PackageInfo newPackage = null;
+                boolean repairNeeded = false;
                 synchronized (mLock) {
                     try {
                         newPackage = findPreferredWebViewPackage();
@@ -161,6 +167,7 @@
                         Slog.e(TAG, "Could not find valid WebView package to create relro with "
                                 + e);
                     }
+                    repairNeeded = shouldTriggerRepairLocked();
                 }
                 if (updateWebView && !removedOrChangedOldPackage
                         && oldProviderName != null) {
@@ -170,12 +177,18 @@
                     // only kills dependents of packages that are being removed.
                     mSystemInterface.killPackageDependents(oldProviderName);
                 }
+                if (repairNeeded) {
+                    attemptRepair();
+                }
                 return;
             }
         }
     }
 
     private boolean shouldTriggerRepairLocked() {
+        if (mAttemptedToRepairBefore) {
+            return false;
+        }
         if (mCurrentWebViewPackage == null) {
             return true;
         }
@@ -189,6 +202,26 @@
         }
     }
 
+    private void attemptRepair() {
+        // We didn't find a valid WebView implementation. Try explicitly re-installing and
+        // re-enabling the default package for all users in case it was disabled. If this actually
+        // changes the state, we will see the PackageManager broadcast shortly and try again.
+        synchronized (mLock) {
+            if (mAttemptedToRepairBefore) {
+                return;
+            }
+            mAttemptedToRepairBefore = true;
+        }
+        Slog.w(
+                TAG,
+                "No provider available for all users, trying to install and enable "
+                        + mDefaultProvider.packageName);
+        mSystemInterface.installExistingPackageForAllUsers(
+                mContext, mDefaultProvider.packageName);
+        mSystemInterface.enablePackageForAllUsers(
+                mContext, mDefaultProvider.packageName, true);
+    }
+
     @Override
     public void prepareWebViewInSystemServer() {
         try {
@@ -211,18 +244,7 @@
             }
 
             if (repairNeeded) {
-                // We didn't find a valid WebView implementation. Try explicitly re-installing and
-                // re-enabling the default package for all users in case it was disabled, even if we
-                // already did the one-time migration before. If this actually changes the state, we
-                // will see the PackageManager broadcast shortly and try again.
-                Slog.w(
-                        TAG,
-                        "No provider available for all users, trying to install and enable "
-                                + mDefaultProvider.packageName);
-                mSystemInterface.installExistingPackageForAllUsers(
-                        mContext, mDefaultProvider.packageName);
-                mSystemInterface.enablePackageForAllUsers(
-                        mContext, mDefaultProvider.packageName, true);
+                attemptRepair();
             }
 
         } catch (Throwable t) {
@@ -332,6 +354,7 @@
         PackageInfo oldPackage = null;
         PackageInfo newPackage = null;
         boolean providerChanged = false;
+        boolean repairNeeded = false;
         synchronized (mLock) {
             oldPackage = mCurrentWebViewPackage;
 
@@ -354,11 +377,19 @@
             if (providerChanged) {
                 onWebViewProviderChanged(newPackage);
             }
+            // Choosing another provider shouldn't break our state. Only check if repair
+            // is needed if this function is called as a result of a user change.
+            if (newProviderName == null) {
+                repairNeeded = shouldTriggerRepairLocked();
+            }
         }
         // Kill apps using the old provider only if we changed provider
         if (providerChanged && oldPackage != null) {
             mSystemInterface.killPackageDependents(oldPackage.packageName);
         }
+        if (repairNeeded) {
+            attemptRepair();
+        }
         // Return the new provider, this is not necessarily the one we were asked to switch to,
         // but the persistent setting will now be pointing to the provider we were asked to
         // switch to anyway.
@@ -383,6 +414,12 @@
                 mNumRelroCreationsFinished = 0;
                 mNumRelroCreationsStarted =
                     mSystemInterface.onWebViewProviderChanged(newPackage);
+                Counter.logIncrement("webview.value_on_webview_provider_changed_counter");
+                if (newPackage.packageName.equals(getDefaultWebViewPackage().packageName)) {
+                    Counter.logIncrement(
+                            "webview.value_on_webview_provider_changed_"
+                            + "with_default_package_counter");
+                }
                 // If the relro creations finish before we know the number of started creations
                 // we will have to do any cleanup/notifying here.
                 checkIfRelrosDoneLocked();
@@ -450,6 +487,7 @@
      * for all users, otherwise use the default provider.
      */
     private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException {
+        Counter.logIncrement("webview.value_find_preferred_webview_package_counter");
         // If the user has chosen provider, use that (if it's installed and enabled for all
         // users).
         String userChosenPackageName = mSystemInterface.getUserChosenWebViewProvider(mContext);
@@ -479,12 +517,15 @@
             PackageInfo packageInfo = mSystemInterface.getPackageInfoForProvider(mDefaultProvider);
             if (validityResult(mDefaultProvider, packageInfo) == VALIDITY_OK) {
                 return packageInfo;
+            } else {
+                Counter.logIncrement("webview.value_default_webview_package_invalid_counter");
             }
         } catch (NameNotFoundException e) {
             Slog.w(TAG, "Default WebView package (" + mDefaultProvider.packageName + ") not found");
         }
 
         // This should never happen during normal operation (only with modified system images).
+        Counter.logIncrement("webview.value_webview_not_usable_for_all_users_counter");
         mAnyWebViewInstalled = false;
         throw new WebViewPackageMissingException("Could not find a loadable WebView package");
     }
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index d08e272..4189988 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -817,14 +817,9 @@
             switch (transition) {
                 case WindowManagerPolicy.TRANSIT_ENTER:
                 case WindowManagerPolicy.TRANSIT_SHOW: {
-                    if (!isMagnifierActivated) {
+                    if (!isMagnifierActivated || !windowState.shouldMagnify()) {
                         break;
                     }
-                    if (Flags.doNotCheckIntersectionWhenNonMagnifiableWindowTransitions()) {
-                        if (!windowState.shouldMagnify()) {
-                            break;
-                        }
-                    }
                     switch (type) {
                         case WindowManager.LayoutParams.TYPE_APPLICATION:
                         case WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION:
@@ -1214,6 +1209,7 @@
                 private boolean mShown;
                 private boolean mLastSurfaceShown;
                 private int mAlpha;
+                private int mPreviousAlpha;
 
                 private volatile boolean mInvalidated;
 
@@ -1349,6 +1345,7 @@
                     // using WindowManagerGlobalLock. Grab copies of these values before
                     // drawing on the canvas so that drawing can be performed outside of the lock.
                     int alpha;
+                    boolean redrawBounds;
                     Rect drawingRect = null;
                     Region drawingBounds = null;
                     synchronized (mService.mGlobalLock) {
@@ -1366,7 +1363,13 @@
                         mInvalidated = false;
 
                         alpha = mAlpha;
-                        if (alpha > 0) {
+                        // For b/325863281, we should ensure the drawn border path is cleared when
+                        // alpha = 0. Therefore, we cache the last used alpha when drawing as
+                        // mPreviousAlpha and check it here. If mPreviousAlpha > 0, which means
+                        // the border is showing now, then we should still redraw the clear path
+                        // on the canvas so the border is cleared.
+                        redrawBounds = mAlpha > 0 || mPreviousAlpha > 0;
+                        if (redrawBounds) {
                             drawingBounds = new Region(mBounds);
                             // Empty dirty rectangle means unspecified.
                             if (mDirtyRect.isEmpty()) {
@@ -1383,7 +1386,7 @@
 
                     final boolean showSurface;
                     // Draw without holding WindowManagerGlobalLock.
-                    if (alpha > 0) {
+                    if (redrawBounds) {
                         Canvas canvas = null;
                         try {
                             canvas = mSurface.lockCanvas(drawingRect);
@@ -1397,11 +1400,11 @@
                         mPaint.setAlpha(alpha);
                         canvas.drawPath(drawingBounds.getBoundaryPath(), mPaint);
                         mSurface.unlockCanvasAndPost(canvas);
-                        showSurface = true;
-                    } else {
-                        showSurface = false;
+                        mPreviousAlpha = alpha;
                     }
 
+                    showSurface = alpha > 0;
+
                     if (showSurface && !mLastSurfaceShown) {
                         mTransaction.show(mSurfaceControl).apply();
                         mLastSurfaceShown = true;
diff --git a/services/core/java/com/android/server/wm/ActivityAssistInfo.java b/services/core/java/com/android/server/wm/ActivityAssistInfo.java
index 3b91780..2dc0046 100644
--- a/services/core/java/com/android/server/wm/ActivityAssistInfo.java
+++ b/services/core/java/com/android/server/wm/ActivityAssistInfo.java
@@ -30,12 +30,14 @@
     private final IBinder mAssistToken;
     private final int mTaskId;
     private final ComponentName mComponentName;
+    private final int mUserId;
 
     public ActivityAssistInfo(ActivityRecord activityRecord) {
         this.mActivityToken = activityRecord.token;
         this.mAssistToken = activityRecord.assistToken;
         this.mTaskId = activityRecord.getTask().mTaskId;
         this.mComponentName = activityRecord.mActivityComponent;
+        this.mUserId = activityRecord.mUserId;
     }
 
     /** @hide */
@@ -57,4 +59,9 @@
     public ComponentName getComponentName() {
         return mComponentName;
     }
+
+    /** @hide */
+    public int getUserId() {
+        return mUserId;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index ed5df5f..981c4c0 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -506,7 +506,8 @@
                     // keep backwards compatibility we remove the task from recents when finishing
                     // task with root activity.
                     mTaskSupervisor.removeTask(tr, false /*killProcess*/,
-                            finishWithRootActivity, "finish-activity", r.getUid(), r.info.name);
+                            finishWithRootActivity, "finish-activity", r.getUid(), r.getPid(),
+                            r.info.name);
                     res = true;
                     // Explicitly dismissing the activity so reset its relaunch flag.
                     r.mRelaunchReason = RELAUNCH_REASON_NONE;
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 78f501a..59a56de 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -1133,10 +1133,12 @@
             isIncremental = true;
             isLoading = isIncrementalLoading(info.packageName, info.userId);
         }
-        final boolean stopped = (info.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0;
+        final boolean stopped = wasStoppedNeedsLogging(info);
         final int packageState = stopped
                 ? APP_START_OCCURRED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED
                 : APP_START_OCCURRED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
+
+        final boolean firstLaunch = wasFirstLaunch(info);
         FrameworkStatsLog.write(
                 FrameworkStatsLog.APP_START_OCCURRED,
                 info.applicationInfo.uid,
@@ -1163,18 +1165,26 @@
                 TimeUnit.NANOSECONDS.toMillis(info.timestampNs),
                 processState,
                 processOomAdj,
-                packageState);
+                packageState,
+                false, // is_xr_activity
+                firstLaunch,
+                0L /* TODO: stoppedDuration */);
+        // Reset the stopped state to avoid reporting stopped again
+        if (info.processRecord != null) {
+            info.processRecord.setWasStoppedLogged(true);
+        }
 
         if (DEBUG_METRICS) {
-            Slog.i(TAG, String.format("APP_START_OCCURRED(%s, %s, %s, %s, %s)",
+            Slog.i(TAG, String.format(
+                    "APP_START_OCCURRED(%s, %s, %s, %s, %s, wasStopped=%b, firstLaunch=%b)",
                     info.applicationInfo.uid,
                     info.packageName,
                     getAppStartTransitionType(info.type, info.relaunched),
                     info.launchedActivityName,
-                    info.launchedActivityLaunchedFromPackage));
+                    info.launchedActivityLaunchedFromPackage,
+                    stopped, firstLaunch));
         }
 
-
         logAppStartMemoryStateCapture(info);
     }
 
@@ -1794,4 +1804,28 @@
                 return -1;
         }
     }
+
+    private boolean wasStoppedNeedsLogging(TransitionInfoSnapshot info) {
+        if (info.processRecord != null) {
+            return (info.processRecord.wasForceStopped()
+                        || info.processRecord.wasFirstLaunch())
+                    && !info.processRecord.getWasStoppedLogged();
+        } else {
+            return (info.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0;
+        }
+    }
+
+    private boolean wasFirstLaunch(TransitionInfoSnapshot info) {
+        if (info.processRecord != null) {
+            return info.processRecord.wasFirstLaunch()
+                    && !info.processRecord.getWasStoppedLogged();
+        }
+        try {
+            return !mSupervisor.mService.getPackageManagerInternalLocked()
+                    .wasPackageEverLaunched(info.packageName, info.userId);
+        } catch (Exception e) {
+            // Couldn't find the state record, so must be a newly installed app
+            return true;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 5036fc6..0069cdd 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -50,6 +50,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.isFloating;
 import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
 import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
 import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
@@ -75,6 +76,7 @@
 import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
 import static android.content.pm.ActivityInfo.FLAG_STATE_NOT_NEEDED;
 import static android.content.pm.ActivityInfo.FLAG_TURN_SCREEN_ON;
+import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED;
 import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
@@ -332,6 +334,7 @@
 import android.service.dreams.DreamActivity;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.ArraySet;
+import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.MergedConfiguration;
@@ -553,7 +556,6 @@
     boolean launchFailed;   // set if a launched failed, to abort on 2nd try
     boolean delayedResume;  // not yet resumed because of stopped app switches?
     boolean finishing;      // activity in pending finish list?
-    int configChangeFlags;  // which config values have changed
     private boolean keysPaused;     // has key dispatching been paused for it?
     int launchMode;         // the launch mode activity attribute.
     int lockTaskLaunchMode; // the lockTaskMode manifest attribute, subject to override
@@ -659,7 +661,7 @@
 
     boolean mVoiceInteraction;
 
-    private int mPendingRelaunchCount;
+    int mPendingRelaunchCount;
     long mRelaunchStartTime;
 
     // True if we are current in the process of removing this app token from the display
@@ -1295,10 +1297,6 @@
         if (mDeferHidingClient) {
             pw.println(prefix + "mDeferHidingClient=" + mDeferHidingClient);
         }
-        if (configChangeFlags != 0) {
-            pw.print(prefix); pw.print(" configChangeFlags=");
-                    pw.println(Integer.toHexString(configChangeFlags));
-        }
         if (mServiceConnectionsHolder != null) {
             pw.print(prefix); pw.print("connections="); pw.println(mServiceConnectionsHolder);
         }
@@ -1456,7 +1454,8 @@
         mSizeConfigurations = sizeConfigurations;
     }
 
-    private void scheduleActivityMovedToDisplay(int displayId, Configuration config) {
+    private void scheduleActivityMovedToDisplay(int displayId, @NonNull Configuration config,
+            @NonNull ActivityWindowInfo activityWindowInfo) {
         if (!attachedToProcess()) {
             ProtoLog.w(WM_DEBUG_SWITCH, "Can't report activity moved "
                     + "to display - client not running, activityRecord=%s, displayId=%d",
@@ -1469,13 +1468,14 @@
                     config);
 
             mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
-                    MoveToDisplayItem.obtain(token, displayId, config));
+                    MoveToDisplayItem.obtain(token, displayId, config, activityWindowInfo));
         } catch (RemoteException e) {
             // If process died, whatever.
         }
     }
 
-    private void scheduleConfigurationChanged(Configuration config) {
+    private void scheduleConfigurationChanged(@NonNull Configuration config,
+            @NonNull ActivityWindowInfo activityWindowInfo) {
         if (!attachedToProcess()) {
             ProtoLog.w(WM_DEBUG_CONFIGURATION, "Can't report activity configuration "
                     + "update - client not running, activityRecord=%s", this);
@@ -1486,7 +1486,7 @@
                     + "config: %s", this, config);
 
             mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
-                    ActivityConfigurationChangeItem.obtain(token, config));
+                    ActivityConfigurationChangeItem.obtain(token, config, activityWindowInfo));
         } catch (RemoteException e) {
             // If process died, whatever.
         }
@@ -2892,6 +2892,11 @@
 
     /** Makes starting window always fill the associated task. */
     private void attachStartingSurfaceToAssociatedTask() {
+        if (mSyncState == SYNC_STATE_NONE && isEmbedded()) {
+            // Collect this activity since it's starting window will reparent to task. To ensure
+            // any starting window's transaction will occur in order.
+            mTransitionController.collect(this);
+        }
         // Associate the configuration of starting window with the task.
         overrideConfigurationPropagation(mStartingWindow, mStartingData.mAssociatedTask);
         getSyncTransaction().reparent(mStartingWindow.mSurfaceControl,
@@ -3715,8 +3720,14 @@
 
             final boolean endTask = task.getTopNonFinishingActivity() == null
                     && !task.isClearingToReuseTask();
+            final WindowContainer<?> trigger = endTask ? task : this;
             final Transition newTransition =
-                    mTransitionController.requestCloseTransitionIfNeeded(endTask ? task : this);
+                    mTransitionController.requestCloseTransitionIfNeeded(trigger);
+            if (newTransition != null) {
+                newTransition.collectClose(trigger);
+            } else if (mTransitionController.isCollecting()) {
+                mTransitionController.getCollectingTransition().collectClose(trigger);
+            }
             if (isState(RESUMED)) {
                 if (endTask) {
                     mAtmService.getTaskChangeNotificationController().notifyTaskRemovalStarted(
@@ -3986,7 +3997,7 @@
         // If the display does not have running activity, the configuration may need to be
         // updated for restoring original orientation of the display.
         if (next == null) {
-            mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(),
+            mRootWindowContainer.ensureVisibilityAndConfig(null /* starting */, mDisplayContent,
                     true /* deferResume */);
         }
         if (activityRemoved) {
@@ -4057,7 +4068,7 @@
             try {
                 if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + this);
                 mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
-                        DestroyActivityItem.obtain(token, finishing, configChangeFlags));
+                        DestroyActivityItem.obtain(token, finishing));
             } catch (Exception e) {
                 // We can just ignore exceptions here...  if the process has crashed, our death
                 // notification will clean things up.
@@ -4099,8 +4110,6 @@
             }
         }
 
-        configChangeFlags = 0;
-
         return removedFromHistory;
     }
 
@@ -4377,7 +4386,12 @@
         // closing the task.
         final WindowContainer trigger = remove && task != null && task.getChildCount() == 1
                 ? task : this;
-        mTransitionController.requestCloseTransitionIfNeeded(trigger);
+        final Transition newTransit = mTransitionController.requestCloseTransitionIfNeeded(trigger);
+        if (newTransit != null) {
+            newTransit.collectClose(trigger);
+        } else if (mTransitionController.isCollecting()) {
+            mTransitionController.getCollectingTransition().collectClose(trigger);
+        }
         cleanUp(true /* cleanServices */, true /* setState */);
         if (remove) {
             if (mStartingData != null && mVisible && task != null) {
@@ -5019,7 +5033,7 @@
                 return PauseActivityItem.obtain(token);
             case STOPPING:
             case STOPPED:
-                return StopActivityItem.obtain(token, configChangeFlags);
+                return StopActivityItem.obtain(token);
             default:
                 // Do not send a result immediately if the activity is in state INITIALIZING,
                 // RESTARTING_PROCESS, FINISHING, DESTROYING, or DESTROYED.
@@ -6293,7 +6307,7 @@
             try {
                 mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
                         PauseActivityItem.obtain(token, finishing, false /* userLeaving */,
-                                configChangeFlags, false /* dontReport */, mAutoEnteringPip));
+                                false /* dontReport */, mAutoEnteringPip));
             } catch (Exception e) {
                 Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e);
             }
@@ -6463,12 +6477,6 @@
      * state to match that fact.
      */
     void completeResumeLocked() {
-        final boolean wasVisible = mVisibleRequested;
-        setVisibility(true);
-        if (!wasVisible) {
-            // Visibility has changed, so take a note of it so we call the TaskStackChangedListener
-            mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause = true;
-        }
         idle = false;
         results = null;
         if (newIntents != null && newIntents.size() > 0) {
@@ -6607,7 +6615,7 @@
             EventLogTags.writeWmStopActivity(
                     mUserId, System.identityHashCode(this), shortComponentName);
             mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
-                    StopActivityItem.obtain(token, configChangeFlags));
+                    StopActivityItem.obtain(token));
 
             mAtmService.mH.postDelayed(mStopTimeoutRunnable, STOP_TIMEOUT);
         } catch (Exception e) {
@@ -6865,6 +6873,7 @@
         // stop tracking
         mSplashScreenStyleSolidColor = true;
 
+        mAtmService.mBackNavigationController.removePredictiveSurfaceIfNeeded(this);
         if (mStartingWindow != null) {
             ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Finish starting %s"
                     + ": first real window is shown, no animation", win.mToken);
@@ -8475,6 +8484,9 @@
         // and back which can cause visible issues (see b/184078928).
         final int parentWindowingMode =
                 newParentConfiguration.windowConfiguration.getWindowingMode();
+
+        applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig);
+
         final boolean isFixedOrientationLetterboxAllowed =
                 parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
                         || parentWindowingMode == WINDOWING_MODE_FULLSCREEN
@@ -8574,6 +8586,87 @@
     }
 
     /**
+     * If necessary, override configuration fields related to app bounds.
+     * This will happen when the app is targeting SDK earlier than 35.
+     * The insets and configuration has decoupled since SDK level 35, to make the system
+     * compatible to existing apps, override the configuration with legacy metrics. In legacy
+     * metrics, fields such as appBounds will exclude some of the system bar areas.
+     * The override contains all potentially affected fields in Configuration, including
+     * screenWidthDp, screenHeightDp, smallestScreenWidthDp, and orientation.
+     * All overrides to those fields should be in this method.
+     */
+    private void applySizeOverrideIfNeeded(Configuration newParentConfiguration,
+            int parentWindowingMode, Configuration inOutConfig) {
+        if (mDisplayContent == null) {
+            return;
+        }
+        final Rect fullBounds = newParentConfiguration.windowConfiguration.getAppBounds();
+        int rotation = newParentConfiguration.windowConfiguration.getRotation();
+        if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) {
+            rotation = mDisplayContent.getRotation();
+        }
+        if (!mWmService.mFlags.mInsetsDecoupledConfiguration
+                || info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
+                || getCompatDisplayInsets() != null
+                || isFloating(parentWindowingMode) || fullBounds == null
+                || fullBounds.isEmpty() || rotation == ROTATION_UNDEFINED) {
+            // If the insets configuration decoupled logic is not enabled for the app, or the app
+            // already has a compat override, or the context doesn't contain enough info to
+            // calculate the override, skip the override.
+            return;
+        }
+
+        // Override starts here.
+        final Rect stableInsets = mDisplayContent.getDisplayPolicy().getDecorInsetsInfo(
+                rotation, fullBounds.width(), fullBounds.height()).mLegacyConfigInsets;
+        // This should be the only place override the configuration for ActivityRecord. Override
+        // the value if not calculated yet.
+        Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+        if (outAppBounds == null || outAppBounds.isEmpty()) {
+            inOutConfig.windowConfiguration.setAppBounds(fullBounds);
+            outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+            outAppBounds.inset(stableInsets);
+        }
+        float density = inOutConfig.densityDpi;
+        if (density == Configuration.DENSITY_DPI_UNDEFINED) {
+            density = newParentConfiguration.densityDpi;
+        }
+        density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
+        if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
+            final int overrideScreenWidthDp = (int) (outAppBounds.width() / density + 0.5f);
+            inOutConfig.screenWidthDp =
+                    Math.min(overrideScreenWidthDp, newParentConfiguration.screenWidthDp);
+        }
+        if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
+            final int overrideScreenHeightDp =
+                    (int) (outAppBounds.height() / density + 0.5f);
+            inOutConfig.screenHeightDp =
+                    Math.min(overrideScreenHeightDp, newParentConfiguration.screenHeightDp);
+        }
+        if (inOutConfig.smallestScreenWidthDp
+                == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
+                && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+            // For the case of PIP transition and multi-window environment, the
+            // smallestScreenWidthDp is handled already. Override only if the app is in
+            // fullscreen.
+            final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+            DisplayInfo info = new DisplayInfo();
+            mDisplayContent.getDisplay().getDisplayInfo(info);
+            mDisplayContent.computeSizeRanges(info, rotated, info.logicalWidth,
+                    info.logicalHeight, mDisplayContent.getDisplayMetrics().density,
+                    inOutConfig, true /* legacyConfig */);
+        }
+
+        // It's possible that screen size will be considered in different orientation with or
+        // without considering the system bar insets. Override orientation as well.
+        if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
+            inOutConfig.orientation =
+                    (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
+                            ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+        }
+    }
+
+    /**
      * @return The orientation to use to understand if reachability is enabled.
      */
     @Configuration.Orientation
@@ -8826,6 +8919,11 @@
         if (mDisplayContent == null) {
             return true;
         }
+        if (mWmService.mFlags.mInsetsDecoupledConfiguration
+                && info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)) {
+            // No insets should be considered any more.
+            return true;
+        }
         // Only need to make changes if activity sets an orientation
         final int requestedOrientation = getRequestedConfigurationOrientation();
         if (requestedOrientation == ORIENTATION_UNDEFINED) {
@@ -8840,7 +8938,8 @@
                 : mDisplayContent.getDisplayInfo();
         final Task task = getTask();
         task.calculateInsetFrames(mTmpBounds /* outNonDecorBounds */,
-                outStableBounds /* outStableBounds */, parentBounds /* bounds */, di);
+                outStableBounds /* outStableBounds */, parentBounds /* bounds */, di,
+                true /* useLegacyInsetsForStableBounds */);
         final int orientationWithInsets = outStableBounds.height() >= outStableBounds.width()
                 ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
         // If orientation does not match the orientation with insets applied, then a
@@ -9784,7 +9883,11 @@
         // configurations because there are cases (like moving a task to the root pinned task) where
         // the combine configurations are equal, but would otherwise differ in the override config
         mTmpConfig.setTo(mLastReportedConfiguration.getMergedConfiguration());
-        if (getConfiguration().equals(mTmpConfig) && !displayChanged) {
+        final ActivityWindowInfo newActivityWindowInfo = getActivityWindowInfo();
+        final boolean isActivityWindowInfoChanged = Flags.activityWindowInfoFlag()
+                && !mLastReportedActivityWindowInfo.equals(newActivityWindowInfo);
+        if (!displayChanged && !isActivityWindowInfoChanged
+                && getConfiguration().equals(mTmpConfig)) {
             ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration & display "
                     + "unchanged in %s", this);
             return true;
@@ -9801,6 +9904,7 @@
         final Configuration newMergedOverrideConfig = getMergedOverrideConfiguration();
 
         setLastReportedConfiguration(getProcessGlobalConfiguration(), newMergedOverrideConfig);
+        setLastReportedActivityWindowInfo(newActivityWindowInfo);
 
         if (mState == INITIALIZING) {
             // No need to relaunch or schedule new config for activity that hasn't been launched
@@ -9817,9 +9921,10 @@
             // There are no significant differences, so we won't relaunch but should still deliver
             // the new configuration to the client process.
             if (displayChanged) {
-                scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig);
+                scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig,
+                        newActivityWindowInfo);
             } else {
-                scheduleConfigurationChanged(newMergedOverrideConfig);
+                scheduleConfigurationChanged(newMergedOverrideConfig, newActivityWindowInfo);
             }
             notifyDisplayCompatPolicyAboutConfigurationChange(
                     mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
@@ -9844,7 +9949,6 @@
 
         if (shouldRelaunchLocked(changes, mTmpConfig)) {
             // Aha, the activity isn't handling the change, so DIE DIE DIE.
-            configChangeFlags |= changes;
             if (mVisible && mAtmService.mTmpUpdateConfigurationResult.mIsUpdating
                     && !mTransitionController.isShellTransitionsEnabled()) {
                 startFreezingScreenLocked(app, mAtmService.mTmpUpdateConfigurationResult.changes);
@@ -9873,7 +9977,7 @@
                 ProtoLog.v(WM_DEBUG_STATES, "Config is relaunching invisible "
                         + "activity %s called by %s", this, Debug.getCallers(4));
             }
-            relaunchActivityLocked(preserveWindow);
+            relaunchActivityLocked(preserveWindow, changes);
 
             // All done...  tell the caller we weren't able to keep this activity around.
             return false;
@@ -9884,9 +9988,10 @@
         // changes is always sent to all processes when they happen so it can just use whatever
         // system level configuration it last got.
         if (displayChanged) {
-            scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig);
+            scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig,
+                    newActivityWindowInfo);
         } else {
-            scheduleConfigurationChanged(newMergedOverrideConfig);
+            scheduleConfigurationChanged(newMergedOverrideConfig, newActivityWindowInfo);
         }
         notifyDisplayCompatPolicyAboutConfigurationChange(
                 mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
@@ -10014,9 +10119,8 @@
                 | CONFIG_SCREEN_LAYOUT)) != 0;
     }
 
-    void relaunchActivityLocked(boolean preserveWindow) {
+    void relaunchActivityLocked(boolean preserveWindow, int configChangeFlags) {
         if (mAtmService.mSuppressResizeConfigChanges && preserveWindow) {
-            configChangeFlags = 0;
             return;
         }
         if (!preserveWindow) {
@@ -10089,8 +10193,6 @@
 
         // The activity may be waiting for stop, but that is no longer appropriate for it.
         mTaskSupervisor.mStoppingActivities.remove(this);
-
-        configChangeFlags = 0;
     }
 
     /**
@@ -10159,7 +10261,7 @@
         // {@link ActivityTaskManagerService.activityStopped}).
         try {
             mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
-                    StopActivityItem.obtain(token, 0 /* configChanges */));
+                    StopActivityItem.obtain(token));
         } catch (RemoteException e) {
             Slog.w(TAG, "Exception thrown during restart " + this, e);
         }
diff --git a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
index 01d077a..4149bd9 100644
--- a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
+++ b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
@@ -41,7 +41,7 @@
     static final String DOC_LINK = "go/android-asm";
 
     /** Used to determine which version of the ASM logic was used in logs while we iterate */
-    static final int ASM_VERSION = 9;
+    static final int ASM_VERSION = 10;
 
     private static final String NAMESPACE = NAMESPACE_WINDOW_MANAGER;
     private static final String KEY_ASM_PREFIX = "ActivitySecurity__";
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index c137c54..2c39c58 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1625,7 +1625,7 @@
         final ActivityRecord currentTop = startedActivityRootTask.topRunningActivity();
         if (currentTop != null && currentTop.shouldUpdateConfigForDisplayChanged()) {
             mRootWindowContainer.ensureVisibilityAndConfig(
-                    currentTop, currentTop.getDisplayId(), false /* deferResume */);
+                    currentTop, currentTop.mDisplayContent, false /* deferResume */);
         }
 
         if (!avoidMoveToFront() && mDoResume && mRootWindowContainer
@@ -2089,7 +2089,7 @@
 
         if (!mSupervisor.getBackgroundActivityLaunchController().checkActivityAllowedToStart(
                 mSourceRecord, r, newTask, avoidMoveToFront(), targetTask, mLaunchFlags, mBalCode,
-                mCallingUid, mRealCallingUid)) {
+                mCallingUid, mRealCallingUid, mPreferredTaskDisplayArea)) {
             return START_ABORTED;
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 4a5b221..c088118 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -822,4 +822,7 @@
      */
     public abstract void unregisterCompatScaleProvider(
             @CompatScaleProvider.CompatScaleModeOrderId int id);
+
+    /** Returns whether assist data is allowed. */
+    public abstract boolean isAssistDataAllowed();
 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 514a3d8..060f1c8 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3665,7 +3665,7 @@
     }
 
     /**
-     * Prepare to enter PiP mode after {@link TransitionController#requestStartTransition}.
+     * Prepare to enter PiP mode after {@link TransitionController#requestStartDisplayTransition}.
      *
      * @param r activity auto entering pip
      * @return true if the activity is about to auto-enter pip or is already in pip mode.
@@ -4163,19 +4163,21 @@
     @Override
     public void onPictureInPictureUiStateChanged(PictureInPictureUiState pipState) {
         enforceTaskPermission("onPictureInPictureUiStateChanged");
-        // The PictureInPictureUiState is sent to current pip task if there is any
-        // -or- the top standard task (state like entering PiP does not require a pinned task).
-        final Task task;
-        if (mRootWindowContainer.getDefaultTaskDisplayArea().hasPinnedTask()) {
-            task = mRootWindowContainer.getDefaultTaskDisplayArea().getRootPinnedTask();
-        } else {
-            task = mRootWindowContainer.getDefaultTaskDisplayArea().getRootTask(
-                    t -> t.isActivityTypeStandard());
-        }
-        if (task != null && task.getTopMostActivity() != null
-                && !task.getTopMostActivity().isState(FINISHING, DESTROYING, DESTROYED)) {
-            mWindowManager.mAtmService.mActivityClientController.onPictureInPictureUiStateChanged(
-                    task.getTopMostActivity(), pipState);
+        synchronized (mGlobalLock) {
+            // The PictureInPictureUiState is sent to current pip task if there is any
+            // -or- the top standard task (state like entering PiP does not require a pinned task).
+            final Task task;
+            if (mRootWindowContainer.getDefaultTaskDisplayArea().hasPinnedTask()) {
+                task = mRootWindowContainer.getDefaultTaskDisplayArea().getRootPinnedTask();
+            } else {
+                task = mRootWindowContainer.getDefaultTaskDisplayArea().getRootTask(
+                        t -> t.isActivityTypeStandard());
+            }
+            if (task != null && task.getTopMostActivity() != null
+                    && !task.getTopMostActivity().isState(FINISHING, DESTROYING, DESTROYED)) {
+                mWindowManager.mAtmService.mActivityClientController
+                        .onPictureInPictureUiStateChanged(task.getTopMostActivity(), pipState);
+            }
         }
     }
 
@@ -6023,6 +6025,10 @@
         public int startActivityWithScreenshot(@NonNull Intent intent,
                 @NonNull String callingPackage, int callingUid, int callingPid,
                 @Nullable IBinder resultTo, @Nullable Bundle options, int userId) {
+            userId = getActivityStartController().checkTargetUser(userId,
+                    false /* validateIncomingUser */, Binder.getCallingPid(),
+                    Binder.getCallingUid(), "startActivityWithScreenshot");
+
             return getActivityStartController()
                     .obtainStarter(intent, "startActivityWithScreenshot")
                     .setCallingUid(callingUid)
@@ -7347,6 +7353,11 @@
                 @CompatScaleProvider.CompatScaleModeOrderId int id) {
             ActivityTaskManagerService.this.unregisterCompatScaleProvider(id);
         }
+
+        @Override
+        public boolean isAssistDataAllowed() {
+            return ActivityTaskManagerService.this.isAssistDataAllowed();
+        }
     }
 
     static boolean isPip2ExperimentEnabled() {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index aefa777..2fc6b5f 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -42,6 +42,7 @@
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
+import static android.os.Process.INVALID_PID;
 import static android.os.Process.INVALID_UID;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
@@ -56,6 +57,7 @@
 import static com.android.server.wm.ActivityRecord.State.PAUSING;
 import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_IDLE;
@@ -73,6 +75,7 @@
 import static com.android.server.wm.ActivityTaskManagerService.ANIMATE;
 import static com.android.server.wm.ActivityTaskManagerService.H.FIRST_SUPERVISOR_TASK_MSG;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
+import static com.android.server.wm.ClientLifecycleManager.shouldDispatchCompatClientTransactionIndependently;
 import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_ALLOWLISTED;
 import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE;
 import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
@@ -102,6 +105,7 @@
 import android.app.servertransaction.LaunchActivityItem;
 import android.app.servertransaction.PauseActivityItem;
 import android.app.servertransaction.ResumeActivityItem;
+import android.app.servertransaction.StopActivityItem;
 import android.companion.virtual.VirtualDeviceManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -840,7 +844,7 @@
                 // Deferring resume here because we're going to launch new activity shortly.
                 // We don't want to perform a redundant launch of the same record while ensuring
                 // configurations and trying to resume top activity of focused root task.
-                mRootWindowContainer.ensureVisibilityAndConfig(r, r.getDisplayId(),
+                mRootWindowContainer.ensureVisibilityAndConfig(r, r.mDisplayContent,
                         true /* deferResume */);
             }
 
@@ -942,11 +946,20 @@
                 if (andResume) {
                     lifecycleItem = ResumeActivityItem.obtain(r.token, isTransitionForward,
                             r.shouldSendCompatFakeFocus());
-                } else {
+                } else if (r.isVisibleRequested()) {
                     lifecycleItem = PauseActivityItem.obtain(r.token);
+                } else {
+                    lifecycleItem = StopActivityItem.obtain(r.token);
                 }
 
                 // Schedule transaction.
+                if (shouldDispatchCompatClientTransactionIndependently(r.mTargetSdk)) {
+                    // LaunchActivityItem has @UnsupportedAppUsage usages.
+                    // Guard the bundleClientTransactionFlag feature with targetSDK on Android 15+.
+                    // To not bundle the transaction, dispatch the pending before schedule new
+                    // transaction.
+                    mService.getLifecycleManager().dispatchPendingTransaction(proc.getThread());
+                }
                 mService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
                         proc.getThread(), launchActivityItem, lifecycleItem,
                         // Immediately dispatch the transaction, so that if it fails, the server can
@@ -1002,8 +1015,9 @@
         if (andResume && readyToResume()) {
             // As part of the process of launching, ActivityThread also performs
             // a resume.
-            rootTask.minimalResumeActivityLocked(r);
-        } else {
+            r.setState(RESUMED, "realStartActivityLocked");
+            r.completeResumeLocked();
+        } else if (r.isVisibleRequested()) {
             // This activity is not starting in the resumed state... which should look like we asked
             // it to pause+stop (but remain visible), and it has done so and reported back the
             // current icicle and other state.
@@ -1011,6 +1025,9 @@
                     + "(starting in paused state)", r);
             r.setState(PAUSED, "realStartActivityLocked");
             mRootWindowContainer.executeAppTransitionForAllDisplay();
+        } else {
+            // This activity is starting while invisible, so it should be stopped.
+            r.setState(STOPPING, "realStartActivityLocked");
         }
         // Perform OOM scoring after the activity state is set, so the process can be updated with
         // the latest state.
@@ -1588,9 +1605,14 @@
     }
 
     private void removePinnedRootTaskInSurfaceTransaction(Task rootTask) {
-        rootTask.mTransitionController.requestTransitionIfNeeded(TRANSIT_TO_BACK, 0 /* flags */,
-                rootTask, rootTask.mDisplayContent, null /* remoteTransition */,
-                null /* displayChange */);
+        final Transition transition = rootTask.mTransitionController.requestTransitionIfNeeded(
+                TRANSIT_TO_BACK, 0 /* flags */, rootTask, rootTask.mDisplayContent);
+        if (transition == null) {
+            rootTask.mTransitionController.collect(rootTask);
+        } else {
+            transition.collect(rootTask);
+        }
+
         /**
          * Workaround: Force-stop all the activities in the root pinned task before we reparent them
          * to the fullscreen root task.  This is to guarantee that when we are removing a root task,
@@ -1652,11 +1674,11 @@
      * @return Returns true if the given task was found and removed.
      */
     boolean removeTaskById(int taskId, boolean killProcess, boolean removeFromRecents,
-            String reason, int callingUid) {
+            String reason, int callingUid, int callingPid) {
         final Task task =
                 mRootWindowContainer.anyTaskForId(taskId, MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
         if (task != null) {
-            removeTask(task, killProcess, removeFromRecents, reason, callingUid, null);
+            removeTask(task, killProcess, removeFromRecents, reason, callingUid, callingPid, null);
             return true;
         }
         Slog.w(TAG, "Request to remove task ignored for non-existent task " + taskId);
@@ -1664,16 +1686,21 @@
     }
 
     void removeTask(Task task, boolean killProcess, boolean removeFromRecents, String reason) {
-        removeTask(task, killProcess, removeFromRecents, reason, SYSTEM_UID, null);
+        removeTask(task, killProcess, removeFromRecents, reason, SYSTEM_UID, INVALID_PID, null);
     }
 
     void removeTask(Task task, boolean killProcess, boolean removeFromRecents, String reason,
-            int callingUid, String callerActivityClassName) {
+            int callingUid, int callingPid, String callerActivityClassName) {
         if (task.mInRemoveTask) {
             // Prevent recursion.
             return;
         }
-        task.mTransitionController.requestCloseTransitionIfNeeded(task);
+        final Transition transit = task.mTransitionController.requestCloseTransitionIfNeeded(task);
+        if (transit != null) {
+            transit.collectClose(task);
+        } else if (task.mTransitionController.isCollecting()) {
+            task.mTransitionController.getCollectingTransition().collectClose(task);
+        }
         // Consume the stopping activities immediately so activity manager won't skip killing
         // the process because it is still foreground state, i.e. RESUMED -> PAUSING set from
         // removeActivities -> finishIfPossible.
@@ -1705,8 +1732,8 @@
             if (task.isPersistable) {
                 mService.notifyTaskPersisterLocked(null, true);
             }
-            mBalController
-                    .checkActivityAllowedToClearTask(task, callingUid, callerActivityClassName);
+            mBalController.checkActivityAllowedToClearTask(
+                            task, callingUid, callingPid, callerActivityClassName);
         } finally {
             task.mInRemoveTask = false;
         }
@@ -1874,7 +1901,7 @@
             // Task was trimmed from the recent tasks list -- remove the active task record as well
             // since the user won't really be able to go back to it
             removeTaskById(task.mTaskId, killProcess, false /* removeFromRecents */,
-                    "recent-task-trimmed", SYSTEM_UID);
+                    "recent-task-trimmed", SYSTEM_UID, INVALID_PID);
         }
         task.removedFromRecents();
     }
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index 50de0b0..d699af8 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -77,11 +77,13 @@
 
         synchronized (mService.mGlobalLock) {
             int origCallingUid = Binder.getCallingUid();
+            int origCallingPid = Binder.getCallingPid();
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
                 // We remove the task from recents to preserve backwards
                 if (!mService.mTaskSupervisor.removeTaskById(mTaskId, false,
-                        REMOVE_FROM_RECENTS, "finish-and-remove-task", origCallingUid)) {
+                        REMOVE_FROM_RECENTS, "finish-and-remove-task", origCallingUid,
+                        origCallingPid)) {
                     throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
                 }
             } finally {
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index c79a8b6..25885ed 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -116,6 +116,7 @@
      */
     class SyncGroup {
         final int mSyncId;
+        final String mSyncName;
         int mSyncMethod = METHOD_BLAST;
         final TransactionReadyListener mListener;
         final Runnable mOnTimeout;
@@ -138,6 +139,7 @@
 
         private SyncGroup(TransactionReadyListener listener, int id, String name) {
             mSyncId = id;
+            mSyncName = name;
             mListener = listener;
             mOnTimeout = () -> {
                 Slog.w(TAG, "Sync group " + mSyncId + " timeout");
@@ -221,15 +223,20 @@
             for (WindowContainer wc : mRootMembers) {
                 wc.waitForSyncTransactionCommit(wcAwaitingCommit);
             }
+
+            final int syncId = mSyncId;
+            final long mergedTxId = merged.getId();
+            final String syncName = mSyncName;
             class CommitCallback implements Runnable {
                 // Can run a second time if the action completes after the timeout.
                 boolean ran = false;
                 public void onCommitted(SurfaceControl.Transaction t) {
+                    // Don't wait to hold the global lock to remove the timeout runnable
+                    mHandler.removeCallbacks(this);
                     synchronized (mWm.mGlobalLock) {
                         if (ran) {
                             return;
                         }
-                        mHandler.removeCallbacks(this);
                         ran = true;
                         for (WindowContainer wc : wcAwaitingCommit) {
                             wc.onSyncTransactionCommitted(t);
@@ -246,8 +253,9 @@
                     // a trace. Since these kind of ANRs can trigger such an issue,
                     // try and ensure we will have some visibility in both cases.
                     Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionCommitTimeout");
-                    Slog.e(TAG, "WM sent Transaction to organized, but never received" +
-                           " commit callback. Application ANR likely to follow.");
+                    Slog.e(TAG, "WM sent Transaction (#" + syncId + ", " + syncName + ", tx="
+                            + mergedTxId + ") to organizer, but never received commit callback."
+                            + " Application ANR likely to follow.");
                     Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                     synchronized (mWm.mGlobalLock) {
                         mListener.onTransactionCommitTimeout();
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index b51f899..e3ac35c 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -60,7 +60,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.TransitionAnimation;
 import com.android.internal.protolog.common.ProtoLog;
-import com.android.server.wm.utils.InsetUtils;
+import com.android.window.flags.Flags;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -644,6 +644,10 @@
         return false;
     }
 
+    void removePredictiveSurfaceIfNeeded(ActivityRecord openActivity) {
+        mAnimationHandler.markWindowHasDrawn(openActivity);
+    }
+
     private class NavigationMonitor {
         // The window which triggering the back navigation.
         private WindowState mNavigatingWindow;
@@ -897,7 +901,8 @@
             mWindowManagerService = wms;
             final Context context = wms.mContext;
             mShowWindowlessSurface = context.getResources().getBoolean(
-                    com.android.internal.R.bool.config_predictShowStartingSurface);
+                    com.android.internal.R.bool.config_predictShowStartingSurface)
+                    && Flags.activitySnapshotByDefault();
         }
         private static final int UNKNOWN = 0;
         private static final int TASK_SWITCH = 1;
@@ -1032,6 +1037,23 @@
             return isAnimateTarget(wc, mCloseAdaptor.mTarget, mSwitchType);
         }
 
+        void markWindowHasDrawn(ActivityRecord activity) {
+            if (!mComposed || mWaitTransition) {
+                return;
+            }
+            boolean allWindowDrawn = true;
+            for (int i = mOpenAnimAdaptor.mAdaptors.length - 1; i >= 0; --i) {
+                final BackWindowAnimationAdaptor next = mOpenAnimAdaptor.mAdaptors[i];
+                if (isAnimateTarget(activity, next.mTarget, mSwitchType)) {
+                    next.mAppWindowDrawn = true;
+                }
+                allWindowDrawn &= next.mAppWindowDrawn;
+            }
+            if (allWindowDrawn) {
+                mOpenAnimAdaptor.cleanUpWindowlessSurface(true);
+            }
+        }
+
         private static boolean isAnimateTarget(@NonNull WindowContainer window,
                 @NonNull WindowContainer animationTarget, int switchType) {
             if (switchType == TASK_SWITCH) {
@@ -1134,15 +1156,17 @@
             final BackWindowAnimationAdaptor adaptor =
                     new BackWindowAnimationAdaptor(target, isOpen, switchType);
             final SurfaceControl.Transaction pt = target.getPendingTransaction();
-            target.startAnimation(pt, adaptor, false /* hidden */, ANIMATION_TYPE_PREDICT_BACK);
             // Workaround to show TaskFragment which can be hide in Transitions and won't show
             // during isAnimating.
             if (isOpen && target.asActivityRecord() != null) {
                 final TaskFragment fragment = target.asActivityRecord().getTaskFragment();
                 if (fragment != null) {
+                    // Ensure task fragment surface has updated, in case configuration has changed.
+                    fragment.updateOrganizedTaskFragmentSurface();
                     pt.show(fragment.mSurfaceControl);
                 }
             }
+            target.startAnimation(pt, adaptor, false /* hidden */, ANIMATION_TYPE_PREDICT_BACK);
             return adaptor;
         }
 
@@ -1181,8 +1205,6 @@
                 for (int i = mAdaptors.length - 1; i >= 0; --i) {
                     mAdaptors[i].mTarget.cancelAnimation();
                 }
-                mRequestedStartingSurfaceId = INVALID_TASK_ID;
-                mStartingSurface = null;
                 if (mCloseTransaction != null) {
                     mCloseTransaction.apply();
                     mCloseTransaction = null;
@@ -1235,7 +1257,7 @@
                         represent.allowEnterPip);
             }
 
-            void createStartingSurface(ActivityRecord[] visibleOpenActivities) {
+            void createStartingSurface(@Nullable TaskSnapshot snapshot) {
                 if (mAdaptors[0].mSwitchType == DIALOG_CLOSE) {
                     return;
                 }
@@ -1253,7 +1275,6 @@
                 if (mainActivity == null) {
                     return;
                 }
-                final TaskSnapshot snapshot = getSnapshot(mainOpen, visibleOpenActivities);
                 // If there is only one adaptor, attach the windowless window to top activity,
                 // because fixed rotation only applies on activity.
                 // Note that embedded activity won't use fixed rotation.
@@ -1321,11 +1342,13 @@
                         .removeWindowlessStartingSurface(mRequestedStartingSurfaceId,
                                 !openTransitionMatch);
                 mRequestedStartingSurfaceId = INVALID_TASK_ID;
+                mStartingSurface = null;
             }
         }
 
         private static class BackWindowAnimationAdaptor implements AnimationAdapter {
             SurfaceControl mCapturedLeash;
+            boolean mAppWindowDrawn;
             private final Rect mBounds = new Rect();
             private final WindowContainer mTarget;
             private final boolean mIsOpen;
@@ -1412,15 +1435,11 @@
                     return null;
                 }
                 final WindowState mainWindow = r.findMainWindow();
-                Rect insets;
-                if (mainWindow != null) {
-                    insets = mainWindow.getInsetsStateWithVisibilityOverride().calculateInsets(
-                            mBounds, WindowInsets.Type.tappableElement(),
-                            false /* ignoreVisibility */).toRect();
-                    InsetUtils.addInsets(insets, mainWindow.mActivityRecord.getLetterboxInsets());
-                } else {
-                    insets = new Rect();
-                }
+                final Rect insets = mainWindow != null
+                        ? mainWindow.getInsetsStateWithVisibilityOverride().calculateInsets(
+                                mBounds, WindowInsets.Type.tappableElement(),
+                                false /* ignoreVisibility */).toRect()
+                        : new Rect();
                 final int mode = mIsOpen ? MODE_OPENING : MODE_CLOSING;
                 mAnimationTarget = new RemoteAnimationTarget(t.mTaskId, mode, mCapturedLeash,
                         !r.fillsParent(), new Rect(),
@@ -1507,9 +1526,16 @@
             private void applyPreviewStrategy(
                     @NonNull BackWindowAnimationAdaptorWrapper openAnimationAdaptor,
                     @NonNull ActivityRecord[] visibleOpenActivities) {
+                boolean needsLaunchBehind = true;
                 if (isSupportWindowlessSurface() && mShowWindowlessSurface && !mIsLaunchBehind) {
-                    openAnimationAdaptor.createStartingSurface(visibleOpenActivities);
-                } else {
+                    final WindowContainer mainOpen = openAnimationAdaptor.mAdaptors[0].mTarget;
+                    final TaskSnapshot snapshot = getSnapshot(mainOpen, visibleOpenActivities);
+                    openAnimationAdaptor.createStartingSurface(snapshot);
+                    // set LaunchBehind if we are creating splash screen surface.
+                    needsLaunchBehind = snapshot == null
+                            && openAnimationAdaptor.mRequestedStartingSurfaceId != INVALID_TASK_ID;
+                }
+                if (needsLaunchBehind) {
                     for (int i = visibleOpenActivities.length - 1; i >= 0; --i) {
                         setLaunchBehind(visibleOpenActivities[i]);
                     }
@@ -1655,7 +1681,7 @@
                 || (wallpaperController.getWallpaperTarget() != null
                 && wallpaperController.wallpaperTransitionReady());
         if (wallpaperReady && mPendingAnimation != null) {
-            startAnimation();
+            mWindowManagerService.mAnimator.addAfterPrepareSurfacesRunnable(this::startAnimation);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index fdae53f..071f403 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -21,13 +21,16 @@
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
-import static android.app.ComponentOptions.BackgroundActivityStartMode;
+import static android.app.ActivityOptions.BackgroundActivityStartMode;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+import static android.os.Process.INVALID_PID;
+import static android.os.Process.INVALID_UID;
 import static android.os.Process.SYSTEM_UID;
 import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER;
 
+import static com.android.server.wm.ActivityStarter.ASM_RESTRICTIONS;
 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;
@@ -67,6 +70,7 @@
 import android.util.Slog;
 import android.widget.Toast;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
@@ -75,6 +79,7 @@
 import com.android.server.am.PendingIntentRecord;
 
 import java.lang.annotation.Retention;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.StringJoiner;
 import java.util.function.Consumer;
@@ -1022,7 +1027,7 @@
     }
 
     /**
-     * Log activity starts which violate one of the following rules of the
+     * Check activity starts which violate one of the following rules of the
      * activity security model (ASM):
      * See go/activity-security for rationale behind the rules.
      * 1. Within a task, only an activity matching a top UID of the task can start activities
@@ -1032,7 +1037,7 @@
     boolean checkActivityAllowedToStart(@Nullable ActivityRecord sourceRecord,
             @NonNull ActivityRecord targetRecord, boolean newTask, boolean avoidMoveTaskToFront,
             @Nullable Task targetTask, int launchFlags, int balCode, int callingUid,
-            int realCallingUid) {
+            int realCallingUid, TaskDisplayArea preferredTaskDisplayArea) {
         // BAL Exception allowed in all cases
         if (balCode == BAL_ALLOW_ALLOWLISTED_UID) {
             return true;
@@ -1055,68 +1060,46 @@
             }
         }
 
-        if (balCode == BAL_ALLOW_GRACE_PERIOD) {
-            // Allow if launching into new task, and caller matches most recently finished activity
-            if (taskToFront && mTopFinishedActivity != null
-                    && mTopFinishedActivity.mUid == callingUid) {
-                return true;
-            }
-
-            // Launching into existing task - allow if matches most recently finished activity
-            // within the task.
-            // We can reach here multiple ways:
-            // 1. activity in fg fires intent (taskToFront = false, sourceRecord is available)
-            // 2. activity in bg fires intent (taskToFront = false, sourceRecord is available)
-            // 3. activity in bg fires intent with NEW_FLAG (taskToFront = true,
-            //         avoidMoveTaskToFront = true, sourceRecord is available)
-            // 4. activity in bg fires PI (taskToFront = true, avoidMoveTaskToFront = true,
-            //         sourceRecord is not available, targetTask may be available)
-            if (!taskToFront || avoidMoveTaskToFront) {
-                if (targetTask != null) {
-                    FinishedActivityEntry finishedEntry =
-                            mTaskIdToFinishedActivity.get(targetTask.mTaskId);
-                    if (finishedEntry != null && finishedEntry.mUid == callingUid) {
-                        return true;
-                    }
-                }
-
-                if (sourceRecord != null) {
-                    FinishedActivityEntry finishedEntry =
-                            mTaskIdToFinishedActivity.get(sourceRecord.getTask().mTaskId);
-                    if (finishedEntry != null && finishedEntry.mUid == callingUid) {
-                        return true;
-                    }
-                }
-            }
-        }
-
-        BlockActivityStart bas = null;
+        BlockActivityStart bas = new BlockActivityStart();
         if (sourceRecord != null) {
-            boolean passesAsmChecks = true;
             Task sourceTask = sourceRecord.getTask();
 
+            Task taskToCheck = taskToFront ? sourceTask : targetTask;
+            bas = checkTopActivityForAsm(taskToCheck, sourceRecord.getUid(),
+                    sourceRecord, bas);
+
             // Allow launching into a new task (or a task matching the launched activity's
             // affinity) only if the current task is foreground or mutating its own task.
             // The latter can happen eg. if caller uses NEW_TASK flag and the activity being
             // launched matches affinity of source task.
-            if (taskToFront) {
-                passesAsmChecks = sourceTask != null
-                        && (sourceTask.isVisible() || sourceTask == targetTask);
-            }
-
-            if (passesAsmChecks) {
-                Task taskToCheck = taskToFront ? sourceTask : targetTask;
-                bas = isTopActivityMatchingUidAbsentForAsm(taskToCheck, sourceRecord.getUid(),
-                        sourceRecord);
+            if (taskToFront && bas.mTopActivityMatchesSource) {
+                bas.mTopActivityMatchesSource = (sourceTask != null
+                        && (sourceTask.isVisible() || sourceTask == targetTask));
             }
         } else if (targetTask != null && (!taskToFront || avoidMoveTaskToFront)) {
             // We don't have a sourceRecord, and we're launching into an existing task.
             // Allow if callingUid is top of stack.
-            bas = isTopActivityMatchingUidAbsentForAsm(targetTask, callingUid,
-                    /*sourceRecord*/null);
+            bas = checkTopActivityForAsm(targetTask, callingUid,
+                    /*sourceRecord*/null, bas);
+        } else {
+            // We're launching from a non-visible activity. Has any visible app opted in?
+            TaskDisplayArea displayArea = targetTask != null && targetTask.getDisplayArea() != null
+                    ? targetTask.getDisplayArea()
+                    : preferredTaskDisplayArea;
+            if (displayArea != null) {
+                ArrayList<Task> visibleTasks = displayArea.getVisibleTasks();
+                for (int i = 0; i < visibleTasks.size(); i++) {
+                    Task task = visibleTasks.get(i);
+                    if (visibleTasks.size() == 1 && task.isActivityTypeHomeOrRecents()) {
+                        bas.optedIn(task.getTopMostActivity());
+                    } else {
+                        bas = checkTopActivityForAsm(task, callingUid, /*sourceRecord*/null, bas);
+                    }
+                }
+            }
         }
 
-        if (bas != null && !bas.mWouldBlockActivityStartIgnoringFlag) {
+        if (bas.mTopActivityMatchesSource) {
             return true;
         }
 
@@ -1140,13 +1123,16 @@
                 ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_SAME_TASK
                 : FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_DIFFERENT_TASK);
 
-        boolean blockActivityStartAndFeatureEnabled = ActivitySecurityModelFeatureFlags
-                .shouldRestrictActivitySwitch(callingUid)
-                && (bas == null || bas.mBlockActivityStartIfFlagEnabled);
+        boolean enforceBlock = bas.mTopActivityOptedIn
+                && ActivitySecurityModelFeatureFlags.shouldRestrictActivitySwitch(callingUid);
+
+        boolean allowedByGracePeriod = allowedByAsmGracePeriod(callingUid, sourceRecord, targetTask,
+                balCode, taskToFront, avoidMoveTaskToFront);
 
         String asmDebugInfo = getDebugInfoForActivitySecurity("Launch", sourceRecord,
                 targetRecord, targetTask, targetTopActivity, realCallingUid, balCode,
-                blockActivityStartAndFeatureEnabled, taskToFront, avoidMoveTaskToFront);
+                enforceBlock, taskToFront, avoidMoveTaskToFront, allowedByGracePeriod,
+                bas.mActivityOptedIn);
 
         FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
                 /* caller_uid */
@@ -1184,7 +1170,7 @@
         String launchedFromPackageName = targetRecord.launchedFromPackage;
         if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)) {
             String toastText = ActivitySecurityModelFeatureFlags.DOC_LINK
-                    + (blockActivityStartAndFeatureEnabled ? " blocked " : " would block ")
+                    + (enforceBlock ? " blocked " : " would block ")
                     + getApplicationLabel(mService.mContext.getPackageManager(),
                     launchedFromPackageName);
             showToast(toastText);
@@ -1192,7 +1178,7 @@
             Slog.i(TAG, asmDebugInfo);
         }
 
-        if (blockActivityStartAndFeatureEnabled) {
+        if (enforceBlock) {
             Slog.e(TAG, "[ASM] Abort Launching r: " + targetRecord
                     + " as source: "
                     + (sourceRecord != null ? sourceRecord : launchedFromPackageName)
@@ -1251,18 +1237,18 @@
 
         // Find the first activity which matches a safe UID and is not finishing. Clear everything
         // above it
+        int[] finishCount = new int[1];
         boolean shouldBlockActivityStart = ActivitySecurityModelFeatureFlags
                 .shouldRestrictActivitySwitch(callingUid);
-        int[] finishCount = new int[0];
-        if (shouldBlockActivityStart
-                && blockCrossUidActivitySwitchFromBelowForActivity(targetTaskTop)) {
+        BlockActivityStart bas = checkCrossUidActivitySwitchFromBelow(
+                targetTaskTop, callingUid, new BlockActivityStart());
+        if (shouldBlockActivityStart && bas.mTopActivityOptedIn) {
             ActivityRecord activity = targetTask.getActivity(isLaunchingOrLaunched);
             if (activity == null) {
                 // mStartActivity is not in task, so clear everything
                 activity = targetRecord;
             }
 
-            finishCount = new int[1];
             targetTask.performClearTop(activity, launchFlags, finishCount);
             if (finishCount[0] > 0) {
                 Slog.w(TAG, "Cleared top n: " + finishCount[0] + " activities from task t: "
@@ -1279,7 +1265,8 @@
 
             Slog.i(TAG, getDebugInfoForActivitySecurity("Clear Top", sourceRecord, targetRecord,
                     targetTask, targetTaskTop, realCallingUid, balCode, shouldBlockActivityStart,
-                    /* taskToFront */ true, /* avoidMoveTaskToFront */ false));
+                    /* taskToFront */ true, /* avoidMoveTaskToFront */ false,
+                    /* allowedByAsmGracePeriod */ false, bas.mActivityOptedIn));
         }
     }
 
@@ -1287,7 +1274,7 @@
      * Returns home if the passed in callingUid is not top of the stack, rather than returning to
      * previous task.
      */
-    void checkActivityAllowedToClearTask(@NonNull Task task, int callingUid,
+    void checkActivityAllowedToClearTask(@NonNull Task task, int callingUid, int callingPid,
             @NonNull String callerActivityClassName) {
         // We may have already checked that the callingUid has additional clearTask privileges, and
         // cleared the calling identify. If so, we infer we do not need further restrictions here.
@@ -1295,14 +1282,28 @@
             return;
         }
 
+        String packageName =  mService.mContext.getPackageManager().getNameForUid(callingUid);
+        BalState state = new BalState(callingUid, callingPid, packageName, INVALID_UID,
+                INVALID_PID, null, null, null, null, null, ActivityOptions.makeBasic());
+        @BalCode int balCode = checkBackgroundActivityStartAllowedByCaller(state).mCode;
+        if (balCode == BAL_ALLOW_ALLOWLISTED_UID
+                || balCode == BAL_ALLOW_ALLOWLISTED_COMPONENT
+                || balCode == BAL_ALLOW_PERMISSION
+                || balCode == BAL_ALLOW_SAW_PERMISSION
+                || balCode == BAL_ALLOW_VISIBLE_WINDOW
+                || balCode == BAL_ALLOW_NON_APP_VISIBLE_WINDOW) {
+            return;
+        }
+
         TaskDisplayArea displayArea = task.getTaskDisplayArea();
         if (displayArea == null) {
             // If there is no associated display area, we can not return home.
             return;
         }
 
-        BlockActivityStart bas = isTopActivityMatchingUidAbsentForAsm(task, callingUid, null);
-        if (!bas.mWouldBlockActivityStartIgnoringFlag) {
+        BlockActivityStart bas = checkTopActivityForAsm(task, callingUid, null,
+                new BlockActivityStart());
+        if (bas.mTopActivityMatchesSource) {
             return;
         }
 
@@ -1339,8 +1340,7 @@
         );
 
         boolean restrictActivitySwitch = ActivitySecurityModelFeatureFlags
-                .shouldRestrictActivitySwitch(callingUid)
-                        && bas.mBlockActivityStartIfFlagEnabled;
+                .shouldRestrictActivitySwitch(callingUid) && bas.mTopActivityOptedIn;
 
         PackageManager pm = mService.mContext.getPackageManager();
         String callingPackage = pm.getNameForUid(callingUid);
@@ -1381,32 +1381,30 @@
      * <p>
      * The 'sourceRecord' can be considered top even if it is 'finishing'
      * <p>
-     * Returns a class where the elements are:
-     * <pre>
-     * shouldBlockActivityStart: {@code true} if we should actually block the transition (takes into
-     * consideration feature flag and targetSdk).
-     * wouldBlockActivityStartIgnoringFlags: {@code true} if we should warn about the transition via
-     * toasts. This happens if the transition would be blocked in case both the app was targeting V+
-     * and the feature was enabled.
-     * </pre>
      */
-    private BlockActivityStart isTopActivityMatchingUidAbsentForAsm(@NonNull Task task,
-            int uid, @Nullable ActivityRecord sourceRecord) {
+    private BlockActivityStart checkTopActivityForAsm(@NonNull Task task,
+            int uid, @Nullable ActivityRecord sourceRecord, BlockActivityStart bas) {
         // If the source is visible, consider it 'top'.
         if (sourceRecord != null && sourceRecord.isVisibleRequested()) {
-            return BlockActivityStart.ACTIVITY_START_ALLOWED;
+            return bas.matchesSource();
         }
 
-        // Always allow actual top activity to clear task
+        // Always allow actual top activity
         ActivityRecord topActivity = task.getTopMostActivity();
-        if (topActivity != null && topActivity.isUid(uid)) {
-            return BlockActivityStart.ACTIVITY_START_ALLOWED;
+        if (topActivity == null) {
+            Slog.wtf(TAG, "Activities for task: " + task + " not found.");
+            return bas.optedIn(topActivity);
+        }
+
+        bas = checkCrossUidActivitySwitchFromBelow(topActivity, uid, bas);
+        if (bas.mTopActivityMatchesSource) {
+            return bas;
         }
 
         // If UID is visible in target task, allow launch
         if (task.forAllActivities((Predicate<ActivityRecord>)
                 ar -> ar.isUid(uid) && ar.isVisibleRequested())) {
-            return BlockActivityStart.ACTIVITY_START_ALLOWED;
+            return bas.matchesSource();
         }
 
         // Consider the source activity, whether or not it is finishing. Do not consider any other
@@ -1417,82 +1415,91 @@
         // Check top of stack (or the first task fragment for embedding).
         topActivity = task.getActivity(topOfStackPredicate);
         if (topActivity == null) {
-            return new BlockActivityStart(true, true);
+            return bas;
         }
 
-        BlockActivityStart pair = blockCrossUidActivitySwitchFromBelow(topActivity, uid);
-        if (!pair.mBlockActivityStartIfFlagEnabled) {
-            return pair;
+        bas = checkCrossUidActivitySwitchFromBelow(topActivity, uid, bas);
+        if (bas.mTopActivityMatchesSource) {
+            return bas;
         }
 
         // Even if the top activity is not a match, we may be in an embedded activity scenario with
         // an adjacent task fragment. Get the second fragment.
         TaskFragment taskFragment = topActivity.getTaskFragment();
         if (taskFragment == null) {
-            return pair;
+            return bas;
         }
 
         TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
         if (adjacentTaskFragment == null) {
-            return pair;
+            return bas;
         }
 
         // Check the second fragment.
         topActivity = adjacentTaskFragment.getActivity(topOfStackPredicate);
         if (topActivity == null) {
-            return new BlockActivityStart(true, true);
+            return bas;
         }
 
-        return blockCrossUidActivitySwitchFromBelow(topActivity, uid);
+        return checkCrossUidActivitySwitchFromBelow(topActivity, uid, bas);
     }
 
     /**
      * Determines if a source is allowed to add or remove activities from the task,
      * if the current ActivityRecord is above it in the stack
      * <p>
-     * A transition is blocked ({@code false} returned) if all of the following are met:
+     * A transition is blocked if all of the following are met:
      * <pre>
      * 1. The source activity and the current activity record belong to different apps
      * (i.e, have different UIDs).
-     * 2. Both the source activity and the current activity target U+
-     * 3. The current activity has not set
+     * 2. The current activity target V+
+     * 3. The current app has set
+     * {@link R.styleable#AndroidManifestApplication_allowCrossUidActivitySwitchFromBelow}
+     * to {@code false}
+     * 4. The current activity has not set
      * {@link ActivityRecord#setAllowCrossUidActivitySwitchFromBelow(boolean)} to {@code true}
      * </pre>
      *
-     * Returns a class where the elements are:
-     * <pre>
-     * shouldBlockActivityStart: {@code true} if we should actually block the transition (takes into
-     * consideration feature flag and targetSdk).
-     * wouldBlockActivityStartIgnoringFlags: {@code true} if we should warn about the transition via
-     * toasts. This happens if the transition would be blocked in case both the app was targeting V+
-     * and the feature was enabled.
-     * </pre>
      *
      * @param sourceUid The source (s) activity performing the state change
      */
-    private BlockActivityStart blockCrossUidActivitySwitchFromBelow(ActivityRecord ar,
-            int sourceUid) {
+    private BlockActivityStart checkCrossUidActivitySwitchFromBelow(ActivityRecord ar,
+            int sourceUid, BlockActivityStart bas) {
         if (ar.isUid(sourceUid)) {
-            return BlockActivityStart.ACTIVITY_START_ALLOWED;
+            return bas.matchesSource();
         }
 
-        if (!blockCrossUidActivitySwitchFromBelowForActivity(ar)) {
-            return BlockActivityStart.ACTIVITY_START_ALLOWED;
+        // We don't need to check package level if activity has opted out.
+        if (ar.mAllowCrossUidActivitySwitchFromBelow) {
+            bas.mTopActivityOptedIn = false;
+            return bas.matchesSource();
         }
 
-        // At this point, we would block if the feature is launched and both apps were V+
-        // Since we have a feature flag, we need to check that too
-        // TODO(b/258792202) Replace with CompatChanges and replace Pair with boolean once feature
-        // flag is removed
-        boolean restrictActivitySwitch =
-                ActivitySecurityModelFeatureFlags.shouldRestrictActivitySwitch(ar.getUid())
-                        && ActivitySecurityModelFeatureFlags
-                        .shouldRestrictActivitySwitch(sourceUid);
-        if (restrictActivitySwitch) {
-            return BlockActivityStart.BLOCK;
-        } else {
-            return BlockActivityStart.LOG_ONLY;
+        if (!CompatChanges.isChangeEnabled(ASM_RESTRICTIONS, ar.getUid())) {
+            return bas;
         }
+
+        if (ar.isUid(SYSTEM_UID)) {
+            return bas.optedIn(ar);
+        }
+
+        String packageName = ar.packageName;
+        if (packageName == null) {
+            Slog.wtf(TAG, "Package name: " + ar + " not found.");
+            return bas.optedIn(ar);
+        }
+
+        PackageManager pm = mService.mContext.getPackageManager();
+        ApplicationInfo applicationInfo;
+
+        try {
+            applicationInfo = pm.getApplicationInfo(packageName, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.wtf(TAG, "Package name: " + packageName + " not found.");
+            return bas.optedIn(ar);
+        }
+
+        return applicationInfo.allowCrossUidActivitySwitchFromBelow ? bas : bas.optedIn(ar);
     }
 
     /**
@@ -1502,8 +1509,9 @@
             @Nullable ActivityRecord sourceRecord, @NonNull ActivityRecord targetRecord,
             @Nullable Task targetTask, @Nullable ActivityRecord targetTopActivity,
             int realCallingUid, @BalCode int balCode,
-            boolean blockActivityStartAndFeatureEnabled, boolean taskToFront,
-            boolean avoidMoveTaskToFront) {
+            boolean enforceBlock, boolean taskToFront,
+            boolean avoidMoveTaskToFront, boolean allowedByGracePeriod,
+            ActivityRecord activityOptedIn) {
         final String prefix = "[ASM] ";
         Function<ActivityRecord, String> recordToString = (ar) -> {
             if (ar == null) {
@@ -1519,9 +1527,16 @@
 
         StringJoiner joiner = new StringJoiner("\n");
         joiner.add(prefix + "------ Activity Security " + action + " Debug Logging Start ------");
-        joiner.add(prefix + "Block Enabled: " + blockActivityStartAndFeatureEnabled);
+        joiner.add(prefix + "Block Enabled: " + enforceBlock);
+        if (!enforceBlock) {
+            joiner.add(prefix + "Feature Flag Enabled: " + android.security
+                    .Flags.asmRestrictionsEnabled());
+            joiner.add(prefix + "Mendel Override: " + ActivitySecurityModelFeatureFlags
+                    .asmRestrictionsEnabledForAll());
+        }
         joiner.add(prefix + "ASM Version: " + ActivitySecurityModelFeatureFlags.ASM_VERSION);
         joiner.add(prefix + "System Time: " + SystemClock.uptimeMillis());
+        joiner.add(prefix + "Activity Opted In: " + recordToString.apply(activityOptedIn));
 
         boolean targetTaskMatchesSourceTask = targetTask != null
                 && sourceRecord != null && sourceRecord.getTask() == targetTask;
@@ -1561,6 +1576,7 @@
         joiner.add(prefix + "TaskToFront: " + taskToFront);
         joiner.add(prefix + "AvoidMoveToFront: " + avoidMoveTaskToFront);
         joiner.add(prefix + "BalCode: " + balCodeToString(balCode));
+        joiner.add(prefix + "Allowed By Grace Period: " + allowedByGracePeriod);
         joiner.add(prefix + "LastResumedActivity: "
                        + recordToString.apply(mService.mLastResumedActivity));
 
@@ -1588,6 +1604,44 @@
         return joiner.toString();
     }
 
+    private boolean allowedByAsmGracePeriod(int callingUid, @Nullable ActivityRecord sourceRecord,
+            @Nullable Task targetTask, @BalCode int balCode, boolean taskToFront,
+            boolean avoidMoveTaskToFront) {
+        if (balCode == BAL_ALLOW_GRACE_PERIOD) {
+            // Allow if launching into new task, and caller matches most recently finished activity
+            if (taskToFront && mTopFinishedActivity != null
+                    && mTopFinishedActivity.mUid == callingUid) {
+                return true;
+            }
+
+            // Launching into existing task - allow if matches most recently finished activity
+            // within the task.
+            // We can reach here multiple ways:
+            // 1. activity in fg fires intent (taskToFront = false, sourceRecord is available)
+            // 2. activity in bg fires intent (taskToFront = false, sourceRecord is available)
+            // 3. activity in bg fires intent with NEW_FLAG (taskToFront = true,
+            //         avoidMoveTaskToFront = true, sourceRecord is available)
+            // 4. activity in bg fires PI (taskToFront = true, avoidMoveTaskToFront = true,
+            //         sourceRecord is not available, targetTask may be available)
+            if (!taskToFront || avoidMoveTaskToFront) {
+                if (targetTask != null) {
+                    FinishedActivityEntry finishedEntry =
+                            mTaskIdToFinishedActivity.get(targetTask.mTaskId);
+                    if (finishedEntry != null && finishedEntry.mUid == callingUid) {
+                        return true;
+                    }
+                }
+
+                if (sourceRecord != null) {
+                    FinishedActivityEntry finishedEntry =
+                            mTaskIdToFinishedActivity.get(sourceRecord.getTask().mTaskId);
+                    return finishedEntry != null && finishedEntry.mUid == callingUid;
+                }
+            }
+        }
+        return false;
+    }
+
     private static boolean isSystemExemptFlagEnabled() {
         return DeviceConfig.getBoolean(
                 NAMESPACE_WINDOW_MANAGER,
@@ -1698,55 +1752,22 @@
         }
     }
 
-    /**
-     * Activity level allowCrossUidActivitySwitchFromBelow defaults to false.
-     * Package level defaults to true.
-     * We block the launch if dev has explicitly set package level to false, and activity level has
-     * not opted out
-     */
-    private boolean blockCrossUidActivitySwitchFromBelowForActivity(@NonNull ActivityRecord ar) {
-        // We don't need to check package level if activity has opted out.
-        if (ar.mAllowCrossUidActivitySwitchFromBelow) {
-            return false;
-        }
-
-        if (ActivitySecurityModelFeatureFlags.asmRestrictionsEnabledForAll()) {
-            return true;
-        }
-
-        String packageName = ar.packageName;
-        if (packageName == null) {
-            return false;
-        }
-
-        PackageManager pm = mService.mContext.getPackageManager();
-        ApplicationInfo applicationInfo;
-
-        try {
-            applicationInfo = pm.getApplicationInfo(packageName, 0);
-        } catch (PackageManager.NameNotFoundException e) {
-            Slog.wtf(TAG, "Package name: " + packageName + " not found.");
-            return false;
-        }
-
-        return !applicationInfo.allowCrossUidActivitySwitchFromBelow;
-    }
-
     private static class BlockActivityStart {
-        private static final BlockActivityStart ACTIVITY_START_ALLOWED =
-                new BlockActivityStart(false, false);
-        private static final BlockActivityStart LOG_ONLY = new BlockActivityStart(false, true);
-        private static final BlockActivityStart BLOCK = new BlockActivityStart(true, true);
-        // We should block if feature flag is enabled
-        private final boolean mBlockActivityStartIfFlagEnabled;
-        // Used for logging/toasts. Would we block if target sdk was V and feature was
-        // enabled?
-        private final boolean mWouldBlockActivityStartIgnoringFlag;
+        private boolean mTopActivityOptedIn;
+        private boolean mTopActivityMatchesSource;
+        private ActivityRecord mActivityOptedIn;
 
-        private BlockActivityStart(boolean shouldBlockActivityStart,
-                boolean wouldBlockActivityStartIgnoringFlags) {
-            this.mBlockActivityStartIfFlagEnabled = shouldBlockActivityStart;
-            this.mWouldBlockActivityStartIgnoringFlag = wouldBlockActivityStartIgnoringFlags;
+        BlockActivityStart optedIn(ActivityRecord activity) {
+            mTopActivityOptedIn = true;
+            if (mActivityOptedIn == null) {
+                mActivityOptedIn = activity;
+            }
+            return this;
+        }
+
+        BlockActivityStart matchesSource() {
+            mTopActivityMatchesSource = true;
+            return this;
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index e48e4e8..816fe1d 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -22,6 +22,7 @@
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.ClientTransactionItem;
 import android.os.Binder;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.Trace;
@@ -179,6 +180,22 @@
         Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
     }
 
+    /** Executes the pending transaction for the given client process. */
+    void dispatchPendingTransaction(@NonNull IApplicationThread client) {
+        if (!Flags.bundleClientTransactionFlag()) {
+            return;
+        }
+        final ClientTransaction pendingTransaction = mPendingTransactions.remove(client.asBinder());
+        if (pendingTransaction != null) {
+            try {
+                scheduleTransaction(pendingTransaction);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to deliver pending transaction", e);
+                // TODO(b/323801078): apply cleanup for individual transaction item if needed.
+            }
+        }
+    }
+
     /**
      * Called to when {@link WindowSurfacePlacer#continueLayout}.
      * Dispatches all pending transactions unless there is an ongoing/scheduled layout, in which
@@ -233,4 +250,17 @@
                 && !mWms.mWindowPlacerLocked.isTraversalScheduled()
                 && !mWms.mWindowPlacerLocked.isInLayout();
     }
+
+    /**
+     * Guards the bundleClientTransactionFlag feature with targetSDK on Android 15+.
+     *
+     * Suppressing because it can't guard with @EnabledSince on VANILLA_ICE_CREAM yet since the
+     * version is not published.
+     *
+     * TODO(b/324203798): update in V
+     */
+    @SuppressWarnings("AndroidFrameworkCompatChange")
+    static boolean shouldDispatchCompatClientTransactionIndependently(int appTargetSdk) {
+        return appTargetSdk <= Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 8717098..b616d24 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -107,7 +107,9 @@
 
     ContentRecorder(@NonNull DisplayContent displayContent) {
         this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId),
-                new DisplayManagerFlags().isConnectedDisplayManagementEnabled());
+                new DisplayManagerFlags().isConnectedDisplayManagementEnabled()
+                        && !new DisplayManagerFlags()
+                                    .isPixelAnisotropyCorrectionInLogicalDisplayEnabled());
     }
 
     @VisibleForTesting
@@ -650,6 +652,14 @@
         if (isCurrentlyRecording() && mLastRecordedBounds != null) {
             mMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged(
                     isVisibleRequested);
+
+            if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) {
+                // If capturing a task, then the toggle visibility of the recorded surface to match
+                // visibility of the task, so we don't capture any mid-transition frames
+                mRecordedWindowContainer.getSyncTransaction()
+                        .setVisibility(mRecordedSurface, isVisibleRequested);
+                mRecordedWindowContainer.scheduleAnimation();
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index 7052982..a29cb60 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -113,8 +113,10 @@
 
         // Apply whole display info immediately as is if either:
         // * it is the first display update
+        // * the display doesn't have visible content
         // * shell transitions are disabled or temporary unavailable
         if (displayInfoDiff == DIFF_EVERYTHING
+                || !mDisplayContent.getLastHasContent()
                 || !mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
             ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
                     "DeferredDisplayUpdater: applying DisplayInfo immediately");
@@ -171,18 +173,25 @@
                     mDisplayContent.mInitialDisplayHeight);
             final int fromRotation = mDisplayContent.getRotation();
 
-            onStartCollect.run();
+            mDisplayContent.mAtmService.deferWindowLayout();
+            try {
+                onStartCollect.run();
 
-            ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
-                    "DeferredDisplayUpdater: applied DisplayInfo after deferring");
+                ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
+                        "DeferredDisplayUpdater: applied DisplayInfo after deferring");
 
-            if (physicalDisplayUpdated) {
-                onDisplayUpdated(transition, fromRotation, startBounds);
-            } else {
-                final TransitionRequestInfo.DisplayChange displayChange =
-                        getCurrentDisplayChange(fromRotation, startBounds);
-                mDisplayContent.mTransitionController.requestStartTransition(transition,
-                        /* startTask= */ null, /* remoteTransition= */ null, displayChange);
+                if (physicalDisplayUpdated) {
+                    onDisplayUpdated(transition, fromRotation, startBounds);
+                } else {
+                    final TransitionRequestInfo.DisplayChange displayChange =
+                            getCurrentDisplayChange(fromRotation, startBounds);
+                    mDisplayContent.mTransitionController.requestStartTransition(transition,
+                            /* startTask= */ null, /* remoteTransition= */ null, displayChange);
+                }
+            } finally {
+                // Run surface placement after requestStartTransition, so shell side can receive
+                // the transition request before handling task info changes.
+                mDisplayContent.mAtmService.continueWindowLayout();
             }
         });
     }
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index f11d6ec..925a077 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -27,7 +27,7 @@
 import android.util.Slog;
 
 import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;
-import com.android.wm.shell.Flags;
+import com.android.window.flags.Flags;
 /**
  * The class that defines default launch params for tasks in desktop mode
  */
@@ -37,8 +37,6 @@
             TAG_WITH_CLASS_NAME ? "DesktopModeLaunchParamsModifier" : TAG_ATM;
     private static final boolean DEBUG = false;
 
-    // Desktop mode feature flags.
-    private static final boolean ENABLE_DESKTOP_WINDOWING = Flags.enableDesktopWindowing();
     private static final boolean DESKTOP_MODE_PROTO2_SUPPORTED =
             SystemProperties.getBoolean("persist.wm.debug.desktop_mode_2", false);
     public static final float DESKTOP_MODE_INITIAL_BOUNDS_SCALE =
@@ -67,6 +65,11 @@
             LaunchParamsController.LaunchParams currentParams,
             LaunchParamsController.LaunchParams outParams) {
 
+        if (!isDesktopModeEnabled()) {
+            appendLog("desktop mode is not enabled, continuing");
+            return RESULT_CONTINUE;
+        }
+
         if (task == null) {
             appendLog("task null, skipping");
             return RESULT_SKIP;
@@ -87,7 +90,7 @@
         // previous windowing mode to be restored even if the desktop mode state has changed.
         // Let task launches inherit the windowing mode from the source task if available, which
         // should have the desired windowing mode set by WM Shell. See b/286929122.
-        if (isDesktopModeSupported() && source != null && source.getTask() != null) {
+        if (isDesktopModeEnabled() && source != null && source.getTask() != null) {
             final Task sourceTask = source.getTask();
             outParams.mWindowingMode = sourceTask.getWindowingMode();
             appendLog("inherit-from-source=" + outParams.mWindowingMode);
@@ -140,14 +143,8 @@
         if (DEBUG) Slog.d(TAG, mLogBuilder.toString());
     }
 
-    /** Whether desktop mode is supported. */
-    static boolean isDesktopModeSupported() {
-        // Check for aconfig flag first
-        if (ENABLE_DESKTOP_WINDOWING) {
-            return true;
-        }
-        // Fall back to sysprop flag
-        // TODO(b/304778354): remove sysprop once desktop aconfig flag supports dynamic overriding
-        return DESKTOP_MODE_PROTO2_SUPPORTED;
+    /** Whether desktop mode is enabled. */
+    static boolean isDesktopModeEnabled() {
+        return Flags.enableDesktopWindowingMode();
     }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index d3acd71..837d08b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1319,7 +1319,7 @@
         for (int i = 0; i < mChildren.size(); i++)  {
             SurfaceControl sc = mChildren.get(i).getSurfaceControl();
             if (sc != null) {
-                t.reparent(sc, mSurfaceControl);
+                t.reparent(sc, getParentingSurfaceControl());
             }
         }
 
@@ -1625,22 +1625,23 @@
 
         if (configChanged) {
             mWaitingForConfig = true;
-            if (mTransitionController.isShellTransitionsEnabled()) {
+            if (mLastHasContent && mTransitionController.isShellTransitionsEnabled()) {
                 final Rect startBounds = currentDisplayConfig.windowConfiguration.getBounds();
                 final Rect endBounds = mTmpConfiguration.windowConfiguration.getBounds();
-                final Transition transition = mTransitionController.getCollectingTransition();
-                final TransitionRequestInfo.DisplayChange change = transition != null
-                                ? null : new TransitionRequestInfo.DisplayChange(mDisplayId);
-                if (change != null) {
+                if (!mTransitionController.isCollecting()) {
+                    final TransitionRequestInfo.DisplayChange change =
+                            new TransitionRequestInfo.DisplayChange(mDisplayId);
                     change.setStartAbsBounds(startBounds);
                     change.setEndAbsBounds(endBounds);
+                    requestChangeTransition(changes, change);
                 } else {
+                    final Transition transition = mTransitionController.getCollectingTransition();
                     transition.setKnownConfigChanges(this, changes);
                     // A collecting transition is existed. The sync method must be set before
                     // collecting this display, so WindowState#prepareSync can use the sync method.
                     mTransitionController.setDisplaySyncMethod(startBounds, endBounds, this);
+                    collectDisplayChange(transition);
                 }
-                requestChangeTransitionIfNeeded(changes, change);
             } else if (mLastHasContent) {
                 mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */, this);
             }
@@ -2301,7 +2302,8 @@
             mDisplayInfo.flags &= ~Display.FLAG_SCALING_DISABLED;
         }
 
-        computeSizeRanges(mDisplayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig);
+        computeSizeRanges(mDisplayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig,
+                false /* legacyConfig */);
 
         setDisplayInfoOverride();
 
@@ -2437,7 +2439,8 @@
         displayInfo.appHeight = appBounds.height();
         final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(rotation);
         displayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout;
-        computeSizeRanges(displayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig);
+        computeSizeRanges(displayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig,
+                false /* legacyConfig */);
         return displayInfo;
     }
 
@@ -2601,8 +2604,21 @@
         return curSize;
     }
 
-    private void computeSizeRanges(DisplayInfo displayInfo, boolean rotated,
-            int dw, int dh, float density, Configuration outConfig) {
+    /**
+     * Compute size range related fields of DisplayInfo and Configuration based on provided info.
+     * The fields usually contain word such as smallest or largest.
+     *
+     * @param displayInfo In-out display info to compute the result.
+     * @param rotated Whether the display is rotated.
+     * @param dw Display Width in current rotation.
+     * @param dh Display Height in current rotation.
+     * @param density Display density.
+     * @param outConfig The output configuration to
+     * @param legacyConfig Whether we need to report the legacy result, which is excluding system
+     *                     decorations.
+     */
+    void computeSizeRanges(DisplayInfo displayInfo, boolean rotated,
+            int dw, int dh, float density, Configuration outConfig, boolean legacyConfig) {
 
         // We need to determine the smallest width that will occur under normal
         // operation.  To this, start with the base screen size and compute the
@@ -2620,10 +2636,10 @@
         displayInfo.smallestNominalAppHeight = 1<<30;
         displayInfo.largestNominalAppWidth = 0;
         displayInfo.largestNominalAppHeight = 0;
-        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, unrotDw, unrotDh);
-        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, unrotDh, unrotDw);
-        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, unrotDw, unrotDh);
-        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, unrotDh, unrotDw);
+        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, unrotDw, unrotDh, legacyConfig);
+        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, unrotDh, unrotDw, legacyConfig);
+        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, unrotDw, unrotDh, legacyConfig);
+        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, unrotDh, unrotDw, legacyConfig);
 
         if (outConfig == null) {
             return;
@@ -2632,11 +2648,19 @@
                 (int) (displayInfo.smallestNominalAppWidth / density + 0.5f);
     }
 
-    private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation, int dw, int dh) {
+    private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation, int dw, int dh,
+            boolean legacyConfig) {
         final DisplayPolicy.DecorInsets.Info info = mDisplayPolicy.getDecorInsetsInfo(
                 rotation, dw, dh);
-        final int w = info.mConfigFrame.width();
-        final int h = info.mConfigFrame.height();
+        final int w;
+        final int h;
+        if (!legacyConfig) {
+            w = info.mConfigFrame.width();
+            h = info.mConfigFrame.height();
+        } else {
+            w = info.mLegacyConfigFrame.width();
+            h = info.mLegacyConfigFrame.height();
+        }
         if (w < displayInfo.smallestNominalAppWidth) {
             displayInfo.smallestNominalAppWidth = w;
         }
@@ -3551,60 +3575,62 @@
     }
 
     /**
-     * Requests to start a transition for the display configuration change. The given changes must
-     * be non-zero. This method is no-op if the display has been collected.
+     * Collects this display into an already-collecting transition.
      */
-    void requestChangeTransitionIfNeeded(@ActivityInfo.Config int changes,
-            @Nullable TransitionRequestInfo.DisplayChange displayChange) {
+    void collectDisplayChange(@NonNull Transition transition) {
         if (!mLastHasContent) return;
-        final TransitionController controller = mTransitionController;
-        if (controller.isCollecting()) {
-            if (displayChange != null) {
-                throw new IllegalArgumentException("Provided displayChange for non-new transition");
-            }
-            if (!controller.isCollecting(this)) {
-                controller.collect(this);
-                startAsyncRotationIfNeeded();
-                if (mFixedRotationLaunchingApp != null) {
-                    setSeamlessTransitionForFixedRotation(controller.getCollectingTransition());
-                }
-            } else if (mAsyncRotationController != null && !isRotationChanging()) {
-                Slog.i(TAG, "Finish AsyncRotation for previous intermediate change");
-                finishAsyncRotationIfPossible();
-            }
-            return;
+        if (!transition.isCollecting()) {
+            throw new IllegalArgumentException("Can only collect display change if transition"
+                    + " is collecting");
         }
-        final Transition t = controller.requestTransitionIfNeeded(TRANSIT_CHANGE, 0 /* flags */,
-                this, this, null /* remoteTransition */, displayChange);
-        if (t != null) {
-            mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
-            if (mAsyncRotationController != null) {
-                // Give a chance to update the transform if the current rotation is changed when
-                // some windows haven't finished previous rotation.
-                mAsyncRotationController.updateRotation();
-            }
+        if (!transition.mParticipants.contains(this)) {
+            transition.collect(this);
+            startAsyncRotationIfNeeded();
             if (mFixedRotationLaunchingApp != null) {
-                // A fixed-rotation transition is done, then continue to start a seamless display
-                // transition.
-                setSeamlessTransitionForFixedRotation(t);
-            } else if (isRotationChanging()) {
-                if (displayChange != null) {
-                    final boolean seamless = mDisplayRotation.shouldRotateSeamlessly(
-                            displayChange.getStartRotation(), displayChange.getEndRotation(),
-                            false /* forceUpdate */);
-                    if (seamless) {
-                        t.onSeamlessRotating(this);
-                    }
-                }
-                mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
-                controller.mTransitionMetricsReporter.associate(t.getToken(),
-                        startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
-                startAsyncRotation(false /* shouldDebounce */);
+                setSeamlessTransitionForFixedRotation(transition);
             }
-            t.setKnownConfigChanges(this, changes);
+        } else if (mAsyncRotationController != null && !isRotationChanging()) {
+            Slog.i(TAG, "Finish AsyncRotation for previous intermediate change");
+            finishAsyncRotationIfPossible();
         }
     }
 
+    /**
+     * Requests to start a transition for a display change. {@code changes} must be non-zero.
+     */
+    void requestChangeTransition(@ActivityInfo.Config int changes,
+            @Nullable TransitionRequestInfo.DisplayChange displayChange) {
+        final TransitionController controller = mTransitionController;
+        final Transition t = controller.requestStartDisplayTransition(TRANSIT_CHANGE, 0 /* flags */,
+                this, null /* remoteTransition */, displayChange);
+        t.collect(this);
+        mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
+        if (mAsyncRotationController != null) {
+            // Give a chance to update the transform if the current rotation is changed when
+            // some windows haven't finished previous rotation.
+            mAsyncRotationController.updateRotation();
+        }
+        if (mFixedRotationLaunchingApp != null) {
+            // A fixed-rotation transition is done, then continue to start a seamless display
+            // transition.
+            setSeamlessTransitionForFixedRotation(t);
+        } else if (isRotationChanging()) {
+            if (displayChange != null) {
+                final boolean seamless = mDisplayRotation.shouldRotateSeamlessly(
+                        displayChange.getStartRotation(), displayChange.getEndRotation(),
+                        false /* forceUpdate */);
+                if (seamless) {
+                    t.onSeamlessRotating(this);
+                }
+            }
+            mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
+            controller.mTransitionMetricsReporter.associate(t.getToken(),
+                    startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
+            startAsyncRotation(false /* shouldDebounce */);
+        }
+        t.setKnownConfigChanges(this, changes);
+    }
+
     private void setSeamlessTransitionForFixedRotation(Transition t) {
         t.setSeamlessRotation(this);
         // Before the start transaction is applied, the non-app windows need to keep in previous
@@ -5722,14 +5748,8 @@
      */
     void requestTransitionAndLegacyPrepare(@WindowManager.TransitionType int transit,
             @WindowManager.TransitionFlags int flags) {
-        requestTransitionAndLegacyPrepare(transit, flags, null /* trigger */);
-    }
-
-    /** @see #requestTransitionAndLegacyPrepare(int, int) */
-    void requestTransitionAndLegacyPrepare(@WindowManager.TransitionType int transit,
-            @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger) {
         prepareAppTransition(transit, flags);
-        mTransitionController.requestTransitionIfNeeded(transit, flags, trigger, this);
+        mTransitionController.requestTransitionIfNeeded(transit, flags, null /* trigger */, this);
     }
 
     void executeAppTransition() {
@@ -5808,6 +5828,21 @@
                 || supportsSystemDecorations();
     }
 
+    /**
+     * Returns the {@link SurfaceControl} where all the children should be parented on.
+     *
+     * <p> {@link DisplayContent} inserts a RootWrapper leash in the hierarchy above its original
+     * {@link #mSurfaceControl} and then overrides the {@link #mSurfaceControl} to point to the
+     * RootWrapper.
+     * <p> To prevent inconsistent state later where the DAs might get re-parented to the
+     * RootWrapper, this method should be used which returns the correct surface where the
+     * re-parenting should happen.
+     */
+    @Override
+    SurfaceControl getParentingSurfaceControl() {
+        return mWindowingLayer;
+    }
+
     SurfaceControl getWindowingLayer() {
         return mWindowingLayer;
     }
@@ -6175,7 +6210,12 @@
      * @param onDisplayChangeApplied callback that is called when the changes are applied
      */
     void requestDisplayUpdate(@NonNull Runnable onDisplayChangeApplied) {
-        mDisplayUpdater.updateDisplayInfo(onDisplayChangeApplied);
+        mAtmService.deferWindowLayout();
+        try {
+            mDisplayUpdater.updateDisplayInfo(onDisplayChangeApplied);
+        } finally {
+            mAtmService.continueWindowLayout();
+        }
     }
 
     void onDisplayInfoUpdated(@NonNull DisplayInfo newDisplayInfo) {
@@ -6372,8 +6412,13 @@
         if (changes != 0) {
             Slog.i(TAG, "Override config changes=" + Integer.toHexString(changes) + " "
                     + mTempConfig + " for displayId=" + mDisplayId);
-            if (isReady() && mTransitionController.isShellTransitionsEnabled()) {
-                requestChangeTransitionIfNeeded(changes, null /* displayChange */);
+            if (isReady() && mTransitionController.isShellTransitionsEnabled() && mLastHasContent) {
+                final Transition transition = mTransitionController.getCollectingTransition();
+                if (transition != null) {
+                    collectDisplayChange(transition);
+                } else {
+                    requestChangeTransition(changes, null /* displayChange */);
+                }
             }
             onRequestedOverrideConfigurationChanged(mTempConfig);
 
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 7f3df95..e789fec 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -25,6 +25,7 @@
 import static android.view.InsetsFrameProvider.SOURCE_FRAME;
 import static android.view.ViewRootImpl.CLIENT_IMMERSIVE_CONFIRMATION;
 import static android.view.ViewRootImpl.CLIENT_TRANSIENT;
+import static android.view.WindowInsetsController.APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
@@ -984,6 +985,9 @@
                 }
                 break;
         }
+        if ((attrs.insetsFlags.appearance & APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS) != 0) {
+            attrs.insetsFlags.appearance |= APPEARANCE_LIGHT_NAVIGATION_BARS;
+        }
 
         if (LayoutParams.isSystemAlertWindowType(attrs.type)) {
             float maxOpacity = mService.mMaximumObscuringOpacityForTouch;
@@ -1913,6 +1917,11 @@
              */
             final Rect mConfigInsets = new Rect();
 
+            /**
+             * Legacy value of mConfigInsets for app compatibility purpose.
+             */
+            final Rect mLegacyConfigInsets = new Rect();
+
             /** The display frame available after excluding {@link #mNonDecorInsets}. */
             final Rect mNonDecorFrame = new Rect();
 
@@ -1923,6 +1932,11 @@
              */
             final Rect mConfigFrame = new Rect();
 
+            /**
+             * Legacy value of mConfigFrame for app compatibility purpose.
+             */
+            final Rect mLegacyConfigFrame = new Rect();
+
             private boolean mNeedUpdate = true;
 
             InsetsState update(DisplayContent dc, int rotation, int w, int h) {
@@ -1933,15 +1947,26 @@
                 final Rect displayFrame = insetsState.getDisplayFrame();
                 final Insets decor = insetsState.calculateInsets(displayFrame,
                         dc.mWmService.mDecorTypes, true /* ignoreVisibility */);
-                final Insets configInsets = insetsState.calculateInsets(displayFrame,
-                        dc.mWmService.mConfigTypes, true /* ignoreVisibility */);
+                final Insets configInsets = dc.mWmService.mConfigTypes == dc.mWmService.mDecorTypes
+                        ? decor
+                        : insetsState.calculateInsets(displayFrame, dc.mWmService.mConfigTypes,
+                                true /* ignoreVisibility */);
+                final Insets legacyConfigInsets = dc.mWmService.mConfigTypes
+                        == dc.mWmService.mLegacyConfigTypes
+                        ? configInsets
+                        : insetsState.calculateInsets(displayFrame,
+                                dc.mWmService.mLegacyConfigTypes, true /* ignoreVisibility */);
                 mNonDecorInsets.set(decor.left, decor.top, decor.right, decor.bottom);
                 mConfigInsets.set(configInsets.left, configInsets.top, configInsets.right,
                         configInsets.bottom);
+                mLegacyConfigInsets.set(legacyConfigInsets.left, legacyConfigInsets.top,
+                        legacyConfigInsets.right, legacyConfigInsets.bottom);
                 mNonDecorFrame.set(displayFrame);
                 mNonDecorFrame.inset(mNonDecorInsets);
                 mConfigFrame.set(displayFrame);
                 mConfigFrame.inset(mConfigInsets);
+                mLegacyConfigFrame.set(displayFrame);
+                mLegacyConfigFrame.inset(mLegacyConfigInsets);
                 mNeedUpdate = false;
                 return insetsState;
             }
@@ -1949,8 +1974,10 @@
             void set(Info other) {
                 mNonDecorInsets.set(other.mNonDecorInsets);
                 mConfigInsets.set(other.mConfigInsets);
+                mLegacyConfigInsets.set(other.mLegacyConfigInsets);
                 mNonDecorFrame.set(other.mNonDecorFrame);
                 mConfigFrame.set(other.mConfigFrame);
+                mLegacyConfigFrame.set(other.mLegacyConfigFrame);
                 mNeedUpdate = false;
             }
 
@@ -2062,7 +2089,8 @@
         final DecorInsets.Info newInfo = mDecorInsets.mTmpInfo;
         final InsetsState newInsetsState = newInfo.update(mDisplayContent, rotation, dw, dh);
         final DecorInsets.Info currentInfo = getDecorInsetsInfo(rotation, dw, dh);
-        if (newInfo.mConfigFrame.equals(currentInfo.mConfigFrame)) {
+        if (newInfo.mConfigFrame.equals(currentInfo.mConfigFrame)
+                && newInfo.mLegacyConfigFrame.equals(currentInfo.mLegacyConfigFrame)) {
             // Even if the config frame is not changed in current rotation, it may change the
             // insets in other rotations if the frame of insets source is changed.
             final InsetsState currentInsetsState = mDisplayContent.mDisplayFrames.mInsetsState;
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index d376613..384b91a 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -629,13 +629,17 @@
 
         if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
             final boolean wasCollecting = mDisplayContent.mTransitionController.isCollecting();
-            final TransitionRequestInfo.DisplayChange change = wasCollecting ? null
-                    : new TransitionRequestInfo.DisplayChange(mDisplayContent.getDisplayId(),
-                            oldRotation, mRotation);
-
-            mDisplayContent.requestChangeTransitionIfNeeded(
-                    ActivityInfo.CONFIG_WINDOW_CONFIGURATION, change);
-            if (wasCollecting) {
+            if (!wasCollecting) {
+                if (mDisplayContent.getLastHasContent()) {
+                    final TransitionRequestInfo.DisplayChange change =
+                            new TransitionRequestInfo.DisplayChange(mDisplayContent.getDisplayId(),
+                                    oldRotation, mRotation);
+                    mDisplayContent.requestChangeTransition(
+                            ActivityInfo.CONFIG_WINDOW_CONFIGURATION, change);
+                }
+            } else {
+                mDisplayContent.collectDisplayChange(
+                        mDisplayContent.mTransitionController.getCollectingTransition());
                 // Use remote-rotation infra since the transition has already been requested
                 // TODO(shell-transitions): Remove this once lifecycle management can cover all
                 //                          rotation cases.
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index b68e67e..34ea4ac 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -18,6 +18,7 @@
 
 import static android.view.View.DRAG_FLAG_GLOBAL;
 import static android.view.View.DRAG_FLAG_GLOBAL_SAME_APPLICATION;
+import static android.view.View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG;
 
 import static com.android.input.flags.Flags.enablePointerChoreographer;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
@@ -373,8 +374,11 @@
     boolean notifyUnhandledDrop(DragEvent dropEvent, String reason) {
         final boolean isLocalDrag =
                 (mDragState.mFlags & (DRAG_FLAG_GLOBAL_SAME_APPLICATION | DRAG_FLAG_GLOBAL)) == 0;
+        final boolean shouldDelegateUnhandledDrag =
+                (mDragState.mFlags & DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) != 0;
         if (!com.android.window.flags.Flags.delegateUnhandledDrags()
                 || mGlobalDragListener == null
+                || !shouldDelegateUnhandledDrag
                 || isLocalDrag) {
             // Skip if the flag is disabled, there is no unhandled-drag listener, or if this is a
             // purely local drag
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 76038b9..b266caa 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -216,8 +216,7 @@
         mIsClosing = true;
         // Unregister the input interceptor.
         if (mInputInterceptor != null) {
-            if (DEBUG_DRAG)
-                Slog.d(TAG_WM, "unregistering drag input channel");
+            if (DEBUG_DRAG) Slog.d(TAG_WM, "Unregistering drag input channel");
 
             // Input channel should be disposed on the thread where the input is being handled.
             mDragDropController.sendHandlerMessage(
@@ -227,9 +226,7 @@
 
         // Send drag end broadcast if drag start has been sent.
         if (mDragInProgress) {
-            if (DEBUG_DRAG) {
-                Slog.d(TAG_WM, "broadcasting DRAG_ENDED");
-            }
+            if (DEBUG_DRAG) Slog.d(TAG_WM, "Broadcasting DRAG_ENDED");
             for (WindowState ws : mNotifiedWindows) {
                 float x = 0;
                 float y = 0;
@@ -248,6 +245,7 @@
                         x, y, mThumbOffsetX, mThumbOffsetY, null, null, null, dragSurface, null,
                         mDragResult);
                 try {
+                    if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DRAG_ENDED to " + ws);
                     ws.mClient.dispatchDragEvent(event);
                 } catch (RemoteException e) {
                     Slog.w(TAG_WM, "Unable to drag-end window " + ws);
@@ -364,7 +362,7 @@
             return false;
         }
 
-        if (DEBUG_DRAG) Slog.d(TAG_WM, "sending DROP to " + touchedWin);
+        if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DROP to " + touchedWin);
 
         final IBinder clientToken = touchedWin.mClient.asBinder();
         final DragEvent event = createDropEvent(x, y, touchedWin, false /* includePrivateInfo */);
@@ -460,7 +458,7 @@
      */
     CompletableFuture<Void> register(Display display) {
         display.getRealSize(mDisplaySize);
-        if (DEBUG_DRAG) Slog.d(TAG_WM, "registering drag input channel");
+        if (DEBUG_DRAG) Slog.d(TAG_WM, "Registering drag input channel");
         if (mInputInterceptor != null) {
             Slog.e(TAG_WM, "Duplicate register of drag input channel");
             return completedFuture(null);
@@ -489,7 +487,7 @@
                 mSourceUserId, UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
 
         if (DEBUG_DRAG) {
-            Slog.d(TAG_WM, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")");
+            Slog.d(TAG_WM, "Broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")");
         }
 
         final boolean containsAppExtras = containsApplicationExtras(mDataDescription);
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index ea31e63..21326be 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -31,6 +31,7 @@
 import android.annotation.Nullable;
 import android.graphics.Rect;
 import android.os.Trace;
+import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.InsetsSource;
 import android.view.InsetsSourceConsumer;
@@ -50,6 +51,8 @@
  */
 final class ImeInsetsSourceProvider extends InsetsSourceProvider {
 
+    private static final String TAG = ImeInsetsSourceProvider.class.getSimpleName();
+
     /** The token tracking the current IME request or {@code null} otherwise. */
     @Nullable
     private ImeTracker.Token mImeRequesterStatsToken;
@@ -215,17 +218,21 @@
      * when {@link android.inputmethodservice.InputMethodService} requests to show IME
      * on {@param imeTarget}.
      *
-     * @param imeTarget imeTarget on which IME show request is coming from.
-     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
+     * @param imeTarget imeTarget on which IME request is coming from.
+     * @param statsToken the token tracking the current IME request.
      */
     void scheduleShowImePostLayout(InsetsControlTarget imeTarget,
-            @Nullable ImeTracker.Token statsToken) {
+            @NonNull ImeTracker.Token statsToken) {
+        if (mImeRequesterStatsToken != null) {
+            // Cancel the pre-existing stats token, if any.
+            // Log state on pre-existing request cancel.
+            logShowImePostLayoutState();
+            ImeTracker.forLogging().onCancelled(
+                    mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
+        }
+        mImeRequesterStatsToken = statsToken;
         boolean targetChanged = isTargetChangedWithinActivity(imeTarget);
         mImeRequester = imeTarget;
-        // Cancel the pre-existing stats token, if any.
-        ImeTracker.forLogging().onCancelled(
-                mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
-        mImeRequesterStatsToken = statsToken;
         if (targetChanged) {
             // target changed, check if new target can show IME.
             ProtoLog.d(WM_DEBUG_IME, "IME target changed within ActivityRecord");
@@ -297,12 +304,16 @@
      */
     void abortShowImePostLayout() {
         ProtoLog.d(WM_DEBUG_IME, "abortShowImePostLayout");
+        if (mImeRequesterStatsToken != null) {
+            // Log state on abort.
+            logShowImePostLayoutState();
+            ImeTracker.forLogging().onFailed(
+                    mImeRequesterStatsToken, ImeTracker.PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT);
+            mImeRequesterStatsToken = null;
+        }
         mImeRequester = null;
         mIsImeLayoutDrawn = false;
         mShowImeRunner = null;
-        ImeTracker.forLogging().onFailed(
-                mImeRequesterStatsToken, ImeTracker.PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT);
-        mImeRequesterStatsToken = null;
     }
 
     @VisibleForTesting
@@ -337,6 +348,41 @@
                 || sameAsImeControlTarget();
     }
 
+    /**
+     * Logs the current state required for scheduleShowImePostLayout's runnable to be triggered.
+     */
+    private void logShowImePostLayoutState() {
+        final var windowState = mWindowContainer != null ? mWindowContainer.asWindowState() : null;
+        final var dcTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING);
+        final var controlTarget = mDisplayContent.getImeTarget(IME_TARGET_CONTROL);
+        final var sb = new StringBuilder();
+        sb.append("mWindowContainer: ").append(mWindowContainer);
+        sb.append(" windowState: ").append(windowState);
+        if (windowState != null) {
+            sb.append(" windowState.isDrawn(): ").append(windowState.isDrawn());
+            sb.append(" windowState.mGivenInsetsPending: ").append(windowState.mGivenInsetsPending);
+        }
+        sb.append(" mIsImeLayoutDrawn: ").append(mIsImeLayoutDrawn);
+        sb.append(" mShowImeRunner: ").append(mShowImeRunner);
+        sb.append(" mImeRequester: ").append(mImeRequester);
+        sb.append(" dcTarget: ").append(dcTarget);
+        sb.append(" controlTarget: ").append(controlTarget);
+        sb.append(" isReadyToShowIme(): ").append(isReadyToShowIme());
+        if (mImeRequester != null && dcTarget != null && controlTarget != null) {
+            sb.append(" isImeLayeringTarget: ");
+            sb.append(isImeLayeringTarget(mImeRequester, dcTarget));
+            sb.append(" isAboveImeLayeringTarget: ");
+            sb.append(isAboveImeLayeringTarget(mImeRequester, dcTarget));
+            sb.append(" isImeFallbackTarget: ");
+            sb.append(isImeFallbackTarget(mImeRequester));
+            sb.append(" isImeInputTarget: ");
+            sb.append(isImeInputTarget(mImeRequester));
+            sb.append(" sameAsImeControlTarget: ");
+            sb.append(sameAsImeControlTarget());
+        }
+        Slog.d(TAG, sb.toString());
+    }
+
     // ---------------------------------------------------------------------------------------
     // Methods for checking IME insets target changing state.
     //
@@ -399,6 +445,10 @@
             pw.print("showImePostLayout pending for mImeRequester=");
             pw.print(mImeRequester);
             pw.println();
+        } else {
+            pw.print(prefix);
+            pw.print("showImePostLayout not scheduled, mImeRequester=null");
+            pw.println();
         }
         pw.println();
     }
diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java
index b74eb56..cc3de7a 100644
--- a/services/core/java/com/android/server/wm/InsetsControlTarget.java
+++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java
@@ -60,8 +60,8 @@
      * Instructs the control target to show inset sources.
      *
      * @param types to specify which types of insets source window should be shown.
-     * @param fromIme {@code true} if IME show request originated from {@link InputMethodService}.
-     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
+     * @param fromIme {@code true} if the IME request originated from {@link InputMethodService}.
+     * @param statsToken the token tracking the current IME request or {@code null} otherwise.
      */
     default void showInsets(@InsetsType int types, boolean fromIme,
             @Nullable ImeTracker.Token statsToken) {
@@ -71,8 +71,8 @@
      * Instructs the control target to hide inset sources.
      *
      * @param types to specify which types of insets source window should be hidden.
-     * @param fromIme {@code true} if IME hide request originated from {@link InputMethodService}.
-     * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
+     * @param fromIme {@code true} if the IME request originated from {@link InputMethodService}.
+     * @param statsToken the token tracking the current IME request or {@code null} otherwise.
      */
     default void hideInsets(@InsetsType int types, boolean fromIme,
             @Nullable ImeTracker.Token statsToken) {
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index cd96806..83f44d2 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -311,7 +311,7 @@
             return mInsetsHint;
         }
         final WindowState win = mWindowContainer.asWindowState();
-        if (win != null && win.mGivenInsetsPending && win.mAttrs.providedInsets == null) {
+        if (win != null && win.mGivenInsetsPending) {
             return mInsetsHint;
         }
         if (mInsetsHintStale) {
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 6d11804..b8bb258 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -432,15 +432,22 @@
         mService.deferWindowLayout();
         try {
             if (isKeyguardLocked(displayId)) {
-                if (occluded) {
-                    mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare(
-                            TRANSIT_KEYGUARD_OCCLUDE,
-                            TRANSIT_FLAG_KEYGUARD_OCCLUDING,
-                            topActivity != null ? topActivity.getRootTask() : null);
+                final int type = occluded ? TRANSIT_KEYGUARD_OCCLUDE : TRANSIT_KEYGUARD_UNOCCLUDE;
+                final int flag = occluded ? TRANSIT_FLAG_KEYGUARD_OCCLUDING
+                        : TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
+                if (tc.isShellTransitionsEnabled()) {
+                    final Task trigger = (occluded && topActivity != null)
+                            ? topActivity.getRootTask() : null;
+                    Transition transition = tc.requestTransitionIfNeeded(type, flag, trigger,
+                            mRootWindowContainer.getDefaultDisplay());
+                    if (trigger != null) {
+                        if (transition == null) {
+                            transition = tc.getCollectingTransition();
+                        }
+                        transition.collect(trigger);
+                    }
                 } else {
-                    mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare(
-                            TRANSIT_KEYGUARD_UNOCCLUDE,
-                            TRANSIT_FLAG_KEYGUARD_UNOCCLUDING);
+                    mRootWindowContainer.getDefaultDisplay().prepareAppTransition(type, flag);
                 }
             } else {
                 if (tc.inTransition()) {
diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java
index 91bb8d0..97b5925 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsController.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsController.java
@@ -64,10 +64,7 @@
     void registerDefaultModifiers(ActivityTaskSupervisor supervisor) {
         // {@link TaskLaunchParamsModifier} handles window layout preferences.
         registerModifier(new TaskLaunchParamsModifier(supervisor));
-        if (DesktopModeLaunchParamsModifier.isDesktopModeSupported()) {
-            // {@link DesktopModeLaunchParamsModifier} handles default task size changes
-            registerModifier(new DesktopModeLaunchParamsModifier());
-        }
+        registerModifier(new DesktopModeLaunchParamsModifier());
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index e06f215..79eb0dc 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -24,3 +24,5 @@
 per-file Background*Start* = file:/BAL_OWNERS
 per-file Background*Start* = ogunwale@google.com, louischang@google.com
 
+# File related to activity callers
+per-file ActivityCallerState.java = file:/core/java/android/app/COMPONENT_CALLER_OWNERS
diff --git a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
index 2b841fd..4c797f8 100644
--- a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
+++ b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
@@ -115,8 +115,8 @@
 
         if (mTransitionController.isCollecting()) {
             // Add display container to the currently collecting transition
-            mTransitionController.collect(mDisplayContent);
             mTransition = mTransitionController.getCollectingTransition();
+            mTransition.collect(mDisplayContent);
 
             // Make sure that transition is not ready until we finish the remote display change
             mTransition.setReady(mDisplayContent, false);
@@ -134,10 +134,9 @@
             displayChange.setEndAbsBounds(endAbsBounds);
             displayChange.setPhysicalDisplayChanged(true);
 
-            mTransition = mTransitionController.requestTransitionIfNeeded(TRANSIT_CHANGE,
-                    0 /* flags */,
-                    mDisplayContent, mDisplayContent, null /* remoteTransition */,
-                    displayChange);
+            mTransition = mTransitionController.requestStartDisplayTransition(TRANSIT_CHANGE,
+                    0 /* flags */, mDisplayContent, null /* remoteTransition */, displayChange);
+            mTransition.collect(mDisplayContent);
         }
 
         if (mTransition != null) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 07a03eb..445a5c8 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1742,30 +1742,21 @@
      *
      * @param starting                  The currently starting activity or {@code null} if there is
      *                                  none.
-     * @param displayId                 The id of the display where operation is executed.
+     * @param displayContent            The display where the operation is executed.
      * @param deferResume               Whether to defer resume while updating config.
-     * @return 'true' if starting activity was kept or wasn't provided, 'false' if it was relaunched
-     * because of configuration update.
      */
-    boolean ensureVisibilityAndConfig(ActivityRecord starting, int displayId, boolean deferResume) {
+    void ensureVisibilityAndConfig(@Nullable ActivityRecord starting,
+            @NonNull DisplayContent displayContent, boolean deferResume) {
         // First ensure visibility without updating the config just yet. We need this to know what
         // activities are affecting configuration now.
         // Passing null here for 'starting' param value, so that visibility of actual starting
         // activity will be properly updated.
         ensureActivitiesVisible(null /* starting */, false /* notifyClients */);
 
-        if (displayId == INVALID_DISPLAY) {
-            // The caller didn't provide a valid display id, skip updating config.
-            return true;
-        }
-
         // Force-update the orientation from the WindowManager, since we need the true configuration
         // to send to the client now.
-        final DisplayContent displayContent = getDisplayContent(displayId);
-        Configuration config = null;
-        if (displayContent != null) {
-            config = displayContent.updateOrientation(starting, true /* forceUpdate */);
-        }
+        final Configuration config =
+                displayContent.updateOrientation(starting, true /* forceUpdate */);
         // Visibilities may change so let the starting activity have a chance to report. Can't do it
         // when visibility is changed in each AppWindowToken because it may trigger wrong
         // configuration push because the visibility of some activities may not be updated yet.
@@ -1773,13 +1764,8 @@
             starting.reportDescendantOrientationChangeIfNeeded();
         }
 
-        if (displayContent != null) {
-            // Update the configuration of the activities on the display.
-            return displayContent.updateDisplayOverrideConfigurationLocked(config, starting,
-                    deferResume);
-        } else {
-            return true;
-        }
+        // Update the configuration of the activities on the display.
+        displayContent.updateDisplayOverrideConfigurationLocked(config, starting, deferResume);
     }
 
     /**
@@ -2341,7 +2327,7 @@
     }
 
     /**
-     * Finish the topmost activities in all root tasks that belong to the crashed app.
+     * Finish the topmost activities in all leaf tasks that belong to the crashed app.
      *
      * @param app    The app that crashed.
      * @param reason Reason to perform this action.
@@ -2352,14 +2338,14 @@
     Task finishTopCrashedActivities(WindowProcessController app, String reason) {
         Task focusedRootTask = getTopDisplayFocusedRootTask();
         final Task[] finishedTask = new Task[1];
-        forAllRootTasks(rootTask -> {
+        forAllLeafTasks(leafTask -> {
             final boolean recordTopOrVisible = finishedTask[0] == null
-                    && (focusedRootTask == rootTask || rootTask.isVisibleRequested());
-            final Task t = rootTask.finishTopCrashedActivityLocked(app, reason);
+                    && (focusedRootTask == leafTask.getRootTask() || leafTask.isVisibleRequested());
+            final Task t = leafTask.finishTopCrashedActivityLocked(app, reason);
             if (recordTopOrVisible) {
                 finishedTask[0] = t;
             }
-        });
+        }, true);
         return finishedTask[0];
     }
 
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 908cbd3..30134d8 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -336,19 +336,19 @@
     }
 
     @Override
-    public boolean performHapticFeedback(int effectId, boolean always) {
+    public boolean performHapticFeedback(int effectId, boolean always, boolean fromIme) {
         final long ident = Binder.clearCallingIdentity();
         try {
             return mService.mPolicy.performHapticFeedback(mUid, mPackageName,
-                        effectId, always, null);
+                        effectId, always, null, fromIme);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override
-    public void performHapticFeedbackAsync(int effectId, boolean always) {
-        performHapticFeedback(effectId, always);
+    public void performHapticFeedbackAsync(int effectId, boolean always, boolean fromIme) {
+        performHapticFeedback(effectId, always, fromIme);
     }
 
     /* Drag/drop */
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 1353ff0..d87e21c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -978,7 +978,6 @@
         }
         effectiveUid = info.applicationInfo.uid;
         mIsEffectivelySystemApp = info.applicationInfo.isSystemApp();
-        stringName = null;
 
         if (info.targetActivity == null) {
             if (_intent != null) {
@@ -1045,6 +1044,7 @@
             updateTaskDescription();
         }
         mSupportsPictureInPicture = info.supportsPictureInPicture();
+        stringName = null;
 
         // Re-adding the task to Recents once updated
         if (inRecents) {
@@ -1933,8 +1933,8 @@
                 td.setEnsureStatusBarContrastWhenTransparent(
                         atd.getEnsureStatusBarContrastWhenTransparent());
             }
-            if (td.getStatusBarAppearance() == 0) {
-                td.setStatusBarAppearance(atd.getStatusBarAppearance());
+            if (td.getSystemBarsAppearance() == 0) {
+                td.setSystemBarsAppearance(atd.getSystemBarsAppearance());
             }
             if (td.getNavigationBarColor() == 0) {
                 td.setNavigationBarColor(atd.getNavigationBarColor());
@@ -3751,9 +3751,11 @@
                 // Boost the adjacent TaskFragment for dimmer if needed.
                 final TaskFragment taskFragment = wc.asTaskFragment();
                 if (taskFragment != null && taskFragment.isEmbedded()) {
+                    taskFragment.mDimmerSurfaceBoosted = false;
                     final TaskFragment adjacentTf = taskFragment.getAdjacentTaskFragment();
                     if (adjacentTf != null && adjacentTf.shouldBoostDimmer()) {
                         adjacentTf.assignLayer(t, layer++);
+                        adjacentTf.mDimmerSurfaceBoosted = true;
                     }
                 }
 
@@ -4946,13 +4948,6 @@
         }
     }
 
-    void minimalResumeActivityLocked(ActivityRecord r) {
-        ProtoLog.v(WM_DEBUG_STATES, "Moving to RESUMED: %s (starting new instance) "
-                + "callers=%s", r, Debug.getCallers(5));
-        r.setState(RESUMED, "minimalResumeActivityLocked");
-        r.completeResumeLocked();
-    }
-
     void checkReadyForSleep() {
         if (shouldSleepActivities() && goToSleepIfPossible(false /* shuttingDown */)) {
             mTaskSupervisor.checkReadyForSleepLocked(true /* allowDelay */);
@@ -5861,7 +5856,7 @@
             }
 
             mRootWindowContainer.ensureVisibilityAndConfig(null /* starting */,
-                    mDisplayContent.mDisplayId, false /* deferResume */);
+                    mDisplayContent, false /* deferResume */);
         } finally {
             if (mTransitionController.isShellTransitionsEnabled()) {
                 mAtmService.continueWindowLayout();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 85d81c4..c919250 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -216,6 +216,9 @@
     Dimmer mDimmer = Dimmer.DIMMER_REFACTOR
             ? new SmoothDimmer(this) : new LegacyDimmer(this);
 
+    /** {@code true} if the dimmer surface is boosted. {@code false} otherwise. */
+    boolean mDimmerSurfaceBoosted;
+
     /** Apply the dim layer on the embedded TaskFragment. */
     static final int EMBEDDED_DIM_AREA_TASK_FRAGMENT = 0;
 
@@ -1533,10 +1536,6 @@
 
             next.setState(RESUMED, "resumeTopActivity");
 
-            // Have the window manager re-evaluate the orientation of
-            // the screen based on the new activity order.
-            boolean notUpdated = true;
-
             // Activity should also be visible if set mLaunchTaskBehind to true (see
             // ActivityRecord#shouldBeVisibleIgnoringKeyguard()).
             if (shouldBeVisible(next)) {
@@ -1548,28 +1547,15 @@
                 // result of invisible window resize.
                 // TODO: Remove this once visibilities are set correctly immediately when
                 // starting an activity.
-                notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(),
+                final int originalRelaunchingCount = next.mPendingRelaunchCount;
+                mRootWindowContainer.ensureVisibilityAndConfig(next, mDisplayContent,
                         false /* deferResume */);
-            }
-
-            if (notUpdated) {
-                // The configuration update wasn't able to keep the existing
-                // instance of the activity, and instead started a new one.
-                // We should be all done, but let's just make sure our activity
-                // is still at the top and schedule another run if something
-                // weird happened.
-                ActivityRecord nextNext = topRunningActivity();
-                ProtoLog.i(WM_DEBUG_STATES, "Activity config changed during resume: "
-                        + "%s, new next: %s", next, nextNext);
-                if (nextNext != next) {
-                    // Do over!
-                    mTaskSupervisor.scheduleResumeTopActivities();
+                if (next.mPendingRelaunchCount > originalRelaunchingCount) {
+                    // The activity is scheduled to relaunch, then ResumeActivityItem will be also
+                    // included (see ActivityRecord#relaunchActivityLocked) if it should resume.
+                    next.completeResumeLocked();
+                    return true;
                 }
-                if (!next.isVisibleRequested() || next.mAppStopped) {
-                    next.setVisibility(true);
-                }
-                next.completeResumeLocked();
-                return true;
             }
 
             try {
@@ -1652,17 +1638,7 @@
                 return true;
             }
 
-            // From this point on, if something goes wrong there is no way
-            // to recover the activity.
-            try {
-                next.completeResumeLocked();
-            } catch (Exception e) {
-                // If any exception gets thrown, toss away this
-                // activity and try the next one.
-                Slog.w(TAG, "Exception thrown during resume of " + next, e);
-                next.finishIfPossible("resume-exception", true /* oomAdj */);
-                return true;
-            }
+            next.completeResumeLocked();
         } else {
             // Whoops, need to restart this activity!
             if (!next.hasBeenLaunched) {
@@ -1881,7 +1857,7 @@
 
             mAtmService.getLifecycleManager().scheduleTransactionItem(prev.app.getThread(),
                     PauseActivityItem.obtain(prev.token, prev.finishing, userLeaving,
-                            prev.configChangeFlags, pauseImmediately, autoEnteringPip));
+                            pauseImmediately, autoEnteringPip));
         } catch (Exception e) {
             // Ignore exception, if process died other code will cleanup.
             Slog.w(TAG, "Exception thrown during pause", e);
@@ -2331,7 +2307,8 @@
                 // area, i.e. the screen area without the system bars.
                 // The non decor inset are areas that could never be removed in Honeycomb. See
                 // {@link WindowManagerPolicy#getNonDecorInsetsLw}.
-                calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di);
+                calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di,
+                        false /* useLegacyInsetsForStableBounds */);
             } else {
                 // Apply the given non-decor and stable insets to calculate the corresponding bounds
                 // for screen size of configuration.
@@ -2429,9 +2406,11 @@
      * @param outNonDecorBounds where to place bounds with non-decor insets applied.
      * @param outStableBounds where to place bounds with stable insets applied.
      * @param bounds the bounds to inset.
+     * @param useLegacyInsetsForStableBounds {@code true} if we need to use the legacy insets frame
+     *                for apps targeting U or before when calculating stable bounds.
      */
     void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds,
-            DisplayInfo displayInfo) {
+            DisplayInfo displayInfo, boolean useLegacyInsetsForStableBounds) {
         outNonDecorBounds.set(bounds);
         outStableBounds.set(bounds);
         if (mDisplayContent == null) {
@@ -2443,7 +2422,11 @@
         final DisplayPolicy.DecorInsets.Info info = policy.getDecorInsetsInfo(
                 displayInfo.rotation, displayInfo.logicalWidth, displayInfo.logicalHeight);
         intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mNonDecorInsets);
-        intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
+        if (!useLegacyInsetsForStableBounds) {
+            intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
+        } else {
+            intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mLegacyConfigInsets);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 7fc61e1..a84a99a 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -573,7 +573,7 @@
 
     // Capture the animation surface control for activity's main window
     static class StartingWindowAnimationAdaptor implements AnimationAdapter {
-        SurfaceControl mAnimationLeash;
+
         @Override
         public boolean getShowWallpaper() {
             return false;
@@ -582,14 +582,10 @@
         @Override
         public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
                 int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
-            mAnimationLeash = animationLeash;
         }
 
         @Override
         public void onAnimationCancelled(SurfaceControl animationLeash) {
-            if (mAnimationLeash == animationLeash) {
-                mAnimationLeash = null;
-            }
         }
 
         @Override
@@ -604,9 +600,6 @@
 
         @Override
         public void dump(PrintWriter pw, String prefix) {
-            pw.print(prefix + "StartingWindowAnimationAdaptor mCapturedLeash=");
-            pw.print(mAnimationLeash);
-            pw.println();
         }
 
         @Override
@@ -616,16 +609,16 @@
 
     static SurfaceControl applyStartingWindowAnimation(WindowState window) {
         final SurfaceControl.Transaction t = window.getPendingTransaction();
-        final Rect mainFrame = window.getRelativeFrame();
         final StartingWindowAnimationAdaptor adaptor = new StartingWindowAnimationAdaptor();
         window.startAnimation(t, adaptor, false, ANIMATION_TYPE_STARTING_REVEAL);
-        if (adaptor.mAnimationLeash == null) {
+        final SurfaceControl leash = window.getAnimationLeash();
+        if (leash == null) {
             Slog.e(TAG, "Cannot start starting window animation, the window " + window
                     + " was removed");
             return null;
         }
-        t.setPosition(adaptor.mAnimationLeash, mainFrame.left, mainFrame.top);
-        return adaptor.mAnimationLeash;
+        t.setPosition(leash, window.mSurfacePosition.x, window.mSurfacePosition.y);
+        return leash;
     }
 
     boolean addStartingWindow(Task task, ActivityRecord activity, int launchTheme,
@@ -696,7 +689,9 @@
                 removalInfo.roundedCornerRadius =
                         topActivity.mLetterboxUiController.getRoundedCornersRadius(mainWindow);
                 removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
-                removalInfo.mainFrame = mainWindow.getRelativeFrame();
+                removalInfo.mainFrame = new Rect(mainWindow.getFrame());
+                removalInfo.mainFrame.offsetTo(mainWindow.mSurfacePosition.x,
+                        mainWindow.mSurfacePosition.y);
             }
         }
         try {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 7edc3a2..6d8b030 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -503,6 +503,8 @@
             }
             mConfigAtEndActivities.add(ar);
             ar.pauseConfigurationDispatch();
+            snapshotStartState(ar);
+            mChanges.get(ar).mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END;
         });
         snapshotStartState(wc);
         mChanges.get(wc).mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END;
@@ -623,7 +625,8 @@
             throw new IllegalStateException("Attempting to re-use a transition");
         }
         mState = STATE_COLLECTING;
-        mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG,
+        mSyncId = mSyncEngine.startSyncSet(this, timeoutMs,
+                TAG + "-" + transitTypeToString(mType),
                 mParallelCollectType != PARALLEL_TYPE_NONE);
         mSyncEngine.setSyncMethod(mSyncId, TransitionController.SYNC_METHOD);
 
@@ -856,6 +859,19 @@
     }
 
     /**
+     * Collects a window container which will be removed or invisible.
+     */
+    void collectClose(@NonNull WindowContainer<?> wc) {
+        if (wc.isVisibleRequested()) {
+            collectExistenceChange(wc);
+        } else {
+            // Removing a non-visible window doesn't require a transition, but if there is one
+            // collecting, this should be a member just in case.
+            collect(wc);
+        }
+    }
+
+    /**
      * @return {@code true} if `wc` is a participant or is a descendant of one.
      */
     boolean isInTransition(WindowContainer wc) {
@@ -2381,9 +2397,7 @@
             } else {
                 parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_YES_ANIMATION;
             }
-            final ActivityRecord ar = targetChange.mContainer.asActivityRecord();
-            if ((ar != null && ar.isConfigurationDispatchPaused())
-                    || ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) != 0)) {
+            if ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) != 0) {
                 parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END;
             }
         }
@@ -2470,6 +2484,14 @@
                     } else {
                         intermediates.add(parentChange);
                     }
+                    // for config-at-end, we want to promote the flag based on the end-state even
+                    // if the activity was reparented because it operates after the animation. So,
+                    // check that here since the promote code skips reparents.
+                    if ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) != 0
+                            && targetChange.mContainer.asActivityRecord() != null
+                            && targetChange.mContainer.getParent() == p) {
+                        parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END;
+                    }
                     foundParentInTargets = true;
                     break;
                 } else if (reportIfNotTop(p) && !skipIntermediateReports) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 503f925..ac03a1b 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -21,7 +21,6 @@
 import static android.view.WindowManager.TRANSIT_CLOSE;
 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 com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
 
@@ -616,30 +615,6 @@
         return changeInfo != null && changeInfo.mRotation != targetRotation;
     }
 
-    /**
-     * @see #requestTransitionIfNeeded(int, int, WindowContainer, WindowContainer, RemoteTransition)
-     */
-    @Nullable
-    Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type,
-            @NonNull WindowContainer trigger) {
-        return requestTransitionIfNeeded(type, 0 /* flags */, trigger, trigger /* readyGroupRef */);
-    }
-
-    /**
-     * @see #requestTransitionIfNeeded(int, int, WindowContainer, WindowContainer, RemoteTransition)
-     */
-    @Nullable
-    Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type,
-            @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger,
-            @NonNull WindowContainer readyGroupRef) {
-        return requestTransitionIfNeeded(type, flags, trigger, readyGroupRef,
-                null /* remoteTransition */, null /* displayChange */);
-    }
-
-    private static boolean isExistenceType(@WindowManager.TransitionType int type) {
-        return type == TRANSIT_OPEN || type == TRANSIT_CLOSE;
-    }
-
     /** Sets the sync method for the display change. */
     private void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange,
             @NonNull DisplayContent displayContent) {
@@ -672,21 +647,17 @@
      * start it. Collection can start immediately.
      * @param trigger if non-null, this is the first container that will be collected
      * @param readyGroupRef Used to identify which ready-group this request is for.
-     * @return the created transition if created or null otherwise.
+     * @return the created transition if created or null otherwise (already global collecting)
      */
     @Nullable
     Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type,
             @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger,
-            @NonNull WindowContainer readyGroupRef, @Nullable RemoteTransition remoteTransition,
-            @Nullable TransitionRequestInfo.DisplayChange displayChange) {
+            @NonNull WindowContainer readyGroupRef) {
         if (mTransitionPlayer == null) {
             return null;
         }
         Transition newTransition = null;
         if (isCollecting()) {
-            if (displayChange != null) {
-                Slog.e(TAG, "Provided displayChange for a non-new request", new Throwable());
-            }
             // Make the collecting transition wait until this request is ready.
             mCollectingTransition.setReady(readyGroupRef, false);
             if ((flags & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) {
@@ -695,18 +666,26 @@
             }
         } else {
             newTransition = requestStartTransition(createTransition(type, flags),
-                    trigger != null ? trigger.asTask() : null, remoteTransition, displayChange);
-            if (newTransition != null && displayChange != null && trigger != null
-                    && trigger.asDisplayContent() != null) {
-                setDisplaySyncMethod(displayChange, trigger.asDisplayContent());
-            }
+                    trigger != null ? trigger.asTask() : null, null /* remote */, null /* disp */);
         }
-        if (trigger != null) {
-            if (isExistenceType(type)) {
-                collectExistenceChange(trigger);
-            } else {
-                collect(trigger);
-            }
+        return newTransition;
+    }
+
+    /**
+     * Creates a transition and asks the TransitionPlayer (Shell) to
+     * start it. Collection can start immediately.
+     * @param trigger if non-null, this is the first container that will be collected
+     * @return the created transition if created or null otherwise.
+     */
+    @NonNull
+    Transition requestStartDisplayTransition(@WindowManager.TransitionType int type,
+            @WindowManager.TransitionFlags int flags, @NonNull DisplayContent trigger,
+            @Nullable RemoteTransition remoteTransition,
+            @Nullable TransitionRequestInfo.DisplayChange displayChange) {
+        final Transition newTransition = createTransition(type, flags);
+        requestStartTransition(newTransition, null /* trigger */, remoteTransition, displayChange);
+        if (displayChange != null) {
+            setDisplaySyncMethod(displayChange, trigger);
         }
         return newTransition;
     }
@@ -770,20 +749,10 @@
      * @return the new transition if it was created for this request, `null` otherwise.
      */
     Transition requestCloseTransitionIfNeeded(@NonNull WindowContainer<?> wc) {
-        if (mTransitionPlayer == null) return null;
-        Transition out = null;
-        if (wc.isVisibleRequested()) {
-            if (!isCollecting()) {
-                out = requestStartTransition(createTransition(TRANSIT_CLOSE, 0 /* flags */),
-                        wc.asTask(), null /* remoteTransition */, null /* displayChange */);
-            }
-            collectExistenceChange(wc);
-        } else {
-            // Removing a non-visible window doesn't require a transition, but if there is one
-            // collecting, this should be a member just in case.
-            collect(wc);
-        }
-        return out;
+        if (mTransitionPlayer == null || isCollecting()) return null;
+        if (!wc.isVisibleRequested()) return null;
+        return requestStartTransition(createTransition(TRANSIT_CLOSE, 0 /* flags */), wc.asTask(),
+                null /* remoteTransition */, null /* displayChange */);
     }
 
     /** @see Transition#collect */
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 594043d..3fb5998 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -157,13 +157,21 @@
             mFindResults.setUseTopWallpaperAsTarget(true);
         }
 
-        if (mService.mPolicy.isKeyguardLocked() && w.canShowWhenLocked()) {
-            if (mService.mPolicy.isKeyguardOccluded() || (useShellTransition
-                    ? w.inTransition() : mService.mPolicy.isKeyguardUnoccluding())) {
-                // The lowest show when locked window decides whether we need to put the wallpaper
-                // behind.
-                mFindResults.mNeedsShowWhenLockedWallpaper = !isFullscreen(w.mAttrs)
-                        || (w.mActivityRecord != null && !w.mActivityRecord.fillsParent());
+        if (mService.mPolicy.isKeyguardLocked()) {
+            if (w.canShowWhenLocked()) {
+                if (mService.mPolicy.isKeyguardOccluded() || (useShellTransition
+                        ? w.inTransition() : mService.mPolicy.isKeyguardUnoccluding())) {
+                    // The lowest show-when-locked window decides whether to show wallpaper.
+                    mFindResults.mNeedsShowWhenLockedWallpaper = !isFullscreen(w.mAttrs)
+                            || (w.mActivityRecord != null && !w.mActivityRecord.fillsParent());
+                }
+            } else if (w.hasWallpaper() && mService.mPolicy.isKeyguardHostWindow(w.mAttrs)
+                    && w.mTransitionController.isTransitionOnDisplay(mDisplayContent)) {
+                // If we have no candidates at all, notification shade is allowed to be the target
+                // of last resort even if it has not been made visible yet.
+                if (DEBUG_WALLPAPER) Slog.v(TAG, "Found keyguard as wallpaper target: " + w);
+                mFindResults.setWallpaperTarget(w);
+                return false;
             }
         }
 
@@ -200,14 +208,7 @@
 
     private boolean isRecentsTransitionTarget(WindowState w) {
         if (w.mTransitionController.isShellTransitionsEnabled()) {
-            // Because the recents activity is invisible in background while keyguard is occluded
-            // (the activity window is on screen while keyguard is locked) with recents animation,
-            // the task animating by recents needs to be wallpaper target to make wallpaper visible.
-            // While for unlocked case, because recents activity will be moved to top, it can be
-            // the wallpaper target naturally.
-            return w.mActivityRecord != null && w.mAttrs.type == TYPE_BASE_APPLICATION
-                    && mDisplayContent.isKeyguardLocked()
-                    && w.mTransitionController.isTransientHide(w.getTask());
+            return false;
         }
         // The window is either the recents activity or is in the task animating by the recents.
         final RecentsAnimationController controller = mService.getRecentsAnimationController();
@@ -349,7 +350,7 @@
         final Rect lastWallpaperBounds = wallpaperWin.getParentFrame();
         int screenWidth = lastWallpaperBounds.width();
         int screenHeight = lastWallpaperBounds.height();
-        float screenRatio = ((float) screenWidth) / screenHeight;
+        float screenRatio = (float) screenWidth / screenHeight;
         Point screenSize = new Point(screenWidth, screenHeight);
 
         WallpaperWindowToken token = wallpaperWin.mToken.asWallpaperToken();
@@ -399,20 +400,32 @@
             Point bitmapSize = new Point(
                     wallpaperWin.mRequestedWidth, wallpaperWin.mRequestedHeight);
             SparseArray<Rect> cropHints = token.getCropHints();
-            wallpaperFrame = mWallpaperCropUtils.getCrop(
-                    screenSize, bitmapSize, cropHints, wallpaperWin.isRtl());
+            wallpaperFrame = bitmapSize.x <= 0 || bitmapSize.y <= 0 ? wallpaperWin.getFrame()
+                    : mWallpaperCropUtils.getCrop(screenSize, bitmapSize, cropHints,
+                            wallpaperWin.isRtl());
+            int frameWidth = wallpaperFrame.width();
+            int frameHeight = wallpaperFrame.height();
+            float frameRatio = (float) frameWidth / frameHeight;
 
-            cropZoom = wallpaperFrame.isEmpty() ? 1f
-                    : ((float) screenHeight) / wallpaperFrame.height() / wallpaperWin.mVScale;
+            // If the crop is proportionally wider/taller than the screen, scale it so that its
+            // height/width matches the screen height/width, and use the additional width/height
+            // for parallax (respectively).
+            boolean scaleHeight = frameRatio >= screenRatio;
+            cropZoom = wallpaperFrame.isEmpty() ? 1f : scaleHeight
+                    ? (float) screenHeight / frameHeight / wallpaperWin.mVScale
+                    : (float) screenWidth / frameWidth / wallpaperWin.mHScale;
 
-            // A positive x / y offset shifts the wallpaper to the right / bottom respectively.
-            cropOffsetX = -wallpaperFrame.left
-                    + (int) ((cropZoom - 1f) * wallpaperFrame.height() * screenRatio / 2f);
-            cropOffsetY = -wallpaperFrame.top
-                    + (int) ((cropZoom - 1f) * wallpaperFrame.height() / 2f);
+            // The dimensions of the frame, without the additional width or height for parallax.
+            float w = scaleHeight ? frameHeight * screenRatio : frameWidth;
+            float h = scaleHeight ? frameHeight : frameWidth / screenRatio;
 
-            diffWidth = (int) (wallpaperFrame.width() * wallpaperWin.mHScale) - screenWidth;
-            diffHeight = (int) (wallpaperFrame.height() * wallpaperWin.mVScale) - screenHeight;
+            // Note: a positive x/y offset shifts the wallpaper to the right/bottom respectively.
+            cropOffsetX = -wallpaperFrame.left + (int) ((cropZoom - 1f) * w / 2f);
+            cropOffsetY = -wallpaperFrame.top + (int) ((cropZoom - 1f) * h / 2f);
+
+            // Available width or height for parallax
+            diffWidth = (int) ((frameWidth - w) * wallpaperWin.mHScale);
+            diffHeight = (int) ((frameHeight - h) * wallpaperWin.mVScale);
         } else {
             wallpaperFrame = wallpaperWin.getFrame();
             cropZoom = 1f;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index fd0289e..f2af852 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -716,7 +716,7 @@
 
         // If parent is null, the layer should be placed offscreen so reparent to null. Otherwise,
         // set to the available parent.
-        t.reparent(mSurfaceControl, mParent == null ? null : mParent.getSurfaceControl());
+        t.reparent(mSurfaceControl, mParent == null ? null : mParent.getParentingSurfaceControl());
 
         if (mLastRelativeToLayer != null) {
             t.setRelativeLayer(mSurfaceControl, mLastRelativeToLayer, mLastLayer);
@@ -2907,6 +2907,17 @@
     }
 
     /**
+     * Returns the {@link SurfaceControl} where all the children should be parented on.
+     *
+     * A {@link WindowContainer} might insert intermediate leashes in the hierarchy and hence
+     * {@link #getSurfaceControl} won't return the correct surface where the children should be
+     * re-parented on.
+     */
+    SurfaceControl getParentingSurfaceControl() {
+        return getSurfaceControl();
+    }
+
+    /**
      * Use this method instead of {@link #getPendingTransaction()} if the Transaction should be
      * synchronized with the client.
      *
diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java
index 7b0d931..294733e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerFlags.java
+++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java
@@ -48,5 +48,7 @@
     final boolean mAllowsScreenSizeDecoupledFromStatusBarAndCutout =
             Flags.allowsScreenSizeDecoupledFromStatusBarAndCutout();
 
+    final boolean mInsetsDecoupledConfiguration = Flags.insetsDecoupledConfiguration();
+
     /* End Available Flags */
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 4698b6b..77319cc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -829,20 +829,20 @@
      * Show IME on imeTargetWindow once IME has finished layout.
      *
      * @param imeTargetWindowToken token of the (IME target) window which IME should be shown.
-     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
+     * @param statsToken the token tracking the current IME request.
      */
     public abstract void showImePostLayout(IBinder imeTargetWindowToken,
-            @Nullable ImeTracker.Token statsToken);
+            @NonNull ImeTracker.Token statsToken);
 
     /**
      * Hide IME using imeTargetWindow when requested.
      *
-     * @param imeTargetWindowToken token of the (IME target) window on which requests hiding IME.
+     * @param imeTargetWindowToken token of the (IME target) window which requests hiding IME.
      * @param displayId the id of the display the IME is on.
-     * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
+     * @param statsToken the token tracking the current IME request.
      */
     public abstract void hideIme(IBinder imeTargetWindowToken, int displayId,
-            @Nullable ImeTracker.Token statsToken);
+            @NonNull ImeTracker.Token statsToken);
 
     /**
      * Tell window manager about a package that should be running with a restricted range of
@@ -1079,4 +1079,10 @@
      * Moves the current focus to the top activity window if the top activity is embedded.
      */
     public abstract boolean moveFocusToTopEmbeddedWindowIfNeeded();
+
+    /**
+     * Returns an instance of {@link ScreenCapture.ScreenshotHardwareBuffer} containing the current
+     * screenshot.
+     */
+    public abstract ScreenCapture.ScreenshotHardwareBuffer takeAssistScreenshot();
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 08d43ae..71ffabf 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -561,6 +561,8 @@
     /** Device default insets types shall be excluded from config app sizes. */
     final int mConfigTypes;
 
+    final int mLegacyConfigTypes;
+
     final boolean mLimitedAlphaCompositing;
     final int mMaxUiWidth;
 
@@ -1190,13 +1192,23 @@
         final boolean isScreenSizeDecoupledFromStatusBarAndCutout = context.getResources()
                 .getBoolean(R.bool.config_decoupleStatusBarAndDisplayCutoutFromScreenSize)
                 && mFlags.mAllowsScreenSizeDecoupledFromStatusBarAndCutout;
-        if (!isScreenSizeDecoupledFromStatusBarAndCutout) {
+        if (mFlags.mInsetsDecoupledConfiguration) {
+            mDecorTypes = 0;
+            mConfigTypes = 0;
+        } else if (isScreenSizeDecoupledFromStatusBarAndCutout) {
+            mDecorTypes = WindowInsets.Type.navigationBars();
+            mConfigTypes = WindowInsets.Type.navigationBars();
+        } else {
             mDecorTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.navigationBars();
             mConfigTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.statusBars()
                     | WindowInsets.Type.navigationBars();
+        }
+        if (isScreenSizeDecoupledFromStatusBarAndCutout) {
+            // Do not fallback to legacy value for enabled devices.
+            mLegacyConfigTypes = WindowInsets.Type.navigationBars();
         } else {
-            mDecorTypes = WindowInsets.Type.navigationBars();
-            mConfigTypes = WindowInsets.Type.navigationBars();
+            mLegacyConfigTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.statusBars()
+                    | WindowInsets.Type.navigationBars();
         }
 
         mLetterboxConfiguration = new LetterboxConfiguration(
@@ -2109,7 +2121,15 @@
                         + ", touchableRegion=" + w.mGivenTouchableRegion + " -> " + touchableRegion
                         + ", touchableInsets " + w.mTouchableInsets + " -> " + touchableInsets);
                 if (w != null) {
+                    final boolean wasGivenInsetsPending = w.mGivenInsetsPending;
                     w.mGivenInsetsPending = false;
+                    if ((!wasGivenInsetsPending || !w.hasInsetsSourceProvider())
+                            && w.mTouchableInsets == touchableInsets
+                            && w.mGivenContentInsets.equals(contentInsets)
+                            && w.mGivenVisibleInsets.equals(visibleInsets)
+                            && w.mGivenTouchableRegion.equals(touchableRegion)) {
+                        return;
+                    }
                     w.mGivenContentInsets.set(contentInsets);
                     w.mGivenVisibleInsets.set(visibleInsets);
                     w.mGivenTouchableRegion.set(touchableRegion);
@@ -3638,7 +3658,11 @@
 
     public void setCurrentUser(@UserIdInt int newUserId) {
         synchronized (mGlobalLock) {
-            mAtmService.getTransitionController().requestTransitionIfNeeded(TRANSIT_OPEN, null);
+            final TransitionController controller = mAtmService.getTransitionController();
+            if (!controller.isCollecting() && controller.isShellTransitionsEnabled()) {
+                controller.requestStartTransition(controller.createTransition(TRANSIT_OPEN),
+                        null /* trigger */, null /* remote */, null /* disp */);
+            }
             mCurrentUserId = newUserId;
             mPolicy.setCurrentUserLw(newUserId);
             mKeyguardDisableHandler.setCurrentUser(newUserId);
@@ -4081,13 +4105,8 @@
         }
     }
 
-    /**
-     * Takes a snapshot of the screen.  In landscape mode this grabs the whole screen.
-     * In portrait mode, it grabs the upper region of the screen based on the vertical dimension
-     * of the target image.
-     */
-    @Override
-    public boolean requestAssistScreenshot(final IAssistDataReceiver receiver) {
+    @Nullable
+    private ScreenCapture.ScreenshotHardwareBuffer takeAssistScreenshot() {
         if (!checkCallingPermission(READ_FRAME_BUFFER, "requestAssistScreenshot()")) {
             throw new SecurityException("Requires READ_FRAME_BUFFER permission");
         }
@@ -4106,24 +4125,34 @@
             }
         }
 
-        final Bitmap bm;
+        final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer;
         if (captureArgs != null) {
             ScreenCapture.SynchronousScreenCaptureListener syncScreenCapture =
                     ScreenCapture.createSyncCaptureListener();
 
             ScreenCapture.captureLayers(captureArgs, syncScreenCapture);
 
-            final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
-                    syncScreenCapture.getBuffer();
-            bm = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
+            screenshotBuffer = syncScreenCapture.getBuffer();
         } else {
-            bm = null;
+            screenshotBuffer = null;
         }
 
-        if (bm == null) {
+        if (screenshotBuffer == null) {
             Slog.w(TAG_WM, "Failed to take screenshot");
         }
 
+        return screenshotBuffer;
+    }
+
+    /**
+     * Takes a snapshot of the screen.  In landscape mode this grabs the whole screen.
+     * In portrait mode, it grabs the upper region of the screen based on the vertical dimension
+     * of the target image.
+     */
+    @Override
+    public boolean requestAssistScreenshot(final IAssistDataReceiver receiver) {
+        final ScreenCapture.ScreenshotHardwareBuffer shb = takeAssistScreenshot();
+        final Bitmap bm = shb != null ? shb.asBitmap() : null;
         FgThread.getHandler().post(() -> {
             try {
                 receiver.onHandleAssistScreenshot(bm);
@@ -6701,7 +6730,7 @@
 
     private void dumpLogStatus(PrintWriter pw) {
         pw.println("WINDOW MANAGER LOGGING (dumpsys window logging)");
-        if (android.tracing.Flags.perfettoProtolog()) {
+        if (android.tracing.Flags.perfettoProtologTracing()) {
             pw.println("Deprecated legacy command. Use Perfetto commands instead.");
             return;
         }
@@ -8253,12 +8282,17 @@
 
         @Override
         public void showImePostLayout(IBinder imeTargetWindowToken,
-                @Nullable ImeTracker.Token statsToken) {
+                @NonNull ImeTracker.Token statsToken) {
             synchronized (mGlobalLock) {
                 InputTarget imeTarget = getInputTargetFromWindowTokenLocked(imeTargetWindowToken);
                 if (imeTarget == null) {
+                    ImeTracker.forLogging().onFailed(statsToken,
+                            ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET);
                     return;
                 }
+                ImeTracker.forLogging().onProgress(statsToken,
+                        ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET);
+
                 Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
                 final InsetsControlTarget controlTarget = imeTarget.getImeControlTarget();
                 imeTarget = controlTarget.getWindow();
@@ -8273,7 +8307,7 @@
 
         @Override
         public void hideIme(IBinder imeTargetWindowToken, int displayId,
-                @Nullable ImeTracker.Token statsToken) {
+                @NonNull ImeTracker.Token statsToken) {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.hideIme");
             synchronized (mGlobalLock) {
                 WindowState imeTarget = mWindowMap.get(imeTargetWindowToken);
@@ -8688,6 +8722,12 @@
                 return false;
             }
         }
+
+        @Override
+        public ScreenCapture.ScreenshotHardwareBuffer takeAssistScreenshot() {
+            // WMS.takeAssistScreenshot takes care of the locking.
+            return WindowManagerService.this.takeAssistScreenshot();
+        }
     }
 
     private final class ImeTargetVisibilityPolicyImpl extends ImeTargetVisibilityPolicy {
@@ -9198,6 +9238,11 @@
             return false;
         }
 
+        if (taskFragment.mDimmerSurfaceBoosted) {
+            // Skip if the TaskFragment currently has dimmer surface boosted.
+            return false;
+        }
+
         final ActivityRecord topActivity =
                 taskFragment.getTask().topRunningActivity(true /* focusableOnly */);
         if (topActivity == null || topActivity == focusedWindow.mActivityRecord) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 0b29f96..a6db310f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -49,6 +49,7 @@
 
 import com.android.internal.os.ByteTransferPipe;
 import com.android.internal.protolog.LegacyProtoLogImpl;
+import com.android.internal.protolog.PerfettoProtoLogImpl;
 import com.android.internal.protolog.common.IProtoLog;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.IoThread;
@@ -111,8 +112,13 @@
                 case "logging":
                     IProtoLog instance = ProtoLog.getSingleInstance();
                     int result = 0;
-                    if (instance instanceof LegacyProtoLogImpl) {
-                        result = ((LegacyProtoLogImpl) instance).onShellCommand(this);
+                    if (instance instanceof LegacyProtoLogImpl
+                            || instance instanceof PerfettoProtoLogImpl) {
+                        if (instance instanceof LegacyProtoLogImpl) {
+                            result = ((LegacyProtoLogImpl) instance).onShellCommand(this);
+                        } else {
+                            result = ((PerfettoProtoLogImpl) instance).onShellCommand(this);
+                        }
                         if (result != 0) {
                             pw.println("Not handled, please use "
                                     + "`adb shell dumpsys activity service SystemUIService "
@@ -120,8 +126,7 @@
                         }
                     } else {
                         result = -1;
-                        pw.println("Command not supported. "
-                                + "Only supported when using legacy ProtoLog.");
+                        pw.println("ProtoLog impl doesn't support handling commands");
                     }
                     return result;
                 case "user-rotation":
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index d6fc01a..a63e106 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -329,15 +329,7 @@
                                         deferred);
                                 wctApplied.meet();
                                 if (needsSetReady) {
-                                    // TODO(b/294925498): Remove this once we have accurate ready
-                                    //                    tracking.
-                                    if (hasActivityLaunch(wct) && !mService.mRootWindowContainer
-                                            .allPausedActivitiesComplete()) {
-                                        // WCT is launching an activity, so we need to wait for its
-                                        // lifecycle events.
-                                        return;
-                                    }
-                                    nextTransition.setAllReady();
+                                    setAllReadyIfNeeded(nextTransition, wct);
                                 }
                             });
                     return nextTransition.getToken();
@@ -390,7 +382,7 @@
         }
     }
 
-    private static boolean hasActivityLaunch(WindowContainerTransaction wct) {
+    private static boolean hasActivityLaunch(@NonNull WindowContainerTransaction wct) {
         for (int i = 0; i < wct.getHierarchyOps().size(); ++i) {
             if (wct.getHierarchyOps().get(i).getType() == HIERARCHY_OP_TYPE_LAUNCH_TASK) {
                 return true;
@@ -399,6 +391,46 @@
         return false;
     }
 
+    private boolean isCreatedTaskFragmentReady(@NonNull WindowContainerTransaction wct) {
+        for (int i = 0; i < wct.getHierarchyOps().size(); ++i) {
+            final WindowContainerTransaction.HierarchyOp op = wct.getHierarchyOps().get(i);
+            if (op.getType() != HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION
+                    || op.getTaskFragmentOperation().getOpType()
+                    != OP_TYPE_CREATE_TASK_FRAGMENT) {
+                continue;
+            }
+            final IBinder tfToken = op.getTaskFragmentOperation()
+                    .getTaskFragmentCreationParams().getFragmentToken();
+            final TaskFragment taskFragment = getTaskFragment(tfToken);
+            if (taskFragment != null && !taskFragment.isReadyToTransit()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void setAllReadyIfNeeded(@NonNull Transition transition,
+            @NonNull WindowContainerTransaction wct) {
+        // TODO(b/294925498): Remove this once we have accurate ready tracking.
+        if (hasActivityLaunch(wct) && !mService.mRootWindowContainer
+                .allPausedActivitiesComplete()) {
+            // WCT is launching an activity, so we need to wait for its
+            // lifecycle events.
+            return;
+        }
+        if (!isCreatedTaskFragmentReady(wct)) {
+            // When the organizer intercepts a #startActivity, it will create an empty TaskFragment
+            // for that specific incoming starting activity. We don't want to set all ready here,
+            // because we requires that #startActivity to be included in this transition, and NOT be
+            // in its own transition.
+            // TODO(b/232042367): explicitly ensure the #startActivity and this transaction are in
+            // the same transition instead of relying on this possible racing condition.
+            return;
+        }
+
+        transition.setAllReady();
+    }
+
     @Override
     public int startLegacyTransition(int type, @NonNull RemoteAnimationAdapter adapter,
             @NonNull IWindowContainerTransactionCallback callback,
@@ -529,7 +561,7 @@
                 }
                 mTransitionController.requestStartTransition(transition, null /* startTask */,
                         remoteTransition, null /* displayChange */);
-                transition.setAllReady();
+                setAllReadyIfNeeded(transition, wct);
             };
             mTransitionController.startCollectOrQueue(transition, doApply);
         } finally {
@@ -1986,6 +2018,7 @@
         try {
             callback.onTransactionReady(syncId, t);
         } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to notify transaction (" + syncId + ") ready", e);
             // If there's an exception when trying to send the mergedTransaction to the client, we
             // should immediately apply it here so the transactions aren't lost.
             t.apply();
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index ee16a37..6ac2774 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -114,6 +114,10 @@
     private static final long RAPID_ACTIVITY_LAUNCH_MS = 300;
     private static final long RESET_RAPID_ACTIVITY_LAUNCH_MS = 5 * RAPID_ACTIVITY_LAUNCH_MS;
 
+    public static final int STOPPED_STATE_NOT_STOPPED = 0;
+    public static final int STOPPED_STATE_FIRST_LAUNCH = 1;
+    public static final int STOPPED_STATE_FORCE_STOPPED = 2;
+
     private int mRapidActivityLaunchCount;
 
     // all about the first app in the process
@@ -281,6 +285,22 @@
     @AnimatingReason
     private int mAnimatingReasons;
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            STOPPED_STATE_NOT_STOPPED,
+            STOPPED_STATE_FIRST_LAUNCH,
+            STOPPED_STATE_FORCE_STOPPED
+    })
+    public @interface StoppedState {}
+
+    private volatile @StoppedState int mStoppedState;
+
+    /**
+     * Whether the stopped state was logged for an activity start, as we don't want to log
+     * multiple times.
+     */
+    private volatile boolean mWasStoppedLogged;
+
     // The bits used for mActivityStateFlags.
     private static final int ACTIVITY_STATE_FLAG_IS_VISIBLE = 1 << 16;
     private static final int ACTIVITY_STATE_FLAG_IS_PAUSING_OR_PAUSED = 1 << 17;
@@ -1928,6 +1948,29 @@
                 && (mInfo.flags & ApplicationInfo.FLAG_FACTORY_TEST) != 0;
     }
 
+    /** Sets the current stopped state of the app, which is reset as soon as metrics are logged */
+    public void setStoppedState(@StoppedState int stoppedState) {
+        mStoppedState = stoppedState;
+    }
+
+    boolean getWasStoppedLogged() {
+        return mWasStoppedLogged;
+    }
+
+    void setWasStoppedLogged(boolean logged) {
+        mWasStoppedLogged = logged;
+    }
+
+    /** Returns whether the app had been force-stopped before this launch */
+    public boolean wasForceStopped() {
+        return mStoppedState == STOPPED_STATE_FORCE_STOPPED;
+    }
+
+    /** Returns whether this app is being launched for the first time since install */
+    boolean wasFirstLaunch() {
+        return mStoppedState == STOPPED_STATE_FIRST_LAUNCH;
+    }
+
     void setRunningRecentsAnimation(boolean running) {
         if (running) {
             addAnimatingReason(ANIMATING_REASON_LEGACY_RECENT_ANIMATION);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a7a28c2..46bac16 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -695,7 +695,8 @@
      */
     private boolean mDrawnStateEvaluated;
 
-    private final Point mSurfacePosition = new Point();
+    /** The surface position relative to the parent container. */
+    final Point mSurfacePosition = new Point();
 
     /**
      * A region inside of this window to be excluded from touch.
@@ -1342,7 +1343,7 @@
             // This window doesn't provide any insets.
             return;
         }
-        if (mGivenInsetsPending && mAttrs.providedInsets == null) {
+        if (mGivenInsetsPending) {
             // The given insets are pending, and they are not reliable for now. The source frame
             // should be updated after the new given insets are sent to window manager.
             return;
@@ -2041,6 +2042,13 @@
         if (!isVisible()) {
             return;
         }
+        final WallpaperWindowToken wallpaperToken = mToken.asWallpaperToken();
+        if (wallpaperToken != null) {
+            if (wallpaperToken.hasVisibleNotDrawnWallpaper()) {
+                outWaitingForDrawn.add(this);
+            }
+            return;
+        }
         if (mActivityRecord != null) {
             if (!mActivityRecord.isVisibleRequested()) return;
             if (mActivityRecord.allDrawn) {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 13e1ba78..4dca23b 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -563,9 +563,15 @@
         if (mTransitionController.isShellTransitionsEnabled()
                 && asActivityRecord() != null && isVisible()) {
             // Trigger an activity level rotation transition.
-            mTransitionController.requestTransitionIfNeeded(WindowManager.TRANSIT_CHANGE, this);
-            mTransitionController.collectVisibleChange(this);
-            mTransitionController.setReady(this);
+            Transition transition = mTransitionController.getCollectingTransition();
+            if (transition == null) {
+                transition = mTransitionController.requestStartTransition(
+                        mTransitionController.createTransition(WindowManager.TRANSIT_CHANGE),
+                        null /* trigger */, null /* remote */, null /* disp */);
+            }
+            transition.collect(this);
+            transition.collectVisibleChange(this);
+            transition.setReady(mDisplayContent, true);
         }
         final int originalRotation = getWindowConfiguration().getRotation();
         onConfigurationChanged(parent.getConfiguration());
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index 424d504..b0e71bd 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -109,6 +109,9 @@
             return;
         }
         synchronized (mEnabledLock) {
+            if (!android.tracing.Flags.perfettoProtologTracing()) {
+                ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).startProtoLog(pw);
+            }
             logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
             mBuffer.resetBuffer();
             mEnabled = mEnabledLockFree = true;
@@ -136,6 +139,9 @@
             writeTraceToFileLocked();
             logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
         }
+        if (!android.tracing.Flags.perfettoProtologTracing()) {
+            ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).stopProtoLog(pw, true);
+        }
     }
 
     /**
@@ -155,13 +161,13 @@
             logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
             writeTraceToFileLocked();
             logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
-            if (!android.tracing.Flags.perfettoProtolog()) {
+            if (!android.tracing.Flags.perfettoProtologTracing()) {
                 ((LegacyProtoLogImpl) mProtoLog).stopProtoLog(pw, true);
             }
             logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
             mBuffer.resetBuffer();
             mEnabled = mEnabledLockFree = true;
-            if (!android.tracing.Flags.perfettoProtolog()) {
+            if (!android.tracing.Flags.perfettoProtologTracing()) {
                 ((LegacyProtoLogImpl) mProtoLog).startProtoLog(pw);
             }
         }
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 3607ddd..7a710dc 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -40,6 +40,7 @@
         "com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
         "com_android_server_ConsumerIrService.cpp",
         "com_android_server_companion_virtual_InputController.cpp",
+        "com_android_server_companion_virtual_VirtualDeviceImpl.cpp",
         "com_android_server_devicepolicy_CryptoTestHelper.cpp",
         "com_android_server_display_DisplayControl.cpp",
         "com_android_server_display_SmallAreaDetectionController.cpp",
@@ -214,6 +215,7 @@
     static_libs: [
         "android.hardware.broadcastradio@common-utils-1x-lib",
         "libaidlcommonsupport",
+        "libvirtualdevicebuildflags",
     ],
 
     product_variables: {
diff --git a/services/core/jni/com_android_server_accessibility_BrailleDisplayConnection.cpp b/services/core/jni/com_android_server_accessibility_BrailleDisplayConnection.cpp
index 9a509a7..c337523 100644
--- a/services/core/jni/com_android_server_accessibility_BrailleDisplayConnection.cpp
+++ b/services/core/jni/com_android_server_accessibility_BrailleDisplayConnection.cpp
@@ -40,7 +40,7 @@
 } // anonymous namespace
 
 static jint com_android_server_accessibility_BrailleDisplayConnection_getHidrawDescSize(
-        JNIEnv* env, jobject thiz, int fd) {
+        JNIEnv* env, jclass /*clazz*/, int fd) {
     int size = 0;
     if (ioctl(fd, HIDIOCGRDESCSIZE, &size) < 0) {
         return -1;
@@ -49,7 +49,7 @@
 }
 
 static jbyteArray com_android_server_accessibility_BrailleDisplayConnection_getHidrawDesc(
-        JNIEnv* env, jobject thiz, int fd, int descSize) {
+        JNIEnv* env, jclass /*clazz*/, int fd, int descSize) {
     struct hidraw_report_descriptor desc;
     desc.size = descSize;
     if (ioctl(fd, HIDIOCGRDESC, &desc) < 0) {
@@ -63,9 +63,8 @@
     return result;
 }
 
-static jstring com_android_server_accessibility_BrailleDisplayConnection_getHidrawUniq(JNIEnv* env,
-                                                                                       jobject thiz,
-                                                                                       int fd) {
+static jstring com_android_server_accessibility_BrailleDisplayConnection_getHidrawUniq(
+        JNIEnv* env, jclass /*clazz*/, int fd) {
     char buf[UNIQ_SIZE_MAX];
     if (ioctl(fd, HIDIOCGRAWUNIQ(UNIQ_SIZE_MAX), buf) < 0) {
         return nullptr;
@@ -74,9 +73,8 @@
     return env->NewStringUTF(buf);
 }
 
-static jint com_android_server_accessibility_BrailleDisplayConnection_getHidrawBusType(JNIEnv* env,
-                                                                                       jobject thiz,
-                                                                                       int fd) {
+static jint com_android_server_accessibility_BrailleDisplayConnection_getHidrawBusType(
+        JNIEnv* env, jclass /*clazz*/, int fd) {
     struct hidraw_devinfo info;
     if (ioctl(fd, HIDIOCGRAWINFO, &info) < 0) {
         return -1;
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 4403bce..95e7b19 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -393,6 +393,7 @@
                 ++pageoutVmaIndex;
                 break;
         }
+        return true;
     };
     meminfo.ForEachVmaFromMaps(vmaCollectorCb, mapsBuffer);
     ATRACE_END();
diff --git a/services/core/jni/com_android_server_companion_virtual_VirtualDeviceImpl.cpp b/services/core/jni/com_android_server_companion_virtual_VirtualDeviceImpl.cpp
new file mode 100644
index 0000000..1e6a9db
--- /dev/null
+++ b/services/core/jni/com_android_server_companion_virtual_VirtualDeviceImpl.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android_companion_virtualdevice_build_flags.h>
+#include <nativehelper/JNIHelp.h>
+
+#include <array>
+
+#include "jni.h"
+
+namespace android {
+namespace {
+
+jboolean nativeVirtualCameraServiceBuildFlagEnabled(JNIEnv* env, jobject clazz) {
+    return ::android::companion::virtualdevice::flags::virtual_camera_service_build_flag();
+}
+
+const std::array<JNINativeMethod, 1> kMethods = {
+        {{"nativeVirtualCameraServiceBuildFlagEnabled", "()Z",
+          (void*)nativeVirtualCameraServiceBuildFlagEnabled}},
+};
+
+} // namespace
+
+int register_android_server_companion_virtual_VirtualDeviceImpl(JNIEnv* env) {
+    return jniRegisterNativeMethods(env, "com/android/server/companion/virtual/VirtualDeviceImpl",
+                                    kMethods.data(), kMethods.size());
+}
+
+} // namespace android
diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp
index ccd9bd0..b2bdaa3 100644
--- a/services/core/jni/com_android_server_hint_HintManagerService.cpp
+++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp
@@ -24,16 +24,17 @@
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <powermanager/PowerHalController.h>
+#include <powermanager/PowerHintSessionWrapper.h>
 #include <utils/Log.h>
 
 #include <unordered_map>
 
 #include "jni.h"
 
-using aidl::android::hardware::power::IPowerHintSession;
 using aidl::android::hardware::power::SessionHint;
 using aidl::android::hardware::power::SessionMode;
 using aidl::android::hardware::power::WorkDuration;
+using android::power::PowerHintSessionWrapper;
 
 using android::base::StringPrintf;
 
@@ -49,7 +50,7 @@
 } gWorkDurationInfo;
 
 static power::PowerHalController gPowerHalController;
-static std::unordered_map<jlong, std::shared_ptr<IPowerHintSession>> gSessionMap;
+static std::unordered_map<jlong, std::shared_ptr<PowerHintSessionWrapper>> gSessionMap;
 static std::mutex gSessionMapLock;
 
 static int64_t getHintSessionPreferredRate() {
@@ -76,45 +77,45 @@
 }
 
 static void pauseHintSession(JNIEnv* env, int64_t session_ptr) {
-    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
     appSession->pause();
 }
 
 static void resumeHintSession(JNIEnv* env, int64_t session_ptr) {
-    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
     appSession->resume();
 }
 
 static void closeHintSession(JNIEnv* env, int64_t session_ptr) {
-    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
     appSession->close();
     std::unique_lock<std::mutex> sessionLock(gSessionMapLock);
     gSessionMap.erase(session_ptr);
 }
 
 static void updateTargetWorkDuration(int64_t session_ptr, int64_t targetDurationNanos) {
-    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
     appSession->updateTargetWorkDuration(targetDurationNanos);
 }
 
 static void reportActualWorkDuration(int64_t session_ptr,
                                      const std::vector<WorkDuration>& actualDurations) {
-    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
     appSession->reportActualWorkDuration(actualDurations);
 }
 
 static void sendHint(int64_t session_ptr, SessionHint hint) {
-    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
     appSession->sendHint(hint);
 }
 
 static void setThreads(int64_t session_ptr, const std::vector<int32_t>& threadIds) {
-    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
     appSession->setThreads(threadIds);
 }
 
 static void setMode(int64_t session_ptr, SessionMode mode, bool enabled) {
-    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
     appSession->setMode(mode, enabled);
 }
 
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 0936888..6464081 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -65,6 +65,7 @@
 int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env);
 int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env);
 int register_android_server_companion_virtual_InputController(JNIEnv* env);
+int register_android_server_companion_virtual_VirtualDeviceImpl(JNIEnv* env);
 int register_android_server_app_GameManagerService(JNIEnv* env);
 int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env);
 int register_com_android_server_display_DisplayControl(JNIEnv* env);
@@ -128,6 +129,7 @@
     register_android_server_stats_pull_StatsPullAtomService(env);
     register_android_server_sensor_SensorService(vm, env);
     register_android_server_companion_virtual_InputController(env);
+    register_android_server_companion_virtual_VirtualDeviceImpl(env);
     register_android_server_app_GameManagerService(env);
     register_com_android_server_wm_TaskFpsCallbackController(env);
     register_com_android_server_display_DisplayControl(env);
diff --git a/services/core/xsd/device-state-config/device-state-config.xsd b/services/core/xsd/device-state-config/device-state-config.xsd
index 86f4176..4a94732 100644
--- a/services/core/xsd/device-state-config/device-state-config.xsd
+++ b/services/core/xsd/device-state-config/device-state-config.xsd
@@ -40,14 +40,14 @@
             <xs:element name="name" type="xs:string" minOccurs="0">
                 <xs:annotation name="nullable" />
             </xs:element>
-            <xs:element name="flags" type="flags" />
+            <xs:element name="properties" type="properties" />
             <xs:element name="conditions" type="conditions" />
         </xs:sequence>
     </xs:complexType>
 
-    <xs:complexType name="flags">
+    <xs:complexType name="properties">
         <xs:sequence>
-            <xs:element name="flag" type="xs:string" minOccurs="0" maxOccurs="unbounded">
+            <xs:element name="property" type="xs:string" minOccurs="0" maxOccurs="unbounded">
                 <xs:annotation name="nullable" />
             </xs:element>
         </xs:sequence>
diff --git a/services/core/xsd/device-state-config/schema/current.txt b/services/core/xsd/device-state-config/schema/current.txt
index a98d4e5..5bb216e 100644
--- a/services/core/xsd/device-state-config/schema/current.txt
+++ b/services/core/xsd/device-state-config/schema/current.txt
@@ -11,13 +11,13 @@
   public class DeviceState {
     ctor public DeviceState();
     method public com.android.server.policy.devicestate.config.Conditions getConditions();
-    method public com.android.server.policy.devicestate.config.Flags getFlags();
     method public java.math.BigInteger getIdentifier();
     method @Nullable public String getName();
+    method public com.android.server.policy.devicestate.config.Properties getProperties();
     method public void setConditions(com.android.server.policy.devicestate.config.Conditions);
-    method public void setFlags(com.android.server.policy.devicestate.config.Flags);
     method public void setIdentifier(java.math.BigInteger);
     method public void setName(@Nullable String);
+    method public void setProperties(com.android.server.policy.devicestate.config.Properties);
   }
 
   public class DeviceStateConfig {
@@ -25,11 +25,6 @@
     method public java.util.List<com.android.server.policy.devicestate.config.DeviceState> getDeviceState();
   }
 
-  public class Flags {
-    ctor public Flags();
-    method @Nullable public java.util.List<java.lang.String> getFlag();
-  }
-
   public class LidSwitchCondition {
     ctor public LidSwitchCondition();
     method public boolean getOpen();
@@ -48,6 +43,11 @@
     method public void setMin_optional(@Nullable java.math.BigDecimal);
   }
 
+  public class Properties {
+    ctor public Properties();
+    method @Nullable public java.util.List<java.lang.String> getProperty();
+  }
+
   public class SensorCondition {
     ctor public SensorCondition();
     method public String getName();
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 9d45cfb..f5e1e41 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -18,7 +18,6 @@
 import static android.credentials.selection.Constants.EXTRA_FINAL_RESPONSE_RECEIVER;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
@@ -114,8 +113,8 @@
 
     /** Creates intent that is ot be invoked to cancel an in-progress UI session. */
     public Intent createCancelIntent(IBinder requestId, String packageName) {
-        return IntentFactory.createCancelUiIntent(requestId, /*shouldShowCancellationUi=*/ true,
-                packageName);
+        return IntentFactory.createCancelUiIntent(mContext, requestId,
+                /*shouldShowCancellationUi=*/ true, packageName);
     }
 
     /**
@@ -161,26 +160,6 @@
      */
     public PendingIntent createPendingIntent(
             RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) {
-        return createPendingIntent(requestInfo, providerDataList, /*forAutofill=*/ false);
-    }
-
-    /**
-     * Creates a {@link PendingIntent} to be used to invoke the credential manager selector UI,
-     * by the calling app process. This intent is invoked from the Autofill flow, when the user
-     * requests to bring up the 'All Options' page of the credential bottom-sheet. When the user
-     * clicks on the pinned entry, the intent will bring up the 'All Options' page of the
-     * bottom-sheet. The provider data list is processed by the credential autofill service for
-     * each autofill id and passed in as an auth extra.
-     *
-     * @param requestInfo            the information about the request
-     */
-    public PendingIntent createPendingIntentForAutofill(RequestInfo requestInfo) {
-        return createPendingIntent(requestInfo, /*providerDataList=*/ null, /*forAutofill=*/ true);
-    }
-
-    private PendingIntent createPendingIntent(
-            RequestInfo requestInfo, @Nullable ArrayList<ProviderData> providerDataList,
-            boolean forAutofill) {
         List<CredentialProviderInfo> allProviders =
                 CredentialProviderInfoFactory.getCredentialProviderServices(
                         mContext,
@@ -196,15 +175,9 @@
                         disabledProvider.getComponentName().flattenToString())).toList();
 
         Intent intent;
-        if (forAutofill) {
-            intent = IntentFactory.createCredentialSelectorIntentForAutofill(
-                    mContext, requestInfo, new ArrayList<>(disabledProviderDataList),
-                    mResultReceiver);
-        } else {
-            intent = IntentFactory.createCredentialSelectorIntent(
-                    mContext, requestInfo, providerDataList,
-                    new ArrayList<>(disabledProviderDataList), mResultReceiver);
-        }
+        intent = IntentFactory.createCredentialSelectorIntent(
+                mContext, requestInfo, providerDataList,
+                new ArrayList<>(disabledProviderDataList), mResultReceiver);
         intent.setAction(UUID.randomUUID().toString());
         //TODO: Create unique pending intent using request code and cancel any pre-existing pending
         // intents
@@ -213,4 +186,21 @@
                 PendingIntent.FLAG_MUTABLE, /*options=*/null,
                 UserHandle.of(mUserId));
     }
+
+    /**
+     * Creates an {@link Intent} to be used to invoke the credential manager selector UI,
+     * by the calling app process. This intent is invoked from the Autofill flow, when the user
+     * requests to bring up the 'All Options' page of the credential bottom-sheet. When the user
+     * clicks on the pinned entry, the intent will bring up the 'All Options' page of the
+     * bottom-sheet. The provider data list is processed by the credential autofill service for
+     * each autofill id and passed in as extras in the pending intent set as authentication
+     * of the pinned entry.
+     *
+     * @param requestInfo            the information about the request
+     */
+    public Intent createIntentForAutofill(RequestInfo requestInfo) {
+        return IntentFactory.createCredentialSelectorIntentForAutofill(
+                mContext, requestInfo, new ArrayList<>(),
+                mResultReceiver);
+    }
 }
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index ca72638..eff53de 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.credentials.Constants;
 import android.credentials.CredentialProviderInfo;
 import android.credentials.GetCandidateCredentialsException;
@@ -40,6 +41,8 @@
 import android.service.credentials.PermissionUtils;
 import android.util.Slog;
 
+import com.android.server.credentials.metrics.ApiStatus;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
@@ -114,8 +117,7 @@
             return;
         }
 
-        cancelExistingPendingIntent();
-        mPendingIntent = mCredentialManagerUi.createPendingIntentForAutofill(
+        Intent intent = mCredentialManagerUi.createIntentForAutofill(
                 RequestInfo.newGetRequestInfo(
                         mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
                         PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
@@ -129,7 +131,7 @@
 
         try {
             invokeClientCallbackSuccess(new GetCandidateCredentialsResponse(
-                    candidateProviderDataList, mPendingIntent));
+                    candidateProviderDataList, intent));
         } catch (RemoteException e) {
             Slog.e(TAG, "Issue while responding to client with error : " + e);
         }
@@ -180,7 +182,7 @@
         } else {
             Slog.w(TAG, "onUiCancellation called but finalResponseReceiver not found");
         }
-        finishSession(/*propagateCancellation=*/false);
+        finishSession(/*propagateCancellation=*/false, ApiStatus.FAILURE.getMetricCode());
     }
 
     @Override
@@ -221,9 +223,10 @@
             resultData.putParcelable(
                     CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, response);
             mFinalResponseReceiver.send(Constants.SUCCESS_CREDMAN_SELECTOR, resultData);
-            finishSession(/*propagateCancellation=*/ false);
+            finishSession(/*propagateCancellation=*/ false, ApiStatus.SUCCESS.getMetricCode());
         } else {
             Slog.w(TAG, "onFinalResponseReceived result receiver not found for pinned entry");
+            finishSession(/*propagateCancellation=*/ false, ApiStatus.FAILURE.getMetricCode());
         }
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index fa63bc8..cad9a09 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -93,7 +93,7 @@
         android.credentials.GetCredentialRequest filteredRequest =
                 filterOptions(providerInfo.getCapabilities(),
                         getRequestSession.mClientRequest,
-                        providerInfo);
+                        providerInfo, getRequestSession.mHybridService);
         if (filteredRequest != null) {
             Map<String, CredentialOption> beginGetOptionToCredentialOptionMap =
                     new HashMap<>();
@@ -129,7 +129,7 @@
         android.credentials.GetCredentialRequest filteredRequest =
                 filterOptions(providerInfo.getCapabilities(),
                         getRequestSession.mClientRequest,
-                        providerInfo);
+                        providerInfo, getRequestSession.mHybridService);
         if (filteredRequest != null) {
             Map<String, CredentialOption> beginGetOptionToCredentialOptionMap =
                     new HashMap<>();
@@ -179,9 +179,19 @@
     private static android.credentials.GetCredentialRequest filterOptions(
             List<String> providerCapabilities,
             android.credentials.GetCredentialRequest clientRequest,
-            CredentialProviderInfo info
-    ) {
+            CredentialProviderInfo info,
+            String hybridService) {
         Slog.i(TAG, "Filtering request options for: " + info.getComponentName());
+        if (android.credentials.flags.Flags.hybridFilterFixEnabled()) {
+            ComponentName hybridComponentName = ComponentName.unflattenFromString(hybridService);
+            if (hybridComponentName != null && hybridComponentName
+                    .equals(info.getComponentName())) {
+                Slog.i(TAG, "Skipping filtering of options for hybrid service");
+                return clientRequest;
+            }
+            Slog.w(TAG, "Could not parse hybrid service while filtering options");
+        }
+
         List<CredentialOption> filteredOptions = new ArrayList<>();
         for (CredentialOption option : clientRequest.getCredentialOptions()) {
             if (providerCapabilities.contains(option.getType())
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 633c9c4..a5b9aa6 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -175,7 +175,7 @@
                 () -> {
                     Slog.d(TAG, "Cancellation invoked from the client - clearing session");
                     boolean isUiActive = maybeCancelUi();
-                    finishSession(!isUiActive);
+                    finishSession(!isUiActive, ApiStatus.CLIENT_CANCELED.getMetricCode());
                 }
         );
     }
@@ -231,7 +231,8 @@
             return;
         }
         if (isSessionCancelled()) {
-            finishSession(/*propagateCancellation=*/true);
+            finishSession(/*propagateCancellation=*/true,
+                    ApiStatus.CLIENT_CANCELED.getMetricCode());
             return;
         }
         String providerId = selection.getProviderId();
@@ -257,11 +258,12 @@
         }
     }
 
-    protected void finishSession(boolean propagateCancellation) {
+    protected void finishSession(boolean propagateCancellation, int apiStatus) {
         Slog.i(TAG, "finishing session with propagateCancellation " + propagateCancellation);
         if (propagateCancellation) {
             mProviders.values().forEach(ProviderSession::cancelProviderRemoteSession);
         }
+        mRequestSessionMetric.logApiCalledAtFinish(apiStatus);
         mRequestSessionStatus = RequestSessionStatus.COMPLETE;
         mProviders.clear();
         clearRequestSessionLocked();
@@ -326,7 +328,8 @@
         mRequestSessionMetric.logCandidatePhaseMetrics(mProviders);
 
         if (isSessionCancelled()) {
-            finishSession(/*propagateCancellation=*/true);
+            finishSession(/*propagateCancellation=*/true,
+                    ApiStatus.CLIENT_CANCELED.getMetricCode());
             return providerDataList;
         }
 
@@ -353,23 +356,20 @@
             return;
         }
         if (isSessionCancelled()) {
-            mRequestSessionMetric.logApiCalledAtFinish(
-                    /*apiStatus=*/ ApiStatus.CLIENT_CANCELED.getMetricCode());
-            finishSession(/*propagateCancellation=*/true);
+            finishSession(/*propagateCancellation=*/true,
+                    ApiStatus.CLIENT_CANCELED.getMetricCode());
             return;
         }
         try {
             invokeClientCallbackSuccess(response);
-            mRequestSessionMetric.logApiCalledAtFinish(
-                    /*apiStatus=*/ ApiStatus.SUCCESS.getMetricCode());
+            finishSession(/*propagateCancellation=*/false,
+                    ApiStatus.SUCCESS.getMetricCode());
         } catch (RemoteException e) {
             mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(
                     /*has_exception=*/ true, ProviderStatusForMetrics.FINAL_FAILURE);
             Slog.e(TAG, "Issue while responding to client with a response : " + e);
-            mRequestSessionMetric.logApiCalledAtFinish(
-                    /*apiStatus=*/ ApiStatus.FAILURE.getMetricCode());
+            finishSession(/*propagateCancellation=*/false, ApiStatus.FAILURE.getMetricCode());
         }
-        finishSession(/*propagateCancellation=*/false);
     }
 
     /**
@@ -387,9 +387,7 @@
             return;
         }
         if (isSessionCancelled()) {
-            mRequestSessionMetric.logApiCalledAtFinish(
-                    /*apiStatus=*/ ApiStatus.CLIENT_CANCELED.getMetricCode());
-            finishSession(/*propagateCancellation=*/true);
+            finishSession(/*propagateCancellation=*/true, ApiStatus.CLIENT_CANCELED.getMetricCode());
             return;
         }
 
@@ -399,8 +397,14 @@
             Slog.e(TAG, "Issue while responding to client with error : " + e);
         }
         boolean isUserCanceled = errorType.contains(MetricUtilities.USER_CANCELED_SUBSTRING);
-        mRequestSessionMetric.logFailureOrUserCancel(isUserCanceled);
-        finishSession(/*propagateCancellation=*/false);
+        if (isUserCanceled) {
+            mRequestSessionMetric.setHasExceptionFinalPhase(/* has_exception */ false);
+            finishSession(/*propagateCancellation=*/false,
+                    ApiStatus.USER_CANCELED.getMetricCode());
+        } else {
+            finishSession(/*propagateCancellation=*/false,
+                    ApiStatus.FAILURE.getMetricCode());
+        }
     }
 
     /**
@@ -419,7 +423,7 @@
         @Override
         public void binderDied() {
             Slog.d(TAG, "Client binder died - clearing session");
-            finishSession(isUiWaitingForData());
+            finishSession(isUiWaitingForData(), ApiStatus.CLIENT_CANCELED.getMetricCode());
         }
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
index 8adcfbc..a77bd3e 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
@@ -247,7 +247,7 @@
      *
      * @param exceptionBitFinalPhase represents if the final phase provider had an exception
      */
-    private void setHasExceptionFinalPhase(boolean exceptionBitFinalPhase) {
+    public void setHasExceptionFinalPhase(boolean exceptionBitFinalPhase) {
         try {
             mChosenProviderFinalPhaseMetric.setHasException(exceptionBitFinalPhase);
         } catch (Exception e) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 12f4407..6aeb4fd5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -225,7 +225,7 @@
 
         synchronized (mLock) {
             PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
-            if (Flags.devicePolicySizeTrackingEnabled() && false) {
+            if (Flags.devicePolicySizeTrackingInternalEnabled()) {
                 if (!handleAdminPolicySizeLimit(localPolicyState, enforcingAdmin, value,
                         policyDefinition, userId)) {
                     return;
@@ -350,7 +350,7 @@
             }
             PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
 
-            if (Flags.devicePolicySizeTrackingEnabled() && false) {
+            if (Flags.devicePolicySizeTrackingInternalEnabled()) {
                 decreasePolicySizeForAdmin(localPolicyState, enforcingAdmin);
             }
 
@@ -496,7 +496,7 @@
 
         synchronized (mLock) {
             PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
-            if (Flags.devicePolicySizeTrackingEnabled() && false) {
+            if (Flags.devicePolicySizeTrackingInternalEnabled()) {
                 if (!handleAdminPolicySizeLimit(globalPolicyState, enforcingAdmin, value,
                         policyDefinition, UserHandle.USER_ALL)) {
                     return;
@@ -568,7 +568,7 @@
         synchronized (mLock) {
             PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
 
-            if (Flags.devicePolicySizeTrackingEnabled() && false) {
+            if (Flags.devicePolicySizeTrackingInternalEnabled()) {
                 decreasePolicySizeForAdmin(policyState, enforcingAdmin);
             }
 
@@ -1892,7 +1892,7 @@
 
         private void writeEnforcingAdminSizeInner(TypedXmlSerializer serializer)
                 throws IOException {
-            if (Flags.devicePolicySizeTrackingEnabled() && false) {
+            if (Flags.devicePolicySizeTrackingInternalEnabled()) {
                 if (mAdminPolicySize != null) {
                     for (int i = 0; i < mAdminPolicySize.size(); i++) {
                         int userId = mAdminPolicySize.keyAt(i);
@@ -1916,7 +1916,7 @@
 
         private void writeMaxPolicySizeInner(TypedXmlSerializer serializer)
                 throws IOException {
-            if (!Flags.devicePolicySizeTrackingEnabled() || true) {
+            if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
                 return;
             }
             serializer.startTag(/* namespace= */ null, TAG_MAX_POLICY_SIZE_LIMIT);
@@ -2081,7 +2081,7 @@
 
         private void readMaxPolicySizeInner(TypedXmlPullParser parser)
                 throws XmlPullParserException, IOException {
-            if (!Flags.devicePolicySizeTrackingEnabled() || true) {
+            if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
                 return;
             }
             mPolicySizeLimit = parser.getAttributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 0f97f4a..ee72db0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -27,6 +27,7 @@
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AIRPLANE_MODE;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_RESTRICTIONS;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIO_OUTPUT;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUTOFILL;
@@ -83,7 +84,6 @@
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_TIME;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER;
-import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_VPN;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WALLPAPER;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIFI;
@@ -196,11 +196,11 @@
 import static android.app.admin.DevicePolicyManager.STATUS_DEVICE_ADMIN_NOT_SUPPORTED;
 import static android.app.admin.DevicePolicyManager.STATUS_HAS_DEVICE_OWNER;
 import static android.app.admin.DevicePolicyManager.STATUS_HAS_PAIRED;
+import static android.app.admin.DevicePolicyManager.STATUS_HEADLESS_ONLY_SYSTEM_USER;
 import static android.app.admin.DevicePolicyManager.STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED;
 import static android.app.admin.DevicePolicyManager.STATUS_MANAGED_USERS_NOT_SUPPORTED;
 import static android.app.admin.DevicePolicyManager.STATUS_NONSYSTEM_USER_EXISTS;
 import static android.app.admin.DevicePolicyManager.STATUS_NOT_SYSTEM_USER;
-import static android.app.admin.DevicePolicyManager.STATUS_HEADLESS_ONLY_SYSTEM_USER;
 import static android.app.admin.DevicePolicyManager.STATUS_OK;
 import static android.app.admin.DevicePolicyManager.STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS;
 import static android.app.admin.DevicePolicyManager.STATUS_SYSTEM_USER;
@@ -3395,7 +3395,9 @@
                     if (shouldMigrateV1ToDevicePolicyEngine()) {
                         migrateV1PoliciesToDevicePolicyEngine();
                     }
+                    maybeMigratePoliciesPostUpgradeToDevicePolicyEngineLocked();
                     migratePoliciesToPolicyEngineLocked();
+
                 }
                 maybeStartSecurityLogMonitorOnActivityManagerReady();
                 break;
@@ -12062,7 +12064,7 @@
         }
 
         if (packageList != null) {
-            if (!Flags.devicePolicySizeTrackingEnabled()) {
+            if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
                 for (String pkg : packageList) {
                     PolicySizeVerifier.enforceMaxPackageNameLength(pkg);
                 }
@@ -13771,7 +13773,7 @@
             return;
         }
 
-        if (!Flags.devicePolicySizeTrackingEnabled()) {
+        if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
             PolicySizeVerifier.enforceMaxStringLength(accountType, "account type");
         }
 
@@ -14385,7 +14387,7 @@
     public void setLockTaskPackages(ComponentName who, String callerPackageName, String[] packages)
             throws SecurityException {
         Objects.requireNonNull(packages, "packages is null");
-        if (!Flags.devicePolicySizeTrackingEnabled()) {
+        if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
             for (String pkg : packages) {
                 PolicySizeVerifier.enforceMaxPackageNameLength(pkg);
             }
@@ -16877,6 +16879,8 @@
     private int checkDeviceOwnerProvisioningPreCondition(@UserIdInt int callingUserId) {
         synchronized (getLockObject()) {
             final int deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
+                    && (!Flags.headlessDeviceOwnerProvisioningFixEnabled()
+                    || getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED)
                     ? UserHandle.USER_SYSTEM
                     : callingUserId;
             Slogf.i(LOG_TAG, "Calling user %d, device owner will be set on user %d",
@@ -21549,10 +21553,21 @@
             setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
             setLocale(provisioningParams.getLocale());
 
+
+
+            boolean isSingleUserMode;
+            if (Flags.headlessDeviceOwnerProvisioningFixEnabled()) {
+                DeviceAdminInfo adminInfo = findAdmin(
+                        deviceAdmin, caller.getUserId(), /* throwForMissingPermission= */ false);
+                isSingleUserMode = (adminInfo != null && adminInfo.getHeadlessDeviceOwnerMode()
+                        == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER);
+            } else {
+                isSingleUserMode =
+                        (getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER);
+            }
             int deviceOwnerUserId = Flags.headlessDeviceOwnerSingleUserEnabled()
-                    && getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER
-                    ? mUserManagerInternal.getMainUserId()
-                    : UserHandle.USER_SYSTEM;
+                    && isSingleUserMode
+                    ? mUserManagerInternal.getMainUserId() : UserHandle.USER_SYSTEM;
 
             if (!removeNonRequiredAppsForManagedDevice(
                     deviceOwnerUserId,
@@ -23389,7 +23404,7 @@
                 DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG);
     }
 
-    private boolean isUnicornFlagEnabled() {
+    static boolean isUnicornFlagEnabled() {
         return false;
     }
 
@@ -23736,7 +23751,9 @@
             if (!canForceMigration && !shouldMigrateV1ToDevicePolicyEngine()) {
                 return false;
             }
-            return migrateV1PoliciesToDevicePolicyEngine();
+            boolean migrated = migrateV1PoliciesToDevicePolicyEngine();
+            migrated &= migratePoliciesPostUpgradeToDevicePolicyEngineLocked();
+            return migrated;
         });
     }
 
@@ -23765,6 +23782,30 @@
 
     /**
      * Migrates the initial set of policies to use policy engine.
+     * [b/318497672] Migrate policies that weren't migrated properly in the initial migration on
+     * update from Android T to Android U
+     */
+    private void maybeMigratePoliciesPostUpgradeToDevicePolicyEngineLocked() {
+        if (!mOwners.isMigratedToPolicyEngine() || mOwners.isMigratedPostUpdate()) {
+            return;
+        }
+        migratePoliciesPostUpgradeToDevicePolicyEngineLocked();
+        mOwners.markPostUpgradeMigration();
+    }
+
+    private boolean migratePoliciesPostUpgradeToDevicePolicyEngineLocked() {
+        try {
+            migrateScreenCapturePolicyLocked();
+            migrateLockTaskPolicyLocked();
+            return true;
+        } catch (Exception e) {
+            Slogf.e(LOG_TAG, e, "Error occurred during post upgrade migration to the device "
+                    + "policy engine.");
+            return false;
+        }
+    }
+
+    /**
      * @return {@code true} if policies were migrated successfully, {@code false} otherwise.
      */
     private boolean migrateV1PoliciesToDevicePolicyEngine() {
@@ -23777,7 +23818,6 @@
                         migrateAutoTimezonePolicy();
                         migratePermissionGrantStatePolicies();
                     }
-                    migrateScreenCapturePolicyLocked();
                     migratePermittedInputMethodsPolicyLocked();
                     migrateAccountManagementDisabledPolicyLocked();
                     migrateUserControlDisabledPackagesLocked();
@@ -23858,14 +23898,12 @@
 
     private void migrateScreenCapturePolicyLocked() {
         Binder.withCleanCallingIdentity(() -> {
-            if (mPolicyCache.getScreenCaptureDisallowedUser() == UserHandle.USER_NULL) {
-                return;
-            }
             ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
             if (admin != null
                     && ((isDeviceOwner(admin) && admin.disableScreenCapture)
                     || (admin.getParentActiveAdmin() != null
                     && admin.getParentActiveAdmin().disableScreenCapture))) {
+
                 EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
                         admin.info.getComponent(),
                         admin.getUserHandle().getIdentifier(),
@@ -23894,6 +23932,48 @@
         });
     }
 
+    private void migrateLockTaskPolicyLocked() {
+        Binder.withCleanCallingIdentity(() -> {
+            ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+            if (deviceOwner != null) {
+                int doUserId = deviceOwner.getUserHandle().getIdentifier();
+                DevicePolicyData policies = getUserData(doUserId);
+                List<String> packages = policies.mLockTaskPackages;
+                int features = policies.mLockTaskFeatures;
+                // TODO: find out about persistent preferred activities
+                if (!packages.isEmpty()) {
+                    setLockTaskPolicyInPolicyEngine(deviceOwner, doUserId, packages, features);
+                }
+            }
+
+            for (int userId : mUserManagerInternal.getUserIds()) {
+                ActiveAdmin profileOwner = getProfileOwnerLocked(userId);
+                if (profileOwner != null && canDPCManagedUserUseLockTaskLocked(userId)) {
+                    DevicePolicyData policies = getUserData(userId);
+                    List<String> packages = policies.mLockTaskPackages;
+                    int features = policies.mLockTaskFeatures;
+                    if (!packages.isEmpty()) {
+                        setLockTaskPolicyInPolicyEngine(profileOwner, userId, packages, features);
+                    }
+                }
+            }
+        });
+    }
+
+    private void setLockTaskPolicyInPolicyEngine(
+            ActiveAdmin admin, int userId, List<String> packages, int features) {
+        EnforcingAdmin enforcingAdmin =
+                EnforcingAdmin.createEnterpriseEnforcingAdmin(
+                        admin.info.getComponent(),
+                        userId,
+                        admin);
+        mDevicePolicyEngine.setLocalPolicy(
+                PolicyDefinition.LOCK_TASK,
+                enforcingAdmin,
+                new LockTaskPolicy(new HashSet<>(packages), features),
+                userId);
+    }
+
     private void migratePermittedInputMethodsPolicyLocked() {
         Binder.withCleanCallingIdentity(() -> {
             List<UserInfo> users = mUserManager.getUsers();
@@ -24235,7 +24315,7 @@
 
     @Override
     public void setMaxPolicyStorageLimit(String callerPackageName, int storageLimit) {
-        if (!Flags.devicePolicySizeTrackingEnabled() || true) {
+        if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
             return;
         }
         CallerIdentity caller = getCallerIdentity(callerPackageName);
@@ -24247,7 +24327,7 @@
 
     @Override
     public int getMaxPolicyStorageLimit(String callerPackageName) {
-        if (!Flags.devicePolicySizeTrackingEnabled() || true) {
+        if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
             return -1;
         }
         CallerIdentity caller = getCallerIdentity(callerPackageName);
@@ -24256,4 +24336,13 @@
 
         return mDevicePolicyEngine.getMaxPolicyStorageLimit();
     }
+
+    @Override
+    public int getHeadlessDeviceOwnerMode(String callerPackageName) {
+        final CallerIdentity caller = getCallerIdentity(callerPackageName);
+        enforcePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS, caller.getPackageName(),
+                caller.getUserId());
+
+        return Binder.withCleanCallingIdentity(() -> getHeadlessDeviceOwnerMode());
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
index df7f308..1000bfa 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
@@ -53,7 +53,17 @@
         TelephonyManager telephonyService = context.getSystemService(TelephonyManager.class);
         Preconditions.checkState(telephonyService != null, "Unable to access telephony service");
         mImei = telephonyService.getImei(0);
-        mMeid = telephonyService.getMeid(0);
+        String meid;
+        try {
+            meid = telephonyService.getMeid(0);
+        } catch (UnsupportedOperationException doesNotSupportCdma) {
+            // Instead of catching the exception, we could check for FEATURE_TELEPHONY_CDMA.
+            // However that runs the risk of changing a device's existing ESID if on these devices
+            // telephonyService.getMeid() actually returns non-null even when the device does not
+            // declare FEATURE_TELEPHONY_CDMA.
+            meid = null;
+        }
+        mMeid = meid;
         mSerialNumber = Build.getSerial();
         WifiManager wifiManager = context.getSystemService(WifiManager.class);
         Preconditions.checkState(wifiManager != null, "Unable to access WiFi service");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index c5a9888..7912cbc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -623,12 +623,25 @@
         }
     }
 
+    void markPostUpgradeMigration() {
+        synchronized (mData) {
+            mData.mPoliciesMigratedPostUpdate = true;
+            mData.writeDeviceOwner();
+        }
+    }
+
     boolean isSecurityLoggingMigrated() {
         synchronized (mData) {
             return mData.mSecurityLoggingMigrated;
         }
     }
 
+    boolean isMigratedPostUpdate() {
+        synchronized (mData) {
+            return mData.mPoliciesMigratedPostUpdate;
+        }
+    }
+
     @GuardedBy("mData")
     void pushToAppOpsLocked() {
         if (!mSystemReady) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 9d73ed0..42ac998 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -89,6 +89,8 @@
     private static final String ATTR_MIGRATED_TO_POLICY_ENGINE = "migratedToPolicyEngine";
     private static final String ATTR_SECURITY_LOG_MIGRATED = "securityLogMigrated";
 
+    private static final String ATTR_MIGRATED_POST_UPGRADE = "migratedPostUpgrade";
+
     // Internal state for the device owner package.
     OwnerInfo mDeviceOwner;
     int mDeviceOwnerUserId = UserHandle.USER_NULL;
@@ -117,6 +119,8 @@
     boolean mMigratedToPolicyEngine = false;
     boolean mSecurityLoggingMigrated = false;
 
+    boolean mPoliciesMigratedPostUpdate = false;
+
     OwnersData(PolicyPathProvider pathProvider) {
         mPathProvider = pathProvider;
     }
@@ -400,6 +404,7 @@
 
             out.startTag(null, TAG_POLICY_ENGINE_MIGRATION);
             out.attributeBoolean(null, ATTR_MIGRATED_TO_POLICY_ENGINE, mMigratedToPolicyEngine);
+            out.attributeBoolean(null, ATTR_MIGRATED_POST_UPGRADE, mPoliciesMigratedPostUpdate);
             if (Flags.securityLogV2Enabled()) {
                 out.attributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, mSecurityLoggingMigrated);
             }
@@ -463,8 +468,11 @@
                 case TAG_POLICY_ENGINE_MIGRATION:
                     mMigratedToPolicyEngine = parser.getAttributeBoolean(
                             null, ATTR_MIGRATED_TO_POLICY_ENGINE, false);
+                    mPoliciesMigratedPostUpdate = parser.getAttributeBoolean(
+                            null, ATTR_MIGRATED_POST_UPGRADE, false);
                     mSecurityLoggingMigrated = Flags.securityLogV2Enabled()
                             && parser.getAttributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, false);
+
                     break;
                 default:
                     Slog.e(TAG, "Unexpected tag: " + tag);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 71facab..e713a82 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -506,6 +506,10 @@
                 UserManager.DISALLOW_SIM_GLOBALLY,
                 POLICY_FLAG_GLOBAL_ONLY_POLICY);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ASSIST_CONTENT, /* flags= */ 0);
+        if (com.android.net.thread.platform.flags.Flags.threadUserRestrictionEnabled()) {
+            USER_RESTRICTION_FLAGS.put(
+                    UserManager.DISALLOW_THREAD_NETWORK, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+        }
 
         for (String key : USER_RESTRICTION_FLAGS.keySet()) {
             createAndAddUserRestrictionPolicyDefinition(key, USER_RESTRICTION_FLAGS.get(key));
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index c108deaf..a7adc5b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -66,6 +66,10 @@
     private static final String LOG_TAG = "PolicyEnforcerCallbacks";
 
     static boolean setAutoTimezoneEnabled(@Nullable Boolean enabled, @NonNull Context context) {
+        if (!DevicePolicyManagerService.isUnicornFlagEnabled()) {
+            Slogf.w(LOG_TAG, "Trying to enforce setAutoTimezoneEnabled while flag is off.");
+            return true;
+        }
         return Binder.withCleanCallingIdentity(() -> {
             Objects.requireNonNull(context);
 
@@ -79,6 +83,10 @@
     static boolean setPermissionGrantState(
             @Nullable Integer grantState, @NonNull Context context, int userId,
             @NonNull PolicyKey policyKey) {
+        if (!DevicePolicyManagerService.isUnicornFlagEnabled()) {
+            Slogf.w(LOG_TAG, "Trying to enforce setPermissionGrantState while flag is off.");
+            return true;
+        }
         return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
             if (!(policyKey instanceof PackagePermissionPolicyKey)) {
                 throw new IllegalArgumentException("policyKey is not of type "
diff --git a/services/fakes/Android.bp b/services/fakes/Android.bp
new file mode 100644
index 0000000..148054b
--- /dev/null
+++ b/services/fakes/Android.bp
@@ -0,0 +1,20 @@
+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"],
+}
+
+// NOTE: These "fake" services are intended for use under the Ravenwood
+// deviceless test environment, and should *not* be included in the build
+// artifacts for physical devices, as they already supply "real" services
+filegroup {
+    name: "services.fakes-sources",
+    srcs: [
+        "java/**/*.java",
+    ],
+    path: "java",
+    visibility: ["//frameworks/base"],
+}
diff --git a/services/fakes/java/com/android/server/FakeClipboardService.java b/services/fakes/java/com/android/server/FakeClipboardService.java
new file mode 100644
index 0000000..0101621
--- /dev/null
+++ b/services/fakes/java/com/android/server/FakeClipboardService.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Context;
+import android.content.IClipboard;
+import android.content.IOnPrimaryClipChangedListener;
+import android.os.PermissionEnforcer;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Fake implementation of {@code ClipboardManager} since the real implementation is tightly
+ * coupled with many other internal services.
+ */
+public class FakeClipboardService extends IClipboard.Stub {
+    private final RemoteCallbackList<IOnPrimaryClipChangedListener> mListeners =
+            new RemoteCallbackList<>();
+
+    private ClipData mPrimaryClip;
+    private String mPrimaryClipSource;
+
+    public FakeClipboardService(Context context) {
+        super(PermissionEnforcer.fromContext(context));
+    }
+
+    public static class Lifecycle extends SystemService {
+        private FakeClipboardService mService;
+
+        public Lifecycle(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onStart() {
+            mService = new FakeClipboardService(getContext());
+            publishBinderService(Context.CLIPBOARD_SERVICE, mService);
+        }
+    }
+
+    private static void checkArguments(int userId, int deviceId) {
+        Preconditions.checkArgument(userId == UserHandle.USER_SYSTEM,
+                "Fake only supports USER_SYSTEM user");
+        Preconditions.checkArgument(deviceId == Context.DEVICE_ID_DEFAULT,
+                "Fake only supports DEVICE_ID_DEFAULT device");
+    }
+
+    private void dispatchPrimaryClipChanged() {
+        mListeners.broadcast((listener) -> {
+            try {
+                listener.dispatchPrimaryClipChanged();
+            } catch (RemoteException ignored) {
+            }
+        });
+    }
+
+    @Override
+    public void setPrimaryClip(ClipData clip, String callingPackage, String attributionTag,
+            int userId, int deviceId) {
+        checkArguments(userId, deviceId);
+        mPrimaryClip = clip;
+        mPrimaryClipSource = callingPackage;
+        dispatchPrimaryClipChanged();
+    }
+
+    @Override
+    @android.annotation.EnforcePermission(android.Manifest.permission.SET_CLIP_SOURCE)
+    public void setPrimaryClipAsPackage(ClipData clip, String callingPackage, String attributionTag,
+            int userId, int deviceId, String sourcePackage) {
+        setPrimaryClipAsPackage_enforcePermission();
+        checkArguments(userId, deviceId);
+        mPrimaryClip = clip;
+        mPrimaryClipSource = sourcePackage;
+        dispatchPrimaryClipChanged();
+    }
+
+    @Override
+    public void clearPrimaryClip(String callingPackage, String attributionTag, int userId,
+            int deviceId) {
+        checkArguments(userId, deviceId);
+        mPrimaryClip = null;
+        mPrimaryClipSource = null;
+        dispatchPrimaryClipChanged();
+    }
+
+    @Override
+    public ClipData getPrimaryClip(String pkg, String attributionTag, int userId, int deviceId) {
+        checkArguments(userId, deviceId);
+        return mPrimaryClip;
+    }
+
+    @Override
+    public ClipDescription getPrimaryClipDescription(String callingPackage, String attributionTag,
+            int userId, int deviceId) {
+        checkArguments(userId, deviceId);
+        return (mPrimaryClip != null) ? mPrimaryClip.getDescription() : null;
+    }
+
+    @Override
+    public boolean hasPrimaryClip(String callingPackage, String attributionTag, int userId,
+            int deviceId) {
+        checkArguments(userId, deviceId);
+        return mPrimaryClip != null;
+    }
+
+    @Override
+    public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
+            String callingPackage, String attributionTag, int userId, int deviceId) {
+        checkArguments(userId, deviceId);
+        mListeners.register(listener);
+    }
+
+    @Override
+    public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
+            String callingPackage, String attributionTag, int userId, int deviceId) {
+        checkArguments(userId, deviceId);
+        mListeners.unregister(listener);
+    }
+
+    @Override
+    public boolean hasClipboardText(String callingPackage, String attributionTag, int userId,
+            int deviceId) {
+        checkArguments(userId, deviceId);
+        return (mPrimaryClip != null) && (mPrimaryClip.getItemCount() > 0)
+                && (mPrimaryClip.getItemAt(0).getText() != null);
+    }
+
+    @Override
+    @android.annotation.EnforcePermission(android.Manifest.permission.SET_CLIP_SOURCE)
+    public String getPrimaryClipSource(String callingPackage, String attributionTag, int userId,
+            int deviceId) {
+        getPrimaryClipSource_enforcePermission();
+        checkArguments(userId, deviceId);
+        return mPrimaryClipSource;
+    }
+
+    @Override
+    public boolean areClipboardAccessNotificationsEnabledForUser(int userId) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setClipboardAccessNotificationsEnabledForUser(boolean enable, int userId) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/services/fakes/java/com/android/server/example/BlueManagerService.java b/services/fakes/java/com/android/server/example/BlueManagerService.java
new file mode 100644
index 0000000..8e3c8d7
--- /dev/null
+++ b/services/fakes/java/com/android/server/example/BlueManagerService.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.example;
+
+import android.content.Context;
+import android.os.Binder;
+import android.ravenwood.example.BlueManager;
+
+import com.android.server.SystemService;
+
+public class BlueManagerService extends Binder {
+    public static class Lifecycle extends SystemService {
+        private BlueManagerService mService;
+
+        public Lifecycle(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onStart() {
+            mService = new BlueManagerService();
+            publishBinderService(BlueManager.SERVICE_NAME, mService);
+        }
+    }
+
+    @Override
+    public String getInterfaceDescriptor() {
+        return "blue";
+    }
+}
diff --git a/services/fakes/java/com/android/server/example/RedManagerService.java b/services/fakes/java/com/android/server/example/RedManagerService.java
new file mode 100644
index 0000000..e0be733
--- /dev/null
+++ b/services/fakes/java/com/android/server/example/RedManagerService.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.example;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.ravenwood.example.BlueManager;
+import android.ravenwood.example.RedManager;
+
+import com.android.server.SystemService;
+
+import java.util.List;
+
+public class RedManagerService extends Binder {
+    private IBinder mBlueService;
+
+    public static class Lifecycle extends SystemService {
+        private RedManagerService mService;
+
+        public Lifecycle(Context context) {
+            super(context, List.of(BlueManager.class));
+        }
+
+        @Override
+        public void onStart() {
+            mService = new RedManagerService();
+            publishBinderService(RedManager.SERVICE_NAME, mService);
+        }
+
+        @Override
+        public void onBootPhase(int phase) {
+            if (phase == PHASE_SYSTEM_SERVICES_READY) {
+                mService.mBlueService = getBinderService(BlueManager.SERVICE_NAME);
+            }
+        }
+    }
+
+    @Override
+    public String getInterfaceDescriptor() {
+        try {
+            // Obtain the answer from dependency, but then augment it to prove that the answer
+            // was channeled through us
+            return mBlueService.getInterfaceDescriptor() + "+red";
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
index 82d5247..ce4126a 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
@@ -37,6 +37,7 @@
 import android.util.ArraySet;
 import android.util.Dumpable;
 import android.view.Display;
+import android.view.DisplayInfo;
 import android.view.Surface;
 
 import com.android.server.policy.BookStylePreferredScreenCalculator.PreferredScreen;
@@ -65,6 +66,7 @@
     private final Handler mHandler = new Handler();
     private final PostureEstimator mPostureEstimator;
     private final DisplayManager mDisplayManager;
+    private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
 
     /**
      * Creates {@link BookStyleClosedStatePredicate}. It is expected that the device has a pair
@@ -140,10 +142,11 @@
     public void onDisplayChanged(int displayId) {
         if (displayId == DEFAULT_DISPLAY) {
             final Display display = mDisplayManager.getDisplay(displayId);
+            display.getDisplayInfo(mDefaultDisplayInfo);
             int displayState = display.getState();
             boolean isDisplayOn = displayState == Display.STATE_ON;
             mPostureEstimator.onDisplayPowerStatusChanged(isDisplayOn);
-            mPostureEstimator.onDisplayRotationChanged(display.getRotation());
+            mPostureEstimator.onDisplayRotationChanged(mDefaultDisplayInfo.rotation);
         }
     }
 
@@ -174,8 +177,11 @@
      */
     private static class PostureEstimator implements SensorEventListener, Dumpable {
 
+        private static final String FLAT_INCLINATION_THRESHOLD_DEGREES_PROPERTY
+                = "persist.foldable_postures.wedge_inclination_threshold_degrees";
 
-        private static final int FLAT_INCLINATION_THRESHOLD_DEGREES = 8;
+        private static final int FLAT_INCLINATION_THRESHOLD_DEGREES = Integer.parseInt(
+                System.getProperty(FLAT_INCLINATION_THRESHOLD_DEGREES_PROPERTY, "25"));
 
         /**
          * Alpha parameter of the accelerometer low pass filter: the lower the value, the less high
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
index bc264a4..95c4407 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
@@ -16,29 +16,43 @@
 
 package com.android.server.policy;
 
-import static android.hardware.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
-import static android.hardware.devicestate.DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
-import static android.hardware.devicestate.DeviceState.FLAG_EMULATED_ONLY;
-import static android.hardware.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE;
-import static android.hardware.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL;
+import static android.hardware.devicestate.DeviceState.PROPERTY_EMULATED_ONLY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE;
 
 import static com.android.server.policy.BookStyleStateTransitions.DEFAULT_STATE_TRANSITIONS;
-import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createConfig;
-import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createTentModeClosedState;
+import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStatePredicateWrapper.createConfig;
+import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStatePredicateWrapper.createTentModeClosedState;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.display.DisplayManager;
 
 import com.android.server.devicestate.DeviceStatePolicy;
 import com.android.server.devicestate.DeviceStateProvider;
-import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration;
+import com.android.server.policy.FoldableDeviceStateProvider.DeviceStatePredicateWrapper;
 import com.android.server.policy.feature.flags.FeatureFlags;
 
 import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 import java.util.function.Predicate;
 
 /**
@@ -55,9 +69,8 @@
     private static final int DEVICE_STATE_CLOSED = 0;
     private static final int DEVICE_STATE_HALF_OPENED = 1;
     private static final int DEVICE_STATE_OPENED = 2;
-    private static final int DEVICE_STATE_REAR_DISPLAY_STATE = 3;
+    private static final int DEVICE_STATE_REAR_DISPLAY = 3;
     private static final int DEVICE_STATE_CONCURRENT_INNER_DEFAULT = 4;
-
     private static final int TENT_MODE_SWITCH_ANGLE_DEGREES = 90;
     private static final int TABLE_TOP_MODE_SWITCH_ANGLE_DEGREES = 125;
     private static final int MIN_CLOSED_ANGLE_DEGREES = 0;
@@ -92,81 +105,60 @@
         mEnablePostureBasedClosedState = featureFlags.enableFoldablesPostureBasedClosedState();
         mIsDualDisplayBlockingEnabled = featureFlags.enableDualDisplayBlocking();
 
-        final DeviceStateConfiguration[] configuration = createConfiguration(
+        final DeviceStatePredicateWrapper[] configuration = createConfiguration(
                 leftAccelerometerSensor, rightAccelerometerSensor, closeAngleDegrees);
 
-        mProvider = new FoldableDeviceStateProvider(mContext, sensorManager,
-                hingeAngleSensor, hallSensor, displayManager, configuration);
+        mProvider = new FoldableDeviceStateProvider(mContext, sensorManager, hingeAngleSensor,
+                hallSensor, displayManager, configuration);
     }
 
-    private DeviceStateConfiguration[] createConfiguration(@Nullable Sensor leftAccelerometerSensor,
-            @Nullable Sensor rightAccelerometerSensor, Integer closeAngleDegrees) {
-        return new DeviceStateConfiguration[]{
+    private DeviceStatePredicateWrapper[] createConfiguration(
+            @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
+            Integer closeAngleDegrees) {
+        return new DeviceStatePredicateWrapper[]{
                 createClosedConfiguration(leftAccelerometerSensor, rightAccelerometerSensor,
                         closeAngleDegrees),
-                createConfig(DEVICE_STATE_HALF_OPENED,
-                        /* name= */ "HALF_OPENED",
-                        /* activeStatePredicate= */ (provider) -> {
+                createConfig(getHalfOpenedDeviceState(), /* activeStatePredicate= */
+                        (provider) -> {
                             final float hingeAngle = provider.getHingeAngle();
                             return hingeAngle >= MAX_CLOSED_ANGLE_DEGREES
                                     && hingeAngle <= TABLE_TOP_MODE_SWITCH_ANGLE_DEGREES;
                         }),
-                createConfig(DEVICE_STATE_OPENED,
-                        /* name= */ "OPENED",
-                        /* activeStatePredicate= */ ALLOWED),
-                createConfig(DEVICE_STATE_REAR_DISPLAY_STATE,
-                        /* name= */ "REAR_DISPLAY_STATE",
-                        /* flags= */ FLAG_EMULATED_ONLY,
-                        /* activeStatePredicate= */ NOT_ALLOWED),
-                createConfig(DEVICE_STATE_CONCURRENT_INNER_DEFAULT,
-                        /* name= */ "CONCURRENT_INNER_DEFAULT",
-                        /* flags= */ FLAG_EMULATED_ONLY | FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP
-                                | FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
-                                | FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE,
-                        /* activeStatePredicate= */ NOT_ALLOWED,
-                        /* availabilityPredicate= */
+                createConfig(getOpenedDeviceState(), /* activeStatePredicate= */
+                        ALLOWED),
+                createConfig(getRearDisplayDeviceState(), /* activeStatePredicate= */
+                        NOT_ALLOWED),
+                createConfig(getDualDisplayDeviceState(), /* activeStatePredicate= */
+                        NOT_ALLOWED, /* availabilityPredicate= */
                         provider -> !mIsDualDisplayBlockingEnabled
-                                || provider.hasNoConnectedExternalDisplay())
-        };
+                                || provider.hasNoConnectedExternalDisplay())};
     }
 
-    private DeviceStateConfiguration createClosedConfiguration(
+    private DeviceStatePredicateWrapper createClosedConfiguration(
             @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
             @Nullable Integer closeAngleDegrees) {
+
         if (closeAngleDegrees != null) {
             // Switch displays at closeAngleDegrees in both ways (folding and unfolding)
-            return createConfig(
-                    DEVICE_STATE_CLOSED,
-                    /* name= */ "CLOSED",
-                    /* flags= */ FLAG_CANCEL_OVERRIDE_REQUESTS,
-                    /* activeStatePredicate= */ (provider) -> {
+            return createConfig(getClosedDeviceState(), /* activeStatePredicate= */
+                    (provider) -> {
                         final float hingeAngle = provider.getHingeAngle();
                         return hingeAngle <= closeAngleDegrees;
-                    }
-            );
+                    });
         }
 
         if (mEnablePostureBasedClosedState) {
             // Use smart closed state predicate that will use different switch angles
             // based on the device posture (e.g. wedge mode, tent mode, reverse wedge mode)
-            return createConfig(
-                    DEVICE_STATE_CLOSED,
-                    /* name= */ "CLOSED",
-                    /* flags= */ FLAG_CANCEL_OVERRIDE_REQUESTS,
-                    /* activeStatePredicate= */ new BookStyleClosedStatePredicate(mContext,
-                            this, leftAccelerometerSensor, rightAccelerometerSensor,
-                            DEFAULT_STATE_TRANSITIONS)
-            );
+            return createConfig(getClosedDeviceState(), /* activeStatePredicate= */
+                    new BookStyleClosedStatePredicate(mContext, this, leftAccelerometerSensor,
+                            rightAccelerometerSensor, DEFAULT_STATE_TRANSITIONS));
         }
 
         // Switch to the outer display only at 0 degrees but use TENT_MODE_SWITCH_ANGLE_DEGREES
         // angle when switching to the inner display
-        return createTentModeClosedState(DEVICE_STATE_CLOSED,
-                /* name= */ "CLOSED",
-                /* flags= */ FLAG_CANCEL_OVERRIDE_REQUESTS,
-                MIN_CLOSED_ANGLE_DEGREES,
-                MAX_CLOSED_ANGLE_DEGREES,
-                TENT_MODE_SWITCH_ANGLE_DEGREES);
+        return createTentModeClosedState(getClosedDeviceState(),
+                MIN_CLOSED_ANGLE_DEGREES, MAX_CLOSED_ANGLE_DEGREES, TENT_MODE_SWITCH_ANGLE_DEGREES);
     }
 
     @Override
@@ -188,4 +180,84 @@
     public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
         mProvider.dump(writer, args);
     }
+
+    /** Returns the {@link DeviceState.Configuration} that represents the closed state. */
+    @NonNull
+    private DeviceState getClosedDeviceState() {
+        Set<@DeviceState.SystemDeviceStateProperties Integer> systemProperties = new HashSet<>(
+                List.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
+                        PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY,
+                        PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP));
+
+        Set<@DeviceState.PhysicalDeviceStateProperties Integer> physicalProperties = new HashSet<>(
+                List.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED));
+
+        return new DeviceState(new  DeviceState.Configuration.Builder(DEVICE_STATE_CLOSED, "CLOSED")
+                .setSystemProperties(systemProperties)
+                .setPhysicalProperties(physicalProperties)
+                .build());
+    }
+
+    /** Returns the {@link DeviceState.Configuration} that represents the half_opened state. */
+    @NonNull
+    private DeviceState getHalfOpenedDeviceState() {
+        Set<@DeviceState.SystemDeviceStateProperties Integer> systemProperties = new HashSet<>(
+                List.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY,
+                        PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE));
+
+        Set<@DeviceState.PhysicalDeviceStateProperties Integer> physicalProperties = new HashSet<>(
+                List.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN));
+
+        return new DeviceState(new DeviceState.Configuration.Builder(DEVICE_STATE_HALF_OPENED,
+                "HALF_OPENED")
+                .setSystemProperties(systemProperties)
+                .setPhysicalProperties(physicalProperties)
+                .build());
+    }
+
+    /** Returns the {@link DeviceState.Configuration} that represents the opened state */
+    @NonNull
+    private DeviceState getOpenedDeviceState() {
+        Set<@DeviceState.SystemDeviceStateProperties Integer> systemProperties = new HashSet<>(
+                List.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY,
+                        PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE));
+        Set<@DeviceState.PhysicalDeviceStateProperties Integer> physicalProperties = new HashSet<>(
+                List.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN));
+
+        return new DeviceState(new DeviceState.Configuration.Builder(DEVICE_STATE_OPENED, "OPENED")
+                .setSystemProperties(systemProperties)
+                .setPhysicalProperties(physicalProperties)
+                .build());
+    }
+
+    /** Returns the {@link DeviceState.Configuration} that represents the rear display state. */
+    @NonNull
+    private DeviceState getRearDisplayDeviceState() {
+        Set<@DeviceState.SystemDeviceStateProperties Integer> systemProperties = new HashSet<>(
+                List.of(PROPERTY_EMULATED_ONLY,
+                        PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY,
+                        PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST, PROPERTY_FEATURE_REAR_DISPLAY));
+
+        return new DeviceState(new DeviceState.Configuration.Builder(DEVICE_STATE_REAR_DISPLAY,
+                "REAR_DISPLAY_STATE")
+                .setSystemProperties(systemProperties)
+                .build());
+    }
+
+    /** Returns the {@link DeviceState.Configuration} that represents the dual display state. */
+    @NonNull
+    private DeviceState getDualDisplayDeviceState() {
+        Set<@DeviceState.SystemDeviceStateProperties Integer> systemProperties = new HashSet<>(
+                List.of(PROPERTY_EMULATED_ONLY, PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP,
+                        PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST,
+                        PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL,
+                        PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE,
+                        PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY,
+                        PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT));
+
+        return new DeviceState(new DeviceState.Configuration.Builder(
+                DEVICE_STATE_CONCURRENT_INNER_DEFAULT, "CONCURRENT_INNER_DEFAULT")
+                .setSystemProperties(systemProperties)
+                .build());
+    }
 }
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
index 42e41d5..bc8643f 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
@@ -17,13 +17,12 @@
 package com.android.server.policy;
 
 import static android.hardware.SensorManager.SENSOR_DELAY_FASTEST;
-import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
-import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE_IDENTIFIER;
-import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE_IDENTIFIER;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.TYPE_EXTERNAL;
 
-import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
@@ -89,7 +88,7 @@
     // the conditions needed for availability.
     private final SparseArray<BooleanSupplier> mStateAvailabilityConditions = new SparseArray<>();
 
-    private final DeviceStateConfiguration[] mConfigurations;
+    private final DeviceStatePredicateWrapper[] mConfigurations;
 
     @GuardedBy("mLock")
     private final SparseBooleanArray mExternalDisplaysConnected = new SparseBooleanArray();
@@ -103,7 +102,7 @@
     @GuardedBy("mLock")
     private Listener mListener = null;
     @GuardedBy("mLock")
-    private int mLastReportedState = INVALID_DEVICE_STATE;
+    private int mLastReportedState = INVALID_DEVICE_STATE_IDENTIFIER;
     @GuardedBy("mLock")
     private SensorEvent mLastHingeAngleSensorEvent = null;
     @GuardedBy("mLock")
@@ -125,9 +124,9 @@
             @NonNull Sensor hingeAngleSensor,
             @NonNull Sensor hallSensor,
             @NonNull DisplayManager displayManager,
-            @NonNull DeviceStateConfiguration[] deviceStateConfigurations) {
+            @NonNull DeviceStatePredicateWrapper[] deviceStatePredicateWrappers) {
         this(new FeatureFlagsImpl(), context, sensorManager, hingeAngleSensor, hallSensor,
-                displayManager, deviceStateConfigurations);
+                displayManager, deviceStatePredicateWrappers);
     }
 
     @VisibleForTesting
@@ -138,23 +137,23 @@
             @NonNull Sensor hingeAngleSensor,
             @NonNull Sensor hallSensor,
             @NonNull DisplayManager displayManager,
-            @NonNull DeviceStateConfiguration[] deviceStateConfigurations) {
+            @NonNull DeviceStatePredicateWrapper[] deviceStatePredicateWrappers) {
 
-        Preconditions.checkArgument(deviceStateConfigurations.length > 0,
+        Preconditions.checkArgument(deviceStatePredicateWrappers.length > 0,
                 "Device state configurations array must not be empty");
 
         mHingeAngleSensor = hingeAngleSensor;
         mHallSensor = hallSensor;
         mDisplayManager = displayManager;
-        mConfigurations = deviceStateConfigurations;
+        mConfigurations = deviceStatePredicateWrappers;
         mIsDualDisplayBlockingEnabled = featureFlags.enableDualDisplayBlocking();
 
         sensorManager.registerListener(this, mHingeAngleSensor, SENSOR_DELAY_FASTEST);
         sensorManager.registerListener(this, mHallSensor, SENSOR_DELAY_FASTEST);
 
-        mOrderedStates = new DeviceState[deviceStateConfigurations.length];
-        for (int i = 0; i < deviceStateConfigurations.length; i++) {
-            final DeviceStateConfiguration configuration = deviceStateConfigurations[i];
+        mOrderedStates = new DeviceState[deviceStatePredicateWrappers.length];
+        for (int i = 0; i < deviceStatePredicateWrappers.length; i++) {
+            final DeviceStatePredicateWrapper configuration = deviceStatePredicateWrappers[i];
             mOrderedStates[i] = configuration.mDeviceState;
 
             assertUniqueDeviceStateIdentifier(configuration);
@@ -174,14 +173,14 @@
             // If any of the device states are thermal sensitive, i.e. it should be disabled when
             // the device is overheating, then we will update the list of supported states when
             // thermal status changes.
-            if (hasThermalSensitiveState(deviceStateConfigurations)) {
+            if (hasThermalSensitiveState(deviceStatePredicateWrappers)) {
                 powerManager.addThermalStatusListener(this);
             }
 
             // If any of the device states are power sensitive, i.e. it should be disabled when
             // power save mode is enabled, then we will update the list of supported states when
             // power save mode is toggled.
-            if (hasPowerSaveSensitiveState(deviceStateConfigurations)) {
+            if (hasPowerSaveSensitiveState(deviceStatePredicateWrappers)) {
                 IntentFilter filter = new IntentFilter(
                         PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL);
                 BroadcastReceiver receiver = new BroadcastReceiver() {
@@ -198,7 +197,7 @@
         }
     }
 
-    private void assertUniqueDeviceStateIdentifier(DeviceStateConfiguration configuration) {
+    private void assertUniqueDeviceStateIdentifier(DeviceStatePredicateWrapper configuration) {
         if (mStateConditions.get(configuration.mDeviceState.getIdentifier()) != null) {
             throw new IllegalArgumentException("Device state configurations must have unique"
                     + " device state identifiers, found duplicated identifier: "
@@ -206,12 +205,12 @@
         }
     }
 
-    private void initialiseStateConditions(DeviceStateConfiguration configuration) {
+    private void initialiseStateConditions(DeviceStatePredicateWrapper configuration) {
         mStateConditions.put(configuration.mDeviceState.getIdentifier(), () ->
                 configuration.mActiveStatePredicate.test(this));
     }
 
-    private void initialiseStateAvailabilityConditions(DeviceStateConfiguration configuration) {
+    private void initialiseStateAvailabilityConditions(DeviceStatePredicateWrapper configuration) {
             mStateAvailabilityConditions.put(configuration.mDeviceState.getIdentifier(), () ->
                     configuration.mAvailabilityPredicate.test(this));
     }
@@ -250,13 +249,12 @@
 
     @GuardedBy("mLock")
     private boolean isStateSupported(DeviceState deviceState) {
-        if (isThermalStatusCriticalOrAbove(mThermalStatus)
-                && deviceState.hasFlag(
-                DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
+        if (isThermalStatusCriticalOrAbove(mThermalStatus) && deviceState.hasProperty(
+                PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
             return false;
         }
-        if (mPowerSaveModeEnabled && deviceState.hasFlag(
-                DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
+        if (mPowerSaveModeEnabled && deviceState.hasProperty(
+                PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
             return false;
         }
         if (mIsDualDisplayBlockingEnabled
@@ -270,7 +268,7 @@
 
     /** Computes the current device state and notifies the listener of a change, if needed. */
     void notifyDeviceStateChangedIfNeeded() {
-        int stateToReport = INVALID_DEVICE_STATE;
+        int stateToReport = INVALID_DEVICE_STATE_IDENTIFIER;
         Listener listener;
         synchronized (mLock) {
             if (mListener == null) {
@@ -279,7 +277,7 @@
 
             listener = mListener;
 
-            int newState = INVALID_DEVICE_STATE;
+            int newState = INVALID_DEVICE_STATE_IDENTIFIER;
             for (int i = 0; i < mOrderedStates.length; i++) {
                 int state = mOrderedStates[i].getIdentifier();
                 if (DEBUG) {
@@ -307,18 +305,18 @@
                     break;
                 }
             }
-            if (newState == INVALID_DEVICE_STATE) {
+            if (newState == INVALID_DEVICE_STATE_IDENTIFIER) {
                 Slog.e(TAG, "No declared device states match any of the required conditions.");
                 dumpSensorValues();
             }
 
-            if (newState != INVALID_DEVICE_STATE && newState != mLastReportedState) {
+            if (newState != INVALID_DEVICE_STATE_IDENTIFIER && newState != mLastReportedState) {
                 mLastReportedState = newState;
                 stateToReport = newState;
             }
         }
 
-        if (stateToReport != INVALID_DEVICE_STATE) {
+        if (stateToReport != INVALID_DEVICE_STATE_IDENTIFIER) {
             listener.onStateChanged(stateToReport);
         }
     }
@@ -441,7 +439,7 @@
         writer.println("  Predicates:");
 
         for (int i = 0; i < mConfigurations.length; i++) {
-            final DeviceStateConfiguration configuration = mConfigurations[i];
+            final DeviceStatePredicateWrapper configuration = mConfigurations[i];
             final Predicate<FoldableDeviceStateProvider> predicate =
                     configuration.mActiveStatePredicate;
 
@@ -452,22 +450,23 @@
     }
 
     /**
-     * Configuration for a single device state, contains information about the state like
+     * Configuration wrapper for a single device state, contains information about the state like
      * identifier, name, flags and a predicate that should return true if the state should
      * be selected.
      */
-    public static class DeviceStateConfiguration {
+    public static class DeviceStatePredicateWrapper {
         private final DeviceState mDeviceState;
         private final Predicate<FoldableDeviceStateProvider> mActiveStatePredicate;
         private final Predicate<FoldableDeviceStateProvider> mAvailabilityPredicate;
 
-        private DeviceStateConfiguration(
+        private DeviceStatePredicateWrapper(
                 @NonNull DeviceState deviceState,
                 @NonNull Predicate<FoldableDeviceStateProvider> predicate) {
             this(deviceState, predicate, ALLOWED);
         }
 
-        private DeviceStateConfiguration(
+        /** Create a configuration with availability and availability predicate **/
+        private DeviceStatePredicateWrapper(
                 @NonNull DeviceState deviceState,
                 @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate,
                 @NonNull Predicate<FoldableDeviceStateProvider> availabilityPredicate) {
@@ -477,38 +476,22 @@
             mAvailabilityPredicate = availabilityPredicate;
         }
 
-        public static DeviceStateConfiguration createConfig(
-                @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to =
-                        MAXIMUM_DEVICE_STATE_IDENTIFIER) int identifier,
-                @NonNull String name,
-                @DeviceState.DeviceStateFlags int flags,
+        /** Create a configuration with an active state predicate **/
+        public static DeviceStatePredicateWrapper createConfig(
+                @NonNull DeviceState deviceState,
                 @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate
         ) {
-            return new DeviceStateConfiguration(new DeviceState(identifier, name, flags),
-                    activeStatePredicate);
+            return new DeviceStatePredicateWrapper(deviceState, activeStatePredicate);
         }
 
-        public static DeviceStateConfiguration createConfig(
-                @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to =
-                        MAXIMUM_DEVICE_STATE_IDENTIFIER) int identifier,
-                @NonNull String name,
-                @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate
-        ) {
-            return new DeviceStateConfiguration(new DeviceState(identifier, name, /* flags= */ 0),
-                    activeStatePredicate);
-        }
-
-        /** Create a configuration with availability predicate **/
-        public static DeviceStateConfiguration createConfig(
-                @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to =
-                        MAXIMUM_DEVICE_STATE_IDENTIFIER) int identifier,
-                @NonNull String name,
-                @DeviceState.DeviceStateFlags int flags,
+        /** Create a configuration with availability and active state predicate **/
+        public static DeviceStatePredicateWrapper createConfig(
+                @NonNull DeviceState deviceState,
                 @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate,
                 @NonNull Predicate<FoldableDeviceStateProvider> availabilityPredicate
         ) {
-            return new DeviceStateConfiguration(new DeviceState(identifier, name, flags),
-                    activeStatePredicate, availabilityPredicate);
+            return new DeviceStatePredicateWrapper(deviceState, activeStatePredicate,
+                    availabilityPredicate);
         }
 
         /**
@@ -536,25 +519,20 @@
          *    so the switch from the inner display to the outer will become only when the device
          *    is fully closed.
          *
-         * @param identifier state identifier
-         * @param name state name
-         * @param flags state flags
+         * @param deviceState {@link DeviceState} that corresponds to this state.
          * @param minClosedAngleDegrees minimum (inclusive) hinge angle value for the closed state
          * @param maxClosedAngleDegrees maximum (non-inclusive) hinge angle value for the closed
          *                              state
          * @param tentModeSwitchAngleDegrees the angle when this state should switch when unfolding
          * @return device state configuration
          */
-        public static DeviceStateConfiguration createTentModeClosedState(
-                @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to =
-                        MAXIMUM_DEVICE_STATE_IDENTIFIER) int identifier,
-                @NonNull String name,
-                @DeviceState.DeviceStateFlags int flags,
+        public static DeviceStatePredicateWrapper createTentModeClosedState(
+                @NonNull DeviceState deviceState,
                 int minClosedAngleDegrees,
                 int maxClosedAngleDegrees,
                 int tentModeSwitchAngleDegrees
         ) {
-            return new DeviceStateConfiguration(new DeviceState(identifier, name, flags),
+            return new DeviceStatePredicateWrapper(deviceState,
                     (stateContext) -> {
                         final boolean hallSensorClosed = stateContext.isHallSensorClosed();
                         final float hingeAngle = stateContext.getHingeAngle();
@@ -564,9 +542,9 @@
                         final int switchingDegrees =
                                 isScreenOn ? tentModeSwitchAngleDegrees : maxClosedAngleDegrees;
 
-                        final int closedDeviceState = identifier;
+                        final int closedDeviceState = deviceState.getIdentifier();
                         final boolean isLastStateClosed = lastState == closedDeviceState
-                                || lastState == INVALID_DEVICE_STATE;
+                                || lastState == INVALID_DEVICE_STATE_IDENTIFIER;
 
                         final boolean shouldBeClosedBecauseTentMode = isLastStateClosed
                                 && hingeAngle >= minClosedAngleDegrees
@@ -671,21 +649,21 @@
         }
     }
 
-    private static boolean hasThermalSensitiveState(DeviceStateConfiguration[] deviceStates) {
+    private static boolean hasThermalSensitiveState(DeviceStatePredicateWrapper[] deviceStates) {
         for (int i = 0; i < deviceStates.length; i++) {
-            DeviceStateConfiguration state = deviceStates[i];
-            if (state.mDeviceState
-                    .hasFlag(DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
+            DeviceStatePredicateWrapper state = deviceStates[i];
+            if (state.mDeviceState.hasProperty(
+                    PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
                 return true;
             }
         }
         return false;
     }
 
-    private static boolean hasPowerSaveSensitiveState(DeviceStateConfiguration[] deviceStates) {
+    private static boolean hasPowerSaveSensitiveState(DeviceStatePredicateWrapper[] deviceStates) {
         for (int i = 0; i < deviceStates.length; i++) {
-            if (deviceStates[i].mDeviceState
-                    .hasFlag(DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
+            if (deviceStates[i].mDeviceState.hasProperty(
+                    PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
                 return true;
             }
         }
diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java
index 8d01b7a..901f24d 100644
--- a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java
+++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java
@@ -48,6 +48,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
 import android.view.Display;
+import android.view.DisplayInfo;
 import android.view.Surface;
 
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -629,7 +630,11 @@
     }
 
     private void sendScreenRotation(int rotation) {
-        when(mDisplay.getRotation()).thenReturn(rotation);
+        doAnswer(invocation -> {
+            final DisplayInfo displayInfo = invocation.getArgument(0);
+            displayInfo.rotation = rotation;
+            return null;
+        }).when(mDisplay).getDisplayInfo(any());
         mDisplayListenerCaptor.getAllValues().forEach((l) -> l.onDisplayChanged(DEFAULT_DISPLAY));
     }
 
diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java
index 930f4a6..c9bbfee 100644
--- a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java
+++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java
@@ -30,7 +30,7 @@
 import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED;
 import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL;
 import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL;
-import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createConfig;
+import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStatePredicateWrapper.createConfig;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -59,8 +59,10 @@
 import android.testing.AndroidTestingRunner;
 import android.view.Display;
 
+import androidx.annotation.NonNull;
+
 import com.android.server.devicestate.DeviceStateProvider.Listener;
-import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration;
+import com.android.server.policy.FoldableDeviceStateProvider.DeviceStatePredicateWrapper;
 import com.android.server.policy.feature.flags.FakeFeatureFlagsImpl;
 import com.android.server.policy.feature.flags.Flags;
 
@@ -73,6 +75,11 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.internal.util.reflection.FieldSetter;
 
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * Unit tests for {@link FoldableDeviceStateProvider}.
  * <p/>
@@ -81,8 +88,14 @@
 @RunWith(AndroidTestingRunner.class)
 public final class FoldableDeviceStateProviderTest {
 
-    private final ArgumentCaptor<DeviceState[]> mDeviceStateArrayCaptor = ArgumentCaptor.forClass(
-            DeviceState[].class);
+    private static final Set<Integer> EMPTY_PROPERTY_SET = new HashSet<>();
+    private static final Set<Integer> THERMAL_PROPERTY_SET = new HashSet<>(
+            Arrays.asList(DeviceState.PROPERTY_EMULATED_ONLY,
+                    DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL,
+                    DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE));
+
+    private final ArgumentCaptor<DeviceState[]> mDeviceStateArrayCaptor =
+            ArgumentCaptor.forClass(DeviceState[].class);
     @Captor
     private ArgumentCaptor<Integer> mIntegerCaptor;
     @Captor
@@ -122,22 +135,17 @@
     public void create_duplicatedDeviceStateIdentifiers_throwsException() {
         assertThrows(IllegalArgumentException.class,
                 () -> createProvider(
-                        createConfig(
-                                /* identifier= */ 0, /* name= */ "ONE", (c) -> true),
-                        createConfig(
-                                /* identifier= */ 0, /* name= */ "TWO", (c) -> true)
+                        createConfig(createDeviceState(0, "ONE"), (c) -> true),
+                        createConfig(createDeviceState(0, "TWO"), (c) -> true)
                 ));
     }
 
     @Test
     public void create_allMatchingStatesDefaultsToTheFirstIdentifier() {
         createProvider(
-                createConfig(
-                        /* identifier= */ 1, /* name= */ "ONE", (c) -> true),
-                createConfig(
-                        /* identifier= */ 2, /* name= */ "TWO", (c) -> true),
-                createConfig(
-                        /* identifier= */ 3, /* name= */ "THREE", (c) -> true)
+                createConfig(createDeviceState(1, "ONE"), (c) -> true),
+                createConfig(createDeviceState(2, "TWO"), (c) -> true),
+                createConfig(createDeviceState(3, "THREE"), (c) -> true)
         );
 
         Listener listener = mock(Listener.class);
@@ -146,9 +154,9 @@
         verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
                 eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         final DeviceState[] expectedStates = new DeviceState[]{
-                new DeviceState(1, "ONE", /* flags= */ 0),
-                new DeviceState(2, "TWO", /* flags= */ 0),
-                new DeviceState(3, "THREE", /* flags= */ 0),
+                createDeviceState(1, "ONE", EMPTY_PROPERTY_SET),
+                createDeviceState(2, "TWO", EMPTY_PROPERTY_SET),
+                createDeviceState(3, "THREE", EMPTY_PROPERTY_SET)
         };
         assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
 
@@ -159,14 +167,10 @@
     @Test
     public void create_multipleMatchingStatesDefaultsToTheLowestIdentifier() {
         createProvider(
-                createConfig(
-                        /* identifier= */ 1, /* name= */ "ONE", (c) -> false),
-                createConfig(
-                        /* identifier= */ 3, /* name= */ "THREE", (c) -> false),
-                createConfig(
-                        /* identifier= */ 4, /* name= */ "FOUR", (c) -> true),
-                createConfig(
-                        /* identifier= */ 2, /* name= */ "TWO", (c) -> true)
+                createConfig(createDeviceState(1, "ONE"), (c) -> false),
+                createConfig(createDeviceState(3, "THREE"), (c) -> false),
+                createConfig(createDeviceState(4, "FOUR"), (c) -> true),
+                createConfig(createDeviceState(2, "TWO"), (c) -> true)
         );
 
         Listener listener = mock(Listener.class);
@@ -178,9 +182,9 @@
 
     @Test
     public void test_hingeAngleUpdatedFirstTime_switchesToMatchingState() throws Exception {
-        createProvider(createConfig(/* identifier= */ 1, /* name= */ "ONE",
+        createProvider(createConfig(createDeviceState(1, "ONE"),
                         (c) -> c.getHingeAngle() < 90f),
-                createConfig(/* identifier= */ 2, /* name= */ "TWO",
+                createConfig(createDeviceState(2, "TWO"),
                         (c) -> c.getHingeAngle() >= 90f));
         Listener listener = mock(Listener.class);
         mProvider.setListener(listener);
@@ -195,9 +199,9 @@
 
     @Test
     public void test_hallSensorUpdatedFirstTime_switchesToMatchingState() throws Exception {
-        createProvider(createConfig(/* identifier= */ 1, /* name= */ "ONE",
+        createProvider(createConfig(createDeviceState(1, "ONE"),
                         (c) -> !c.isHallSensorClosed()),
-                createConfig(/* identifier= */ 2, /* name= */ "TWO",
+                createConfig(createDeviceState(2, "TWO"),
                         FoldableDeviceStateProvider::isHallSensorClosed));
         Listener listener = mock(Listener.class);
         mProvider.setListener(listener);
@@ -213,9 +217,9 @@
 
     @Test
     public void test_hingeAngleUpdatedSecondTime_switchesToMatchingState() throws Exception {
-        createProvider(createConfig(/* identifier= */ 1, /* name= */ "ONE",
+        createProvider(createConfig(createDeviceState(1, "ONE"),
                         (c) -> c.getHingeAngle() < 90f),
-                createConfig(/* identifier= */ 2, /* name= */ "TWO",
+                createConfig(createDeviceState(2, "TWO"),
                         (c) -> c.getHingeAngle() >= 90f));
         sendSensorEvent(mHingeAngleSensor, /* value= */ 30f);
         Listener listener = mock(Listener.class);
@@ -232,9 +236,9 @@
 
     @Test
     public void test_hallSensorUpdatedSecondTime_switchesToMatchingState() throws Exception {
-        createProvider(createConfig(/* identifier= */ 1, /* name= */ "ONE",
+        createProvider(createConfig(createDeviceState(1, "ONE"),
                         (c) -> !c.isHallSensorClosed()),
-                createConfig(/* identifier= */ 2, /* name= */ "TWO",
+                createConfig(createDeviceState(2, "TWO"),
                         FoldableDeviceStateProvider::isHallSensorClosed));
         sendSensorEvent(mHallSensor, /* value= */ 0f);
         Listener listener = mock(Listener.class);
@@ -252,9 +256,9 @@
 
     @Test
     public void test_invalidSensorValues_onStateChangedIsNotTriggered() throws Exception {
-        createProvider(createConfig(/* identifier= */ 1, /* name= */ "ONE",
+        createProvider(createConfig(createDeviceState(1, "ONE"),
                         (c) -> c.getHingeAngle() < 90f),
-                createConfig(/* identifier= */ 2, /* name= */ "TWO",
+                createConfig(createDeviceState(2, "TWO"),
                         (c) -> c.getHingeAngle() >= 90f));
         Listener listener = mock(Listener.class);
         mProvider.setListener(listener);
@@ -277,23 +281,21 @@
 
     @Test
     public void test_nullSensorValues_noExceptionThrown() throws Exception {
-        createProvider(createConfig( /* identifier= */ 1, /* name= */ "ONE",
-                        (c) -> c.getHingeAngle() < 90f));
+        createProvider(createConfig(createDeviceState(1, "ONE"),
+                (c) -> c.getHingeAngle() < 90f));
         sendInvalidSensorEvent(null);
     }
 
     @Test
     public void test_flagDisableWhenThermalStatusCritical() throws Exception {
-        createProvider(createConfig(/* identifier= */ 1, /* name= */ "CLOSED",
+        createProvider(createConfig(createDeviceState(1, "CLOSED"),
                         (c) -> c.getHingeAngle() < 5f),
-                createConfig(/* identifier= */ 2, /* name= */ "HALF_OPENED",
+                createConfig(createDeviceState(2, "HALF_OPENED"),
                         (c) -> c.getHingeAngle() < 90f),
-                createConfig(/* identifier= */ 3, /* name= */ "OPENED",
+                createConfig(createDeviceState(3, "OPENED"),
                         (c) -> c.getHingeAngle() < 180f),
-                createConfig(/* identifier= */ 4, /* name= */ "THERMAL_TEST",
-                        DeviceState.FLAG_EMULATED_ONLY
-                                | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
-                                | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE,
+                createConfig(
+                        createDeviceState(4, "THERMAL_TEST", THERMAL_PROPERTY_SET),
                         (c) -> true));
         Listener listener = mock(Listener.class);
         mProvider.setListener(listener);
@@ -301,13 +303,10 @@
                 eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         assertArrayEquals(
                 new DeviceState[]{
-                        new DeviceState(1, "CLOSED", 0 /* flags */),
-                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
-                        new DeviceState(3, "OPENED", 0 /* flags */),
-                        new DeviceState(4, "THERMAL_TEST",
-                                DeviceState.FLAG_EMULATED_ONLY
-                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
-                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)},
+                        createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+                        createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(4, "THERMAL_TEST", THERMAL_PROPERTY_SET)},
                 mDeviceStateArrayCaptor.getValue());
         clearInvocations(listener);
 
@@ -322,9 +321,9 @@
                 eq(SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL));
         assertArrayEquals(
                 new DeviceState[]{
-                        new DeviceState(1, "CLOSED", 0 /* flags */),
-                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
-                        new DeviceState(3, "OPENED", 0 /* flags */)},
+                        createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+                        createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET)},
                 mDeviceStateArrayCaptor.getValue());
         clearInvocations(listener);
 
@@ -334,28 +333,23 @@
                 eq(SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL));
         assertArrayEquals(
                 new DeviceState[]{
-                        new DeviceState(1, "CLOSED", 0 /* flags */),
-                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
-                        new DeviceState(3, "OPENED", 0 /* flags */),
-                        new DeviceState(4, "THERMAL_TEST",
-                                DeviceState.FLAG_EMULATED_ONLY
-                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
-                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)},
+                        createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+                        createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(4, "THERMAL_TEST", THERMAL_PROPERTY_SET)},
                 mDeviceStateArrayCaptor.getValue());
     }
 
     @Test
     public void test_flagDisableWhenPowerSaveEnabled() throws Exception {
-        createProvider(createConfig(/* identifier= */ 1, /* name= */ "CLOSED",
+        createProvider(createConfig(createDeviceState(1, "CLOSED"),
                         (c) -> c.getHingeAngle() < 5f),
-                createConfig(/* identifier= */ 2, /* name= */ "HALF_OPENED",
+                createConfig(createDeviceState(2, "HALF_OPENED"),
                         (c) -> c.getHingeAngle() < 90f),
-                createConfig(/* identifier= */ 3, /* name= */ "OPENED",
+                createConfig(createDeviceState(3, "OPENED"),
                         (c) -> c.getHingeAngle() < 180f),
-                createConfig(/* identifier= */ 4, /* name= */ "THERMAL_TEST",
-                        DeviceState.FLAG_EMULATED_ONLY
-                                | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
-                                | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE,
+                createConfig(
+                        createDeviceState(4, "THERMAL_TEST", THERMAL_PROPERTY_SET),
                         (c) -> true));
         mProvider.onPowerSaveModeChanged(false /* isPowerSaveModeEnabled */);
         Listener listener = mock(Listener.class);
@@ -365,13 +359,10 @@
                 eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         assertArrayEquals(
                 new DeviceState[]{
-                        new DeviceState(1, "CLOSED", 0 /* flags */),
-                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
-                        new DeviceState(3, "OPENED", 0 /* flags */),
-                        new DeviceState(4, "THERMAL_TEST",
-                                DeviceState.FLAG_EMULATED_ONLY
-                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
-                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
+                        createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+                        createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(4, "THERMAL_TEST", THERMAL_PROPERTY_SET)},
                 mDeviceStateArrayCaptor.getValue());
         clearInvocations(listener);
 
@@ -386,9 +377,9 @@
                 eq(SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED));
         assertArrayEquals(
                 new DeviceState[]{
-                        new DeviceState(1, "CLOSED", 0 /* flags */),
-                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
-                        new DeviceState(3, "OPENED", 0 /* flags */) },
+                        createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+                        createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET)},
                 mDeviceStateArrayCaptor.getValue());
         clearInvocations(listener);
 
@@ -398,13 +389,10 @@
                 eq(SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED));
         assertArrayEquals(
                 new DeviceState[]{
-                        new DeviceState(1, "CLOSED", 0 /* flags */),
-                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
-                        new DeviceState(3, "OPENED", 0 /* flags */),
-                        new DeviceState(4, "THERMAL_TEST",
-                                DeviceState.FLAG_EMULATED_ONLY
-                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
-                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
+                        createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+                        createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(4, "THERMAL_TEST", THERMAL_PROPERTY_SET)},
                 mDeviceStateArrayCaptor.getValue());
     }
 
@@ -413,13 +401,11 @@
         // Create a configuration where state TWO could be matched only if
         // the previous state was 'THREE'
         createProvider(
-                createConfig(
-                        /* identifier= */ 1, /* name= */ "ONE", (c) -> c.getHingeAngle() < 30f),
-                createConfig(
-                        /* identifier= */ 2, /* name= */ "TWO",
+                createConfig(createDeviceState(1, "ONE"),
+                        (c) -> c.getHingeAngle() < 30f),
+                createConfig(createDeviceState(2, "TWO"),
                         (c) -> c.getLastReportedDeviceState() == 3 && c.getHingeAngle() > 120f),
-                createConfig(
-                        /* identifier= */ 3, /* name= */ "THREE",
+                createConfig(createDeviceState(3, "THREE"),
                         (c) -> c.getHingeAngle() > 90f)
         );
         sendSensorEvent(mHingeAngleSensor, /* value= */ 0f);
@@ -448,8 +434,7 @@
     @Test
     public void isScreenOn_afterDisplayChangedToOn_returnsTrue() {
         createProvider(
-                createConfig(
-                        /* identifier= */ 1, /* name= */ "ONE", (c) -> true)
+                createConfig(createDeviceState(1, "ONE"), (c) -> true)
         );
 
         setScreenOn(true);
@@ -460,8 +445,7 @@
     @Test
     public void isScreenOn_afterDisplayChangedToOff_returnsFalse() {
         createProvider(
-                createConfig(
-                        /* identifier= */ 1, /* name= */ "ONE", (c) -> true)
+                createConfig(createDeviceState(1, "ONE"), (c) -> true)
         );
 
         setScreenOn(false);
@@ -472,8 +456,7 @@
     @Test
     public void isScreenOn_afterDisplayChangedToOnThenOff_returnsFalse() {
         createProvider(
-                createConfig(
-                        /* identifier= */ 1, /* name= */ "ONE", (c) -> true)
+                createConfig(createDeviceState(1, "ONE"), (c) -> true)
         );
 
         setScreenOn(true);
@@ -487,14 +470,14 @@
         when(mDisplayManager.getDisplays()).thenReturn(new Display[]{mDefaultDisplay});
         when(mDefaultDisplay.getType()).thenReturn(TYPE_INTERNAL);
 
-        createProvider(createConfig(/* identifier= */ 1, /* name= */ "CLOSED",
+        createProvider(createConfig(createDeviceState(1, "CLOSED"),
                         (c) -> c.getHingeAngle() < 5f),
-                createConfig(/* identifier= */ 2, /* name= */ "HALF_OPENED",
+                createConfig(createDeviceState(2, "HALF_OPENED"),
                         (c) -> c.getHingeAngle() < 90f),
-                createConfig(/* identifier= */ 3, /* name= */ "OPENED",
+                createConfig(createDeviceState(3, "OPENED"),
                         (c) -> c.getHingeAngle() < 180f),
-                createConfig(/* identifier= */ 4, /* name= */ "DUAL_DISPLAY", /* flags */ 0,
-                        (c) -> false, FoldableDeviceStateProvider::hasNoConnectedExternalDisplay));
+                createConfig(createDeviceState(4, "DUAL_DISPLAY"), (c) -> false,
+                        FoldableDeviceStateProvider::hasNoConnectedExternalDisplay));
 
         Listener listener = mock(Listener.class);
         mProvider.setListener(listener);
@@ -502,10 +485,11 @@
                 eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         assertThat(mDeviceStateArrayCaptor.getValue()).asList().containsExactly(
                 new DeviceState[]{
-                        new DeviceState(1, "CLOSED", 0 /* flags */),
-                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
-                        new DeviceState(3, "OPENED", 0 /* flags */),
-                        new DeviceState(4, "DUAL_DISPLAY", 0 /* flags */)}).inOrder();
+                        createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+                        createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(4, "DUAL_DISPLAY",
+                                EMPTY_PROPERTY_SET)}).inOrder();
 
         clearInvocations(listener);
 
@@ -520,9 +504,9 @@
                 eq(SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_ADDED));
         assertThat(mDeviceStateArrayCaptor.getValue()).asList().containsExactly(
                 new DeviceState[]{
-                        new DeviceState(1, "CLOSED", 0 /* flags */),
-                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
-                        new DeviceState(3, "OPENED", 0 /* flags */)}).inOrder();
+                        createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+                        createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET)}).inOrder();
         clearInvocations(listener);
 
         // The DUAL_DISPLAY state should be re-enabled.
@@ -532,10 +516,11 @@
                 eq(SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_REMOVED));
         assertThat(mDeviceStateArrayCaptor.getValue()).asList().containsExactly(
                 new DeviceState[]{
-                        new DeviceState(1, "CLOSED", 0 /* flags */),
-                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
-                        new DeviceState(3, "OPENED", 0 /* flags */),
-                        new DeviceState(4, "DUAL_DISPLAY", 0 /* flags */)}).inOrder();
+                        createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+                        createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(4, "DUAL_DISPLAY",
+                                EMPTY_PROPERTY_SET)}).inOrder();
     }
 
     @Test
@@ -543,14 +528,14 @@
         when(mDisplayManager.getDisplays()).thenReturn(new Display[]{mDefaultDisplay});
         when(mDefaultDisplay.getType()).thenReturn(TYPE_INTERNAL);
 
-        createProvider(createConfig(/* identifier= */ 1, /* name= */ "CLOSED",
+        createProvider(createConfig(createDeviceState(1, "CLOSED"),
                         (c) -> c.getHingeAngle() < 5f),
-                createConfig(/* identifier= */ 2, /* name= */ "HALF_OPENED",
+                createConfig(createDeviceState(2, "HALF_OPENED"),
                         (c) -> c.getHingeAngle() < 90f),
-                createConfig(/* identifier= */ 3, /* name= */ "OPENED",
+                createConfig(createDeviceState(3, "OPENED"),
                         (c) -> c.getHingeAngle() < 180f),
-                createConfig(/* identifier= */ 4, /* name= */ "DUAL_DISPLAY", /* flags */ 0,
-                        (c) -> false, FoldableDeviceStateProvider::hasNoConnectedExternalDisplay));
+                createConfig(createDeviceState(4, "DUAL_DISPLAY"),
+                        FoldableDeviceStateProvider::hasNoConnectedExternalDisplay));
 
         Listener listener = mock(Listener.class);
         mProvider.setListener(listener);
@@ -558,10 +543,11 @@
                 eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         assertThat(mDeviceStateArrayCaptor.getValue()).asList().containsExactly(
                 new DeviceState[]{
-                        new DeviceState(1, "CLOSED", 0 /* flags */),
-                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
-                        new DeviceState(3, "OPENED", 0 /* flags */),
-                        new DeviceState(4, "DUAL_DISPLAY", 0 /* flags */)}).inOrder();
+                        createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+                        createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(4, "DUAL_DISPLAY",
+                                EMPTY_PROPERTY_SET)}).inOrder();
 
         clearInvocations(listener);
 
@@ -579,10 +565,8 @@
     @Test
     public void hasNoConnectedDisplay_afterExternalDisplayAdded_returnsFalse() {
         createProvider(
-                createConfig(
-                        /* identifier= */ 1, /* name= */ "ONE",
-                        /* flags= */0, (c) -> true,
-                        FoldableDeviceStateProvider::hasNoConnectedExternalDisplay)
+                createConfig(createDeviceState(1, "ONE", Collections.emptySet()),
+                        (c) -> true, FoldableDeviceStateProvider::hasNoConnectedExternalDisplay)
         );
 
         addExternalDisplay(/* displayId */ 1);
@@ -594,9 +578,8 @@
     public void testOnDisplayAddedWithNullDisplayDoesNotThrowNPE() {
         createProvider(
                 createConfig(
-                        /* identifier= */ 1, /* name= */ "ONE",
-                        /* flags= */0, (c) -> true,
-                        FoldableDeviceStateProvider::hasNoConnectedExternalDisplay)
+                        createDeviceState(1, "ONE", Collections.emptySet()),
+                        (c) -> true, FoldableDeviceStateProvider::hasNoConnectedExternalDisplay)
         );
 
         when(mDisplayManager.getDisplay(1)).thenReturn(null);
@@ -608,9 +591,8 @@
     public void hasNoConnectedDisplay_afterExternalDisplayAddedAndRemoved_returnsTrue() {
         createProvider(
                 createConfig(
-                        /* identifier= */ 1, /* name= */ "ONE",
-                        /* flags= */0, (c) -> true,
-                        FoldableDeviceStateProvider::hasNoConnectedExternalDisplay)
+                        createDeviceState(1, "ONE", Collections.emptySet()),
+                        (c) -> true, FoldableDeviceStateProvider::hasNoConnectedExternalDisplay)
         );
 
         addExternalDisplay(/* displayId */ 1);
@@ -657,7 +639,7 @@
         mProvider.onSensorChanged(event);
     }
 
-    private void createProvider(DeviceStateConfiguration... configurations) {
+    private void createProvider(DeviceStatePredicateWrapper... configurations) {
         mProvider = new FoldableDeviceStateProvider(mFakeFeatureFlags, mContext, mSensorManager,
                 mHingeAngleSensor, mHallSensor, mDisplayManager, configurations);
         verify(mDisplayManager)
@@ -665,4 +647,23 @@
                         mDisplayListenerCaptor.capture(),
                         nullable(Handler.class));
     }
+
+    /**
+     * Returns a new {@link DeviceState} object
+     */
+    private DeviceState createDeviceState(int identifier,
+            @NonNull String name,
+            @NonNull Set<@DeviceState.DeviceStateProperties Integer> systemProperties) {
+        return new DeviceState(new DeviceState.Configuration.Builder(identifier, name)
+                .setSystemProperties(systemProperties)
+                .build());
+    }
+
+    /**
+     * Returns a new {@link DeviceState} object
+     */
+    private DeviceState createDeviceState(int identifier,
+            @NonNull String name) {
+        return createDeviceState(identifier, name, Collections.emptySet());
+    }
 }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e19f08c..73d830d 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -76,6 +76,7 @@
 import android.os.StrictMode;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.IStorageManager;
@@ -1089,6 +1090,7 @@
 
         final Context systemUiContext = activityThread.getSystemUiContext();
         systemUiContext.setTheme(DEFAULT_SYSTEM_THEME);
+        Trace.registerWithPerfetto();
     }
 
     /**
@@ -1132,7 +1134,7 @@
         ServiceManager.addService(Context.PLATFORM_COMPAT_SERVICE, platformCompat);
         ServiceManager.addService(Context.PLATFORM_COMPAT_NATIVE_SERVICE,
                 new PlatformCompatNative(platformCompat));
-        AppCompatCallbacks.install(new long[0]);
+        AppCompatCallbacks.install(new long[0], new long[0]);
         t.traceEnd();
 
         // FileIntegrityService responds to requests from apps and the system. It needs to run after
@@ -2774,9 +2776,12 @@
         t.traceEnd();
 
         // OnDevicePersonalizationSystemService
-        t.traceBegin("StartOnDevicePersonalizationSystemService");
-        mSystemServiceManager.startService(ON_DEVICE_PERSONALIZATION_SYSTEM_SERVICE_CLASS);
-        t.traceEnd();
+        if (!com.android.server.flags.Flags.enableOdpFeatureGuard()
+                || SystemProperties.getBoolean("ro.system_settings.service.odp_enabled", true)) {
+            t.traceBegin("StartOnDevicePersonalizationSystemService");
+            mSystemServiceManager.startService(ON_DEVICE_PERSONALIZATION_SYSTEM_SERVICE_CLASS);
+            t.traceEnd();
+        }
 
         // Profiling
         if (android.server.Flags.telemetryApisService()) {
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 d0913d2..58714a8 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
@@ -25,9 +25,9 @@
 import android.util.Slog
 import android.util.SparseLongArray
 import com.android.internal.annotations.GuardedBy
-import com.android.internal.os.BackgroundThread
 import com.android.modules.utils.BinaryXmlPullParser
 import com.android.modules.utils.BinaryXmlSerializer
+import com.android.server.IoThread
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.util.PermissionApex
@@ -47,7 +47,7 @@
     private val writeLock = Any()
 
     fun initialize() {
-        writeHandler = WriteHandler(BackgroundThread.getHandler().looper)
+        writeHandler = WriteHandler(IoThread.getHandler().looper)
     }
 
     /**
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 fc2eb26..c0d988d 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
@@ -20,27 +20,63 @@
 import android.companion.virtual.VirtualDeviceManager
 import android.os.Handler
 import android.os.UserHandle
+import android.permission.flags.Flags
 import android.util.ArrayMap
 import android.util.ArraySet
+import android.util.LongSparseArray
+import android.util.Slog
+import android.util.SparseArray
 import android.util.SparseBooleanArray
 import android.util.SparseIntArray
 import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.util.IntPair
 import com.android.server.appop.AppOpsCheckingServiceInterface
 import com.android.server.appop.AppOpsCheckingServiceInterface.AppOpsModeChangedListener
 import com.android.server.permission.access.AccessCheckingService
 import com.android.server.permission.access.AppOpUri
+import com.android.server.permission.access.DevicePermissionUri
+import com.android.server.permission.access.GetStateScope
 import com.android.server.permission.access.PackageUri
+import com.android.server.permission.access.PermissionUri
 import com.android.server.permission.access.UidUri
+import com.android.server.permission.access.appop.AppOpModes.MODE_ALLOWED
+import com.android.server.permission.access.appop.AppOpModes.MODE_FOREGROUND
+import com.android.server.permission.access.appop.AppOpModes.MODE_IGNORED
 import com.android.server.permission.access.collection.forEachIndexed
 import com.android.server.permission.access.collection.set
+import com.android.server.permission.access.permission.AppIdPermissionPolicy
+import com.android.server.permission.access.permission.DevicePermissionPolicy
+import com.android.server.permission.access.permission.PermissionFlags
+import com.android.server.permission.access.permission.PermissionService
 
 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 permissionPolicy =
+        service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy
+    private val devicePermissionPolicy =
+        service.getSchemePolicy(UidUri.SCHEME, DevicePermissionUri.SCHEME) as DevicePermissionPolicy
 
     private val context = service.context
+
+    // Maps appop code to its runtime permission
+    private val runtimeAppOpToPermissionNames = SparseArray<String>()
+
+    // Maps runtime permission to its appop codes
+    private val runtimePermissionNameToAppOp = ArrayMap<String, Int>()
+
+    private var foregroundableOps = SparseBooleanArray()
+
+    /* Maps foreground permissions to their background permission. Background permissions aren't
+    required to be runtime */
+    private val foregroundToBackgroundPermissionName = ArrayMap<String, String>()
+
+    /* Maps background permissions to their foreground permissions. Background permissions aren't
+    required to be runtime */
+    private val backgroundToForegroundPermissionNames = ArrayMap<String, ArraySet<String>>()
+
     private lateinit var handler: Handler
 
     @Volatile private var listeners = ArraySet<AppOpsModeChangedListener>()
@@ -69,11 +105,60 @@
     }
 
     override fun systemReady() {
-        // Not implemented because upgrades are handled automatically.
+        if (Flags.runtimePermissionAppopsMappingEnabled()) {
+            createPermissionAppOpMapping()
+            val permissionListener = OnPermissionFlagsChangedListener()
+            permissionPolicy.addOnPermissionFlagsChangedListener(permissionListener)
+            devicePermissionPolicy.addOnPermissionFlagsChangedListener(permissionListener)
+        }
+    }
+
+    private fun createPermissionAppOpMapping() {
+        val permissions = service.getState { with(permissionPolicy) { getPermissions() } }
+
+        for (appOpCode in 0 until AppOpsManager._NUM_OP) {
+            AppOpsManager.opToPermission(appOpCode)?.let { permissionName ->
+                // Multiple ops might map to a single permission but only one is considered the
+                // runtime appop calculations.
+                if (appOpCode == AppOpsManager.permissionToOpCode(permissionName)) {
+                    val permission = permissions[permissionName]!!
+                    if (permission.isRuntime) {
+                        runtimePermissionNameToAppOp[permissionName] = appOpCode
+                        runtimeAppOpToPermissionNames[appOpCode] = permissionName
+                        permission.permissionInfo.backgroundPermission?.let {
+                            backgroundPermissionName ->
+                            // Note: background permission may not be runtime,
+                            // e.g. microphone/camera.
+                            foregroundableOps[appOpCode] = true
+                            foregroundToBackgroundPermissionName[permissionName] =
+                                backgroundPermissionName
+                            backgroundToForegroundPermissionNames
+                                .getOrPut(backgroundPermissionName, ::ArraySet)
+                                .add(permissionName)
+                        }
+                    }
+                }
+            }
+        }
     }
 
     override fun getNonDefaultUidModes(uid: Int, persistentDeviceId: String): SparseIntArray {
-        return opNameMapToOpSparseArray(getUidModes(uid))
+        val appId = UserHandle.getAppId(uid)
+        val userId = UserHandle.getUserId(uid)
+        service.getState {
+            val modes =
+                with(appIdPolicy) { opNameMapToOpSparseArray(getAppOpModes(appId, userId)?.map) }
+            if (Flags.runtimePermissionAppopsMappingEnabled()) {
+                runtimePermissionNameToAppOp.forEachIndexed { _, permissionName, appOpCode ->
+                    val mode = getUidModeFromPermissionState(appId, userId, permissionName)
+                    if (mode != AppOpsManager.opToDefaultMode(appOpCode)) {
+                        modes[appOpCode] = mode
+                    }
+                }
+            }
+
+            return modes
+        }
     }
 
     override fun getNonDefaultPackageModes(packageName: String, userId: Int): SparseIntArray {
@@ -84,7 +169,13 @@
         val appId = UserHandle.getAppId(uid)
         val userId = UserHandle.getUserId(uid)
         val opName = AppOpsManager.opToPublicName(op)
-        return service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } }
+        val permissionName = runtimeAppOpToPermissionNames[op]
+
+        return if (!Flags.runtimePermissionAppopsMappingEnabled() || permissionName == null) {
+            service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } }
+        } else {
+            service.getState { getUidModeFromPermissionState(appId, userId, permissionName) }
+        }
     }
 
     private fun getUidModes(uid: Int): ArrayMap<String, Int>? {
@@ -93,13 +184,66 @@
         return service.getState { with(appIdPolicy) { getAppOpModes(appId, userId) } }?.map
     }
 
-    override fun setUidMode(uid: Int, persistentDeviceId: String, op: Int, mode: Int): Boolean {
+    private fun GetStateScope.getUidModeFromPermissionState(
+        appId: Int,
+        userId: Int,
+        permissionName: String
+    ): Int {
+        val permissionFlags =
+            with(permissionPolicy) { getPermissionFlags(appId, userId, permissionName) }
+        val backgroundPermissionName = foregroundToBackgroundPermissionName[permissionName]
+        val backgroundPermissionFlags =
+            if (backgroundPermissionName != null) {
+                with(permissionPolicy) {
+                    getPermissionFlags(appId, userId, backgroundPermissionName)
+                }
+            } else {
+                PermissionFlags.RUNTIME_GRANTED
+            }
+        val result = evaluateModeFromPermissionFlags(permissionFlags, backgroundPermissionFlags)
+        if (result != MODE_IGNORED) {
+            return result
+        }
+
+        val fullerPermissionName =
+            PermissionService.getFullerPermission(permissionName) ?: return result
+        return getUidModeFromPermissionState(appId, userId, fullerPermissionName)
+    }
+
+    private fun evaluateModeFromPermissionFlags(
+        foregroundFlags: Int,
+        backgroundFlags: Int = PermissionFlags.RUNTIME_GRANTED
+    ): Int =
+        if (PermissionFlags.isAppOpGranted(foregroundFlags)) {
+            if (PermissionFlags.isAppOpGranted(backgroundFlags)) {
+                MODE_ALLOWED
+            } else {
+                MODE_FOREGROUND
+            }
+        } else {
+            MODE_IGNORED
+        }
+
+    override fun setUidMode(uid: Int, persistentDeviceId: String, code: Int, mode: Int): Boolean {
+        if (
+            Flags.runtimePermissionAppopsMappingEnabled() && code in runtimeAppOpToPermissionNames
+        ) {
+            Slog.w(
+                LOG_TAG,
+                "Cannot set UID mode for runtime permission app op, uid = $uid," +
+                    " code = ${AppOpsManager.opToName(code)}," +
+                    " mode = ${AppOpsManager.modeToName(mode)}",
+                RuntimeException()
+            )
+            return false
+        }
+
         val appId = UserHandle.getAppId(uid)
         val userId = UserHandle.getUserId(uid)
-        val opName = AppOpsManager.opToPublicName(op)
-        var wasChanged = false
+        val appOpName = AppOpsManager.opToPublicName(code)
+        var wasChanged: Boolean
         service.mutateState {
-            wasChanged = with(appIdPolicy) { setAppOpMode(appId, userId, opName, mode) }
+            wasChanged = with(appIdPolicy) { setAppOpMode(appId, userId, appOpName, mode) }
         }
         return wasChanged
     }
@@ -114,10 +258,23 @@
     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) {
-        val opName = AppOpsManager.opToPublicName(op)
+    override fun setPackageMode(packageName: String, appOpCode: Int, mode: Int, userId: Int) {
+        val appOpName = AppOpsManager.opToPublicName(appOpCode)
+
+        if (
+            Flags.runtimePermissionAppopsMappingEnabled() &&
+                appOpCode in runtimeAppOpToPermissionNames
+        ) {
+            Slog.w(
+                LOG_TAG,
+                "(packageName=$packageName, userId=$userId)'s appop state" +
+                    " for runtime op $appOpName should not be set directly.",
+                RuntimeException()
+            )
+            return
+        }
         service.mutateState {
-            with(packagePolicy) { setAppOpMode(packageName, userId, opName, mode) }
+            with(packagePolicy) { setAppOpMode(packageName, userId, appOpName, mode) }
         }
     }
 
@@ -128,7 +285,7 @@
     }
 
     override fun removePackage(packageName: String, userId: Int): Boolean {
-        var wasChanged = false
+        var wasChanged: Boolean
         service.mutateState {
             wasChanged = with(packagePolicy) { removeAppOpModes(packageName, userId) }
         }
@@ -158,6 +315,13 @@
                     this[AppOpsManager.strOpToOp(op)] = true
                 }
             }
+            if (Flags.runtimePermissionAppopsMappingEnabled()) {
+                foregroundableOps.forEachIndexed { _, op, _ ->
+                    if (getUidMode(uid, persistentDeviceId, op) == AppOpsManager.MODE_FOREGROUND) {
+                        this[op] = true
+                    }
+                }
+            }
         }
     }
 
@@ -168,6 +332,13 @@
                     this[AppOpsManager.strOpToOp(op)] = true
                 }
             }
+            if (Flags.runtimePermissionAppopsMappingEnabled()) {
+                foregroundableOps.forEachIndexed { _, op, _ ->
+                    if (getPackageMode(packageName, op, userId) == AppOpsManager.MODE_FOREGROUND) {
+                        this[op] = true
+                    }
+                }
+            }
         }
     }
 
@@ -189,9 +360,10 @@
         }
     }
 
-    inner class OnAppIdAppOpModeChangedListener : AppIdAppOpPolicy.OnAppOpModeChangedListener() {
+    private inner class OnAppIdAppOpModeChangedListener :
+        AppIdAppOpPolicy.OnAppOpModeChangedListener() {
         // (uid, appOpCode) -> newMode
-        val pendingChanges = ArrayMap<Pair<Int, Int>, Int>()
+        private val pendingChanges = LongSparseArray<Int>()
 
         override fun onAppOpModeChanged(
             appId: Int,
@@ -202,7 +374,7 @@
         ) {
             val uid = UserHandle.getUid(userId, appId)
             val appOpCode = AppOpsManager.strOpToOp(appOpName)
-            val key = Pair(uid, appOpCode)
+            val key = IntPair.of(uid, appOpCode)
 
             pendingChanges[key] = newMode
         }
@@ -211,13 +383,15 @@
             val listenersLocal = listeners
             pendingChanges.forEachIndexed { _, key, mode ->
                 listenersLocal.forEachIndexed { _, listener ->
-                    val uid = key.first
-                    val appOpCode = key.second
+                    val uid = IntPair.first(key)
+                    val appOpCode = IntPair.second(key)
 
-                    listener.onUidModeChanged(uid,
+                    listener.onUidModeChanged(
+                        uid,
                         appOpCode,
                         mode,
-                        VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)
+                        VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT
+                    )
                 }
             }
 
@@ -228,7 +402,7 @@
     private inner class OnPackageAppOpModeChangedListener :
         PackageAppOpPolicy.OnAppOpModeChangedListener() {
         // (packageName, userId, appOpCode) -> newMode
-        val pendingChanges = ArrayMap<Triple<String, Int, Int>, Int>()
+        private val pendingChanges = ArrayMap<Triple<String, Int, Int>, Int>()
 
         override fun onAppOpModeChanged(
             packageName: String,
@@ -258,4 +432,130 @@
             pendingChanges.clear()
         }
     }
+
+    private inner class OnPermissionFlagsChangedListener :
+        AppIdPermissionPolicy.OnPermissionFlagsChangedListener,
+        DevicePermissionPolicy.OnDevicePermissionFlagsChangedListener {
+        // (uid, deviceId, appOpCode) -> newMode
+        private val pendingChanges = ArrayMap<Triple<Int, String, Int>, Int>()
+
+        override fun onPermissionFlagsChanged(
+            appId: Int,
+            userId: Int,
+            permissionName: String,
+            oldFlags: Int,
+            newFlags: Int
+        ) {
+            onDevicePermissionFlagsChanged(
+                appId,
+                userId,
+                VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT,
+                permissionName,
+                oldFlags,
+                newFlags
+            )
+        }
+
+        override fun onDevicePermissionFlagsChanged(
+            appId: Int,
+            userId: Int,
+            deviceId: String,
+            permissionName: String,
+            oldFlags: Int,
+            newFlags: Int
+        ) {
+            backgroundToForegroundPermissionNames[permissionName]?.let { foregroundPermissions ->
+                // This is a background permission; there may be multiple foreground permissions
+                // affected.
+                foregroundPermissions.forEachIndexed { _, foregroundPermissionName ->
+                    runtimePermissionNameToAppOp[foregroundPermissionName]?.let { appOpCode ->
+                        val foregroundPermissionFlags =
+                            getPermissionFlags(appId, userId, foregroundPermissionName)
+                        addPendingChangedModeIfNeeded(
+                            appId,
+                            userId,
+                            deviceId,
+                            appOpCode,
+                            foregroundPermissionFlags,
+                            oldFlags,
+                            foregroundPermissionFlags,
+                            newFlags
+                        )
+                    }
+                }
+            }
+                ?: foregroundToBackgroundPermissionName[permissionName]?.let { backgroundPermission
+                    ->
+                    runtimePermissionNameToAppOp[permissionName]?.let { appOpCode ->
+                        val backgroundPermissionFlags =
+                            getPermissionFlags(appId, userId, backgroundPermission)
+                        addPendingChangedModeIfNeeded(
+                            appId,
+                            userId,
+                            deviceId,
+                            appOpCode,
+                            oldFlags,
+                            backgroundPermissionFlags,
+                            newFlags,
+                            backgroundPermissionFlags
+                        )
+                    }
+                }
+                ?: runtimePermissionNameToAppOp[permissionName]?.let { appOpCode ->
+                    addPendingChangedModeIfNeeded(
+                        appId,
+                        userId,
+                        deviceId,
+                        appOpCode,
+                        oldFlags,
+                        PermissionFlags.RUNTIME_GRANTED,
+                        newFlags,
+                        PermissionFlags.RUNTIME_GRANTED
+                    )
+                }
+        }
+
+        private fun getPermissionFlags(appId: Int, userId: Int, permissionName: String): Int =
+            service.getState {
+                with(permissionPolicy) { getPermissionFlags(appId, userId, permissionName) }
+            }
+
+        private fun addPendingChangedModeIfNeeded(
+            appId: Int,
+            userId: Int,
+            deviceId: String,
+            appOpCode: Int,
+            oldForegroundFlags: Int,
+            oldBackgroundFlags: Int,
+            newForegroundFlags: Int,
+            newBackgroundFlags: Int,
+        ) {
+            val oldMode = evaluateModeFromPermissionFlags(oldForegroundFlags, oldBackgroundFlags)
+            val newMode = evaluateModeFromPermissionFlags(newForegroundFlags, newBackgroundFlags)
+
+            if (oldMode != newMode) {
+                val uid = UserHandle.getUid(userId, appId)
+                pendingChanges[Triple(uid, deviceId, appOpCode)] = newMode
+            }
+        }
+
+        override fun onStateMutated() {
+            val listenersLocal = listeners
+            pendingChanges.forEachIndexed { _, key, mode ->
+                listenersLocal.forEachIndexed { _, listener ->
+                    val uid = key.first
+                    val deviceId = key.second
+                    val appOpCode = key.third
+
+                    listener.onUidModeChanged(uid, appOpCode, mode, deviceId)
+                }
+            }
+
+            pendingChanges.clear()
+        }
+    }
+
+    companion object {
+        private val LOG_TAG = AppOpService::class.java.simpleName
+    }
 }
diff --git a/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt
new file mode 100644
index 0000000..827dd0e
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.collection
+
+import android.util.LongSparseArray
+
+inline fun <T> LongSparseArray<T>.allIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> LongSparseArray<T>.anyIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <T> LongSparseArray<T>.forEachIndexed(action: (Int, Long, T) -> Unit) {
+    for (index in 0 until size) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun <T> LongSparseArray<T>.forEachReversedIndexed(action: (Int, Long, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun <T> LongSparseArray<T>.getOrPut(key: Long, defaultValue: () -> T): T {
+    val index = indexOfKey(key)
+    return if (index >= 0) {
+        valueAt(index)
+    } else {
+        defaultValue().also { put(key, it) }
+    }
+}
+
+inline val <T> LongSparseArray<T>.lastIndex: Int
+    get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> LongSparseArray<T>.minusAssign(key: Long) {
+    delete(key)
+}
+
+inline fun <T> LongSparseArray<T>.noneIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> LongSparseArray<T>.removeAllIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+inline fun <T> LongSparseArray<T>.retainAllIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+inline val <T> LongSparseArray<T>.size: Int
+    get() = size()
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> LongSparseArray<T>.set(key: Long, value: T) {
+    put(key, value)
+}
diff --git a/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt
new file mode 100644
index 0000000..a582431
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.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.server.permission.access.collection
+
+import android.util.SparseIntArray
+
+inline fun SparseIntArray.allIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun SparseIntArray.anyIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun SparseIntArray.forEachIndexed(action: (Int, Int, Int) -> Unit) {
+    for (index in 0 until size) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun SparseIntArray.forEachReversedIndexed(action: (Int, Int, Int) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun SparseIntArray.getOrPut(key: Int, defaultValue: () -> Int): Int {
+    val index = indexOfKey(key)
+    return if (index >= 0) {
+        valueAt(index)
+    } else {
+        defaultValue().also { put(key, it) }
+    }
+}
+
+inline val SparseIntArray.lastIndex: Int
+    get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun SparseIntArray.minusAssign(key: Int) {
+    delete(key)
+}
+
+inline fun SparseIntArray.noneIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+fun SparseIntArray.remove(key: Int) {
+    delete(key)
+}
+
+fun SparseIntArray.remove(key: Int, defaultValue: Int): Int {
+    val index = indexOfKey(key)
+    return if (index >= 0) {
+        val oldValue = valueAt(index)
+        removeAt(index)
+        oldValue
+    } else {
+        defaultValue
+    }
+}
+
+inline fun SparseIntArray.removeAllIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+inline fun SparseIntArray.retainAllIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun SparseIntArray.set(key: Int, value: Int) {
+    put(key, value)
+}
+
+inline val SparseIntArray.size: Int
+    get() = size()
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 4b086b3..67df67f 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
@@ -227,25 +227,59 @@
             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
-                }
-            setPermissionFlags(appId, userId, permissionName, newFlags)
+            updatePermissionExemptFlags(
+                appId,
+                userId,
+                permission,
+                PermissionFlags.UPGRADE_EXEMPT,
+                0
+            )
         }
     }
 
+    fun MutateStateScope.updatePermissionExemptFlags(
+        appId: Int,
+        userId: Int,
+        permission: Permission,
+        exemptFlagMask: Int,
+        exemptFlagValues: Int
+    ) {
+        val permissionName = permission.name
+        val oldFlags = getPermissionFlags(appId, userId, permissionName)
+        var newFlags = (oldFlags andInv exemptFlagMask) or (exemptFlagValues and exemptFlagMask)
+        if (oldFlags == newFlags) {
+            return
+        }
+        val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
+        if (permission.isHardRestricted && !isExempt) {
+            newFlags = newFlags or PermissionFlags.RESTRICTION_REVOKED
+            // If the permission was policy fixed as granted but it is no longer on any of the
+            // allowlists we need to clear the policy fixed flag as allowlisting trumps policy i.e.
+            // policy cannot grant a non grantable permission.
+            if (PermissionFlags.isPermissionGranted(oldFlags)) {
+                newFlags = newFlags andInv PermissionFlags.POLICY_FIXED
+            }
+        } else {
+            newFlags = newFlags andInv PermissionFlags.RESTRICTION_REVOKED
+        }
+        newFlags =
+            if (
+                permission.isSoftRestricted && !isExempt &&
+                    !anyPackageInAppId(appId) {
+                        permissionName in it.androidPackage!!.requestedPermissions &&
+                            isSoftRestrictedPermissionExemptForPackage(it, permissionName)
+                    }
+            ) {
+                newFlags or PermissionFlags.SOFT_RESTRICTED
+            } else {
+                newFlags andInv PermissionFlags.SOFT_RESTRICTED
+            }
+        if (oldFlags == newFlags) {
+            return
+        }
+        setPermissionFlags(appId, userId, permissionName, newFlags)
+    }
+
     override fun MutateStateScope.onPackageUninstalled(
         packageName: String,
         appId: Int,
@@ -1118,7 +1152,12 @@
                     newFlags andInv PermissionFlags.RESTRICTION_REVOKED
                 }
             newFlags =
-                if (permission.isSoftRestricted && !isExempt) {
+                if (
+                    permission.isSoftRestricted && !isExempt &&
+                        !requestingPackageStates.anyIndexed { _, it ->
+                            isSoftRestrictedPermissionExemptForPackage(it, permissionName)
+                        }
+                ) {
                     newFlags or PermissionFlags.SOFT_RESTRICTED
                 } else {
                     newFlags andInv PermissionFlags.SOFT_RESTRICTED
@@ -1398,6 +1437,17 @@
         }
     }
 
+    // See also SoftRestrictedPermissionPolicy.mayGrantPermission()
+    private fun isSoftRestrictedPermissionExemptForPackage(
+        packageState: PackageState,
+        permissionName: String
+    ): Boolean =
+        when (permissionName) {
+            Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ->
+                packageState.androidPackage!!.targetSdkVersion >= Build.VERSION_CODES.Q
+            else -> false
+        }
+
     private inline fun MutateStateScope.anyPackageInAppId(
         appId: Int,
         state: AccessState = newState,
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 b9d89c2..7c75138 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
@@ -346,8 +346,18 @@
         return flags.hasBits(RUNTIME_GRANTED)
     }
 
-    fun isAppOpGranted(flags: Int): Boolean =
-        isPermissionGranted(flags) && !flags.hasBits(APP_OP_REVOKED)
+    fun isAppOpGranted(flags: Int): Boolean {
+        if (!isPermissionGranted(flags)) {
+            return false
+        }
+        if (flags.hasAnyBit(MASK_RESTRICTED)) {
+            return false
+        }
+        if (flags.hasBits(APP_OP_REVOKED)) {
+            return false
+        }
+        return true
+    }
 
     fun toApiFlags(flags: Int): Int {
         var apiFlags = 0
@@ -467,9 +477,7 @@
         if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT)) {
             flags = flags or UPGRADE_EXEMPT
         }
-        // We ignore whether FLAG_PERMISSION_APPLY_RESTRICTION is set here because previously
-        // platform may be relying on the old restorePermissionState() to get it correct later.
-        if (!flags.hasAnyBit(MASK_EXEMPT)) {
+        if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION)) {
             if (permission.isHardRestricted) {
                 flags = flags or RESTRICTION_REVOKED
             }
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 0704c8f..b32c544 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
@@ -88,7 +88,6 @@
 import com.android.server.pm.PackageManagerLocal
 import com.android.server.pm.UserManagerInternal
 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.LegacyPermissionSettings
 import com.android.server.pm.permission.LegacyPermissionState
@@ -97,7 +96,6 @@
 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 java.io.FileDescriptor
 import java.io.PrintWriter
 import java.util.concurrent.CompletableFuture
@@ -1006,25 +1004,14 @@
         }
 
         if (isGranted && oldFlags.hasBits(PermissionFlags.SOFT_RESTRICTED)) {
-            // TODO: Refactor SoftRestrictedPermissionPolicy.
-            val softRestrictedPermissionPolicy =
-                SoftRestrictedPermissionPolicy.forPermission(
-                    context,
-                    AndroidPackageUtils.generateAppInfoWithoutState(androidPackage),
-                    androidPackage,
-                    UserHandle.of(userId),
-                    permissionName
+            if (reportError) {
+                Slog.e(
+                    LOG_TAG,
+                    "$methodName: Cannot grant soft-restricted non-exempt permission" +
+                        " $permissionName to package $packageName"
                 )
-            if (!softRestrictedPermissionPolicy.mayGrantPermission()) {
-                if (reportError) {
-                    Slog.e(
-                        LOG_TAG,
-                        "$methodName: Cannot grant soft-restricted non-exempt permission" +
-                            " $permissionName to package $packageName"
-                    )
-                }
-                return
             }
+            return
         }
 
         val newFlags = PermissionFlags.updateRuntimePermissionGranted(oldFlags, isGranted)
@@ -1125,6 +1112,8 @@
         )
         enforceCallingOrSelfAnyPermission(
             "getAllPermissionStates",
+            Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+            Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
             Manifest.permission.GET_RUNTIME_PERMISSIONS
         )
 
@@ -1135,25 +1124,23 @@
             return emptyMap()
         }
 
-        val permissionFlagsMap =
-            service.getState {
+        service.getState {
+            val permissionFlags =
                 if (deviceId == VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT) {
                     with(policy) { getAllPermissionFlags(packageState.appId, userId) }
                 } else {
                     with(devicePolicy) {
                         getAllPermissionFlags(packageState.appId, deviceId, userId)
                     }
-                }
+                } ?: return emptyMap()
+            val permissionStates = ArrayMap<String, PermissionState>()
+            permissionFlags.forEachIndexed { _, permissionName, flags ->
+                val granted = isPermissionGranted(packageState, userId, permissionName, deviceId)
+                val apiFlags = PermissionFlags.toApiFlags(flags)
+                permissionStates[permissionName] = PermissionState(granted, apiFlags)
             }
-                ?: return emptyMap()
-
-        val permissionStates = ArrayMap<String, PermissionState>()
-        permissionFlagsMap.forEachIndexed { _, permissionName, flags ->
-            val granted = PermissionFlags.isPermissionGranted(flags)
-            val apiFlags = PermissionFlags.toApiFlags(flags)
-            permissionStates[permissionName] = PermissionState(granted, apiFlags)
+            return permissionStates
         }
-        return permissionStates
     }
 
     override fun isPermissionRevokedByPolicy(
@@ -1852,10 +1839,19 @@
         allowlistedFlags: Int,
         userId: Int
     ) {
+        var exemptMask = 0
+        if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM)) {
+            exemptMask = exemptMask or PermissionFlags.SYSTEM_EXEMPT
+        }
+        if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE)) {
+            exemptMask = exemptMask or PermissionFlags.UPGRADE_EXEMPT
+        }
+        if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) {
+            exemptMask = exemptMask or PermissionFlags.INSTALLER_EXEMPT
+        }
+
         service.mutateState {
             with(policy) {
-                val permissionsFlags = getUidPermissionFlags(appId, userId) ?: return@mutateState
-
                 val permissions = getPermissions()
                 androidPackage.requestedPermissions.forEachIndexed { _, requestedPermission ->
                     val permission = permissions[requestedPermission]
@@ -1863,81 +1859,8 @@
                         return@forEachIndexed
                     }
 
-                    val oldFlags = permissionsFlags[requestedPermission] ?: 0
-                    val wasGranted = PermissionFlags.isPermissionGranted(oldFlags)
-
-                    var newFlags = oldFlags
-                    var mask = 0
-                    var allowlistFlagsCopy = allowlistedFlags
-                    while (allowlistFlagsCopy != 0) {
-                        val flag = 1 shl allowlistFlagsCopy.countTrailingZeroBits()
-                        allowlistFlagsCopy = allowlistFlagsCopy and flag.inv()
-                        when (flag) {
-                            PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM -> {
-                                mask = mask or PermissionFlags.SYSTEM_EXEMPT
-                                newFlags =
-                                    if (permissionNames.contains(requestedPermission)) {
-                                        newFlags or PermissionFlags.SYSTEM_EXEMPT
-                                    } else {
-                                        newFlags andInv PermissionFlags.SYSTEM_EXEMPT
-                                    }
-                            }
-                            PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE -> {
-                                mask = mask or PermissionFlags.UPGRADE_EXEMPT
-                                newFlags =
-                                    if (permissionNames.contains(requestedPermission)) {
-                                        newFlags or PermissionFlags.UPGRADE_EXEMPT
-                                    } else {
-                                        newFlags andInv PermissionFlags.UPGRADE_EXEMPT
-                                    }
-                            }
-                            PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER -> {
-                                mask = mask or PermissionFlags.INSTALLER_EXEMPT
-                                newFlags =
-                                    if (permissionNames.contains(requestedPermission)) {
-                                        newFlags or PermissionFlags.INSTALLER_EXEMPT
-                                    } else {
-                                        newFlags andInv PermissionFlags.INSTALLER_EXEMPT
-                                    }
-                            }
-                        }
-                    }
-
-                    if (oldFlags == newFlags) {
-                        return@forEachIndexed
-                    }
-
-                    val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
-
-                    // If the permission is policy fixed as granted but it is no longer
-                    // on any of the allowlists we need to clear the policy fixed flag
-                    // as allowlisting trumps policy i.e. policy cannot grant a non
-                    // grantable permission.
-                    if (oldFlags.hasBits(PermissionFlags.POLICY_FIXED)) {
-                        if (!isExempt && wasGranted) {
-                            mask = mask or PermissionFlags.POLICY_FIXED
-                            newFlags = newFlags andInv PermissionFlags.POLICY_FIXED
-                        }
-                    }
-
-                    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)
+                    var exemptFlags = if (requestedPermission in permissionNames) exemptMask else 0
+                    updatePermissionExemptFlags(appId, userId, permission, exemptMask, exemptFlags)
                 }
             }
         }
@@ -2905,5 +2828,8 @@
             } else {
                 emptySet<String>()
             }
+
+        fun getFullerPermission(permissionName: String): String? =
+            FULLER_PERMISSIONS[permissionName]
     }
 }
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 1c71a62..1d225ba 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -30,7 +30,10 @@
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME_IMPLICIT;
 
 import static org.junit.Assert.assertThrows;
-import static org.mockito.Mockito.any;
+import static org.mockito.AdditionalMatchers.and;
+import static org.mockito.AdditionalMatchers.not;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
@@ -40,6 +43,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.view.Display;
+import android.view.inputmethod.ImeTracker;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
@@ -56,7 +60,7 @@
  * Test the behavior of {@link DefaultImeVisibilityApplier} when performing or applying the IME
  * visibility state.
  *
- * Build/Install/Run:
+ * <p>Build/Install/Run:
  * atest FrameworksInputMethodSystemServerTests:DefaultImeVisibilityApplierTest
  */
 @RunWith(AndroidJUnit4.class)
@@ -75,7 +79,8 @@
     public void testPerformShowIme() throws Exception {
         synchronized (ImfLock.class) {
             mVisibilityApplier.performShowIme(new Binder() /* showInputToken */,
-                    null /* statsToken */, 0 /* showFlags */, null, SHOW_SOFT_INPUT);
+                    ImeTracker.Token.empty(), 0 /* showFlags */, null /* resultReceiver */,
+                    SHOW_SOFT_INPUT);
         }
         verifyShowSoftInput(false, true, 0 /* showFlags */);
     }
@@ -84,46 +89,66 @@
     public void testPerformHideIme() throws Exception {
         synchronized (ImfLock.class) {
             mVisibilityApplier.performHideIme(new Binder() /* hideInputToken */,
-                    null /* statsToken */, null, HIDE_SOFT_INPUT);
+                    ImeTracker.Token.empty(), null /* resultReceiver */, HIDE_SOFT_INPUT);
         }
         verifyHideSoftInput(false, true);
     }
 
     @Test
     public void testApplyImeVisibility_throwForInvalidState() {
-        assertThrows(IllegalArgumentException.class,
-                () -> mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_INVALID));
+        assertThrows(IllegalArgumentException.class, () -> {
+            synchronized (ImfLock.class) {
+                mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(),
+                        STATE_INVALID);
+            }
+        });
     }
 
     @Test
     public void testApplyImeVisibility_showIme() {
-        mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_SHOW_IME);
-        verify(mMockWindowManagerInternal).showImePostLayout(eq(mWindowToken), any());
+        final var statsToken = ImeTracker.Token.empty();
+        synchronized (ImfLock.class) {
+            mVisibilityApplier.applyImeVisibility(mWindowToken, statsToken, STATE_SHOW_IME);
+        }
+        verify(mMockWindowManagerInternal).showImePostLayout(eq(mWindowToken), eq(statsToken));
     }
 
     @Test
     public void testApplyImeVisibility_hideIme() {
-        mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME);
-        verify(mMockWindowManagerInternal).hideIme(eq(mWindowToken), anyInt(), any());
+        final var statsToken = ImeTracker.Token.empty();
+        synchronized (ImfLock.class) {
+            mVisibilityApplier.applyImeVisibility(mWindowToken, statsToken, STATE_HIDE_IME);
+        }
+        verify(mMockWindowManagerInternal).hideIme(eq(mWindowToken), anyInt() /* displayId */,
+                eq(statsToken));
     }
 
     @Test
     public void testApplyImeVisibility_hideImeExplicit() throws Exception {
         mInputMethodManagerService.mImeWindowVis = IME_ACTIVE;
-        mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME_EXPLICIT);
+        synchronized (ImfLock.class) {
+            mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(),
+                    STATE_HIDE_IME_EXPLICIT);
+        }
         verifyHideSoftInput(true, true);
     }
 
     @Test
     public void testApplyImeVisibility_hideNotAlways() throws Exception {
         mInputMethodManagerService.mImeWindowVis = IME_ACTIVE;
-        mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME_NOT_ALWAYS);
+        synchronized (ImfLock.class) {
+            mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(),
+                    STATE_HIDE_IME_NOT_ALWAYS);
+        }
         verifyHideSoftInput(true, true);
     }
 
     @Test
     public void testApplyImeVisibility_showImeImplicit() throws Exception {
-        mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_SHOW_IME_IMPLICIT);
+        synchronized (ImfLock.class) {
+            mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(),
+                    STATE_SHOW_IME_IMPLICIT);
+        }
         verifyShowSoftInput(true, true, 0 /* showFlags */);
     }
 
@@ -135,21 +160,21 @@
         mInputMethodManagerService.setAttachedClientForTesting(null);
         startInputOrWindowGainedFocus(mWindowToken, SOFT_INPUT_STATE_ALWAYS_VISIBLE);
 
+        final var statsToken = ImeTracker.Token.empty();
         synchronized (ImfLock.class) {
             final int displayIdToShowIme = mInputMethodManagerService.getDisplayIdToShowImeLocked();
             // Verify hideIme will apply the expected displayId when the default IME
             // visibility applier app STATE_HIDE_IME.
-            mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME);
+            mVisibilityApplier.applyImeVisibility(mWindowToken, statsToken, STATE_HIDE_IME);
             verify(mInputMethodManagerService.mWindowManagerInternal).hideIme(
-                    eq(mWindowToken), eq(displayIdToShowIme), eq(null));
+                    eq(mWindowToken), eq(displayIdToShowIme), eq(statsToken));
         }
     }
 
     @Test
     public void testShowImeScreenshot() {
         synchronized (ImfLock.class) {
-            mVisibilityApplier.showImeScreenshot(mWindowToken, Display.DEFAULT_DISPLAY,
-                    null /* statsToken */);
+            mVisibilityApplier.showImeScreenshot(mWindowToken, Display.DEFAULT_DISPLAY);
         }
 
         verify(mMockImeTargetVisibilityPolicy).showImeScreenshot(eq(mWindowToken),
@@ -174,17 +199,20 @@
         synchronized (ImfLock.class) {
             // Simulate the system hides the IME when switching IME services in different users.
             // (e.g. unbinding the IME from the current user to the profile user)
+            final var statsToken = ImeTracker.Token.empty();
             final int displayIdToShowIme = mInputMethodManagerService.getDisplayIdToShowImeLocked();
-            mInputMethodManagerService.hideCurrentInputLocked(mWindowToken, null, 0, null,
+            mInputMethodManagerService.hideCurrentInputLocked(mWindowToken,
+                    statsToken, 0 /* flags */, null /* resultReceiver */,
                     HIDE_SWITCH_USER);
             mInputMethodManagerService.onUnbindCurrentMethodByReset();
 
             // Expects applyImeVisibility() -> hideIme() will be called to notify WM for syncing
             // the IME hidden state.
-            verify(mVisibilityApplier).applyImeVisibility(eq(mWindowToken), any(),
-                    eq(STATE_HIDE_IME));
+            // The unbind will cancel the previous stats token, and create a new one internally.
+            verify(mVisibilityApplier).applyImeVisibility(
+                    eq(mWindowToken), any(), eq(STATE_HIDE_IME));
             verify(mInputMethodManagerService.mWindowManagerInternal).hideIme(
-                    eq(mWindowToken), eq(displayIdToShowIme), eq(null));
+                    eq(mWindowToken), eq(displayIdToShowIme), and(not(eq(statsToken)), notNull()));
         }
     }
 
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
index fae5f86..a22cacb 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
@@ -39,9 +39,12 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.notNull;
+
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputMethodManager;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -58,7 +61,7 @@
  * Test the behavior of {@link ImeVisibilityStateComputer} and {@link ImeVisibilityApplier} when
  * requesting the IME visibility.
  *
- * Build/Install/Run:
+ * <p> Build/Install/Run:
  * atest FrameworksInputMethodSystemServerTests:ImeVisibilityStateComputerTest
  */
 @RunWith(AndroidJUnit4.class)
@@ -91,7 +94,8 @@
     @Test
     public void testRequestImeVisibility_showImplicit() {
         initImeTargetWindowState(mWindowToken);
-        boolean res = mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT);
+        boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(),
+                InputMethodManager.SHOW_IMPLICIT);
         mComputer.requestImeVisibility(mWindowToken, res);
 
         final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
@@ -106,7 +110,7 @@
     @Test
     public void testRequestImeVisibility_showExplicit() {
         initImeTargetWindowState(mWindowToken);
-        boolean res = mComputer.onImeShowFlags(null, 0 /* showFlags */);
+        boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */);
         mComputer.requestImeVisibility(mWindowToken, res);
 
         final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
@@ -125,7 +129,7 @@
     @Test
     public void testRequestImeVisibility_showExplicit_thenShowImplicit() {
         initImeTargetWindowState(mWindowToken);
-        mComputer.onImeShowFlags(null, 0 /* showFlags */);
+        mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */);
         assertThat(mComputer.mRequestedShowExplicitly).isTrue();
 
         mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT);
@@ -139,10 +143,10 @@
     @Test
     public void testRequestImeVisibility_showForced_thenShowExplicit() {
         initImeTargetWindowState(mWindowToken);
-        mComputer.onImeShowFlags(null, InputMethodManager.SHOW_FORCED);
+        mComputer.onImeShowFlags(ImeTracker.Token.empty(), InputMethodManager.SHOW_FORCED);
         assertThat(mComputer.mShowForced).isTrue();
 
-        mComputer.onImeShowFlags(null, 0 /* showFlags */);
+        mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */);
         assertThat(mComputer.mShowForced).isTrue();
     }
 
@@ -152,7 +156,8 @@
         mComputer.getImePolicy().setA11yRequestNoSoftKeyboard(SHOW_MODE_HIDDEN);
 
         initImeTargetWindowState(mWindowToken);
-        boolean res = mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT);
+        boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(),
+                InputMethodManager.SHOW_IMPLICIT);
         mComputer.requestImeVisibility(mWindowToken, res);
 
         final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
@@ -170,7 +175,8 @@
         mComputer.getImePolicy().setImeHiddenByDisplayPolicy(true);
 
         initImeTargetWindowState(mWindowToken);
-        boolean res = mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT);
+        boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(),
+                InputMethodManager.SHOW_IMPLICIT);
         mComputer.requestImeVisibility(mWindowToken, res);
 
         final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
@@ -188,7 +194,8 @@
         mComputer.setInputShown(true);
 
         initImeTargetWindowState(mWindowToken);
-        assertThat(mComputer.canHideIme(null, InputMethodManager.HIDE_NOT_ALWAYS)).isTrue();
+        assertThat(mComputer.canHideIme(ImeTracker.Token.empty(),
+                InputMethodManager.HIDE_NOT_ALWAYS)).isTrue();
         mComputer.requestImeVisibility(mWindowToken, false);
 
         final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
@@ -281,7 +288,7 @@
         final ArgumentCaptor<ImeVisibilityResult> resultCaptor = ArgumentCaptor.forClass(
                 ImeVisibilityResult.class);
         verify(mInputMethodManagerService).onApplyImeVisibilityFromComputer(targetCaptor.capture(),
-                resultCaptor.capture());
+                notNull() /* statsToken */, resultCaptor.capture());
         final IBinder imeInputTarget = targetCaptor.getValue();
         final ImeVisibilityResult result = resultCaptor.getValue();
 
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index a1be00a..b9c5b36 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -27,6 +27,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -200,6 +201,7 @@
         when(mMockUserManagerInternal.isUserRunning(anyInt())).thenReturn(true);
         when(mMockUserManagerInternal.getProfileIds(anyInt(), anyBoolean()))
                 .thenReturn(new int[] {0});
+        when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[] {0});
         when(mMockActivityManagerInternal.isSystemReady()).thenReturn(true);
         when(mMockPackageManagerInternal.getPackageUid(anyString(), anyLong(), anyInt()))
                 .thenReturn(Binder.getCallingUid());
@@ -277,8 +279,9 @@
                     .setCurrentMethodVisible();
         }
         verify(mMockInputMethod, times(showSoftInput ? 1 : 0))
-                .showSoftInput(any(), any(),
-                        showFlags != NO_VERIFY_SHOW_FLAGS ? eq(showFlags) : anyInt(), any());
+                .showSoftInput(any() /* showInputToken */ , notNull() /* statsToken */,
+                        showFlags != NO_VERIFY_SHOW_FLAGS ? eq(showFlags) : anyInt() /* flags*/,
+                        any() /* resultReceiver */);
     }
 
     protected void verifyHideSoftInput(boolean setNotVisible, boolean hideSoftInput)
@@ -288,6 +291,7 @@
                     .setCurrentMethodNotVisible();
         }
         verify(mMockInputMethod, times(hideSoftInput ? 1 : 0))
-                .hideSoftInput(any(), any(), anyInt(), any());
+                .hideSoftInput(any() /* hideInputToken */, notNull() /* statsToken */,
+                        anyInt() /* flags */, any() /* resultReceiver */);
     }
 }
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
index a33e52f..e5d3153 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
@@ -91,8 +91,8 @@
     @Test
     public void testSoftInputShowHideHistoryDump_withNulls_doesntThrow() {
         var writer = new StringWriter();
-        var history = new InputMethodManagerService.SoftInputShowHideHistory();
-        history.addEntry(new InputMethodManagerService.SoftInputShowHideHistory.Entry(
+        var history = new SoftInputShowHideHistory();
+        history.addEntry(new SoftInputShowHideHistory.Entry(
                 null,
                 null,
                 null,
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index 4f27bc2..ea7bb8b 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -94,7 +94,7 @@
         "libutils",
         "netd_aidl_interface-V5-cpp",
     ],
-
+    compile_multilib: "both",
     dxflags: ["--multi-dex"],
 
     java_resources: [
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index d4b57f1..96e9ca0 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -275,6 +275,7 @@
         AndroidPackage::isUpdatableSystem,
         AndroidPackage::getEmergencyInstaller,
         AndroidPackage::isAllowCrossUidActivitySwitchFromBelow,
+        PackageImpl::isAppMetadataFileInApk,
     )
 
     override fun extraParams() = listOf(
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
index 66e0717..c54a94e 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
@@ -83,11 +83,7 @@
         }
 
         val bundle = service.getUriRelativeFilterGroups(PKG_ONE, listOf(DOMAIN_1, DOMAIN_2))
-        assertThat(bundle.keySet()).containsExactlyElementsIn(listOf(DOMAIN_1, DOMAIN_2))
-        assertThat(bundle.getParcelableArrayList(DOMAIN_1, UriRelativeFilterGroup::class.java))
-            .isEmpty()
-        assertThat(bundle.getParcelableArrayList(DOMAIN_2, UriRelativeFilterGroup::class.java))
-            .isEmpty()
+        assertThat(bundle.keySet()).isEmpty()
 
         val pathGroup = UriRelativeFilterGroup(UriRelativeFilterGroup.ACTION_ALLOW)
         pathGroup.addUriRelativeFilter(
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt
index cde46ab..96753b6 100644
--- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt
@@ -233,24 +233,6 @@
             .isEqualTo(expectedNewFlags)
     }
 
-    @Test
-    fun testOnPackageInstalled_restrictedPermissionsIsExempted_clearsRestrictionFlags() {
-        val oldFlags = PermissionFlags.SOFT_RESTRICTED or PermissionFlags.INSTALLER_EXEMPT
-        testOnPackageInstalled(
-            oldFlags,
-            permissionInfoFlags = PermissionInfo.FLAG_SOFT_RESTRICTED
-        ) {}
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.INSTALLER_EXEMPT
-        assertWithMessage(
-            "After onPackageInstalled() is called for a non-system app that requests a runtime" +
-                " soft restricted permission that is exempted. The actual permission flags" +
-                " $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
     private fun testOnPackageInstalled(
         oldFlags: Int,
         permissionInfoFlags: Int = 0,
diff --git a/services/tests/VpnTests/Android.bp b/services/tests/VpnTests/Android.bp
new file mode 100644
index 0000000..a5011a8
--- /dev/null
+++ b/services/tests/VpnTests/Android.bp
@@ -0,0 +1,45 @@
+//########################################################################
+// Build FrameworksVpnTests package
+//########################################################################
+package {
+    default_team: "trendy_team_fwk_core_networking",
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "Android-Apache-2.0"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "FrameworksVpnTests",
+    srcs: [
+        "java/**/*.java",
+        "java/**/*.kt",
+    ],
+    sdk_version: "core_platform", // tests can use @CorePlatformApi's
+    test_suites: ["device-tests"],
+    static_libs: [
+        "androidx.test.rules",
+        "frameworks-base-testutils",
+        "framework-protos",
+        "mockito-target-minus-junit4",
+        "net-tests-utils",
+        "platform-test-annotations",
+        "services.core",
+        "cts-net-utils",
+        "service-connectivity-tiramisu-pre-jarjar",
+    ],
+    libs: [
+        // order matters: classes in framework-connectivity are resolved before framework,
+        // meaning @hide APIs in framework-connectivity are resolved before @SystemApi
+        // stubs in framework
+        "framework-connectivity.impl",
+        "framework-connectivity-t.impl",
+        "framework",
+        "framework-res",
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+    ],
+}
diff --git a/services/tests/VpnTests/AndroidManifest.xml b/services/tests/VpnTests/AndroidManifest.xml
new file mode 100644
index 0000000..d884084
--- /dev/null
+++ b/services/tests/VpnTests/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.frameworks.tests.vpn">
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.frameworks.tests.vpn"
+        android:label="Frameworks VPN Tests" />
+</manifest>
\ No newline at end of file
diff --git a/services/tests/VpnTests/AndroidTest.xml b/services/tests/VpnTests/AndroidTest.xml
new file mode 100644
index 0000000..ebeeac7
--- /dev/null
+++ b/services/tests/VpnTests/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Runs VPN Tests.">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="FrameworksVpnTests.apk" />
+    </target_preparer>
+
+    <option name="test-tag" value="FrameworksVpnTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.frameworks.tests.vpn" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/services/tests/VpnTests/OWNERS b/services/tests/VpnTests/OWNERS
new file mode 100644
index 0000000..45ea251
--- /dev/null
+++ b/services/tests/VpnTests/OWNERS
@@ -0,0 +1,2 @@
+set noparent
+file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking
\ No newline at end of file
diff --git a/services/tests/VpnTests/java/android/net/Ikev2VpnProfileTest.java b/services/tests/VpnTests/java/android/net/Ikev2VpnProfileTest.java
new file mode 100644
index 0000000..180f54e
--- /dev/null
+++ b/services/tests/VpnTests/java/android/net/Ikev2VpnProfileTest.java
@@ -0,0 +1,563 @@
+/*
+ * 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 android.net;
+
+import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
+import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS_V6;
+import static android.net.cts.util.IkeSessionTestUtils.getTestIkeSessionParams;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.net.ipsec.ike.IkeKeyIdIdentification;
+import android.net.ipsec.ike.IkeTunnelConnectionParams;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.net.VpnProfile;
+import com.android.internal.org.bouncycastle.x509.X509V1CertificateGenerator;
+import com.android.net.module.util.ProxyUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.security.auth.x500.X500Principal;
+
+/** Unit tests for {@link Ikev2VpnProfile.Builder}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class Ikev2VpnProfileTest {
+    private static final String SERVER_ADDR_STRING = "1.2.3.4";
+    private static final String IDENTITY_STRING = "Identity";
+    private static final String USERNAME_STRING = "username";
+    private static final String PASSWORD_STRING = "pa55w0rd";
+    private static final String EXCL_LIST = "exclList";
+    private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
+    private static final int TEST_MTU = 1300;
+
+    private final ProxyInfo mProxy = ProxyInfo.buildDirectProxy(
+            SERVER_ADDR_STRING, -1, ProxyUtils.exclusionStringAsList(EXCL_LIST));
+
+    private X509Certificate mUserCert;
+    private X509Certificate mServerRootCa;
+    private PrivateKey mPrivateKey;
+
+    @Before
+    public void setUp() throws Exception {
+        mServerRootCa = generateRandomCertAndKeyPair().cert;
+
+        final CertificateAndKey userCertKey = generateRandomCertAndKeyPair();
+        mUserCert = userCertKey.cert;
+        mPrivateKey = userCertKey.key;
+    }
+
+    private Ikev2VpnProfile.Builder getBuilderWithDefaultOptions() {
+        final Ikev2VpnProfile.Builder builder =
+                new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING);
+
+        builder.setBypassable(true);
+        builder.setProxy(mProxy);
+        builder.setMaxMtu(TEST_MTU);
+        builder.setMetered(true);
+
+        return builder;
+    }
+
+    @Test
+    public void testBuildValidProfileWithOptions() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+        builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+        final Ikev2VpnProfile profile = builder.build();
+        assertNotNull(profile);
+
+        // Check non-auth parameters correctly stored
+        assertEquals(SERVER_ADDR_STRING, profile.getServerAddr());
+        assertEquals(IDENTITY_STRING, profile.getUserIdentity());
+        assertEquals(mProxy, profile.getProxyInfo());
+        assertTrue(profile.isBypassable());
+        assertTrue(profile.isMetered());
+        assertEquals(TEST_MTU, profile.getMaxMtu());
+        assertEquals(Ikev2VpnProfile.DEFAULT_ALGORITHMS, profile.getAllowedAlgorithms());
+    }
+
+    @Test
+    public void testBuildUsernamePasswordProfile() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+        builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+        final Ikev2VpnProfile profile = builder.build();
+        assertNotNull(profile);
+
+        assertEquals(USERNAME_STRING, profile.getUsername());
+        assertEquals(PASSWORD_STRING, profile.getPassword());
+        assertEquals(mServerRootCa, profile.getServerRootCaCert());
+
+        assertNull(profile.getPresharedKey());
+        assertNull(profile.getRsaPrivateKey());
+        assertNull(profile.getUserCert());
+    }
+
+    @Test
+    public void testBuildDigitalSignatureProfile() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+        builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+        final Ikev2VpnProfile profile = builder.build();
+        assertNotNull(profile);
+
+        assertEquals(profile.getUserCert(), mUserCert);
+        assertEquals(mPrivateKey, profile.getRsaPrivateKey());
+        assertEquals(profile.getServerRootCaCert(), mServerRootCa);
+
+        assertNull(profile.getPresharedKey());
+        assertNull(profile.getUsername());
+        assertNull(profile.getPassword());
+    }
+
+    @Test
+    public void testBuildPresharedKeyProfile() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+        builder.setAuthPsk(PSK_BYTES);
+        final Ikev2VpnProfile profile = builder.build();
+        assertNotNull(profile);
+
+        assertArrayEquals(PSK_BYTES, profile.getPresharedKey());
+
+        assertNull(profile.getServerRootCaCert());
+        assertNull(profile.getUsername());
+        assertNull(profile.getPassword());
+        assertNull(profile.getRsaPrivateKey());
+        assertNull(profile.getUserCert());
+    }
+
+    @Test
+    public void testBuildWithAllowedAlgorithmsAead() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+        builder.setAuthPsk(PSK_BYTES);
+
+        List<String> allowedAlgorithms =
+                Arrays.asList(
+                        IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
+                        IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305);
+        builder.setAllowedAlgorithms(allowedAlgorithms);
+
+        final Ikev2VpnProfile profile = builder.build();
+        assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms());
+    }
+
+    @Test
+    public void testBuildWithAllowedAlgorithmsNormal() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+        builder.setAuthPsk(PSK_BYTES);
+
+        List<String> allowedAlgorithms =
+                Arrays.asList(
+                        IpSecAlgorithm.AUTH_HMAC_SHA512,
+                        IpSecAlgorithm.AUTH_AES_XCBC,
+                        IpSecAlgorithm.AUTH_AES_CMAC,
+                        IpSecAlgorithm.CRYPT_AES_CBC,
+                        IpSecAlgorithm.CRYPT_AES_CTR);
+        builder.setAllowedAlgorithms(allowedAlgorithms);
+
+        final Ikev2VpnProfile profile = builder.build();
+        assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms());
+    }
+
+    @Test
+    public void testSetAllowedAlgorithmsEmptyList() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+        try {
+            builder.setAllowedAlgorithms(new ArrayList<>());
+            fail("Expected exception due to no valid algorithm set");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testSetAllowedAlgorithmsInvalidList() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+        try {
+            builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA256));
+            fail("Expected exception due to missing encryption");
+        } catch (IllegalArgumentException expected) {
+        }
+
+        try {
+            builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.CRYPT_AES_CBC));
+            fail("Expected exception due to missing authentication");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testSetAllowedAlgorithmsInsecureAlgorithm() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+        try {
+            builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_MD5));
+            fail("Expected exception due to insecure algorithm");
+        } catch (IllegalArgumentException expected) {
+        }
+
+        try {
+            builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA1));
+            fail("Expected exception due to insecure algorithm");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testBuildNoAuthMethodSet() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+        try {
+            builder.build();
+            fail("Expected exception due to lack of auth method");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testBuildExcludeLocalRoutesSet() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+        builder.setAuthPsk(PSK_BYTES);
+        builder.setLocalRoutesExcluded(true);
+
+        final Ikev2VpnProfile profile = builder.build();
+        assertNotNull(profile);
+        assertTrue(profile.areLocalRoutesExcluded());
+
+        builder.setBypassable(false);
+        try {
+            builder.build();
+            fail("Expected exception because excludeLocalRoutes should be set only"
+                    + " on the bypassable VPN");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testBuildInvalidMtu() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+        try {
+            builder.setMaxMtu(500);
+            fail("Expected exception due to too-small MTU");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    private void verifyVpnProfileCommon(VpnProfile profile) {
+        assertEquals(SERVER_ADDR_STRING, profile.server);
+        assertEquals(IDENTITY_STRING, profile.ipsecIdentifier);
+        assertEquals(mProxy, profile.proxy);
+        assertTrue(profile.isBypassable);
+        assertTrue(profile.isMetered);
+        assertEquals(TEST_MTU, profile.maxMtu);
+    }
+
+    @Test
+    public void testPskConvertToVpnProfile() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+        builder.setAuthPsk(PSK_BYTES);
+        final VpnProfile profile = builder.build().toVpnProfile();
+
+        verifyVpnProfileCommon(profile);
+        assertEquals(Ikev2VpnProfile.encodeForIpsecSecret(PSK_BYTES), profile.ipsecSecret);
+
+        // Check nothing else is set
+        assertEquals("", profile.username);
+        assertEquals("", profile.password);
+        assertEquals("", profile.ipsecUserCert);
+        assertEquals("", profile.ipsecCaCert);
+    }
+
+    @Test
+    public void testUsernamePasswordConvertToVpnProfile() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+        builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+        final VpnProfile profile = builder.build().toVpnProfile();
+
+        verifyVpnProfileCommon(profile);
+        assertEquals(USERNAME_STRING, profile.username);
+        assertEquals(PASSWORD_STRING, profile.password);
+        assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert);
+
+        // Check nothing else is set
+        assertEquals("", profile.ipsecUserCert);
+        assertEquals("", profile.ipsecSecret);
+    }
+
+    @Test
+    public void testRsaConvertToVpnProfile() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+        builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+        final VpnProfile profile = builder.build().toVpnProfile();
+
+        final String expectedSecret = Ikev2VpnProfile.PREFIX_INLINE
+                + Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded());
+        verifyVpnProfileCommon(profile);
+        assertEquals(Ikev2VpnProfile.certificateToPemString(mUserCert), profile.ipsecUserCert);
+        assertEquals(
+                expectedSecret,
+                profile.ipsecSecret);
+        assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert);
+
+        // Check nothing else is set
+        assertEquals("", profile.username);
+        assertEquals("", profile.password);
+    }
+
+    @Test
+    public void testPskFromVpnProfileDiscardsIrrelevantValues() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+        builder.setAuthPsk(PSK_BYTES);
+        final VpnProfile profile = builder.build().toVpnProfile();
+        profile.username = USERNAME_STRING;
+        profile.password = PASSWORD_STRING;
+        profile.ipsecCaCert = Ikev2VpnProfile.certificateToPemString(mServerRootCa);
+        profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert);
+
+        final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
+        assertNull(result.getUsername());
+        assertNull(result.getPassword());
+        assertNull(result.getUserCert());
+        assertNull(result.getRsaPrivateKey());
+        assertNull(result.getServerRootCaCert());
+    }
+
+    @Test
+    public void testUsernamePasswordFromVpnProfileDiscardsIrrelevantValues() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+        builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+        final VpnProfile profile = builder.build().toVpnProfile();
+        profile.ipsecSecret = new String(PSK_BYTES);
+        profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert);
+
+        final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
+        assertNull(result.getPresharedKey());
+        assertNull(result.getUserCert());
+        assertNull(result.getRsaPrivateKey());
+    }
+
+    @Test
+    public void testRsaFromVpnProfileDiscardsIrrelevantValues() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+        builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+        final VpnProfile profile = builder.build().toVpnProfile();
+        profile.username = USERNAME_STRING;
+        profile.password = PASSWORD_STRING;
+
+        final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
+        assertNull(result.getUsername());
+        assertNull(result.getPassword());
+        assertNull(result.getPresharedKey());
+    }
+
+    @Test
+    public void testPskConversionIsLossless() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+        builder.setAuthPsk(PSK_BYTES);
+        final Ikev2VpnProfile ikeProfile = builder.build();
+
+        assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+    }
+
+    @Test
+    public void testUsernamePasswordConversionIsLossless() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+        builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+        final Ikev2VpnProfile ikeProfile = builder.build();
+
+        assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+    }
+
+    @Test
+    public void testRsaConversionIsLossless() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+        builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+        final Ikev2VpnProfile ikeProfile = builder.build();
+
+        assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+    }
+
+    @Test
+    public void testBuildWithIkeTunConnParamsConvertToVpnProfile() throws Exception {
+        // Special keyId that contains delimiter character of VpnProfile
+        final byte[] keyId = "foo\0bar".getBytes();
+        final IkeTunnelConnectionParams tunnelParams = new IkeTunnelConnectionParams(
+                getTestIkeSessionParams(true /* testIpv6 */, new IkeKeyIdIdentification(keyId)),
+                CHILD_PARAMS);
+        final Ikev2VpnProfile ikev2VpnProfile = new Ikev2VpnProfile.Builder(tunnelParams).build();
+        final VpnProfile vpnProfile = ikev2VpnProfile.toVpnProfile();
+
+        assertEquals(VpnProfile.TYPE_IKEV2_FROM_IKE_TUN_CONN_PARAMS, vpnProfile.type);
+
+        // Username, password, server, ipsecIdentifier, ipsecCaCert, ipsecSecret, ipsecUserCert and
+        // getAllowedAlgorithms should not be set if IkeTunnelConnectionParams is set.
+        assertEquals("", vpnProfile.server);
+        assertEquals("", vpnProfile.ipsecIdentifier);
+        assertEquals("", vpnProfile.username);
+        assertEquals("", vpnProfile.password);
+        assertEquals("", vpnProfile.ipsecCaCert);
+        assertEquals("", vpnProfile.ipsecSecret);
+        assertEquals("", vpnProfile.ipsecUserCert);
+        assertEquals(0, vpnProfile.getAllowedAlgorithms().size());
+
+        // IkeTunnelConnectionParams should stay the same.
+        assertEquals(tunnelParams, vpnProfile.ikeTunConnParams);
+
+        // Convert to disk-stable format and then back to Ikev2VpnProfile should be the same.
+        final VpnProfile decodedVpnProfile =
+                VpnProfile.decode(vpnProfile.key, vpnProfile.encode());
+        final Ikev2VpnProfile convertedIkev2VpnProfile =
+                Ikev2VpnProfile.fromVpnProfile(decodedVpnProfile);
+        assertEquals(ikev2VpnProfile, convertedIkev2VpnProfile);
+    }
+
+    @Test
+    public void testConversionIsLosslessWithIkeTunConnParams() throws Exception {
+        final IkeTunnelConnectionParams tunnelParams =
+                new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
+        // Config authentication related fields is not required while building with
+        // IkeTunnelConnectionParams.
+        final Ikev2VpnProfile ikeProfile = new Ikev2VpnProfile.Builder(tunnelParams).build();
+        assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+    }
+
+    @Test
+    public void testAutomaticNattAndIpVersionConversionIsLossless() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+        builder.setAutomaticNattKeepaliveTimerEnabled(true);
+        builder.setAutomaticIpVersionSelectionEnabled(true);
+
+        builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+        final Ikev2VpnProfile ikeProfile = builder.build();
+
+        assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+    }
+
+    @Test
+    public void testAutomaticNattAndIpVersionDefaults() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+        builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+        final Ikev2VpnProfile ikeProfile = builder.build();
+
+        assertEquals(false, ikeProfile.isAutomaticNattKeepaliveTimerEnabled());
+        assertEquals(false, ikeProfile.isAutomaticIpVersionSelectionEnabled());
+    }
+
+    @Test
+    public void testEquals() throws Exception {
+        // Verify building without IkeTunnelConnectionParams
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+        builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+        assertEquals(builder.build(), builder.build());
+
+        // Verify building with IkeTunnelConnectionParams
+        final IkeTunnelConnectionParams tunnelParams =
+                new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
+        final IkeTunnelConnectionParams tunnelParams2 =
+                new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
+        assertEquals(new Ikev2VpnProfile.Builder(tunnelParams).build(),
+                new Ikev2VpnProfile.Builder(tunnelParams2).build());
+    }
+
+    @Test
+    public void testBuildProfileWithNullProxy() throws Exception {
+        final Ikev2VpnProfile ikev2VpnProfile =
+                new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING)
+                        .setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa)
+                        .build();
+
+        // ProxyInfo should be null for the profile without setting ProxyInfo.
+        assertNull(ikev2VpnProfile.getProxyInfo());
+
+        // ProxyInfo should stay null after performing toVpnProfile() and fromVpnProfile()
+        final VpnProfile vpnProfile = ikev2VpnProfile.toVpnProfile();
+        assertNull(vpnProfile.proxy);
+
+        final Ikev2VpnProfile convertedIkev2VpnProfile = Ikev2VpnProfile.fromVpnProfile(vpnProfile);
+        assertNull(convertedIkev2VpnProfile.getProxyInfo());
+    }
+
+    private static class CertificateAndKey {
+        public final X509Certificate cert;
+        public final PrivateKey key;
+
+        CertificateAndKey(X509Certificate cert, PrivateKey key) {
+            this.cert = cert;
+            this.key = key;
+        }
+    }
+
+    private static CertificateAndKey generateRandomCertAndKeyPair() throws Exception {
+        final Date validityBeginDate =
+                new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1L));
+        final Date validityEndDate =
+                new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1L));
+
+        // Generate a keypair
+        final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+        keyPairGenerator.initialize(512);
+        final KeyPair keyPair = keyPairGenerator.generateKeyPair();
+
+        final X500Principal dnName = new X500Principal("CN=test.android.com");
+        final X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
+        certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
+        certGen.setSubjectDN(dnName);
+        certGen.setIssuerDN(dnName);
+        certGen.setNotBefore(validityBeginDate);
+        certGen.setNotAfter(validityEndDate);
+        certGen.setPublicKey(keyPair.getPublic());
+        certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
+
+        final X509Certificate cert = certGen.generate(keyPair.getPrivate(), "AndroidOpenSSL");
+        return new CertificateAndKey(cert, keyPair.getPrivate());
+    }
+}
diff --git a/services/tests/VpnTests/java/android/net/VpnManagerTest.java b/services/tests/VpnTests/java/android/net/VpnManagerTest.java
new file mode 100644
index 0000000..f5b83f0
--- /dev/null
+++ b/services/tests/VpnTests/java/android/net/VpnManagerTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assume.assumeFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.test.mock.MockContext;
+import android.util.SparseArray;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.net.VpnProfile;
+import com.android.internal.util.MessageUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link VpnManager}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class VpnManagerTest {
+
+    private static final String PKG_NAME = "fooPackage";
+
+    private static final String SERVER_ADDR_STRING = "1.2.3.4";
+    private static final String IDENTITY_STRING = "Identity";
+    private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
+
+    private IVpnManager mMockService;
+    private VpnManager mVpnManager;
+    private final MockContext mMockContext =
+            new MockContext() {
+                @Override
+                public String getOpPackageName() {
+                    return PKG_NAME;
+                }
+            };
+
+    @Before
+    public void setUp() throws Exception {
+        assumeFalse("Skipping test because watches don't support VPN",
+                InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_WATCH));
+        mMockService = mock(IVpnManager.class);
+        mVpnManager = new VpnManager(mMockContext, mMockService);
+    }
+
+    @Test
+    public void testProvisionVpnProfilePreconsented() throws Exception {
+        final PlatformVpnProfile profile = getPlatformVpnProfile();
+        when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
+                .thenReturn(true);
+
+        // Expect there to be no intent returned, as consent has already been granted.
+        assertNull(mVpnManager.provisionVpnProfile(profile));
+        verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
+    }
+
+    @Test
+    public void testProvisionVpnProfileNeedsConsent() throws Exception {
+        final PlatformVpnProfile profile = getPlatformVpnProfile();
+        when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
+                .thenReturn(false);
+
+        // Expect intent to be returned, as consent has not already been granted.
+        final Intent intent = mVpnManager.provisionVpnProfile(profile);
+        assertNotNull(intent);
+
+        final ComponentName expectedComponentName =
+                ComponentName.unflattenFromString(
+                        "com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog");
+        assertEquals(expectedComponentName, intent.getComponent());
+        verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
+    }
+
+    @Test
+    public void testDeleteProvisionedVpnProfile() throws Exception {
+        mVpnManager.deleteProvisionedVpnProfile();
+        verify(mMockService).deleteVpnProfile(eq(PKG_NAME));
+    }
+
+    @Test
+    public void testStartProvisionedVpnProfile() throws Exception {
+        mVpnManager.startProvisionedVpnProfile();
+        verify(mMockService).startVpnProfile(eq(PKG_NAME));
+    }
+
+    @Test
+    public void testStopProvisionedVpnProfile() throws Exception {
+        mVpnManager.stopProvisionedVpnProfile();
+        verify(mMockService).stopVpnProfile(eq(PKG_NAME));
+    }
+
+    private Ikev2VpnProfile getPlatformVpnProfile() throws Exception {
+        return new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING)
+                .setBypassable(true)
+                .setMaxMtu(1300)
+                .setMetered(true)
+                .setAuthPsk(PSK_BYTES)
+                .build();
+    }
+
+    @Test
+    public void testVpnTypesEqual() throws Exception {
+        SparseArray<String> vmVpnTypes = MessageUtils.findMessageNames(
+                new Class[] { VpnManager.class }, new String[]{ "TYPE_VPN_" });
+        SparseArray<String> nativeVpnType = MessageUtils.findMessageNames(
+                new Class[] { NativeVpnType.class }, new String[]{ "" });
+
+        // TYPE_VPN_NONE = -1 is only defined in VpnManager.
+        assertEquals(vmVpnTypes.size() - 1, nativeVpnType.size());
+        for (int i = VpnManager.TYPE_VPN_SERVICE; i < vmVpnTypes.size(); i++) {
+            assertEquals(vmVpnTypes.get(i), "TYPE_VPN_" + nativeVpnType.get(i));
+        }
+    }
+}
diff --git a/services/tests/VpnTests/java/com/android/internal/net/VpnProfileTest.java b/services/tests/VpnTests/java/com/android/internal/net/VpnProfileTest.java
new file mode 100644
index 0000000..acbe8b8
--- /dev/null
+++ b/services/tests/VpnTests/java/com/android/internal/net/VpnProfileTest.java
@@ -0,0 +1,308 @@
+/*
+ * 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.internal.net;
+
+import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
+import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS_V4;
+
+import static com.android.testutils.ParcelUtils.assertParcelSane;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.IpSecAlgorithm;
+import android.net.ipsec.ike.IkeTunnelConnectionParams;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/** Unit tests for {@link VpnProfile}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class VpnProfileTest {
+    private static final String DUMMY_PROFILE_KEY = "Test";
+
+    private static final int ENCODED_INDEX_AUTH_PARAMS_INLINE = 23;
+    private static final int ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS = 24;
+    private static final int ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE = 25;
+    private static final int ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION = 26;
+    private static final int ENCODED_INDEX_IKE_TUN_CONN_PARAMS = 27;
+    private static final int ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED = 28;
+    private static final int ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED = 29;
+
+    @Test
+    public void testDefaults() throws Exception {
+        final VpnProfile p = new VpnProfile(DUMMY_PROFILE_KEY);
+
+        assertEquals(DUMMY_PROFILE_KEY, p.key);
+        assertEquals("", p.name);
+        assertEquals(VpnProfile.TYPE_PPTP, p.type);
+        assertEquals("", p.server);
+        assertEquals("", p.username);
+        assertEquals("", p.password);
+        assertEquals("", p.dnsServers);
+        assertEquals("", p.searchDomains);
+        assertEquals("", p.routes);
+        assertTrue(p.mppe);
+        assertEquals("", p.l2tpSecret);
+        assertEquals("", p.ipsecIdentifier);
+        assertEquals("", p.ipsecSecret);
+        assertEquals("", p.ipsecUserCert);
+        assertEquals("", p.ipsecCaCert);
+        assertEquals("", p.ipsecServerCert);
+        assertEquals(null, p.proxy);
+        assertTrue(p.getAllowedAlgorithms() != null && p.getAllowedAlgorithms().isEmpty());
+        assertFalse(p.isBypassable);
+        assertFalse(p.isMetered);
+        assertEquals(1360, p.maxMtu);
+        assertFalse(p.areAuthParamsInline);
+        assertFalse(p.isRestrictedToTestNetworks);
+        assertFalse(p.excludeLocalRoutes);
+        assertFalse(p.requiresInternetValidation);
+        assertFalse(p.automaticNattKeepaliveTimerEnabled);
+        assertFalse(p.automaticIpVersionSelectionEnabled);
+    }
+
+    private VpnProfile getSampleIkev2Profile(String key) {
+        final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */,
+                false /* excludesLocalRoutes */, true /* requiresPlatformValidation */,
+                null /* ikeTunConnParams */, true /* mAutomaticNattKeepaliveTimerEnabled */,
+                true /* automaticIpVersionSelectionEnabled */);
+
+        p.name = "foo";
+        p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
+        p.server = "bar";
+        p.username = "baz";
+        p.password = "qux";
+        p.dnsServers = "8.8.8.8";
+        p.searchDomains = "";
+        p.routes = "0.0.0.0/0";
+        p.mppe = false;
+        p.l2tpSecret = "";
+        p.ipsecIdentifier = "quux";
+        p.ipsecSecret = "quuz";
+        p.ipsecUserCert = "corge";
+        p.ipsecCaCert = "grault";
+        p.ipsecServerCert = "garply";
+        p.proxy = null;
+        p.setAllowedAlgorithms(
+                Arrays.asList(
+                        IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
+                        IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305,
+                        IpSecAlgorithm.AUTH_HMAC_SHA512,
+                        IpSecAlgorithm.CRYPT_AES_CBC));
+        p.isBypassable = true;
+        p.isMetered = true;
+        p.maxMtu = 1350;
+        p.areAuthParamsInline = true;
+
+        // Not saved, but also not compared.
+        p.saveLogin = true;
+
+        return p;
+    }
+
+    private VpnProfile getSampleIkev2ProfileWithIkeTunConnParams(String key) {
+        final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */,
+                false /* excludesLocalRoutes */, true /* requiresPlatformValidation */,
+                new IkeTunnelConnectionParams(IKE_PARAMS_V4, CHILD_PARAMS),
+                true /* mAutomaticNattKeepaliveTimerEnabled */,
+                true /* automaticIpVersionSelectionEnabled */);
+
+        p.name = "foo";
+        p.server = "bar";
+        p.dnsServers = "8.8.8.8";
+        p.searchDomains = "";
+        p.routes = "0.0.0.0/0";
+        p.mppe = false;
+        p.proxy = null;
+        p.setAllowedAlgorithms(
+                Arrays.asList(
+                        IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
+                        IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305,
+                        IpSecAlgorithm.AUTH_HMAC_SHA512,
+                        IpSecAlgorithm.CRYPT_AES_CBC));
+        p.isBypassable = true;
+        p.isMetered = true;
+        p.maxMtu = 1350;
+        p.areAuthParamsInline = true;
+
+        // Not saved, but also not compared.
+        p.saveLogin = true;
+
+        return p;
+    }
+
+    @Test
+    public void testEquals() {
+        assertEquals(
+                getSampleIkev2Profile(DUMMY_PROFILE_KEY), getSampleIkev2Profile(DUMMY_PROFILE_KEY));
+
+        final VpnProfile modified = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+        modified.maxMtu--;
+        assertNotEquals(getSampleIkev2Profile(DUMMY_PROFILE_KEY), modified);
+    }
+
+    @Test
+    public void testParcelUnparcel() {
+        assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 28);
+        assertParcelSane(getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY), 28);
+    }
+
+    @Test
+    public void testEncodeDecodeWithIkeTunConnParams() {
+        final VpnProfile profile = getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY);
+        final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
+        assertEquals(profile, decoded);
+    }
+
+    @Test
+    public void testEncodeDecode() {
+        final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+        final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
+        assertEquals(profile, decoded);
+    }
+
+    @Test
+    public void testEncodeDecodeTooManyValues() {
+        final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+        final byte[] tooManyValues =
+                (new String(profile.encode()) + VpnProfile.VALUE_DELIMITER + "invalid").getBytes();
+
+        assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooManyValues));
+    }
+
+    private String getEncodedDecodedIkev2ProfileMissingValues(int... missingIndices) {
+        // Sort to ensure when we remove, we can do it from greatest first.
+        Arrays.sort(missingIndices);
+
+        final String encoded = new String(getSampleIkev2Profile(DUMMY_PROFILE_KEY).encode());
+        final List<String> parts =
+                new ArrayList<>(Arrays.asList(encoded.split(VpnProfile.VALUE_DELIMITER)));
+
+        // Remove from back first to ensure indexing is consistent.
+        for (int i = missingIndices.length - 1; i >= 0; i--) {
+            parts.remove(missingIndices[i]);
+        }
+
+        return String.join(VpnProfile.VALUE_DELIMITER, parts.toArray(new String[0]));
+    }
+
+    @Test
+    public void testEncodeDecodeInvalidNumberOfValues() {
+        final String tooFewValues =
+                getEncodedDecodedIkev2ProfileMissingValues(
+                        ENCODED_INDEX_AUTH_PARAMS_INLINE,
+                        ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS,
+                        ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE,
+                        ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION,
+                        ENCODED_INDEX_IKE_TUN_CONN_PARAMS,
+                        ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED,
+                        ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED
+                        /* missingIndices */);
+
+        assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()));
+    }
+
+    private String getEncodedDecodedIkev2ProfileWithtooFewValues() {
+        return getEncodedDecodedIkev2ProfileMissingValues(
+                ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS,
+                ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE,
+                ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION,
+                ENCODED_INDEX_IKE_TUN_CONN_PARAMS,
+                ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED,
+                ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED /* missingIndices */);
+    }
+
+    @Test
+    public void testEncodeDecodeMissingIsRestrictedToTestNetworks() {
+        final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+        // Verify decoding without isRestrictedToTestNetworks defaults to false
+        final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+        assertFalse(decoded.isRestrictedToTestNetworks);
+    }
+
+    @Test
+    public void testEncodeDecodeMissingExcludeLocalRoutes() {
+        final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+        // Verify decoding without excludeLocalRoutes defaults to false
+        final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+        assertFalse(decoded.excludeLocalRoutes);
+    }
+
+    @Test
+    public void testEncodeDecodeMissingRequiresValidation() {
+        final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+        // Verify decoding without requiresValidation defaults to false
+        final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+        assertFalse(decoded.requiresInternetValidation);
+    }
+
+    @Test
+    public void testEncodeDecodeMissingAutomaticNattKeepaliveTimerEnabled() {
+        final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+        // Verify decoding without automaticNattKeepaliveTimerEnabled defaults to false
+        final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+        assertFalse(decoded.automaticNattKeepaliveTimerEnabled);
+    }
+
+    @Test
+    public void testEncodeDecodeMissingAutomaticIpVersionSelectionEnabled() {
+        final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+        // Verify decoding without automaticIpVersionSelectionEnabled defaults to false
+        final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+        assertFalse(decoded.automaticIpVersionSelectionEnabled);
+    }
+
+    @Test
+    public void testEncodeDecodeLoginsNotSaved() {
+        final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+        profile.saveLogin = false;
+
+        final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
+        assertNotEquals(profile, decoded);
+
+        // Add the username/password back, everything else must be equal.
+        decoded.username = profile.username;
+        decoded.password = profile.password;
+        assertEquals(profile, decoded);
+    }
+
+    @Test
+    public void testClone() {
+        final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+        final VpnProfile clone = profile.clone();
+        assertEquals(profile, clone);
+        assertNotSame(profile, clone);
+    }
+}
diff --git a/services/tests/VpnTests/java/com/android/server/VpnManagerServiceTest.java b/services/tests/VpnTests/java/com/android/server/VpnManagerServiceTest.java
new file mode 100644
index 0000000..ecc70e3
--- /dev/null
+++ b/services/tests/VpnTests/java/com/android/server/VpnManagerServiceTest.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static com.android.testutils.ContextUtils.mockService;
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.UserIdInt;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.INetd;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.INetworkManagementService;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.security.Credentials;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.net.VpnProfile;
+import com.android.server.connectivity.Vpn;
+import com.android.server.connectivity.VpnProfileStore;
+import com.android.server.net.LockdownVpnTracker;
+import com.android.testutils.HandlerUtils;
+
+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.nio.charset.StandardCharsets;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VpnManagerServiceTest extends VpnTestBase {
+    private static final String CONTEXT_ATTRIBUTION_TAG = "VPN_MANAGER";
+
+    private static final int TIMEOUT_MS = 2_000;
+
+    @Mock Context mContext;
+    @Mock Context mContextWithoutAttributionTag;
+    @Mock Context mSystemContext;
+    @Mock Context mUserAllContext;
+    private HandlerThread mHandlerThread;
+    @Mock private Vpn mVpn;
+    @Mock private INetworkManagementService mNms;
+    @Mock private ConnectivityManager mCm;
+    @Mock private UserManager mUserManager;
+    @Mock private INetd mNetd;
+    @Mock private PackageManager mPackageManager;
+    @Mock private VpnProfileStore mVpnProfileStore;
+    @Mock private LockdownVpnTracker mLockdownVpnTracker;
+
+    private VpnManagerServiceDependencies mDeps;
+    private VpnManagerService mService;
+    private BroadcastReceiver mUserPresentReceiver;
+    private BroadcastReceiver mIntentReceiver;
+    private final String mNotMyVpnPkg = "com.not.my.vpn";
+
+    class VpnManagerServiceDependencies extends VpnManagerService.Dependencies {
+        @Override
+        public HandlerThread makeHandlerThread() {
+            return mHandlerThread;
+        }
+
+        @Override
+        public INetworkManagementService getINetworkManagementService() {
+            return mNms;
+        }
+
+        @Override
+        public INetd getNetd() {
+            return mNetd;
+        }
+
+        @Override
+        public Vpn createVpn(Looper looper, Context context, INetworkManagementService nms,
+                INetd netd, @UserIdInt int userId) {
+            return mVpn;
+        }
+
+        @Override
+        public VpnProfileStore getVpnProfileStore() {
+            return mVpnProfileStore;
+        }
+
+        @Override
+        public LockdownVpnTracker createLockDownVpnTracker(Context context, Handler handler,
+                Vpn vpn, VpnProfile profile) {
+            return mLockdownVpnTracker;
+        }
+
+        @Override
+        public @UserIdInt int getMainUserId() {
+            return UserHandle.USER_SYSTEM;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mHandlerThread = new HandlerThread("TestVpnManagerService");
+        mDeps = new VpnManagerServiceDependencies();
+
+        // The attribution tag is a dependency for IKE library to collect VPN metrics correctly
+        // and thus should not be changed without updating the IKE code.
+        doReturn(mContext)
+                .when(mContextWithoutAttributionTag)
+                .createAttributionContext(CONTEXT_ATTRIBUTION_TAG);
+
+        doReturn(mUserAllContext).when(mContext).createContextAsUser(UserHandle.ALL, 0);
+        doReturn(mSystemContext).when(mContext).createContextAsUser(UserHandle.SYSTEM, 0);
+        doReturn(mPackageManager).when(mContext).getPackageManager();
+        setMockedPackages(mPackageManager, sPackages);
+
+        mockService(mContext, ConnectivityManager.class, Context.CONNECTIVITY_SERVICE, mCm);
+        mockService(mContext, UserManager.class, Context.USER_SERVICE, mUserManager);
+        doReturn(SYSTEM_USER).when(mUserManager).getUserInfo(eq(SYSTEM_USER_ID));
+
+        mService = new VpnManagerService(mContextWithoutAttributionTag, mDeps);
+        mService.systemReady();
+
+        final ArgumentCaptor<BroadcastReceiver> intentReceiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        final ArgumentCaptor<BroadcastReceiver> userPresentReceiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mSystemContext).registerReceiver(
+                userPresentReceiverCaptor.capture(), any(), any(), any());
+        verify(mUserAllContext, times(2)).registerReceiver(
+                intentReceiverCaptor.capture(), any(), any(), any());
+        mUserPresentReceiver = userPresentReceiverCaptor.getValue();
+        mIntentReceiver = intentReceiverCaptor.getValue();
+
+        // Add user to create vpn in mVpn
+        onUserStarted(SYSTEM_USER_ID);
+        assertNotNull(mService.mVpns.get(SYSTEM_USER_ID));
+    }
+
+    @Test
+    public void testUpdateAppExclusionList() {
+        // Start vpn
+        mService.startVpnProfile(TEST_VPN_PKG);
+        verify(mVpn).startVpnProfile(eq(TEST_VPN_PKG));
+
+        // Remove package due to package replaced.
+        onPackageRemoved(PKGS[0], PKG_UIDS[0], true /* isReplacing */);
+        verify(mVpn, never()).refreshPlatformVpnAppExclusionList();
+
+        // Add package due to package replaced.
+        onPackageAdded(PKGS[0], PKG_UIDS[0], true /* isReplacing */);
+        verify(mVpn, never()).refreshPlatformVpnAppExclusionList();
+
+        // Remove package
+        onPackageRemoved(PKGS[0], PKG_UIDS[0], false /* isReplacing */);
+        verify(mVpn).refreshPlatformVpnAppExclusionList();
+
+        // Add the package back
+        onPackageAdded(PKGS[0], PKG_UIDS[0], false /* isReplacing */);
+        verify(mVpn, times(2)).refreshPlatformVpnAppExclusionList();
+    }
+
+    @Test
+    public void testStartVpnProfileFromDiffPackage() {
+        assertThrows(
+                SecurityException.class, () -> mService.startVpnProfile(mNotMyVpnPkg));
+    }
+
+    @Test
+    public void testStopVpnProfileFromDiffPackage() {
+        assertThrows(SecurityException.class, () -> mService.stopVpnProfile(mNotMyVpnPkg));
+    }
+
+    @Test
+    public void testGetProvisionedVpnProfileStateFromDiffPackage() {
+        assertThrows(SecurityException.class, () ->
+                mService.getProvisionedVpnProfileState(mNotMyVpnPkg));
+    }
+
+    @Test
+    public void testGetProvisionedVpnProfileState() {
+        mService.getProvisionedVpnProfileState(TEST_VPN_PKG);
+        verify(mVpn).getProvisionedVpnProfileState(TEST_VPN_PKG);
+    }
+
+    private Intent buildIntent(String action, String packageName, int userId, int uid,
+            boolean isReplacing) {
+        final Intent intent = new Intent(action);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+        intent.putExtra(Intent.EXTRA_UID, uid);
+        intent.putExtra(Intent.EXTRA_REPLACING, isReplacing);
+        if (packageName != null) {
+            intent.setData(Uri.fromParts("package" /* scheme */, packageName, null /* fragment */));
+        }
+
+        return intent;
+    }
+
+    private void sendIntent(Intent intent) {
+        sendIntent(mIntentReceiver, mContext, intent);
+    }
+
+    private void sendIntent(BroadcastReceiver receiver, Context context, Intent intent) {
+        final Handler h = mHandlerThread.getThreadHandler();
+
+        // Send in handler thread.
+        h.post(() -> receiver.onReceive(context, intent));
+        HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS);
+    }
+
+    private void onUserStarted(int userId) {
+        sendIntent(buildIntent(Intent.ACTION_USER_STARTED,
+                null /* packageName */, userId, -1 /* uid */, false /* isReplacing */));
+    }
+
+    private void onUserUnlocked(int userId) {
+        sendIntent(buildIntent(Intent.ACTION_USER_UNLOCKED,
+                null /* packageName */, userId, -1 /* uid */, false /* isReplacing */));
+    }
+
+    private void onUserStopped(int userId) {
+        sendIntent(buildIntent(Intent.ACTION_USER_STOPPED,
+                null /* packageName */, userId, -1 /* uid */, false /* isReplacing */));
+    }
+
+    private void onLockDownReset() {
+        sendIntent(buildIntent(LockdownVpnTracker.ACTION_LOCKDOWN_RESET, null /* packageName */,
+                UserHandle.USER_SYSTEM, -1 /* uid */, false /* isReplacing */));
+    }
+
+    private void onPackageAdded(String packageName, int userId, int uid, boolean isReplacing) {
+        sendIntent(buildIntent(Intent.ACTION_PACKAGE_ADDED, packageName, userId, uid, isReplacing));
+    }
+
+    private void onPackageAdded(String packageName, int uid, boolean isReplacing) {
+        onPackageAdded(packageName, UserHandle.USER_SYSTEM, uid, isReplacing);
+    }
+
+    private void onPackageRemoved(String packageName, int userId, int uid, boolean isReplacing) {
+        sendIntent(buildIntent(Intent.ACTION_PACKAGE_REMOVED, packageName, userId, uid,
+                isReplacing));
+    }
+
+    private void onPackageRemoved(String packageName, int uid, boolean isReplacing) {
+        onPackageRemoved(packageName, UserHandle.USER_SYSTEM, uid, isReplacing);
+    }
+
+    @Test
+    public void testReceiveIntentFromNonHandlerThread() {
+        assertThrows(IllegalStateException.class, () ->
+                mIntentReceiver.onReceive(mContext, buildIntent(Intent.ACTION_PACKAGE_REMOVED,
+                        PKGS[0], UserHandle.USER_SYSTEM, PKG_UIDS[0], true /* isReplacing */)));
+
+        assertThrows(IllegalStateException.class, () ->
+                mUserPresentReceiver.onReceive(mContext, new Intent(Intent.ACTION_USER_PRESENT)));
+    }
+
+    private void setupLockdownVpn(String packageName) {
+        final byte[] profileTag = packageName.getBytes(StandardCharsets.UTF_8);
+        doReturn(profileTag).when(mVpnProfileStore).get(Credentials.LOCKDOWN_VPN);
+    }
+
+    private void setupVpnProfile(String profileName) {
+        final VpnProfile profile = new VpnProfile(profileName);
+        profile.name = profileName;
+        profile.server = "192.0.2.1";
+        profile.dnsServers = "8.8.8.8";
+        profile.type = VpnProfile.TYPE_IPSEC_XAUTH_PSK;
+        final byte[] encodedProfile = profile.encode();
+        doReturn(encodedProfile).when(mVpnProfileStore).get(Credentials.VPN + profileName);
+    }
+
+    @Test
+    public void testUserPresent() {
+        // Verify that LockDownVpnTracker is not created.
+        verify(mLockdownVpnTracker, never()).init();
+
+        setupLockdownVpn(TEST_VPN_PKG);
+        setupVpnProfile(TEST_VPN_PKG);
+
+        // mUserPresentReceiver only registers ACTION_USER_PRESENT intent and does no verification
+        // on action, so an empty intent is enough.
+        sendIntent(mUserPresentReceiver, mSystemContext, new Intent());
+
+        verify(mLockdownVpnTracker).init();
+        verify(mSystemContext).unregisterReceiver(mUserPresentReceiver);
+        verify(mUserAllContext, never()).unregisterReceiver(any());
+    }
+
+    @Test
+    public void testUpdateLockdownVpn() {
+        setupLockdownVpn(TEST_VPN_PKG);
+        onUserUnlocked(SYSTEM_USER_ID);
+
+        // Will not create lockDownVpnTracker w/o valid profile configured in the keystore
+        verify(mLockdownVpnTracker, never()).init();
+
+        setupVpnProfile(TEST_VPN_PKG);
+
+        // Remove the user from mVpns
+        onUserStopped(SYSTEM_USER_ID);
+        onUserUnlocked(SYSTEM_USER_ID);
+        verify(mLockdownVpnTracker, never()).init();
+
+        // Add user back
+        onUserStarted(SYSTEM_USER_ID);
+        verify(mLockdownVpnTracker).init();
+
+        // Trigger another update. The existing LockDownVpnTracker should be shut down and
+        // initialize another one.
+        onUserUnlocked(SYSTEM_USER_ID);
+        verify(mLockdownVpnTracker).shutdown();
+        verify(mLockdownVpnTracker, times(2)).init();
+    }
+
+    @Test
+    public void testLockdownReset() {
+        // Init LockdownVpnTracker
+        setupLockdownVpn(TEST_VPN_PKG);
+        setupVpnProfile(TEST_VPN_PKG);
+        onUserUnlocked(SYSTEM_USER_ID);
+        verify(mLockdownVpnTracker).init();
+
+        onLockDownReset();
+        verify(mLockdownVpnTracker).reset();
+    }
+
+    @Test
+    public void testLockdownResetWhenLockdownVpnTrackerIsNotInit() {
+        setupLockdownVpn(TEST_VPN_PKG);
+        setupVpnProfile(TEST_VPN_PKG);
+
+        onLockDownReset();
+
+        // LockDownVpnTracker is not created. Lockdown reset will not take effect.
+        verify(mLockdownVpnTracker, never()).reset();
+    }
+
+    @Test
+    public void testIsVpnLockdownEnabled() {
+        // Vpn is created but the VPN lockdown is not enabled.
+        assertFalse(mService.isVpnLockdownEnabled(SYSTEM_USER_ID));
+
+        // Set lockdown for the SYSTEM_USER_ID VPN.
+        doReturn(true).when(mVpn).getLockdown();
+        assertTrue(mService.isVpnLockdownEnabled(SYSTEM_USER_ID));
+
+        // Even lockdown is enabled but no Vpn is created for SECONDARY_USER.
+        assertFalse(mService.isVpnLockdownEnabled(SECONDARY_USER.id));
+    }
+
+    @Test
+    public void testGetVpnLockdownAllowlist() {
+        doReturn(null).when(mVpn).getLockdownAllowlist();
+        assertNull(mService.getVpnLockdownAllowlist(SYSTEM_USER_ID));
+
+        final List<String> expected = List.of(PKGS);
+        doReturn(expected).when(mVpn).getLockdownAllowlist();
+        assertEquals(expected, mService.getVpnLockdownAllowlist(SYSTEM_USER_ID));
+
+        // Even lockdown is enabled but no Vpn is created for SECONDARY_USER.
+        assertNull(mService.getVpnLockdownAllowlist(SECONDARY_USER.id));
+    }
+}
diff --git a/services/tests/VpnTests/java/com/android/server/VpnTestBase.java b/services/tests/VpnTests/java/com/android/server/VpnTestBase.java
new file mode 100644
index 0000000..6113872
--- /dev/null
+++ b/services/tests/VpnTests/java/com/android/server/VpnTestBase.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static android.content.pm.UserInfo.FLAG_ADMIN;
+import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
+import static android.content.pm.UserInfo.FLAG_PRIMARY;
+import static android.content.pm.UserInfo.FLAG_RESTRICTED;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/** Common variables or methods shared between VpnTest and VpnManagerServiceTest. */
+public class VpnTestBase {
+    protected static final String TEST_VPN_PKG = "com.testvpn.vpn";
+    /**
+     * Names and UIDs for some fake packages. Important points:
+     *  - UID is ordered increasing.
+     *  - One pair of packages have consecutive UIDs.
+     */
+    protected static final String[] PKGS = {"com.example", "org.example", "net.example", "web.vpn"};
+    protected static final int[] PKG_UIDS = {10066, 10077, 10078, 10400};
+    // Mock packages
+    protected static final Map<String, Integer> sPackages = new ArrayMap<>();
+    static {
+        for (int i = 0; i < PKGS.length; i++) {
+            sPackages.put(PKGS[i], PKG_UIDS[i]);
+        }
+        sPackages.put(TEST_VPN_PKG, Process.myUid());
+    }
+
+    // Mock users
+    protected static final int SYSTEM_USER_ID = 0;
+    protected static final UserInfo SYSTEM_USER = new UserInfo(0, "system", UserInfo.FLAG_PRIMARY);
+    protected static final UserInfo PRIMARY_USER = new UserInfo(27, "Primary",
+            FLAG_ADMIN | FLAG_PRIMARY);
+    protected static final UserInfo SECONDARY_USER = new UserInfo(15, "Secondary", FLAG_ADMIN);
+    protected static final UserInfo RESTRICTED_PROFILE_A = new UserInfo(40, "RestrictedA",
+            FLAG_RESTRICTED);
+    protected static final UserInfo RESTRICTED_PROFILE_B = new UserInfo(42, "RestrictedB",
+            FLAG_RESTRICTED);
+    protected static final UserInfo MANAGED_PROFILE_A = new UserInfo(45, "ManagedA",
+            FLAG_MANAGED_PROFILE);
+    static {
+        RESTRICTED_PROFILE_A.restrictedProfileParentId = PRIMARY_USER.id;
+        RESTRICTED_PROFILE_B.restrictedProfileParentId = SECONDARY_USER.id;
+        MANAGED_PROFILE_A.profileGroupId = PRIMARY_USER.id;
+    }
+
+    // Populate a fake packageName-to-UID mapping.
+    protected void setMockedPackages(PackageManager mockPm, final Map<String, Integer> packages) {
+        try {
+            doAnswer(invocation -> {
+                final String appName = (String) invocation.getArguments()[0];
+                final int userId = (int) invocation.getArguments()[1];
+
+                final Integer appId = packages.get(appName);
+                if (appId == null) {
+                    throw new PackageManager.NameNotFoundException(appName);
+                }
+
+                return UserHandle.getUid(userId, appId);
+            }).when(mockPm).getPackageUidAsUser(anyString(), anyInt());
+        } catch (Exception e) {
+        }
+    }
+
+    protected List<Integer> toList(int[] arr) {
+        return Arrays.stream(arr).boxed().collect(Collectors.toList());
+    }
+}
diff --git a/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java
new file mode 100644
index 0000000..9115f95
--- /dev/null
+++ b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java
@@ -0,0 +1,3293 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import static android.Manifest.permission.BIND_VPN_SERVICE;
+import static android.Manifest.permission.CONTROL_VPN;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
+import static android.net.ConnectivityDiagnosticsManager.DataStallReport;
+import static android.net.ConnectivityManager.NetworkCallback;
+import static android.net.INetd.IF_STATE_DOWN;
+import static android.net.INetd.IF_STATE_UP;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.RouteInfo.RTN_UNREACHABLE;
+import static android.net.VpnManager.TYPE_VPN_PLATFORM;
+import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
+import static android.net.cts.util.IkeSessionTestUtils.TEST_IDENTITY;
+import static android.net.cts.util.IkeSessionTestUtils.TEST_KEEPALIVE_TIMEOUT_UNSET;
+import static android.net.cts.util.IkeSessionTestUtils.getTestIkeSessionParams;
+import static android.net.ipsec.ike.IkeSessionConfiguration.EXTENSION_TYPE_MOBIKE;
+import static android.net.ipsec.ike.IkeSessionParams.ESP_ENCAP_TYPE_AUTO;
+import static android.net.ipsec.ike.IkeSessionParams.ESP_ENCAP_TYPE_NONE;
+import static android.net.ipsec.ike.IkeSessionParams.ESP_ENCAP_TYPE_UDP;
+import static android.net.ipsec.ike.IkeSessionParams.ESP_IP_VERSION_AUTO;
+import static android.net.ipsec.ike.IkeSessionParams.ESP_IP_VERSION_IPV4;
+import static android.net.ipsec.ike.IkeSessionParams.ESP_IP_VERSION_IPV6;
+import static android.os.UserHandle.PER_USER_RANGE;
+import static android.telephony.CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL;
+import static android.telephony.CarrierConfigManager.KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT;
+import static android.telephony.CarrierConfigManager.KEY_PREFERRED_IKE_PROTOCOL_INT;
+
+import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
+import static com.android.server.connectivity.Vpn.AUTOMATIC_KEEPALIVE_DELAY_SECONDS;
+import static com.android.server.connectivity.Vpn.DEFAULT_LONG_LIVED_TCP_CONNS_EXPENSIVE_TIMEOUT_SEC;
+import static com.android.server.connectivity.Vpn.DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT;
+import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_AUTO;
+import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_IPV4_UDP;
+import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_IPV6_ESP;
+import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_IPV6_UDP;
+import static com.android.testutils.HandlerUtils.waitForIdleSerialExecutor;
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.longThat;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.AppOpsManager;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.net.ConnectivityDiagnosticsManager;
+import android.net.ConnectivityManager;
+import android.net.INetd;
+import android.net.Ikev2VpnProfile;
+import android.net.InetAddresses;
+import android.net.InterfaceConfigurationParcel;
+import android.net.IpPrefix;
+import android.net.IpSecConfig;
+import android.net.IpSecManager;
+import android.net.IpSecTransform;
+import android.net.IpSecTunnelInterfaceResponse;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo.DetailedState;
+import android.net.RouteInfo;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.UidRangeParcel;
+import android.net.VpnManager;
+import android.net.VpnProfileState;
+import android.net.VpnService;
+import android.net.VpnTransportInfo;
+import android.net.ipsec.ike.ChildSessionCallback;
+import android.net.ipsec.ike.ChildSessionConfiguration;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeSessionCallback;
+import android.net.ipsec.ike.IkeSessionConfiguration;
+import android.net.ipsec.ike.IkeSessionConnectionInfo;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.IkeTrafficSelector;
+import android.net.ipsec.ike.IkeTunnelConnectionParams;
+import android.net.ipsec.ike.exceptions.IkeException;
+import android.net.ipsec.ike.exceptions.IkeNetworkLostException;
+import android.net.ipsec.ike.exceptions.IkeNonProtocolException;
+import android.net.ipsec.ike.exceptions.IkeProtocolException;
+import android.net.ipsec.ike.exceptions.IkeTimeoutException;
+import android.net.vcn.VcnTransportInfo;
+import android.net.wifi.WifiInfo;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.os.INetworkManagementService;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.PowerWhitelistManager;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.security.Credentials;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Pair;
+import android.util.Range;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.internal.net.LegacyVpnInfo;
+import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
+import com.android.internal.util.HexDump;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.DeviceIdleInternal;
+import com.android.server.IpSecService;
+import com.android.server.VpnTestBase;
+import com.android.server.vcn.util.PersistableBundleUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.AdditionalAnswers;
+import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Tests for {@link Vpn}.
+ *
+ * Build, install and run with:
+ *  runtest frameworks-net -c com.android.server.connectivity.VpnTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VpnTest extends VpnTestBase {
+    private static final String TAG = "VpnTest";
+
+    static final Network EGRESS_NETWORK = new Network(101);
+    static final String EGRESS_IFACE = "wlan0";
+    private static final String TEST_VPN_CLIENT = "2.4.6.8";
+    private static final String TEST_VPN_SERVER = "1.2.3.4";
+    private static final String TEST_VPN_IDENTITY = "identity";
+    private static final byte[] TEST_VPN_PSK = "psk".getBytes();
+
+    private static final int IP4_PREFIX_LEN = 32;
+    private static final int IP6_PREFIX_LEN = 64;
+    private static final int MIN_PORT = 0;
+    private static final int MAX_PORT = 65535;
+
+    private static final InetAddress TEST_VPN_CLIENT_IP =
+            InetAddresses.parseNumericAddress(TEST_VPN_CLIENT);
+    private static final InetAddress TEST_VPN_SERVER_IP =
+            InetAddresses.parseNumericAddress(TEST_VPN_SERVER);
+    private static final InetAddress TEST_VPN_CLIENT_IP_2 =
+            InetAddresses.parseNumericAddress("192.0.2.200");
+    private static final InetAddress TEST_VPN_SERVER_IP_2 =
+            InetAddresses.parseNumericAddress("192.0.2.201");
+    private static final InetAddress TEST_VPN_INTERNAL_IP =
+            InetAddresses.parseNumericAddress("198.51.100.10");
+    private static final InetAddress TEST_VPN_INTERNAL_IP6 =
+            InetAddresses.parseNumericAddress("2001:db8::1");
+    private static final InetAddress TEST_VPN_INTERNAL_DNS =
+            InetAddresses.parseNumericAddress("8.8.8.8");
+    private static final InetAddress TEST_VPN_INTERNAL_DNS6 =
+            InetAddresses.parseNumericAddress("2001:4860:4860::8888");
+
+    private static final IkeTrafficSelector IN_TS =
+            new IkeTrafficSelector(MIN_PORT, MAX_PORT, TEST_VPN_INTERNAL_IP, TEST_VPN_INTERNAL_IP);
+    private static final IkeTrafficSelector IN_TS6 =
+            new IkeTrafficSelector(
+                    MIN_PORT, MAX_PORT, TEST_VPN_INTERNAL_IP6, TEST_VPN_INTERNAL_IP6);
+    private static final IkeTrafficSelector OUT_TS =
+            new IkeTrafficSelector(MIN_PORT, MAX_PORT,
+                    InetAddresses.parseNumericAddress("0.0.0.0"),
+                    InetAddresses.parseNumericAddress("255.255.255.255"));
+    private static final IkeTrafficSelector OUT_TS6 =
+            new IkeTrafficSelector(
+                    MIN_PORT,
+                    MAX_PORT,
+                    InetAddresses.parseNumericAddress("::"),
+                    InetAddresses.parseNumericAddress("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"));
+
+    private static final Network TEST_NETWORK = new Network(Integer.MAX_VALUE);
+    private static final Network TEST_NETWORK_2 = new Network(Integer.MAX_VALUE - 1);
+    private static final String TEST_IFACE_NAME = "TEST_IFACE";
+    private static final int TEST_TUNNEL_RESOURCE_ID = 0x2345;
+    private static final long TEST_TIMEOUT_MS = 500L;
+    private static final long TIMEOUT_CROSSTHREAD_MS = 20_000L;
+    private static final String PRIMARY_USER_APP_EXCLUDE_KEY =
+            "VPNAPPEXCLUDED_27_com.testvpn.vpn";
+    static final String PKGS_BYTES = getPackageByteString(List.of(PKGS));
+    private static final Range<Integer> PRIMARY_USER_RANGE = uidRangeForUser(PRIMARY_USER.id);
+    private static final int TEST_KEEPALIVE_TIMER = 800;
+    private static final int TEST_SUB_ID = 1234;
+    private static final String TEST_MCCMNC = "12345";
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext;
+    @Mock private UserManager mUserManager;
+    @Mock private PackageManager mPackageManager;
+    @Mock private INetworkManagementService mNetService;
+    @Mock private INetd mNetd;
+    @Mock private AppOpsManager mAppOps;
+    @Mock private NotificationManager mNotificationManager;
+    @Mock private Vpn.SystemServices mSystemServices;
+    @Mock private Vpn.IkeSessionWrapper mIkeSessionWrapper;
+    @Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator;
+    @Mock private Vpn.VpnNetworkAgentWrapper mMockNetworkAgent;
+    @Mock private ConnectivityManager mConnectivityManager;
+    @Mock private ConnectivityDiagnosticsManager mCdm;
+    @Mock private TelephonyManager mTelephonyManager;
+    @Mock private TelephonyManager mTmPerSub;
+    @Mock private CarrierConfigManager mConfigManager;
+    @Mock private SubscriptionManager mSubscriptionManager;
+    @Mock private IpSecService mIpSecService;
+    @Mock private VpnProfileStore mVpnProfileStore;
+    private final TestExecutor mExecutor;
+    @Mock DeviceIdleInternal mDeviceIdleInternal;
+    private final VpnProfile mVpnProfile;
+
+    @Captor private ArgumentCaptor<Collection<Range<Integer>>> mUidRangesCaptor;
+
+    private IpSecManager mIpSecManager;
+    private TestDeps mTestDeps;
+
+    public static class TestExecutor extends ScheduledThreadPoolExecutor {
+        public static final long REAL_DELAY = -1;
+
+        // For the purposes of the test, run all scheduled tasks after 10ms to save
+        // execution time, unless overridden by the specific test. Set to REAL_DELAY
+        // to actually wait for the delay specified by the real call to schedule().
+        public long delayMs = 10;
+        // If this is true, execute() will call the runnable inline. This is useful because
+        // super.execute() calls schedule(), which messes with checks that scheduled() is
+        // called a given number of times.
+        public boolean executeDirect = false;
+
+        public TestExecutor() {
+            super(1);
+        }
+
+        @Override
+        public void execute(final Runnable command) {
+            // See |executeDirect| for why this is necessary.
+            if (executeDirect) {
+                command.run();
+            } else {
+                super.execute(command);
+            }
+        }
+
+        @Override
+        public ScheduledFuture<?> schedule(final Runnable command, final long delay,
+                TimeUnit unit) {
+            if (0 == delay || delayMs == REAL_DELAY) {
+                // super.execute() calls schedule() with 0, so use the real delay if it's 0.
+                return super.schedule(command, delay, unit);
+            } else {
+                return super.schedule(command, delayMs, TimeUnit.MILLISECONDS);
+            }
+        }
+    }
+
+    public VpnTest() throws Exception {
+        // Build an actual VPN profile that is capable of being converted to and from an
+        // Ikev2VpnProfile
+        final Ikev2VpnProfile.Builder builder =
+                new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY);
+        builder.setAuthPsk(TEST_VPN_PSK);
+        builder.setBypassable(true /* isBypassable */);
+        mExecutor = spy(new TestExecutor());
+        mVpnProfile = builder.build().toVpnProfile();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mIpSecManager = new IpSecManager(mContext, mIpSecService);
+        mTestDeps = spy(new TestDeps());
+        doReturn(IPV6_MIN_MTU)
+                .when(mTestDeps)
+                .calculateVpnMtu(any(), anyInt(), anyInt(), anyBoolean());
+        doReturn(1500).when(mTestDeps).getJavaNetworkInterfaceMtu(any(), anyInt());
+
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        setMockedPackages(sPackages);
+
+        when(mContext.getPackageName()).thenReturn(TEST_VPN_PKG);
+        when(mContext.getOpPackageName()).thenReturn(TEST_VPN_PKG);
+        mockService(UserManager.class, Context.USER_SERVICE, mUserManager);
+        mockService(AppOpsManager.class, Context.APP_OPS_SERVICE, mAppOps);
+        mockService(NotificationManager.class, Context.NOTIFICATION_SERVICE, mNotificationManager);
+        mockService(ConnectivityManager.class, Context.CONNECTIVITY_SERVICE, mConnectivityManager);
+        mockService(IpSecManager.class, Context.IPSEC_SERVICE, mIpSecManager);
+        mockService(ConnectivityDiagnosticsManager.class, Context.CONNECTIVITY_DIAGNOSTICS_SERVICE,
+                mCdm);
+        mockService(TelephonyManager.class, Context.TELEPHONY_SERVICE, mTelephonyManager);
+        mockService(CarrierConfigManager.class, Context.CARRIER_CONFIG_SERVICE, mConfigManager);
+        mockService(SubscriptionManager.class, Context.TELEPHONY_SUBSCRIPTION_SERVICE,
+                mSubscriptionManager);
+        doReturn(mTmPerSub).when(mTelephonyManager).createForSubscriptionId(anyInt());
+        when(mContext.getString(R.string.config_customVpnAlwaysOnDisconnectedDialogComponent))
+                .thenReturn(Resources.getSystem().getString(
+                        R.string.config_customVpnAlwaysOnDisconnectedDialogComponent));
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS))
+                .thenReturn(true);
+
+        // Used by {@link Notification.Builder}
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT;
+        when(mContext.getApplicationInfo()).thenReturn(applicationInfo);
+        when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+                .thenReturn(applicationInfo);
+
+        doNothing().when(mNetService).registerObserver(any());
+
+        // Deny all appops by default.
+        when(mAppOps.noteOpNoThrow(anyString(), anyInt(), anyString(), any(), any()))
+                .thenReturn(AppOpsManager.MODE_IGNORED);
+
+        // Setup IpSecService
+        final IpSecTunnelInterfaceResponse tunnelResp =
+                new IpSecTunnelInterfaceResponse(
+                        IpSecManager.Status.OK, TEST_TUNNEL_RESOURCE_ID, TEST_IFACE_NAME);
+        when(mIpSecService.createTunnelInterface(any(), any(), any(), any(), any()))
+                .thenReturn(tunnelResp);
+        doReturn(new LinkProperties()).when(mConnectivityManager).getLinkProperties(any());
+
+        // The unit test should know what kind of permission it needs and set the permission by
+        // itself, so set the default value of Context#checkCallingOrSelfPermission to
+        // PERMISSION_DENIED.
+        doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(any());
+
+        // Set up mIkev2SessionCreator and mExecutor
+        resetIkev2SessionCreator(mIkeSessionWrapper);
+    }
+
+    private void resetIkev2SessionCreator(Vpn.IkeSessionWrapper ikeSession) {
+        reset(mIkev2SessionCreator);
+        when(mIkev2SessionCreator.createIkeSession(any(), any(), any(), any(), any(), any()))
+                .thenReturn(ikeSession);
+    }
+
+    private <T> void mockService(Class<T> clazz, String name, T service) {
+        doReturn(service).when(mContext).getSystemService(name);
+        doReturn(name).when(mContext).getSystemServiceName(clazz);
+        if (mContext.getSystemService(clazz).getClass().equals(Object.class)) {
+            // Test is using mockito-extended (mContext uses Answers.RETURNS_DEEP_STUBS and returned
+            // a mock object on a final method)
+            doCallRealMethod().when(mContext).getSystemService(clazz);
+        }
+    }
+
+    private Set<Range<Integer>> rangeSet(Range<Integer> ... ranges) {
+        final Set<Range<Integer>> range = new ArraySet<>();
+        for (Range<Integer> r : ranges) range.add(r);
+
+        return range;
+    }
+
+    private static Range<Integer> uidRangeForUser(int userId) {
+        return new Range<Integer>(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1);
+    }
+
+    private Range<Integer> uidRange(int start, int stop) {
+        return new Range<Integer>(start, stop);
+    }
+
+    private static String getPackageByteString(List<String> packages) {
+        try {
+            return HexDump.toHexString(
+                    PersistableBundleUtils.toDiskStableBytes(PersistableBundleUtils.fromList(
+                            packages, PersistableBundleUtils.STRING_SERIALIZER)),
+                        true /* upperCase */);
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    @Test
+    public void testRestrictedProfilesAreAddedToVpn() {
+        setMockedUsers(PRIMARY_USER, SECONDARY_USER, RESTRICTED_PROFILE_A, RESTRICTED_PROFILE_B);
+
+        final Vpn vpn = createVpn(PRIMARY_USER.id);
+
+        // Assume the user can have restricted profiles.
+        doReturn(true).when(mUserManager).canHaveRestrictedProfile();
+        final Set<Range<Integer>> ranges =
+                vpn.createUserAndRestrictedProfilesRanges(PRIMARY_USER.id, null, null);
+
+        assertEquals(rangeSet(PRIMARY_USER_RANGE, uidRangeForUser(RESTRICTED_PROFILE_A.id)),
+                 ranges);
+    }
+
+    @Test
+    public void testManagedProfilesAreNotAddedToVpn() {
+        setMockedUsers(PRIMARY_USER, MANAGED_PROFILE_A);
+
+        final Vpn vpn = createVpn(PRIMARY_USER.id);
+        final Set<Range<Integer>> ranges = vpn.createUserAndRestrictedProfilesRanges(
+                PRIMARY_USER.id, null, null);
+
+        assertEquals(rangeSet(PRIMARY_USER_RANGE), ranges);
+    }
+
+    @Test
+    public void testAddUserToVpnOnlyAddsOneUser() {
+        setMockedUsers(PRIMARY_USER, RESTRICTED_PROFILE_A, MANAGED_PROFILE_A);
+
+        final Vpn vpn = createVpn(PRIMARY_USER.id);
+        final Set<Range<Integer>> ranges = new ArraySet<>();
+        vpn.addUserToRanges(ranges, PRIMARY_USER.id, null, null);
+
+        assertEquals(rangeSet(PRIMARY_USER_RANGE), ranges);
+    }
+
+    @Test
+    public void testUidAllowAndDenylist() throws Exception {
+        final Vpn vpn = createVpn(PRIMARY_USER.id);
+        final Range<Integer> user = PRIMARY_USER_RANGE;
+        final int userStart = user.getLower();
+        final int userStop = user.getUpper();
+        final String[] packages = {PKGS[0], PKGS[1], PKGS[2]};
+
+        // Allowed list
+        final Set<Range<Integer>> allow = vpn.createUserAndRestrictedProfilesRanges(PRIMARY_USER.id,
+                Arrays.asList(packages), null /* disallowedApplications */);
+        assertEquals(rangeSet(
+                uidRange(userStart + PKG_UIDS[0], userStart + PKG_UIDS[0]),
+                uidRange(userStart + PKG_UIDS[1], userStart + PKG_UIDS[2]),
+                uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[0]),
+                         Process.toSdkSandboxUid(userStart + PKG_UIDS[0])),
+                uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[1]),
+                         Process.toSdkSandboxUid(userStart + PKG_UIDS[2]))),
+                allow);
+
+        // Denied list
+        final Set<Range<Integer>> disallow =
+                vpn.createUserAndRestrictedProfilesRanges(PRIMARY_USER.id,
+                        null /* allowedApplications */, Arrays.asList(packages));
+        assertEquals(rangeSet(
+                uidRange(userStart, userStart + PKG_UIDS[0] - 1),
+                uidRange(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1),
+                /* Empty range between UIDS[1] and UIDS[2], should be excluded, */
+                uidRange(userStart + PKG_UIDS[2] + 1,
+                         Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+                         Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)),
+                disallow);
+    }
+
+    private void verifyPowerSaveTempWhitelistApp(String packageName) {
+        verify(mDeviceIdleInternal, timeout(TEST_TIMEOUT_MS)).addPowerSaveTempWhitelistApp(
+                anyInt(), eq(packageName), anyLong(), anyInt(), eq(false),
+                eq(PowerWhitelistManager.REASON_VPN), eq("VpnManager event"));
+    }
+
+    @Test
+    public void testGetAlwaysAndOnGetLockDown() throws Exception {
+        final Vpn vpn = createVpn(PRIMARY_USER.id);
+
+        // Default state.
+        assertFalse(vpn.getAlwaysOn());
+        assertFalse(vpn.getLockdown());
+
+        // Set always-on without lockdown.
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList()));
+        assertTrue(vpn.getAlwaysOn());
+        assertFalse(vpn.getLockdown());
+
+        // Set always-on with lockdown.
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList()));
+        assertTrue(vpn.getAlwaysOn());
+        assertTrue(vpn.getLockdown());
+
+        // Remove always-on configuration.
+        assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList()));
+        assertFalse(vpn.getAlwaysOn());
+        assertFalse(vpn.getLockdown());
+    }
+
+    @Test
+    public void testAlwaysOnWithoutLockdown() throws Exception {
+        final Vpn vpn = createVpn(PRIMARY_USER.id);
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[1], false /* lockdown */, null /* lockdownAllowlist */));
+        verify(mConnectivityManager, never()).setRequireVpnForUids(anyBoolean(), any());
+
+        assertTrue(vpn.setAlwaysOnPackage(
+                null /* packageName */, false /* lockdown */, null /* lockdownAllowlist */));
+        verify(mConnectivityManager, never()).setRequireVpnForUids(anyBoolean(), any());
+    }
+
+    @Test
+    public void testLockdownChangingPackage() throws Exception {
+        final Vpn vpn = createVpn(PRIMARY_USER.id);
+        final Range<Integer> user = PRIMARY_USER_RANGE;
+        final int userStart = user.getLower();
+        final int userStop = user.getUpper();
+        // Set always-on without lockdown.
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null));
+
+        // Set always-on with lockdown.
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null));
+        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
+                new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
+                new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
+        }));
+
+        // Switch to another app.
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
+        verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
+                new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
+                new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
+        }));
+        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
+                new UidRangeParcel(userStart, userStart + PKG_UIDS[3] - 1),
+                new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop)
+        }));
+    }
+
+    @Test
+    public void testLockdownAllowlist() throws Exception {
+        final Vpn vpn = createVpn(PRIMARY_USER.id);
+        final Range<Integer> user = PRIMARY_USER_RANGE;
+        final int userStart = user.getLower();
+        final int userStop = user.getUpper();
+        // Set always-on with lockdown and allow app PKGS[2] from lockdown.
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[1], true, Collections.singletonList(PKGS[2])));
+        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[]  {
+                new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
+                new UidRangeParcel(userStart + PKG_UIDS[2] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1]) - 1),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)
+        }));
+        // Change allowed app list to PKGS[3].
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[1], true, Collections.singletonList(PKGS[3])));
+        verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
+                new UidRangeParcel(userStart + PKG_UIDS[2] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)
+        }));
+        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
+                new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStart + PKG_UIDS[3] - 1),
+                new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1),
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop)
+        }));
+
+        // Change the VPN app.
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[0], true, Collections.singletonList(PKGS[3])));
+        verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
+                new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
+                new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStart + PKG_UIDS[3] - 1),
+                new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1),
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1))
+        }));
+        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
+                new UidRangeParcel(userStart, userStart + PKG_UIDS[0] - 1),
+                new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[3] - 1),
+                new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1))
+        }));
+
+        // Remove the list of allowed packages.
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null));
+        verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
+                new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[3] - 1),
+                new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop)
+        }));
+        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
+                new UidRangeParcel(userStart + PKG_UIDS[0] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1), userStop),
+        }));
+
+        // Add the list of allowed packages.
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[0], true, Collections.singletonList(PKGS[1])));
+        verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
+                new UidRangeParcel(userStart + PKG_UIDS[0] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1), userStop),
+        }));
+        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
+                new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1),
+                new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
+        }));
+
+        // Try allowing a package with a comma, should be rejected.
+        assertFalse(vpn.setAlwaysOnPackage(
+                PKGS[0], true, Collections.singletonList("a.b,c.d")));
+
+        // Pass a non-existent packages in the allowlist, they (and only they) should be ignored.
+        // allowed package should change from PGKS[1] to PKGS[2].
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app")));
+        verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
+                new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1),
+                new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
+        }));
+        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
+                new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[2] - 1),
+                new UidRangeParcel(userStart + PKG_UIDS[2] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[2] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)
+        }));
+    }
+
+    @Test
+    public void testLockdownSystemUser() throws Exception {
+        final Vpn vpn = createVpn(SYSTEM_USER_ID);
+
+        // Uid 0 is always excluded and PKG_UIDS[1] is the uid of the VPN.
+        final List<Integer> excludedUids = new ArrayList<>(List.of(0, PKG_UIDS[1]));
+        final List<Range<Integer>> ranges = makeVpnUidRange(SYSTEM_USER_ID, excludedUids);
+
+        // Set always-on with lockdown.
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[1], true /* lockdown */, null /* lockdownAllowlist */));
+        verify(mConnectivityManager).setRequireVpnForUids(true, ranges);
+
+        // Disable always-on with lockdown.
+        assertTrue(vpn.setAlwaysOnPackage(
+                null /* packageName */, false /* lockdown */, null /* lockdownAllowlist */));
+        verify(mConnectivityManager).setRequireVpnForUids(false, ranges);
+
+        // Set always-on with lockdown and allow the app PKGS[2].
+        excludedUids.add(PKG_UIDS[2]);
+        final List<Range<Integer>> ranges2 = makeVpnUidRange(SYSTEM_USER_ID, excludedUids);
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[1], true /* lockdown */, Collections.singletonList(PKGS[2])));
+        verify(mConnectivityManager).setRequireVpnForUids(true, ranges2);
+
+        // Disable always-on with lockdown.
+        assertTrue(vpn.setAlwaysOnPackage(
+                null /* packageName */, false /* lockdown */, null /* lockdownAllowlist */));
+        verify(mConnectivityManager).setRequireVpnForUids(false, ranges2);
+    }
+
+    @Test
+    public void testLockdownRuleRepeatability() throws Exception {
+        final Vpn vpn = createVpn(PRIMARY_USER.id);
+        final UidRangeParcel[] primaryUserRangeParcel = new UidRangeParcel[] {
+                new UidRangeParcel(PRIMARY_USER_RANGE.getLower(), PRIMARY_USER_RANGE.getUpper())};
+        // Given legacy lockdown is already enabled,
+        vpn.setLockdown(true);
+        verify(mConnectivityManager, times(1)).setRequireVpnForUids(true,
+                toRanges(primaryUserRangeParcel));
+
+        // Enabling legacy lockdown twice should do nothing.
+        vpn.setLockdown(true);
+        verify(mConnectivityManager, times(1)).setRequireVpnForUids(anyBoolean(), any());
+
+        // And disabling should remove the rules exactly once.
+        vpn.setLockdown(false);
+        verify(mConnectivityManager, times(1)).setRequireVpnForUids(false,
+                toRanges(primaryUserRangeParcel));
+
+        // Removing the lockdown again should have no effect.
+        vpn.setLockdown(false);
+        verify(mConnectivityManager, times(2)).setRequireVpnForUids(anyBoolean(), any());
+    }
+
+    private ArrayList<Range<Integer>> toRanges(UidRangeParcel[] ranges) {
+        ArrayList<Range<Integer>> rangesArray = new ArrayList<>(ranges.length);
+        for (int i = 0; i < ranges.length; i++) {
+            rangesArray.add(new Range<>(ranges[i].start, ranges[i].stop));
+        }
+        return rangesArray;
+    }
+
+    @Test
+    public void testLockdownRuleReversibility() throws Exception {
+        doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN);
+        final Vpn vpn = createVpn(PRIMARY_USER.id);
+        final UidRangeParcel[] entireUser = {
+            new UidRangeParcel(PRIMARY_USER_RANGE.getLower(), PRIMARY_USER_RANGE.getUpper())
+        };
+        final UidRangeParcel[] exceptPkg0 = {
+            new UidRangeParcel(entireUser[0].start, entireUser[0].start + PKG_UIDS[0] - 1),
+            new UidRangeParcel(entireUser[0].start + PKG_UIDS[0] + 1,
+                               Process.toSdkSandboxUid(entireUser[0].start + PKG_UIDS[0] - 1)),
+            new UidRangeParcel(Process.toSdkSandboxUid(entireUser[0].start + PKG_UIDS[0] + 1),
+                               entireUser[0].stop),
+        };
+
+        final InOrder order = inOrder(mConnectivityManager);
+
+        // Given lockdown is enabled with no package (legacy VPN),
+        vpn.setLockdown(true);
+        order.verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(entireUser));
+
+        // When a new VPN package is set the rules should change to cover that package.
+        vpn.prepare(null, PKGS[0], VpnManager.TYPE_VPN_SERVICE);
+        order.verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(entireUser));
+        order.verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(exceptPkg0));
+
+        // When that VPN package is unset, everything should be undone again in reverse.
+        vpn.prepare(null, VpnConfig.LEGACY_VPN, VpnManager.TYPE_VPN_SERVICE);
+        order.verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(exceptPkg0));
+        order.verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(entireUser));
+    }
+
+    @Test
+    public void testOnUserAddedAndRemoved_restrictedUser() throws Exception {
+        final InOrder order = inOrder(mMockNetworkAgent);
+        final Vpn vpn = createVpn(PRIMARY_USER.id);
+        final Set<Range<Integer>> initialRange = rangeSet(PRIMARY_USER_RANGE);
+        // Note since mVpnProfile is a Ikev2VpnProfile, this starts an IkeV2VpnRunner.
+        startLegacyVpn(vpn, mVpnProfile);
+        // Set an initial Uid range and mock the network agent
+        vpn.mNetworkCapabilities.setUids(initialRange);
+        vpn.mNetworkAgent = mMockNetworkAgent;
+
+        // Add the restricted user
+        setMockedUsers(PRIMARY_USER, RESTRICTED_PROFILE_A);
+        vpn.onUserAdded(RESTRICTED_PROFILE_A.id);
+        // Expect restricted user range to be added to the NetworkCapabilities.
+        final Set<Range<Integer>> expectRestrictedRange =
+                rangeSet(PRIMARY_USER_RANGE, uidRangeForUser(RESTRICTED_PROFILE_A.id));
+        assertEquals(expectRestrictedRange, vpn.mNetworkCapabilities.getUids());
+        order.verify(mMockNetworkAgent).doSendNetworkCapabilities(
+                argThat(nc -> expectRestrictedRange.equals(nc.getUids())));
+
+        // Remove the restricted user
+        vpn.onUserRemoved(RESTRICTED_PROFILE_A.id);
+        // Expect restricted user range to be removed from the NetworkCapabilities.
+        assertEquals(initialRange, vpn.mNetworkCapabilities.getUids());
+        order.verify(mMockNetworkAgent).doSendNetworkCapabilities(
+                argThat(nc -> initialRange.equals(nc.getUids())));
+    }
+
+    @Test
+    public void testOnUserAddedAndRemoved_restrictedUserLockdown() throws Exception {
+        final UidRangeParcel[] primaryUserRangeParcel = new UidRangeParcel[] {
+                new UidRangeParcel(PRIMARY_USER_RANGE.getLower(), PRIMARY_USER_RANGE.getUpper())};
+        final Range<Integer> restrictedUserRange = uidRangeForUser(RESTRICTED_PROFILE_A.id);
+        final UidRangeParcel[] restrictedUserRangeParcel = new UidRangeParcel[] {
+                new UidRangeParcel(restrictedUserRange.getLower(), restrictedUserRange.getUpper())};
+        final Vpn vpn = createVpn(PRIMARY_USER.id);
+
+        // Set lockdown calls setRequireVpnForUids
+        vpn.setLockdown(true);
+        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(primaryUserRangeParcel));
+
+        // Add the restricted user
+        doReturn(true).when(mUserManager).canHaveRestrictedProfile();
+        setMockedUsers(PRIMARY_USER, RESTRICTED_PROFILE_A);
+        vpn.onUserAdded(RESTRICTED_PROFILE_A.id);
+
+        // Expect restricted user range to be added.
+        verify(mConnectivityManager).setRequireVpnForUids(true,
+                toRanges(restrictedUserRangeParcel));
+
+        // Mark as partial indicates that the user is removed, mUserManager.getAliveUsers() does not
+        // return the restricted user but it is still returned in mUserManager.getUserInfo().
+        RESTRICTED_PROFILE_A.partial = true;
+        // Remove the restricted user
+        vpn.onUserRemoved(RESTRICTED_PROFILE_A.id);
+        verify(mConnectivityManager).setRequireVpnForUids(false,
+                toRanges(restrictedUserRangeParcel));
+        // reset to avoid affecting other tests since RESTRICTED_PROFILE_A is static.
+        RESTRICTED_PROFILE_A.partial = false;
+    }
+
+    @Test
+    public void testOnUserAddedAndRemoved_restrictedUserAlwaysOn() throws Exception {
+        final Vpn vpn = createVpn(PRIMARY_USER.id);
+
+        // setAlwaysOnPackage() calls setRequireVpnForUids()
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[0], true /* lockdown */, null /* lockdownAllowlist */));
+        final List<Integer> excludedUids = List.of(PKG_UIDS[0]);
+        final List<Range<Integer>> primaryRanges =
+                makeVpnUidRange(PRIMARY_USER.id, excludedUids);
+        verify(mConnectivityManager).setRequireVpnForUids(true, primaryRanges);
+
+        // Add the restricted user
+        doReturn(true).when(mUserManager).canHaveRestrictedProfile();
+        setMockedUsers(PRIMARY_USER, RESTRICTED_PROFILE_A);
+        vpn.onUserAdded(RESTRICTED_PROFILE_A.id);
+
+        final List<Range<Integer>> restrictedRanges =
+                makeVpnUidRange(RESTRICTED_PROFILE_A.id, excludedUids);
+        // Expect restricted user range to be added.
+        verify(mConnectivityManager).setRequireVpnForUids(true, restrictedRanges);
+
+        // Mark as partial indicates that the user is removed, mUserManager.getAliveUsers() does not
+        // return the restricted user but it is still returned in mUserManager.getUserInfo().
+        RESTRICTED_PROFILE_A.partial = true;
+        // Remove the restricted user
+        vpn.onUserRemoved(RESTRICTED_PROFILE_A.id);
+        verify(mConnectivityManager).setRequireVpnForUids(false, restrictedRanges);
+
+        // reset to avoid affecting other tests since RESTRICTED_PROFILE_A is static.
+        RESTRICTED_PROFILE_A.partial = false;
+    }
+
+    @Test
+    public void testPrepare_throwSecurityExceptionWhenGivenPackageDoesNotBelongToTheCaller()
+            throws Exception {
+        mTestDeps.mIgnoreCallingUidChecks = false;
+        final Vpn vpn = createVpn();
+        assertThrows(SecurityException.class,
+                () -> vpn.prepare("com.not.vpn.owner", null, VpnManager.TYPE_VPN_SERVICE));
+        assertThrows(SecurityException.class,
+                () -> vpn.prepare(null, "com.not.vpn.owner", VpnManager.TYPE_VPN_SERVICE));
+        assertThrows(SecurityException.class,
+                () -> vpn.prepare("com.not.vpn.owner1", "com.not.vpn.owner2",
+                        VpnManager.TYPE_VPN_SERVICE));
+    }
+
+    @Test
+    public void testPrepare_bothOldPackageAndNewPackageAreNull() throws Exception {
+        final Vpn vpn = createVpn();
+        assertTrue(vpn.prepare(null, null, VpnManager.TYPE_VPN_SERVICE));
+
+    }
+
+    @Test
+    public void testPrepare_legacyVpnWithoutControlVpn()
+            throws Exception {
+        doThrow(new SecurityException("no CONTROL_VPN")).when(mContext)
+                .enforceCallingOrSelfPermission(eq(CONTROL_VPN), any());
+        final Vpn vpn = createVpn();
+        assertThrows(SecurityException.class,
+                () -> vpn.prepare(null, VpnConfig.LEGACY_VPN, VpnManager.TYPE_VPN_SERVICE));
+
+        // CONTROL_VPN can be held by the caller or another system server process - both are
+        // allowed. Just checking for `enforceCallingPermission` may not be sufficient.
+        verify(mContext, never()).enforceCallingPermission(eq(CONTROL_VPN), any());
+    }
+
+    @Test
+    public void testPrepare_legacyVpnWithControlVpn()
+            throws Exception {
+        doNothing().when(mContext).enforceCallingOrSelfPermission(eq(CONTROL_VPN), any());
+        final Vpn vpn = createVpn();
+        assertTrue(vpn.prepare(null, VpnConfig.LEGACY_VPN, VpnManager.TYPE_VPN_SERVICE));
+
+        // CONTROL_VPN can be held by the caller or another system server process - both are
+        // allowed. Just checking for `enforceCallingPermission` may not be sufficient.
+        verify(mContext, never()).enforceCallingPermission(eq(CONTROL_VPN), any());
+    }
+
+    @Test
+    public void testIsAlwaysOnPackageSupported() throws Exception {
+        final Vpn vpn = createVpn(PRIMARY_USER.id);
+
+        ApplicationInfo appInfo = new ApplicationInfo();
+        when(mPackageManager.getApplicationInfoAsUser(eq(PKGS[0]), anyInt(), eq(PRIMARY_USER.id)))
+                .thenReturn(appInfo);
+
+        ServiceInfo svcInfo = new ServiceInfo();
+        ResolveInfo resInfo = new ResolveInfo();
+        resInfo.serviceInfo = svcInfo;
+        when(mPackageManager.queryIntentServicesAsUser(any(), eq(PackageManager.GET_META_DATA),
+                eq(PRIMARY_USER.id)))
+                .thenReturn(Collections.singletonList(resInfo));
+
+        // null package name should return false
+        assertFalse(vpn.isAlwaysOnPackageSupported(null));
+
+        // Pre-N apps are not supported
+        appInfo.targetSdkVersion = VERSION_CODES.M;
+        assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+
+        // N+ apps are supported by default
+        appInfo.targetSdkVersion = VERSION_CODES.N;
+        assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+
+        // Apps that opt out explicitly are not supported
+        appInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT;
+        Bundle metaData = new Bundle();
+        metaData.putBoolean(VpnService.SERVICE_META_DATA_SUPPORTS_ALWAYS_ON, false);
+        svcInfo.metaData = metaData;
+        assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+    }
+
+    @Test
+    public void testNotificationShownForAlwaysOnApp() throws Exception {
+        final UserHandle userHandle = UserHandle.of(PRIMARY_USER.id);
+        final Vpn vpn = createVpn(PRIMARY_USER.id);
+        setMockedUsers(PRIMARY_USER);
+
+        final InOrder order = inOrder(mNotificationManager);
+
+        // Don't show a notification for regular disconnected states.
+        vpn.updateState(DetailedState.DISCONNECTED, TAG);
+        order.verify(mNotificationManager, atLeastOnce()).cancel(anyString(), anyInt());
+
+        // Start showing a notification for disconnected once always-on.
+        vpn.setAlwaysOnPackage(PKGS[0], false, null);
+        order.verify(mNotificationManager).notify(anyString(), anyInt(), any());
+
+        // Stop showing the notification once connected.
+        vpn.updateState(DetailedState.CONNECTED, TAG);
+        order.verify(mNotificationManager).cancel(anyString(), anyInt());
+
+        // Show the notification if we disconnect again.
+        vpn.updateState(DetailedState.DISCONNECTED, TAG);
+        order.verify(mNotificationManager).notify(anyString(), anyInt(), any());
+
+        // Notification should be cleared after unsetting always-on package.
+        vpn.setAlwaysOnPackage(null, false, null);
+        order.verify(mNotificationManager).cancel(anyString(), anyInt());
+    }
+
+    /**
+     * The profile name should NOT change between releases for backwards compatibility
+     *
+     * <p>If this is changed between releases, the {@link Vpn#getVpnProfilePrivileged()} method MUST
+     * be updated to ensure backward compatibility.
+     */
+    @Test
+    public void testGetProfileNameForPackage() throws Exception {
+        final Vpn vpn = createVpn(PRIMARY_USER.id);
+        setMockedUsers(PRIMARY_USER);
+
+        final String expected = Credentials.PLATFORM_VPN + PRIMARY_USER.id + "_" + TEST_VPN_PKG;
+        assertEquals(expected, vpn.getProfileNameForPackage(TEST_VPN_PKG));
+    }
+
+    private Vpn createVpn(String... grantedOps) throws Exception {
+        return createVpn(PRIMARY_USER, grantedOps);
+    }
+
+    private Vpn createVpn(UserInfo user, String... grantedOps) throws Exception {
+        final Vpn vpn = createVpn(user.id);
+        setMockedUsers(user);
+
+        for (final String opStr : grantedOps) {
+            when(mAppOps.noteOpNoThrow(opStr, Process.myUid(), TEST_VPN_PKG,
+                    null /* attributionTag */, null /* message */))
+                    .thenReturn(AppOpsManager.MODE_ALLOWED);
+        }
+
+        return vpn;
+    }
+
+    private void checkProvisionVpnProfile(Vpn vpn, boolean expectedResult, String... checkedOps) {
+        assertEquals(expectedResult, vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile));
+
+        // The profile should always be stored, whether or not consent has been previously granted.
+        verify(mVpnProfileStore)
+                .put(
+                        eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)),
+                        eq(mVpnProfile.encode()));
+
+        for (final String checkedOpStr : checkedOps) {
+            verify(mAppOps).noteOpNoThrow(checkedOpStr, Process.myUid(), TEST_VPN_PKG,
+                    null /* attributionTag */, null /* message */);
+        }
+    }
+
+    @Test
+    public void testProvisionVpnProfileNoIpsecTunnels() throws Exception {
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS))
+                .thenReturn(false);
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+
+        try {
+            checkProvisionVpnProfile(
+                    vpn, true /* expectedResult */, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+            fail("Expected exception due to missing feature");
+        } catch (UnsupportedOperationException expected) {
+        }
+    }
+
+    private String startVpnForVerifyAppExclusionList(Vpn vpn) throws Exception {
+        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+                .thenReturn(mVpnProfile.encode());
+        when(mVpnProfileStore.get(PRIMARY_USER_APP_EXCLUDE_KEY))
+                .thenReturn(HexDump.hexStringToByteArray(PKGS_BYTES));
+        final String sessionKey = vpn.startVpnProfile(TEST_VPN_PKG);
+        final Set<Range<Integer>> uidRanges = vpn.createUserAndRestrictedProfilesRanges(
+                PRIMARY_USER.id, null /* allowedApplications */, Arrays.asList(PKGS));
+        verify(mConnectivityManager).setVpnDefaultForUids(eq(sessionKey), eq(uidRanges));
+        clearInvocations(mConnectivityManager);
+        verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
+        vpn.mNetworkAgent = mMockNetworkAgent;
+
+        return sessionKey;
+    }
+
+    private Vpn prepareVpnForVerifyAppExclusionList() throws Exception {
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        startVpnForVerifyAppExclusionList(vpn);
+
+        return vpn;
+    }
+
+    @Test
+    public void testSetAndGetAppExclusionList() throws Exception {
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        final String sessionKey = startVpnForVerifyAppExclusionList(vpn);
+        verify(mVpnProfileStore, never()).put(eq(PRIMARY_USER_APP_EXCLUDE_KEY), any());
+        vpn.setAppExclusionList(TEST_VPN_PKG, Arrays.asList(PKGS));
+        verify(mVpnProfileStore)
+                .put(eq(PRIMARY_USER_APP_EXCLUDE_KEY),
+                     eq(HexDump.hexStringToByteArray(PKGS_BYTES)));
+        final Set<Range<Integer>> uidRanges = vpn.createUserAndRestrictedProfilesRanges(
+                PRIMARY_USER.id, null /* allowedApplications */, Arrays.asList(PKGS));
+        verify(mConnectivityManager).setVpnDefaultForUids(eq(sessionKey), eq(uidRanges));
+        assertEquals(uidRanges, vpn.mNetworkCapabilities.getUids());
+        assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG));
+    }
+
+    @Test
+    public void testRefreshPlatformVpnAppExclusionList_updatesExcludedUids() throws Exception {
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        final String sessionKey = startVpnForVerifyAppExclusionList(vpn);
+        vpn.setAppExclusionList(TEST_VPN_PKG, Arrays.asList(PKGS));
+        final Set<Range<Integer>> uidRanges = vpn.createUserAndRestrictedProfilesRanges(
+                PRIMARY_USER.id, null /* allowedApplications */, Arrays.asList(PKGS));
+        verify(mConnectivityManager).setVpnDefaultForUids(eq(sessionKey), eq(uidRanges));
+        verify(mMockNetworkAgent).doSendNetworkCapabilities(any());
+        assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG));
+
+        reset(mMockNetworkAgent);
+
+        // Remove one of the package
+        List<Integer> newExcludedUids = toList(PKG_UIDS);
+        newExcludedUids.remove((Integer) PKG_UIDS[0]);
+        Set<Range<Integer>> newUidRanges = makeVpnUidRangeSet(PRIMARY_USER.id, newExcludedUids);
+        sPackages.remove(PKGS[0]);
+        vpn.refreshPlatformVpnAppExclusionList();
+
+        // List in keystore is not changed, but UID for the removed packages is no longer exempted.
+        assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG));
+        assertEquals(newUidRanges, vpn.mNetworkCapabilities.getUids());
+        ArgumentCaptor<NetworkCapabilities> ncCaptor =
+                ArgumentCaptor.forClass(NetworkCapabilities.class);
+        verify(mMockNetworkAgent).doSendNetworkCapabilities(ncCaptor.capture());
+        assertEquals(newUidRanges, ncCaptor.getValue().getUids());
+        verify(mConnectivityManager).setVpnDefaultForUids(eq(sessionKey), eq(newUidRanges));
+
+        reset(mMockNetworkAgent);
+
+        // Add the package back
+        newExcludedUids.add(PKG_UIDS[0]);
+        newUidRanges = makeVpnUidRangeSet(PRIMARY_USER.id, newExcludedUids);
+        sPackages.put(PKGS[0], PKG_UIDS[0]);
+        vpn.refreshPlatformVpnAppExclusionList();
+
+        // List in keystore is not changed and the uid list should be updated in the net cap.
+        assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG));
+        assertEquals(newUidRanges, vpn.mNetworkCapabilities.getUids());
+        verify(mMockNetworkAgent).doSendNetworkCapabilities(ncCaptor.capture());
+        assertEquals(newUidRanges, ncCaptor.getValue().getUids());
+
+        // The uidRange is the same as the original setAppExclusionList so this is the second call
+        verify(mConnectivityManager, times(2))
+                .setVpnDefaultForUids(eq(sessionKey), eq(newUidRanges));
+    }
+
+    private List<Range<Integer>> makeVpnUidRange(int userId, List<Integer> excludedAppIdList) {
+        final SortedSet<Integer> list = new TreeSet<>();
+
+        final int userBase = userId * UserHandle.PER_USER_RANGE;
+        for (int appId : excludedAppIdList) {
+            final int uid = UserHandle.getUid(userId, appId);
+            list.add(uid);
+            if (Process.isApplicationUid(uid)) {
+                list.add(Process.toSdkSandboxUid(uid)); // Add Sdk Sandbox UID
+            }
+        }
+
+        final int minUid = userBase;
+        final int maxUid = userBase + UserHandle.PER_USER_RANGE - 1;
+        final List<Range<Integer>> ranges = new ArrayList<>();
+
+        // Iterate the list to create the ranges between each uid.
+        int start = minUid;
+        for (int uid : list) {
+            if (uid == start) {
+                start++;
+            } else {
+                ranges.add(new Range<>(start, uid - 1));
+                start = uid + 1;
+            }
+        }
+
+        // Create the range between last uid and max uid.
+        if (start <= maxUid) {
+            ranges.add(new Range<>(start, maxUid));
+        }
+
+        return ranges;
+    }
+
+    private Set<Range<Integer>> makeVpnUidRangeSet(int userId, List<Integer> excludedAppIdList) {
+        return new ArraySet<>(makeVpnUidRange(userId, excludedAppIdList));
+    }
+
+    @Test
+    public void testSetAndGetAppExclusionListRestrictedUser() throws Exception {
+        final Vpn vpn = prepareVpnForVerifyAppExclusionList();
+
+        // Mock it to restricted profile
+        when(mUserManager.getUserInfo(anyInt())).thenReturn(RESTRICTED_PROFILE_A);
+
+        // Restricted users cannot configure VPNs
+        assertThrows(SecurityException.class,
+                () -> vpn.setAppExclusionList(TEST_VPN_PKG, new ArrayList<>()));
+
+        assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG));
+    }
+
+    @Test
+    public void testProvisionVpnProfilePreconsented() throws Exception {
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+
+        checkProvisionVpnProfile(
+                vpn, true /* expectedResult */, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+    }
+
+    @Test
+    public void testProvisionVpnProfileNotPreconsented() throws Exception {
+        final Vpn vpn = createVpn();
+
+        // Expect that both the ACTIVATE_VPN and ACTIVATE_PLATFORM_VPN were tried, but the caller
+        // had neither.
+        checkProvisionVpnProfile(vpn, false /* expectedResult */,
+                AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN, AppOpsManager.OPSTR_ACTIVATE_VPN);
+    }
+
+    @Test
+    public void testProvisionVpnProfileVpnServicePreconsented() throws Exception {
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_VPN);
+
+        checkProvisionVpnProfile(vpn, true /* expectedResult */, AppOpsManager.OPSTR_ACTIVATE_VPN);
+    }
+
+    @Test
+    public void testProvisionVpnProfileTooLarge() throws Exception {
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+
+        final VpnProfile bigProfile = new VpnProfile("");
+        bigProfile.name = new String(new byte[Vpn.MAX_VPN_PROFILE_SIZE_BYTES + 1]);
+
+        try {
+            vpn.provisionVpnProfile(TEST_VPN_PKG, bigProfile);
+            fail("Expected IAE due to profile size");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testProvisionVpnProfileRestrictedUser() throws Exception {
+        final Vpn vpn =
+                createVpn(
+                        RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+
+        try {
+            vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile);
+            fail("Expected SecurityException due to restricted user");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    @Test
+    public void testDeleteVpnProfile() throws Exception {
+        final Vpn vpn = createVpn();
+
+        vpn.deleteVpnProfile(TEST_VPN_PKG);
+
+        verify(mVpnProfileStore)
+                .remove(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
+    }
+
+    @Test
+    public void testDeleteVpnProfileRestrictedUser() throws Exception {
+        final Vpn vpn =
+                createVpn(
+                        RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+
+        try {
+            vpn.deleteVpnProfile(TEST_VPN_PKG);
+            fail("Expected SecurityException due to restricted user");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    @Test
+    public void testGetVpnProfilePrivileged() throws Exception {
+        final Vpn vpn = createVpn();
+
+        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+                .thenReturn(new VpnProfile("").encode());
+
+        vpn.getVpnProfilePrivileged(TEST_VPN_PKG);
+
+        verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
+    }
+
+    private void verifyPlatformVpnIsActivated(String packageName) {
+        verify(mAppOps).noteOpNoThrow(
+                eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
+                eq(Process.myUid()),
+                eq(packageName),
+                eq(null) /* attributionTag */,
+                eq(null) /* message */);
+        verify(mAppOps).startOp(
+                eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER),
+                eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
+                eq(packageName),
+                eq(null) /* attributionTag */,
+                eq(null) /* message */);
+    }
+
+    private void verifyPlatformVpnIsDeactivated(String packageName) {
+        // Add a small delay to double confirm that finishOp is only called once.
+        verify(mAppOps, after(100)).finishOp(
+                eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER),
+                eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
+                eq(packageName),
+                eq(null) /* attributionTag */);
+    }
+
+    @Test
+    public void testStartVpnProfile() throws Exception {
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+
+        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+                .thenReturn(mVpnProfile.encode());
+
+        vpn.startVpnProfile(TEST_VPN_PKG);
+
+        verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
+        verifyPlatformVpnIsActivated(TEST_VPN_PKG);
+    }
+
+    @Test
+    public void testStartVpnProfileVpnServicePreconsented() throws Exception {
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_VPN);
+
+        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+                .thenReturn(mVpnProfile.encode());
+
+        vpn.startVpnProfile(TEST_VPN_PKG);
+
+        // Verify that the ACTIVATE_VPN appop was checked, but no error was thrown.
+        verify(mAppOps).noteOpNoThrow(AppOpsManager.OPSTR_ACTIVATE_VPN, Process.myUid(),
+                TEST_VPN_PKG, null /* attributionTag */, null /* message */);
+    }
+
+    @Test
+    public void testStartVpnProfileNotConsented() throws Exception {
+        final Vpn vpn = createVpn();
+
+        try {
+            vpn.startVpnProfile(TEST_VPN_PKG);
+            fail("Expected failure due to no user consent");
+        } catch (SecurityException expected) {
+        }
+
+        // Verify both appops were checked.
+        verify(mAppOps)
+                .noteOpNoThrow(
+                        eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
+                        eq(Process.myUid()),
+                        eq(TEST_VPN_PKG),
+                        eq(null) /* attributionTag */,
+                        eq(null) /* message */);
+        verify(mAppOps).noteOpNoThrow(AppOpsManager.OPSTR_ACTIVATE_VPN, Process.myUid(),
+                TEST_VPN_PKG, null /* attributionTag */, null /* message */);
+
+        // Keystore should never have been accessed.
+        verify(mVpnProfileStore, never()).get(any());
+    }
+
+    @Test
+    public void testStartVpnProfileMissingProfile() throws Exception {
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+
+        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))).thenReturn(null);
+
+        try {
+            vpn.startVpnProfile(TEST_VPN_PKG);
+            fail("Expected failure due to missing profile");
+        } catch (IllegalArgumentException expected) {
+        }
+
+        verify(mVpnProfileStore).get(vpn.getProfileNameForPackage(TEST_VPN_PKG));
+        verify(mAppOps)
+                .noteOpNoThrow(
+                        eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
+                        eq(Process.myUid()),
+                        eq(TEST_VPN_PKG),
+                        eq(null) /* attributionTag */,
+                        eq(null) /* message */);
+    }
+
+    @Test
+    public void testStartVpnProfileRestrictedUser() throws Exception {
+        final Vpn vpn = createVpn(RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+
+        try {
+            vpn.startVpnProfile(TEST_VPN_PKG);
+            fail("Expected SecurityException due to restricted user");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    @Test
+    public void testStopVpnProfileRestrictedUser() throws Exception {
+        final Vpn vpn = createVpn(RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+
+        try {
+            vpn.stopVpnProfile(TEST_VPN_PKG);
+            fail("Expected SecurityException due to restricted user");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    @Test
+    public void testStartOpAndFinishOpWillBeCalledWhenPlatformVpnIsOnAndOff() throws Exception {
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+                .thenReturn(mVpnProfile.encode());
+        vpn.startVpnProfile(TEST_VPN_PKG);
+        verifyPlatformVpnIsActivated(TEST_VPN_PKG);
+        // Add a small delay to make sure that startOp is only called once.
+        verify(mAppOps, after(100).times(1)).startOp(
+                eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER),
+                eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
+                eq(TEST_VPN_PKG),
+                eq(null) /* attributionTag */,
+                eq(null) /* message */);
+        // Check that the startOp is not called with OPSTR_ESTABLISH_VPN_SERVICE.
+        verify(mAppOps, never()).startOp(
+                eq(AppOpsManager.OPSTR_ESTABLISH_VPN_SERVICE),
+                eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
+                eq(TEST_VPN_PKG),
+                eq(null) /* attributionTag */,
+                eq(null) /* message */);
+        vpn.stopVpnProfile(TEST_VPN_PKG);
+        verifyPlatformVpnIsDeactivated(TEST_VPN_PKG);
+    }
+
+    @Test
+    public void testStartOpWithSeamlessHandover() throws Exception {
+        // Create with SYSTEM_USER so that establish() will match the user ID when checking
+        // against Binder.getCallerUid
+        final Vpn vpn = createVpn(SYSTEM_USER, AppOpsManager.OPSTR_ACTIVATE_VPN);
+        assertTrue(vpn.prepare(TEST_VPN_PKG, null, VpnManager.TYPE_VPN_SERVICE));
+        final VpnConfig config = new VpnConfig();
+        config.user = "VpnTest";
+        config.addresses.add(new LinkAddress("192.0.2.2/32"));
+        config.mtu = 1450;
+        final ResolveInfo resolveInfo = new ResolveInfo();
+        final ServiceInfo serviceInfo = new ServiceInfo();
+        serviceInfo.permission = BIND_VPN_SERVICE;
+        resolveInfo.serviceInfo = serviceInfo;
+        when(mPackageManager.resolveService(any(), anyInt())).thenReturn(resolveInfo);
+        when(mContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true);
+        vpn.establish(config);
+        verify(mAppOps, times(1)).startOp(
+                eq(AppOpsManager.OPSTR_ESTABLISH_VPN_SERVICE),
+                eq(Process.myUid()),
+                eq(TEST_VPN_PKG),
+                eq(null) /* attributionTag */,
+                eq(null) /* message */);
+        // Call establish() twice with the same config, it should match seamless handover case and
+        // startOp() shouldn't be called again.
+        vpn.establish(config);
+        verify(mAppOps, times(1)).startOp(
+                eq(AppOpsManager.OPSTR_ESTABLISH_VPN_SERVICE),
+                eq(Process.myUid()),
+                eq(TEST_VPN_PKG),
+                eq(null) /* attributionTag */,
+                eq(null) /* message */);
+    }
+
+    private void verifyVpnManagerEvent(String sessionKey, String category, int errorClass,
+            int errorCode, String[] packageName, @NonNull VpnProfileState... profileState) {
+        final Context userContext =
+                mContext.createContextAsUser(UserHandle.of(PRIMARY_USER.id), 0 /* flags */);
+        final ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+
+        final int verifyTimes = profileState.length;
+        verify(userContext, timeout(TEST_TIMEOUT_MS).times(verifyTimes))
+                .startService(intentArgumentCaptor.capture());
+
+        for (int i = 0; i < verifyTimes; i++) {
+            final Intent intent = intentArgumentCaptor.getAllValues().get(i);
+            assertEquals(packageName[i], intent.getPackage());
+            assertEquals(sessionKey, intent.getStringExtra(VpnManager.EXTRA_SESSION_KEY));
+            final Set<String> categories = intent.getCategories();
+            assertTrue(categories.contains(category));
+            assertEquals(1, categories.size());
+            assertEquals(errorClass,
+                    intent.getIntExtra(VpnManager.EXTRA_ERROR_CLASS, -1 /* defaultValue */));
+            assertEquals(errorCode,
+                    intent.getIntExtra(VpnManager.EXTRA_ERROR_CODE, -1 /* defaultValue */));
+            // CATEGORY_EVENT_DEACTIVATED_BY_USER & CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED won't
+            // send NetworkCapabilities & LinkProperties to VPN app.
+            // For ERROR_CODE_NETWORK_LOST, the NetworkCapabilities & LinkProperties of underlying
+            // network will be cleared. So the VPN app will receive null for those 2 extra values.
+            if (category.equals(VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER)
+                    || category.equals(VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED)
+                    || errorCode == VpnManager.ERROR_CODE_NETWORK_LOST) {
+                assertNull(intent.getParcelableExtra(
+                        VpnManager.EXTRA_UNDERLYING_NETWORK_CAPABILITIES));
+                assertNull(intent.getParcelableExtra(VpnManager.EXTRA_UNDERLYING_LINK_PROPERTIES));
+            } else {
+                assertNotNull(intent.getParcelableExtra(
+                        VpnManager.EXTRA_UNDERLYING_NETWORK_CAPABILITIES));
+                assertNotNull(intent.getParcelableExtra(
+                        VpnManager.EXTRA_UNDERLYING_LINK_PROPERTIES));
+            }
+
+            assertEquals(profileState[i], intent.getParcelableExtra(
+                    VpnManager.EXTRA_VPN_PROFILE_STATE, VpnProfileState.class));
+        }
+        reset(userContext);
+    }
+
+    private void verifyDeactivatedByUser(String sessionKey, String[] packageName) {
+        // CATEGORY_EVENT_DEACTIVATED_BY_USER is not an error event, so both of errorClass and
+        // errorCode won't be set.
+        verifyVpnManagerEvent(sessionKey, VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
+                -1 /* errorClass */, -1 /* errorCode */, packageName,
+                // VPN NetworkAgnet does not switch to CONNECTED in the test, and the state is not
+                // important here. Verify that the state as it is, i.e. CONNECTING state.
+                new VpnProfileState(VpnProfileState.STATE_CONNECTING,
+                        sessionKey, false /* alwaysOn */, false /* lockdown */));
+    }
+
+    private void verifyAlwaysOnStateChanged(String[] packageName, VpnProfileState... profileState) {
+        verifyVpnManagerEvent(null /* sessionKey */,
+                VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
+                -1 /* errorCode */, packageName, profileState);
+    }
+
+    @Test
+    public void testVpnManagerEventForUserDeactivated() throws Exception {
+        // For security reasons, Vpn#prepare() will check that oldPackage and newPackage are either
+        // null or the package of the caller. This test will call Vpn#prepare() to pretend the old
+        // VPN is replaced by a new one. But only Settings can change to some other packages, and
+        // this is checked with CONTROL_VPN so simulate holding CONTROL_VPN in order to pass the
+        // security checks.
+        doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN);
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+                .thenReturn(mVpnProfile.encode());
+
+        // Test the case that the user deactivates the vpn in vpn app.
+        final String sessionKey1 = vpn.startVpnProfile(TEST_VPN_PKG);
+        verifyPlatformVpnIsActivated(TEST_VPN_PKG);
+        vpn.stopVpnProfile(TEST_VPN_PKG);
+        verifyPlatformVpnIsDeactivated(TEST_VPN_PKG);
+        verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
+        reset(mDeviceIdleInternal);
+        verifyDeactivatedByUser(sessionKey1, new String[] {TEST_VPN_PKG});
+        reset(mAppOps);
+
+        // Test the case that the user chooses another vpn and the original one is replaced.
+        final String sessionKey2 = vpn.startVpnProfile(TEST_VPN_PKG);
+        verifyPlatformVpnIsActivated(TEST_VPN_PKG);
+        vpn.prepare(TEST_VPN_PKG, "com.new.vpn" /* newPackage */, TYPE_VPN_PLATFORM);
+        verifyPlatformVpnIsDeactivated(TEST_VPN_PKG);
+        verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
+        reset(mDeviceIdleInternal);
+        verifyDeactivatedByUser(sessionKey2, new String[] {TEST_VPN_PKG});
+    }
+
+    @Test
+    public void testVpnManagerEventForAlwaysOnChanged() throws Exception {
+        // Calling setAlwaysOnPackage() needs to hold CONTROL_VPN.
+        doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN);
+        final Vpn vpn = createVpn(PRIMARY_USER.id);
+        // Enable VPN always-on for PKGS[1].
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */,
+                null /* lockdownAllowlist */));
+        verifyPowerSaveTempWhitelistApp(PKGS[1]);
+        reset(mDeviceIdleInternal);
+        verifyAlwaysOnStateChanged(new String[] {PKGS[1]},
+                new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+                        null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
+
+        // Enable VPN lockdown for PKGS[1].
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true /* lockdown */,
+                null /* lockdownAllowlist */));
+        verifyPowerSaveTempWhitelistApp(PKGS[1]);
+        reset(mDeviceIdleInternal);
+        verifyAlwaysOnStateChanged(new String[] {PKGS[1]},
+                new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+                        null /* sessionKey */, true /* alwaysOn */, true /* lockdown */));
+
+        // Disable VPN lockdown for PKGS[1].
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */,
+                null /* lockdownAllowlist */));
+        verifyPowerSaveTempWhitelistApp(PKGS[1]);
+        reset(mDeviceIdleInternal);
+        verifyAlwaysOnStateChanged(new String[] {PKGS[1]},
+                new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+                        null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
+
+        // Disable VPN always-on.
+        assertTrue(vpn.setAlwaysOnPackage(null, false /* lockdown */,
+                null /* lockdownAllowlist */));
+        verifyPowerSaveTempWhitelistApp(PKGS[1]);
+        reset(mDeviceIdleInternal);
+        verifyAlwaysOnStateChanged(new String[] {PKGS[1]},
+                new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+                        null /* sessionKey */, false /* alwaysOn */, false /* lockdown */));
+
+        // Enable VPN always-on for PKGS[1] again.
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */,
+                null /* lockdownAllowlist */));
+        verifyPowerSaveTempWhitelistApp(PKGS[1]);
+        reset(mDeviceIdleInternal);
+        verifyAlwaysOnStateChanged(new String[] {PKGS[1]},
+                new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+                        null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
+
+        // Enable VPN always-on for PKGS[2].
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[2], false /* lockdown */,
+                null /* lockdownAllowlist */));
+        verifyPowerSaveTempWhitelistApp(PKGS[2]);
+        reset(mDeviceIdleInternal);
+        // PKGS[1] is replaced with PKGS[2].
+        // Pass 2 VpnProfileState objects to verifyVpnManagerEvent(), the first one is sent to
+        // PKGS[1] to notify PKGS[1] that the VPN always-on is disabled, the second one is sent to
+        // PKGS[2] to notify PKGS[2] that the VPN always-on is enabled.
+        verifyAlwaysOnStateChanged(new String[] {PKGS[1], PKGS[2]},
+                new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+                        null /* sessionKey */, false /* alwaysOn */, false /* lockdown */),
+                new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+                        null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
+    }
+
+    @Test
+    public void testReconnectVpnManagerVpnWithAlwaysOnEnabled() throws Exception {
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+                .thenReturn(mVpnProfile.encode());
+        vpn.startVpnProfile(TEST_VPN_PKG);
+        verifyPlatformVpnIsActivated(TEST_VPN_PKG);
+
+        // Enable VPN always-on for TEST_VPN_PKG.
+        assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, false /* lockdown */,
+                null /* lockdownAllowlist */));
+
+        // Reset to verify next startVpnProfile.
+        reset(mAppOps);
+
+        vpn.stopVpnProfile(TEST_VPN_PKG);
+
+        // Reconnect the vpn with different package will cause exception.
+        assertThrows(SecurityException.class, () -> vpn.startVpnProfile(PKGS[0]));
+
+        // Reconnect the vpn again with the vpn always on package w/o exception.
+        vpn.startVpnProfile(TEST_VPN_PKG);
+        verifyPlatformVpnIsActivated(TEST_VPN_PKG);
+    }
+
+    @Test
+    public void testLockdown_enableDisableWhileConnected() throws Exception {
+        final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
+                createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
+
+        final InOrder order = inOrder(mTestDeps);
+        order.verify(mTestDeps, timeout(TIMEOUT_CROSSTHREAD_MS))
+                .newNetworkAgent(any(), any(), any(), any(), any(), any(),
+                        argThat(config -> config.allowBypass), any(), any());
+
+        // Make VPN lockdown.
+        assertTrue(vpnSnapShot.vpn.setAlwaysOnPackage(TEST_VPN_PKG, true /* lockdown */,
+                null /* lockdownAllowlist */));
+
+        order.verify(mTestDeps, timeout(TIMEOUT_CROSSTHREAD_MS))
+                .newNetworkAgent(any(), any(), any(), any(), any(), any(),
+                argThat(config -> !config.allowBypass), any(), any());
+
+        // Disable lockdown.
+        assertTrue(vpnSnapShot.vpn.setAlwaysOnPackage(TEST_VPN_PKG, false /* lockdown */,
+                null /* lockdownAllowlist */));
+
+        order.verify(mTestDeps, timeout(TIMEOUT_CROSSTHREAD_MS))
+                .newNetworkAgent(any(), any(), any(), any(), any(), any(),
+                        argThat(config -> config.allowBypass), any(), any());
+    }
+
+    @Test
+    public void testSetPackageAuthorizationVpnService() throws Exception {
+        final Vpn vpn = createVpn();
+
+        assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_SERVICE));
+        verify(mAppOps)
+                .setMode(
+                        eq(AppOpsManager.OPSTR_ACTIVATE_VPN),
+                        eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
+                        eq(TEST_VPN_PKG),
+                        eq(AppOpsManager.MODE_ALLOWED));
+    }
+
+    @Test
+    public void testSetPackageAuthorizationPlatformVpn() throws Exception {
+        final Vpn vpn = createVpn();
+
+        assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, TYPE_VPN_PLATFORM));
+        verify(mAppOps)
+                .setMode(
+                        eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
+                        eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
+                        eq(TEST_VPN_PKG),
+                        eq(AppOpsManager.MODE_ALLOWED));
+    }
+
+    @Test
+    public void testSetPackageAuthorizationRevokeAuthorization() throws Exception {
+        final Vpn vpn = createVpn();
+
+        assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_NONE));
+        verify(mAppOps)
+                .setMode(
+                        eq(AppOpsManager.OPSTR_ACTIVATE_VPN),
+                        eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
+                        eq(TEST_VPN_PKG),
+                        eq(AppOpsManager.MODE_IGNORED));
+        verify(mAppOps)
+                .setMode(
+                        eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
+                        eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
+                        eq(TEST_VPN_PKG),
+                        eq(AppOpsManager.MODE_IGNORED));
+    }
+
+    private NetworkCallback triggerOnAvailableAndGetCallback() throws Exception {
+        return triggerOnAvailableAndGetCallback(new NetworkCapabilities.Builder().build());
+    }
+
+    private NetworkCallback triggerOnAvailableAndGetCallback(
+            @NonNull final NetworkCapabilities caps) throws Exception {
+        final ArgumentCaptor<NetworkCallback> networkCallbackCaptor =
+                ArgumentCaptor.forClass(NetworkCallback.class);
+        verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS))
+                .registerSystemDefaultNetworkCallback(networkCallbackCaptor.capture(), any());
+
+        // onAvailable() will trigger onDefaultNetworkChanged(), so NetdUtils#setInterfaceUp will be
+        // invoked. Set the return value of INetd#interfaceGetCfg to prevent NullPointerException.
+        final InterfaceConfigurationParcel config = new InterfaceConfigurationParcel();
+        config.flags = new String[] {IF_STATE_DOWN};
+        when(mNetd.interfaceGetCfg(anyString())).thenReturn(config);
+        final NetworkCallback cb = networkCallbackCaptor.getValue();
+        cb.onAvailable(TEST_NETWORK);
+        // Trigger onCapabilitiesChanged() and onLinkPropertiesChanged() so the test can verify that
+        // if NetworkCapabilities and LinkProperties of underlying network will be sent/cleared or
+        // not.
+        // See verifyVpnManagerEvent().
+        cb.onCapabilitiesChanged(TEST_NETWORK, caps);
+        cb.onLinkPropertiesChanged(TEST_NETWORK, new LinkProperties());
+        return cb;
+    }
+
+    private void verifyInterfaceSetCfgWithFlags(String flag) throws Exception {
+        // Add a timeout for waiting for interfaceSetCfg to be called.
+        verify(mNetd, timeout(TEST_TIMEOUT_MS)).interfaceSetCfg(argThat(
+                config -> Arrays.asList(config.flags).contains(flag)));
+    }
+
+    private void doTestPlatformVpnWithException(IkeException exception,
+            String category, int errorType, int errorCode) throws Exception {
+        final ArgumentCaptor<IkeSessionCallback> captor =
+                ArgumentCaptor.forClass(IkeSessionCallback.class);
+
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+                .thenReturn(mVpnProfile.encode());
+
+        doReturn(new NetworkCapabilities()).when(mConnectivityManager)
+                .getRedactedNetworkCapabilitiesForPackage(any(), anyInt(), anyString());
+        doReturn(new LinkProperties()).when(mConnectivityManager)
+                .getRedactedLinkPropertiesForPackage(any(), anyInt(), anyString());
+
+        final String sessionKey = vpn.startVpnProfile(TEST_VPN_PKG);
+        final Set<Range<Integer>> uidRanges = rangeSet(PRIMARY_USER_RANGE);
+        // This is triggered by Ikev2VpnRunner constructor.
+        verify(mConnectivityManager, times(1)).setVpnDefaultForUids(eq(sessionKey), eq(uidRanges));
+        final NetworkCallback cb = triggerOnAvailableAndGetCallback();
+
+        verifyInterfaceSetCfgWithFlags(IF_STATE_UP);
+
+        // Wait for createIkeSession() to be called before proceeding in order to ensure consistent
+        // state
+        verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS))
+                .createIkeSession(any(), any(), any(), any(), captor.capture(), any());
+        // This is triggered by Vpn#startOrMigrateIkeSession().
+        verify(mConnectivityManager, times(2)).setVpnDefaultForUids(eq(sessionKey), eq(uidRanges));
+        reset(mIkev2SessionCreator);
+        // For network lost case, the process should be triggered by calling onLost(), which is the
+        // same process with the real case.
+        if (errorCode == VpnManager.ERROR_CODE_NETWORK_LOST) {
+            cb.onLost(TEST_NETWORK);
+            verify(mExecutor, atLeastOnce()).schedule(any(Runnable.class), anyLong(), any());
+        } else {
+            final IkeSessionCallback ikeCb = captor.getValue();
+            mExecutor.execute(() -> ikeCb.onClosedWithException(exception));
+        }
+
+        verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
+        reset(mDeviceIdleInternal);
+        verifyVpnManagerEvent(sessionKey, category, errorType, errorCode,
+                // VPN NetworkAgnet does not switch to CONNECTED in the test, and the state is not
+                // important here. Verify that the state as it is, i.e. CONNECTING state.
+                new String[] {TEST_VPN_PKG}, new VpnProfileState(VpnProfileState.STATE_CONNECTING,
+                        sessionKey, false /* alwaysOn */, false /* lockdown */));
+        if (errorType == VpnManager.ERROR_CLASS_NOT_RECOVERABLE) {
+            verify(mConnectivityManager).setVpnDefaultForUids(eq(sessionKey),
+                    eq(Collections.EMPTY_LIST));
+            verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS))
+                    .unregisterNetworkCallback(eq(cb));
+        } else if (errorType == VpnManager.ERROR_CLASS_RECOVERABLE
+                // Vpn won't retry when there is no usable underlying network.
+                && errorCode != VpnManager.ERROR_CODE_NETWORK_LOST) {
+            int retryIndex = 0;
+            // First failure occurred above.
+            final IkeSessionCallback retryCb = verifyRetryAndGetNewIkeCb(retryIndex++);
+            // Trigger 2 more failures to let the retry delay increase to 5s.
+            mExecutor.execute(() -> retryCb.onClosedWithException(exception));
+            final IkeSessionCallback retryCb2 = verifyRetryAndGetNewIkeCb(retryIndex++);
+            mExecutor.execute(() -> retryCb2.onClosedWithException(exception));
+            final IkeSessionCallback retryCb3 = verifyRetryAndGetNewIkeCb(retryIndex++);
+
+            // setVpnDefaultForUids may be called again but the uidRanges should not change.
+            verify(mConnectivityManager, atLeast(2)).setVpnDefaultForUids(eq(sessionKey),
+                    mUidRangesCaptor.capture());
+            final List<Collection<Range<Integer>>> capturedUidRanges =
+                    mUidRangesCaptor.getAllValues();
+            for (int i = 2; i < capturedUidRanges.size(); i++) {
+                // Assert equals no order.
+                assertTrue(
+                        "uid ranges should not be modified. Expected: " + uidRanges
+                                + ", actual: " + capturedUidRanges.get(i),
+                        capturedUidRanges.get(i).containsAll(uidRanges)
+                                && capturedUidRanges.get(i).size() == uidRanges.size());
+            }
+
+            // A fourth failure will cause the retry delay to be greater than 5s.
+            mExecutor.execute(() -> retryCb3.onClosedWithException(exception));
+            verifyRetryAndGetNewIkeCb(retryIndex++);
+
+            // The VPN network preference will be cleared when the retry delay is greater than 5s.
+            verify(mConnectivityManager).setVpnDefaultForUids(eq(sessionKey),
+                    eq(Collections.EMPTY_LIST));
+        }
+    }
+
+    private IkeSessionCallback verifyRetryAndGetNewIkeCb(int retryIndex) {
+        final ArgumentCaptor<IkeSessionCallback> ikeCbCaptor =
+                ArgumentCaptor.forClass(IkeSessionCallback.class);
+
+        // Verify retry is scheduled
+        final long expectedDelayMs = mTestDeps.getNextRetryDelayMs(retryIndex);
+        verify(mExecutor, timeout(TEST_TIMEOUT_MS)).schedule(any(Runnable.class),
+                eq(expectedDelayMs), eq(TimeUnit.MILLISECONDS));
+
+        verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS + expectedDelayMs))
+                .createIkeSession(any(), any(), any(), any(), ikeCbCaptor.capture(), any());
+
+        // Forget the mIkev2SessionCreator#createIkeSession call and mExecutor#schedule call
+        // for the next retry verification
+        resetIkev2SessionCreator(mIkeSessionWrapper);
+
+        return ikeCbCaptor.getValue();
+    }
+
+    @Test
+    public void testStartPlatformVpnAuthenticationFailed() throws Exception {
+        final IkeProtocolException exception = mock(IkeProtocolException.class);
+        final int errorCode = IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
+        when(exception.getErrorType()).thenReturn(errorCode);
+        doTestPlatformVpnWithException(exception,
+                VpnManager.CATEGORY_EVENT_IKE_ERROR, VpnManager.ERROR_CLASS_NOT_RECOVERABLE,
+                errorCode);
+    }
+
+    @Test
+    public void testStartPlatformVpnFailedWithRecoverableError() throws Exception {
+        final IkeProtocolException exception = mock(IkeProtocolException.class);
+        final int errorCode = IkeProtocolException.ERROR_TYPE_TEMPORARY_FAILURE;
+        when(exception.getErrorType()).thenReturn(errorCode);
+        doTestPlatformVpnWithException(exception,
+                VpnManager.CATEGORY_EVENT_IKE_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE, errorCode);
+    }
+
+    @Test
+    public void testStartPlatformVpnFailedWithUnknownHostException() throws Exception {
+        final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
+        final UnknownHostException unknownHostException = new UnknownHostException();
+        final int errorCode = VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST;
+        when(exception.getCause()).thenReturn(unknownHostException);
+        doTestPlatformVpnWithException(exception,
+                VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
+                errorCode);
+    }
+
+    @Test
+    public void testStartPlatformVpnFailedWithIkeTimeoutException() throws Exception {
+        final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
+        final IkeTimeoutException ikeTimeoutException =
+                new IkeTimeoutException("IkeTimeoutException");
+        final int errorCode = VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT;
+        when(exception.getCause()).thenReturn(ikeTimeoutException);
+        doTestPlatformVpnWithException(exception,
+                VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
+                errorCode);
+    }
+
+    @Test
+    public void testStartPlatformVpnFailedWithIkeNetworkLostException() throws Exception {
+        final IkeNetworkLostException exception = new IkeNetworkLostException(
+                new Network(100));
+        doTestPlatformVpnWithException(exception,
+                VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
+                VpnManager.ERROR_CODE_NETWORK_LOST);
+    }
+
+    @Test
+    public void testStartPlatformVpnFailedWithIOException() throws Exception {
+        final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
+        final IOException ioException = new IOException();
+        final int errorCode = VpnManager.ERROR_CODE_NETWORK_IO;
+        when(exception.getCause()).thenReturn(ioException);
+        doTestPlatformVpnWithException(exception,
+                VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
+                errorCode);
+    }
+
+    @Test
+    public void testStartPlatformVpnIllegalArgumentExceptionInSetup() throws Exception {
+        when(mIkev2SessionCreator.createIkeSession(any(), any(), any(), any(), any(), any()))
+                .thenThrow(new IllegalArgumentException());
+        final Vpn vpn = startLegacyVpn(createVpn(PRIMARY_USER.id), mVpnProfile);
+        final NetworkCallback cb = triggerOnAvailableAndGetCallback();
+
+        verifyInterfaceSetCfgWithFlags(IF_STATE_UP);
+
+        // Wait for createIkeSession() to be called before proceeding in order to ensure consistent
+        // state
+        verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)).unregisterNetworkCallback(eq(cb));
+        assertEquals(LegacyVpnInfo.STATE_FAILED, vpn.getLegacyVpnInfo().state);
+    }
+
+    @Test
+    public void testVpnManagerEventWillNotBeSentToSettingsVpn() throws Exception {
+        startLegacyVpn(createVpn(PRIMARY_USER.id), mVpnProfile);
+        triggerOnAvailableAndGetCallback();
+
+        verifyInterfaceSetCfgWithFlags(IF_STATE_UP);
+
+        final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
+        final IkeTimeoutException ikeTimeoutException =
+                new IkeTimeoutException("IkeTimeoutException");
+        when(exception.getCause()).thenReturn(ikeTimeoutException);
+
+        final ArgumentCaptor<IkeSessionCallback> captor =
+                ArgumentCaptor.forClass(IkeSessionCallback.class);
+        verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS))
+                .createIkeSession(any(), any(), any(), any(), captor.capture(), any());
+        final IkeSessionCallback ikeCb = captor.getValue();
+        ikeCb.onClosedWithException(exception);
+
+        final Context userContext =
+                mContext.createContextAsUser(UserHandle.of(PRIMARY_USER.id), 0 /* flags */);
+        verify(userContext, never()).startService(any());
+    }
+
+    private void setAndVerifyAlwaysOnPackage(Vpn vpn, int uid, boolean lockdownEnabled) {
+        assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, lockdownEnabled, null));
+
+        verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
+        verify(mAppOps).setMode(
+                eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), eq(uid), eq(TEST_VPN_PKG),
+                eq(AppOpsManager.MODE_ALLOWED));
+
+        verify(mSystemServices).settingsSecurePutStringForUser(
+                eq(Settings.Secure.ALWAYS_ON_VPN_APP), eq(TEST_VPN_PKG), eq(PRIMARY_USER.id));
+        verify(mSystemServices).settingsSecurePutIntForUser(
+                eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN), eq(lockdownEnabled ? 1 : 0),
+                eq(PRIMARY_USER.id));
+        verify(mSystemServices).settingsSecurePutStringForUser(
+                eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST), eq(""), eq(PRIMARY_USER.id));
+    }
+
+    @Test
+    public void testSetAndStartAlwaysOnVpn() throws Exception {
+        final Vpn vpn = createVpn(PRIMARY_USER.id);
+        setMockedUsers(PRIMARY_USER);
+
+        // UID checks must return a different UID; otherwise it'll be treated as already prepared.
+        final int uid = Process.myUid() + 1;
+        when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt()))
+                .thenReturn(uid);
+        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+                .thenReturn(mVpnProfile.encode());
+
+        setAndVerifyAlwaysOnPackage(vpn, uid, false);
+        assertTrue(vpn.startAlwaysOnVpn());
+
+        // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in
+        // a subsequent CL.
+    }
+
+    private Vpn startLegacyVpn(final Vpn vpn, final VpnProfile vpnProfile) throws Exception {
+        setMockedUsers(PRIMARY_USER);
+        vpn.startLegacyVpn(vpnProfile);
+        return vpn;
+    }
+
+    private IkeSessionConnectionInfo createIkeConnectInfo() {
+        return new IkeSessionConnectionInfo(TEST_VPN_CLIENT_IP, TEST_VPN_SERVER_IP, TEST_NETWORK);
+    }
+
+    private IkeSessionConnectionInfo createIkeConnectInfo_2() {
+        return new IkeSessionConnectionInfo(
+                TEST_VPN_CLIENT_IP_2, TEST_VPN_SERVER_IP_2, TEST_NETWORK_2);
+    }
+
+    private IkeSessionConfiguration createIkeConfig(
+            IkeSessionConnectionInfo ikeConnectInfo, boolean isMobikeEnabled) {
+        final IkeSessionConfiguration.Builder builder =
+                new IkeSessionConfiguration.Builder(ikeConnectInfo);
+
+        if (isMobikeEnabled) {
+            builder.addIkeExtension(EXTENSION_TYPE_MOBIKE);
+        }
+
+        return builder.build();
+    }
+
+    private ChildSessionConfiguration createChildConfig() {
+        return new ChildSessionConfiguration.Builder(
+                        Arrays.asList(IN_TS, IN_TS6), Arrays.asList(OUT_TS, OUT_TS6))
+                .addInternalAddress(new LinkAddress(TEST_VPN_INTERNAL_IP, IP4_PREFIX_LEN))
+                .addInternalAddress(new LinkAddress(TEST_VPN_INTERNAL_IP6, IP6_PREFIX_LEN))
+                .addInternalDnsServer(TEST_VPN_INTERNAL_DNS)
+                .addInternalDnsServer(TEST_VPN_INTERNAL_DNS6)
+                .build();
+    }
+
+    private IpSecTransform createIpSecTransform() {
+        return new IpSecTransform(mContext, new IpSecConfig());
+    }
+
+    private void verifyApplyTunnelModeTransforms(int expectedTimes) throws Exception {
+        verify(mIpSecService, times(expectedTimes)).applyTunnelModeTransform(
+                eq(TEST_TUNNEL_RESOURCE_ID), eq(IpSecManager.DIRECTION_IN),
+                anyInt(), anyString());
+        verify(mIpSecService, times(expectedTimes)).applyTunnelModeTransform(
+                eq(TEST_TUNNEL_RESOURCE_ID), eq(IpSecManager.DIRECTION_OUT),
+                anyInt(), anyString());
+    }
+
+    private Pair<IkeSessionCallback, ChildSessionCallback> verifyCreateIkeAndCaptureCbs()
+            throws Exception {
+        final ArgumentCaptor<IkeSessionCallback> ikeCbCaptor =
+                ArgumentCaptor.forClass(IkeSessionCallback.class);
+        final ArgumentCaptor<ChildSessionCallback> childCbCaptor =
+                ArgumentCaptor.forClass(ChildSessionCallback.class);
+
+        verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS)).createIkeSession(
+                any(), any(), any(), any(), ikeCbCaptor.capture(), childCbCaptor.capture());
+
+        return new Pair<>(ikeCbCaptor.getValue(), childCbCaptor.getValue());
+    }
+
+    private static class PlatformVpnSnapshot {
+        public final Vpn vpn;
+        public final NetworkCallback nwCb;
+        public final IkeSessionCallback ikeCb;
+        public final ChildSessionCallback childCb;
+
+        PlatformVpnSnapshot(Vpn vpn, NetworkCallback nwCb,
+                IkeSessionCallback ikeCb, ChildSessionCallback childCb) {
+            this.vpn = vpn;
+            this.nwCb = nwCb;
+            this.ikeCb = ikeCb;
+            this.childCb = childCb;
+        }
+    }
+
+    private PlatformVpnSnapshot verifySetupPlatformVpn(IkeSessionConfiguration ikeConfig)
+            throws Exception {
+        return verifySetupPlatformVpn(ikeConfig, true);
+    }
+
+    private PlatformVpnSnapshot verifySetupPlatformVpn(
+            IkeSessionConfiguration ikeConfig, boolean mtuSupportsIpv6) throws Exception {
+        return verifySetupPlatformVpn(mVpnProfile, ikeConfig, mtuSupportsIpv6);
+    }
+
+    private PlatformVpnSnapshot verifySetupPlatformVpn(VpnProfile vpnProfile,
+            IkeSessionConfiguration ikeConfig, boolean mtuSupportsIpv6) throws Exception {
+        return verifySetupPlatformVpn(vpnProfile, ikeConfig,
+                new NetworkCapabilities.Builder().build() /* underlying network caps */,
+                mtuSupportsIpv6, false /* areLongLivedTcpConnectionsExpensive */);
+    }
+
+    private PlatformVpnSnapshot verifySetupPlatformVpn(VpnProfile vpnProfile,
+            IkeSessionConfiguration ikeConfig,
+            @NonNull final NetworkCapabilities underlyingNetworkCaps,
+            boolean mtuSupportsIpv6,
+            boolean areLongLivedTcpConnectionsExpensive) throws Exception {
+        if (!mtuSupportsIpv6) {
+            doReturn(IPV6_MIN_MTU - 1).when(mTestDeps).calculateVpnMtu(any(), anyInt(), anyInt(),
+                    anyBoolean());
+        }
+
+        doReturn(mMockNetworkAgent).when(mTestDeps)
+                .newNetworkAgent(
+                        any(), any(), anyString(), any(), any(), any(), any(), any(), any());
+        doReturn(TEST_NETWORK).when(mMockNetworkAgent).getNetwork();
+
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+                .thenReturn(vpnProfile.encode());
+
+        final String sessionKey = vpn.startVpnProfile(TEST_VPN_PKG);
+        final Set<Range<Integer>> uidRanges = Collections.singleton(PRIMARY_USER_RANGE);
+        verify(mConnectivityManager).setVpnDefaultForUids(eq(sessionKey), eq(uidRanges));
+        final NetworkCallback nwCb = triggerOnAvailableAndGetCallback(underlyingNetworkCaps);
+        // There are 4 interactions with the executor.
+        // - Network available
+        // - LP change
+        // - NC change
+        // - schedule() calls in scheduleStartIkeSession()
+        // The first 3 calls are triggered from Executor.execute(). The execute() will also call to
+        // schedule() with 0 delay. Verify the exact interaction here so that it won't cause flakes
+        // in the follow-up flow.
+        verify(mExecutor, timeout(TEST_TIMEOUT_MS).times(4))
+                .schedule(any(Runnable.class), anyLong(), any());
+        reset(mExecutor);
+
+        // Mock the setup procedure by firing callbacks
+        final Pair<IkeSessionCallback, ChildSessionCallback> cbPair =
+                verifyCreateIkeAndCaptureCbs();
+        final IkeSessionCallback ikeCb = cbPair.first;
+        final ChildSessionCallback childCb = cbPair.second;
+
+        ikeCb.onOpened(ikeConfig);
+        childCb.onIpSecTransformCreated(createIpSecTransform(), IpSecManager.DIRECTION_IN);
+        childCb.onIpSecTransformCreated(createIpSecTransform(), IpSecManager.DIRECTION_OUT);
+        childCb.onOpened(createChildConfig());
+
+        // Verification VPN setup
+        verifyApplyTunnelModeTransforms(1);
+
+        ArgumentCaptor<LinkProperties> lpCaptor = ArgumentCaptor.forClass(LinkProperties.class);
+        ArgumentCaptor<NetworkCapabilities> ncCaptor =
+                ArgumentCaptor.forClass(NetworkCapabilities.class);
+        ArgumentCaptor<NetworkAgentConfig> nacCaptor =
+                ArgumentCaptor.forClass(NetworkAgentConfig.class);
+        verify(mTestDeps).newNetworkAgent(
+                any(), any(), anyString(), ncCaptor.capture(), lpCaptor.capture(),
+                any(), nacCaptor.capture(), any(), any());
+        verify(mIkeSessionWrapper).setUnderpinnedNetwork(TEST_NETWORK);
+        // Check LinkProperties
+        final LinkProperties lp = lpCaptor.getValue();
+        final List<RouteInfo> expectedRoutes =
+                new ArrayList<>(
+                        Arrays.asList(
+                                new RouteInfo(
+                                        new IpPrefix(Inet4Address.ANY, 0),
+                                        null /* gateway */,
+                                        TEST_IFACE_NAME,
+                                        RouteInfo.RTN_UNICAST)));
+        final List<LinkAddress> expectedAddresses =
+                new ArrayList<>(
+                        Arrays.asList(new LinkAddress(TEST_VPN_INTERNAL_IP, IP4_PREFIX_LEN)));
+        final List<InetAddress> expectedDns = new ArrayList<>(Arrays.asList(TEST_VPN_INTERNAL_DNS));
+
+        if (mtuSupportsIpv6) {
+            expectedRoutes.add(
+                    new RouteInfo(
+                            new IpPrefix(Inet6Address.ANY, 0),
+                            null /* gateway */,
+                            TEST_IFACE_NAME,
+                            RouteInfo.RTN_UNICAST));
+            expectedAddresses.add(new LinkAddress(TEST_VPN_INTERNAL_IP6, IP6_PREFIX_LEN));
+            expectedDns.add(TEST_VPN_INTERNAL_DNS6);
+        } else {
+            expectedRoutes.add(
+                    new RouteInfo(
+                            new IpPrefix(Inet6Address.ANY, 0),
+                            null /* gateway */,
+                            TEST_IFACE_NAME,
+                            RTN_UNREACHABLE));
+        }
+
+        assertEquals(expectedRoutes, lp.getRoutes());
+        assertEquals(expectedAddresses, lp.getLinkAddresses());
+        assertEquals(expectedDns, lp.getDnsServers());
+
+        // Check NetworkCapabilities
+        assertEquals(Arrays.asList(TEST_NETWORK), ncCaptor.getValue().getUnderlyingNetworks());
+
+        // Check if allowBypass is set or not.
+        assertTrue(nacCaptor.getValue().isBypassableVpn());
+        // Check if extra info for VPN is set.
+        assertTrue(nacCaptor.getValue().getLegacyExtraInfo().contains(TEST_VPN_PKG));
+        final VpnTransportInfo info = (VpnTransportInfo) ncCaptor.getValue().getTransportInfo();
+        assertTrue(info.isBypassable());
+        assertEquals(areLongLivedTcpConnectionsExpensive,
+                info.areLongLivedTcpConnectionsExpensive());
+        return new PlatformVpnSnapshot(vpn, nwCb, ikeCb, childCb);
+    }
+
+    @Test
+    public void testStartPlatformVpn() throws Exception {
+        final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
+                createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
+        vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
+        verify(mConnectivityManager).setVpnDefaultForUids(anyString(), eq(Collections.EMPTY_LIST));
+    }
+
+    @Test
+    public void testMigrateIkeSession_FromIkeTunnConnParams_AutoTimerNoTimer() throws Exception {
+        doTestMigrateIkeSession_FromIkeTunnConnParams(
+                false /* isAutomaticIpVersionSelectionEnabled */,
+                true /* isAutomaticNattKeepaliveTimerEnabled */,
+                TEST_KEEPALIVE_TIMEOUT_UNSET /* keepaliveInProfile */,
+                ESP_IP_VERSION_AUTO /* ipVersionInProfile */,
+                ESP_ENCAP_TYPE_AUTO /* encapTypeInProfile */);
+    }
+
+    @Test
+    public void testMigrateIkeSession_FromIkeTunnConnParams_AutoTimerTimerSet() throws Exception {
+        doTestMigrateIkeSession_FromIkeTunnConnParams(
+                false /* isAutomaticIpVersionSelectionEnabled */,
+                true /* isAutomaticNattKeepaliveTimerEnabled */,
+                TEST_KEEPALIVE_TIMER /* keepaliveInProfile */,
+                ESP_IP_VERSION_AUTO /* ipVersionInProfile */,
+                ESP_ENCAP_TYPE_AUTO /* encapTypeInProfile */);
+    }
+
+    @Test
+    public void testMigrateIkeSession_FromIkeTunnConnParams_AutoIp() throws Exception {
+        doTestMigrateIkeSession_FromIkeTunnConnParams(
+                true /* isAutomaticIpVersionSelectionEnabled */,
+                false /* isAutomaticNattKeepaliveTimerEnabled */,
+                TEST_KEEPALIVE_TIMEOUT_UNSET /* keepaliveInProfile */,
+                ESP_IP_VERSION_AUTO /* ipVersionInProfile */,
+                ESP_ENCAP_TYPE_AUTO /* encapTypeInProfile */);
+    }
+
+    @Test
+    public void testMigrateIkeSession_FromIkeTunnConnParams_AssignedIpProtocol() throws Exception {
+        doTestMigrateIkeSession_FromIkeTunnConnParams(
+                false /* isAutomaticIpVersionSelectionEnabled */,
+                false /* isAutomaticNattKeepaliveTimerEnabled */,
+                TEST_KEEPALIVE_TIMEOUT_UNSET /* keepaliveInProfile */,
+                ESP_IP_VERSION_IPV4 /* ipVersionInProfile */,
+                ESP_ENCAP_TYPE_UDP /* encapTypeInProfile */);
+    }
+
+    @Test
+    public void testMigrateIkeSession_FromNotIkeTunnConnParams_AutoTimer() throws Exception {
+        doTestMigrateIkeSession_FromNotIkeTunnConnParams(
+                false /* isAutomaticIpVersionSelectionEnabled */,
+                true /* isAutomaticNattKeepaliveTimerEnabled */);
+    }
+
+    @Test
+    public void testMigrateIkeSession_FromNotIkeTunnConnParams_AutoIp() throws Exception {
+        doTestMigrateIkeSession_FromNotIkeTunnConnParams(
+                true /* isAutomaticIpVersionSelectionEnabled */,
+                false /* isAutomaticNattKeepaliveTimerEnabled */);
+    }
+
+    private void doTestMigrateIkeSession_FromNotIkeTunnConnParams(
+            boolean isAutomaticIpVersionSelectionEnabled,
+            boolean isAutomaticNattKeepaliveTimerEnabled) throws Exception {
+        final Ikev2VpnProfile ikeProfile =
+                new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY)
+                        .setAuthPsk(TEST_VPN_PSK)
+                        .setBypassable(true /* isBypassable */)
+                        .setAutomaticNattKeepaliveTimerEnabled(isAutomaticNattKeepaliveTimerEnabled)
+                        .setAutomaticIpVersionSelectionEnabled(isAutomaticIpVersionSelectionEnabled)
+                        .build();
+
+        final int expectedKeepalive = isAutomaticNattKeepaliveTimerEnabled
+                ? AUTOMATIC_KEEPALIVE_DELAY_SECONDS
+                : DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT;
+        doTestMigrateIkeSession(ikeProfile.toVpnProfile(),
+                expectedKeepalive,
+                ESP_IP_VERSION_AUTO /* expectedIpVersion */,
+                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
+                new NetworkCapabilities.Builder().build());
+    }
+
+    private Ikev2VpnProfile makeIkeV2VpnProfile(
+            boolean isAutomaticIpVersionSelectionEnabled,
+            boolean isAutomaticNattKeepaliveTimerEnabled,
+            int keepaliveInProfile,
+            int ipVersionInProfile,
+            int encapTypeInProfile) {
+        // TODO: Update helper function in IkeSessionTestUtils to support building IkeSessionParams
+        // with IP version and encap type when mainline-prod branch support these two APIs.
+        final IkeSessionParams params = getTestIkeSessionParams(true /* testIpv6 */,
+                new IkeFqdnIdentification(TEST_IDENTITY), keepaliveInProfile);
+        final IkeSessionParams ikeSessionParams = new IkeSessionParams.Builder(params)
+                .setIpVersion(ipVersionInProfile)
+                .setEncapType(encapTypeInProfile)
+                .build();
+
+        final IkeTunnelConnectionParams tunnelParams =
+                new IkeTunnelConnectionParams(ikeSessionParams, CHILD_PARAMS);
+        return new Ikev2VpnProfile.Builder(tunnelParams)
+                .setBypassable(true)
+                .setAutomaticNattKeepaliveTimerEnabled(isAutomaticNattKeepaliveTimerEnabled)
+                .setAutomaticIpVersionSelectionEnabled(isAutomaticIpVersionSelectionEnabled)
+                .build();
+    }
+
+    private void doTestMigrateIkeSession_FromIkeTunnConnParams(
+            boolean isAutomaticIpVersionSelectionEnabled,
+            boolean isAutomaticNattKeepaliveTimerEnabled,
+            int keepaliveInProfile,
+            int ipVersionInProfile,
+            int encapTypeInProfile) throws Exception {
+        doTestMigrateIkeSession_FromIkeTunnConnParams(isAutomaticIpVersionSelectionEnabled,
+                isAutomaticNattKeepaliveTimerEnabled, keepaliveInProfile, ipVersionInProfile,
+                encapTypeInProfile, new NetworkCapabilities.Builder().build());
+    }
+
+    private void doTestMigrateIkeSession_FromIkeTunnConnParams(
+            boolean isAutomaticIpVersionSelectionEnabled,
+            boolean isAutomaticNattKeepaliveTimerEnabled,
+            int keepaliveInProfile,
+            int ipVersionInProfile,
+            int encapTypeInProfile,
+            @NonNull final NetworkCapabilities nc) throws Exception {
+        final Ikev2VpnProfile ikeProfile = makeIkeV2VpnProfile(
+                isAutomaticIpVersionSelectionEnabled,
+                isAutomaticNattKeepaliveTimerEnabled,
+                keepaliveInProfile,
+                ipVersionInProfile,
+                encapTypeInProfile);
+
+        final IkeSessionParams ikeSessionParams =
+                ikeProfile.getIkeTunnelConnectionParams().getIkeSessionParams();
+        final int expectedKeepalive = isAutomaticNattKeepaliveTimerEnabled
+                ? AUTOMATIC_KEEPALIVE_DELAY_SECONDS
+                : ikeSessionParams.getNattKeepAliveDelaySeconds();
+        final int expectedIpVersion = isAutomaticIpVersionSelectionEnabled
+                ? ESP_IP_VERSION_AUTO
+                : ikeSessionParams.getIpVersion();
+        final int expectedEncapType = isAutomaticIpVersionSelectionEnabled
+                ? ESP_ENCAP_TYPE_AUTO
+                : ikeSessionParams.getEncapType();
+        doTestMigrateIkeSession(ikeProfile.toVpnProfile(), expectedKeepalive,
+                expectedIpVersion, expectedEncapType, nc);
+    }
+
+    @Test
+    public void doTestMigrateIkeSession_Vcn() throws Exception {
+        final int expectedKeepalive = 2097; // Any unlikely number will do
+        final NetworkCapabilities vcnNc = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .setTransportInfo(new VcnTransportInfo(TEST_SUB_ID, expectedKeepalive))
+                .build();
+        final Ikev2VpnProfile ikev2VpnProfile = makeIkeV2VpnProfile(
+                true /* isAutomaticIpVersionSelectionEnabled */,
+                true /* isAutomaticNattKeepaliveTimerEnabled */,
+                234 /* keepaliveInProfile */, // Should be ignored, any value will do
+                ESP_IP_VERSION_IPV4, // Should be ignored
+                ESP_ENCAP_TYPE_UDP // Should be ignored
+        );
+        doTestMigrateIkeSession(
+                ikev2VpnProfile.toVpnProfile(),
+                expectedKeepalive,
+                ESP_IP_VERSION_AUTO /* expectedIpVersion */,
+                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
+                vcnNc);
+    }
+
+    private void doTestMigrateIkeSession(
+            @NonNull final VpnProfile profile,
+            final int expectedKeepalive,
+            final int expectedIpVersion,
+            final int expectedEncapType,
+            @NonNull final NetworkCapabilities caps) throws Exception {
+        final PlatformVpnSnapshot vpnSnapShot =
+                verifySetupPlatformVpn(profile,
+                        createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */),
+                        caps /* underlying network capabilities */,
+                        false /* mtuSupportsIpv6 */,
+                        expectedKeepalive < DEFAULT_LONG_LIVED_TCP_CONNS_EXPENSIVE_TIMEOUT_SEC);
+        // Simulate a new network coming up
+        vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
+        verify(mIkeSessionWrapper, never()).setNetwork(any(), anyInt(), anyInt(), anyInt());
+
+        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2, caps);
+        // Verify MOBIKE is triggered
+        verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).setNetwork(TEST_NETWORK_2,
+                expectedIpVersion, expectedEncapType, expectedKeepalive);
+
+        vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
+    }
+
+    @Test
+    public void testLinkPropertiesUpdateTriggerReevaluation() throws Exception {
+        final boolean hasV6 = true;
+
+        mockCarrierConfig(TEST_SUB_ID, TelephonyManager.SIM_STATE_LOADED, TEST_KEEPALIVE_TIMER,
+                PREFERRED_IKE_PROTOCOL_IPV6_ESP);
+        final IkeSessionParams params = getTestIkeSessionParams(hasV6,
+                new IkeFqdnIdentification(TEST_IDENTITY), TEST_KEEPALIVE_TIMER);
+        final IkeTunnelConnectionParams tunnelParams =
+                new IkeTunnelConnectionParams(params, CHILD_PARAMS);
+        final Ikev2VpnProfile ikeProfile = new Ikev2VpnProfile.Builder(tunnelParams)
+                .setBypassable(true)
+                .setAutomaticNattKeepaliveTimerEnabled(false)
+                .setAutomaticIpVersionSelectionEnabled(true)
+                .build();
+        final PlatformVpnSnapshot vpnSnapShot =
+                verifySetupPlatformVpn(ikeProfile.toVpnProfile(),
+                        createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */),
+                        new NetworkCapabilities.Builder().build() /* underlying network caps */,
+                        hasV6 /* mtuSupportsIpv6 */,
+                        false /* areLongLivedTcpConnectionsExpensive */);
+        reset(mExecutor);
+
+        // Simulate a new network coming up
+        final LinkProperties lp = new LinkProperties();
+        lp.addLinkAddress(new LinkAddress("192.0.2.2/32"));
+
+        // Have the executor use the real delay to make sure schedule() was called only
+        // once for all calls. Also, arrange for execute() not to call schedule() to avoid
+        // messing with the checks for schedule().
+        mExecutor.delayMs = TestExecutor.REAL_DELAY;
+        mExecutor.executeDirect = true;
+        vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
+        vpnSnapShot.nwCb.onCapabilitiesChanged(
+                TEST_NETWORK_2, new NetworkCapabilities.Builder().build());
+        vpnSnapShot.nwCb.onLinkPropertiesChanged(TEST_NETWORK_2, new LinkProperties(lp));
+        verify(mExecutor).schedule(any(Runnable.class), longThat(it -> it > 0), any());
+        reset(mExecutor);
+
+        final InOrder order = inOrder(mIkeSessionWrapper);
+
+        // Verify the network is started
+        order.verify(mIkeSessionWrapper, timeout(TIMEOUT_CROSSTHREAD_MS)).setNetwork(TEST_NETWORK_2,
+                ESP_IP_VERSION_AUTO, ESP_ENCAP_TYPE_AUTO, TEST_KEEPALIVE_TIMER);
+
+        // Send the same properties, check that no migration is scheduled
+        vpnSnapShot.nwCb.onLinkPropertiesChanged(TEST_NETWORK_2, new LinkProperties(lp));
+        verify(mExecutor, never()).schedule(any(Runnable.class), anyLong(), any());
+
+        // Add v6 address, verify MOBIKE is triggered
+        lp.addLinkAddress(new LinkAddress("2001:db8::1/64"));
+        vpnSnapShot.nwCb.onLinkPropertiesChanged(TEST_NETWORK_2, new LinkProperties(lp));
+        order.verify(mIkeSessionWrapper, timeout(TIMEOUT_CROSSTHREAD_MS)).setNetwork(TEST_NETWORK_2,
+                ESP_IP_VERSION_AUTO, ESP_ENCAP_TYPE_AUTO, TEST_KEEPALIVE_TIMER);
+
+        // Add another v4 address, verify MOBIKE is triggered
+        final LinkProperties stacked = new LinkProperties();
+        stacked.setInterfaceName("v4-" + lp.getInterfaceName());
+        stacked.addLinkAddress(new LinkAddress("192.168.0.1/32"));
+        lp.addStackedLink(stacked);
+        vpnSnapShot.nwCb.onLinkPropertiesChanged(TEST_NETWORK_2, new LinkProperties(lp));
+        order.verify(mIkeSessionWrapper, timeout(TIMEOUT_CROSSTHREAD_MS)).setNetwork(TEST_NETWORK_2,
+                ESP_IP_VERSION_AUTO, ESP_ENCAP_TYPE_AUTO, TEST_KEEPALIVE_TIMER);
+
+        vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
+    }
+
+    private void mockCarrierConfig(int subId, int simStatus, int keepaliveTimer, int ikeProtocol) {
+        final SubscriptionInfo subscriptionInfo = mock(SubscriptionInfo.class);
+        doReturn(subId).when(subscriptionInfo).getSubscriptionId();
+        doReturn(List.of(subscriptionInfo)).when(mSubscriptionManager)
+                .getActiveSubscriptionInfoList();
+
+        doReturn(simStatus).when(mTmPerSub).getSimApplicationState();
+        doReturn(TEST_MCCMNC).when(mTmPerSub).getSimOperator(subId);
+
+        final PersistableBundle persistableBundle = new PersistableBundle();
+        persistableBundle.putInt(KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT, keepaliveTimer);
+        persistableBundle.putInt(KEY_PREFERRED_IKE_PROTOCOL_INT, ikeProtocol);
+        // For CarrierConfigManager.isConfigForIdentifiedCarrier check
+        persistableBundle.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
+        doReturn(persistableBundle).when(mConfigManager).getConfigForSubId(subId);
+    }
+
+    private CarrierConfigManager.CarrierConfigChangeListener getCarrierConfigListener() {
+        final ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerCaptor =
+                ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
+
+        verify(mConfigManager).registerCarrierConfigChangeListener(any(), listenerCaptor.capture());
+
+        return listenerCaptor.getValue();
+    }
+
+    @Test
+    public void testNattKeepaliveTimerFromCarrierConfig_noSubId() throws Exception {
+        doTestReadCarrierConfig(new NetworkCapabilities(),
+                TelephonyManager.SIM_STATE_LOADED,
+                PREFERRED_IKE_PROTOCOL_IPV4_UDP,
+                AUTOMATIC_KEEPALIVE_DELAY_SECONDS /* expectedKeepaliveTimer */,
+                ESP_IP_VERSION_AUTO /* expectedIpVersion */,
+                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
+                false /* expectedReadFromCarrierConfig*/,
+                true /* areLongLivedTcpConnectionsExpensive */);
+    }
+
+    @Test
+    public void testNattKeepaliveTimerFromCarrierConfig_simAbsent() throws Exception {
+        doTestReadCarrierConfig(new NetworkCapabilities.Builder().build(),
+                TelephonyManager.SIM_STATE_ABSENT,
+                PREFERRED_IKE_PROTOCOL_IPV4_UDP,
+                AUTOMATIC_KEEPALIVE_DELAY_SECONDS /* expectedKeepaliveTimer */,
+                ESP_IP_VERSION_AUTO /* expectedIpVersion */,
+                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
+                false /* expectedReadFromCarrierConfig*/,
+                true /* areLongLivedTcpConnectionsExpensive */);
+    }
+
+    @Test
+    public void testNattKeepaliveTimerFromCarrierConfig() throws Exception {
+        doTestReadCarrierConfig(createTestCellNc(),
+                TelephonyManager.SIM_STATE_LOADED,
+                PREFERRED_IKE_PROTOCOL_AUTO,
+                TEST_KEEPALIVE_TIMER /* expectedKeepaliveTimer */,
+                ESP_IP_VERSION_AUTO /* expectedIpVersion */,
+                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
+                true /* expectedReadFromCarrierConfig*/,
+                false /* areLongLivedTcpConnectionsExpensive */);
+    }
+
+    @Test
+    public void testNattKeepaliveTimerFromCarrierConfig_NotCell() throws Exception {
+        final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_WIFI)
+                .setTransportInfo(new WifiInfo.Builder().build())
+                .build();
+        doTestReadCarrierConfig(nc,
+                TelephonyManager.SIM_STATE_LOADED,
+                PREFERRED_IKE_PROTOCOL_IPV4_UDP,
+                AUTOMATIC_KEEPALIVE_DELAY_SECONDS /* expectedKeepaliveTimer */,
+                ESP_IP_VERSION_AUTO /* expectedIpVersion */,
+                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
+                false /* expectedReadFromCarrierConfig*/,
+                true /* areLongLivedTcpConnectionsExpensive */);
+    }
+
+    @Test
+    public void testPreferredIpProtocolFromCarrierConfig_v4UDP() throws Exception {
+        doTestReadCarrierConfig(createTestCellNc(),
+                TelephonyManager.SIM_STATE_LOADED,
+                PREFERRED_IKE_PROTOCOL_IPV4_UDP,
+                TEST_KEEPALIVE_TIMER /* expectedKeepaliveTimer */,
+                ESP_IP_VERSION_IPV4 /* expectedIpVersion */,
+                ESP_ENCAP_TYPE_UDP /* expectedEncapType */,
+                true /* expectedReadFromCarrierConfig*/,
+                false /* areLongLivedTcpConnectionsExpensive */);
+    }
+
+    @Test
+    public void testPreferredIpProtocolFromCarrierConfig_v6ESP() throws Exception {
+        doTestReadCarrierConfig(createTestCellNc(),
+                TelephonyManager.SIM_STATE_LOADED,
+                PREFERRED_IKE_PROTOCOL_IPV6_ESP,
+                TEST_KEEPALIVE_TIMER /* expectedKeepaliveTimer */,
+                ESP_IP_VERSION_IPV6 /* expectedIpVersion */,
+                ESP_ENCAP_TYPE_NONE /* expectedEncapType */,
+                true /* expectedReadFromCarrierConfig*/,
+                false /* areLongLivedTcpConnectionsExpensive */);
+    }
+
+    @Test
+    public void testPreferredIpProtocolFromCarrierConfig_v6UDP() throws Exception {
+        doTestReadCarrierConfig(createTestCellNc(),
+                TelephonyManager.SIM_STATE_LOADED,
+                PREFERRED_IKE_PROTOCOL_IPV6_UDP,
+                TEST_KEEPALIVE_TIMER /* expectedKeepaliveTimer */,
+                ESP_IP_VERSION_IPV6 /* expectedIpVersion */,
+                ESP_ENCAP_TYPE_UDP /* expectedEncapType */,
+                true /* expectedReadFromCarrierConfig*/,
+                false /* areLongLivedTcpConnectionsExpensive */);
+    }
+
+    private NetworkCapabilities createTestCellNc() {
+        return new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+                        .setSubscriptionId(TEST_SUB_ID)
+                        .build())
+                .build();
+    }
+
+    private void doTestReadCarrierConfig(NetworkCapabilities nc, int simState, int preferredIpProto,
+            int expectedKeepaliveTimer, int expectedIpVersion, int expectedEncapType,
+            boolean expectedReadFromCarrierConfig,
+            boolean areLongLivedTcpConnectionsExpensive)
+            throws Exception {
+        final Ikev2VpnProfile ikeProfile =
+                new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY)
+                        .setAuthPsk(TEST_VPN_PSK)
+                        .setBypassable(true /* isBypassable */)
+                        .setAutomaticNattKeepaliveTimerEnabled(true)
+                        .setAutomaticIpVersionSelectionEnabled(true)
+                        .build();
+
+        final PlatformVpnSnapshot vpnSnapShot =
+                verifySetupPlatformVpn(ikeProfile.toVpnProfile(),
+                        createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */),
+                        new NetworkCapabilities.Builder().build() /* underlying network caps */,
+                        false /* mtuSupportsIpv6 */,
+                        true /* areLongLivedTcpConnectionsExpensive */);
+
+        final CarrierConfigManager.CarrierConfigChangeListener listener =
+                getCarrierConfigListener();
+
+        // Simulate a new network coming up
+        vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
+        // Migration will not be started until receiving network capabilities change.
+        verify(mIkeSessionWrapper, never()).setNetwork(any(), anyInt(), anyInt(), anyInt());
+
+        reset(mIkeSessionWrapper);
+        mockCarrierConfig(TEST_SUB_ID, simState, TEST_KEEPALIVE_TIMER, preferredIpProto);
+        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2, nc);
+        verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).setNetwork(TEST_NETWORK_2,
+                expectedIpVersion, expectedEncapType, expectedKeepaliveTimer);
+        if (expectedReadFromCarrierConfig) {
+            final ArgumentCaptor<NetworkCapabilities> ncCaptor =
+                    ArgumentCaptor.forClass(NetworkCapabilities.class);
+            verify(mMockNetworkAgent, timeout(TEST_TIMEOUT_MS))
+                    .doSendNetworkCapabilities(ncCaptor.capture());
+
+            final VpnTransportInfo info =
+                    (VpnTransportInfo) ncCaptor.getValue().getTransportInfo();
+            assertEquals(areLongLivedTcpConnectionsExpensive,
+                    info.areLongLivedTcpConnectionsExpensive());
+        } else {
+            verify(mMockNetworkAgent, never()).doSendNetworkCapabilities(any());
+        }
+
+        reset(mExecutor);
+        reset(mIkeSessionWrapper);
+        reset(mMockNetworkAgent);
+
+        // Trigger carrier config change
+        listener.onCarrierConfigChanged(1 /* logicalSlotIndex */, TEST_SUB_ID,
+                -1 /* carrierId */, -1 /* specificCarrierId */);
+        verify(mIkeSessionWrapper).setNetwork(TEST_NETWORK_2,
+                expectedIpVersion, expectedEncapType, expectedKeepaliveTimer);
+        // Expect no NetworkCapabilities change.
+        // Call to doSendNetworkCapabilities() will not be triggered.
+        verify(mMockNetworkAgent, never()).doSendNetworkCapabilities(any());
+    }
+
+    @Test
+    public void testStartPlatformVpn_mtuDoesNotSupportIpv6() throws Exception {
+        final PlatformVpnSnapshot vpnSnapShot =
+                verifySetupPlatformVpn(
+                        createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */),
+                        false /* mtuSupportsIpv6 */);
+        vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
+    }
+
+    @Test
+    public void testStartPlatformVpn_underlyingNetworkNotChange() throws Exception {
+        final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
+                createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
+        // Trigger update on the same network should not cause underlying network change in NC of
+        // the VPN network
+        vpnSnapShot.nwCb.onAvailable(TEST_NETWORK);
+        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK,
+                new NetworkCapabilities.Builder()
+                        .setSubscriptionIds(Set.of(TEST_SUB_ID))
+                        .build());
+        // Verify setNetwork() called but no underlying network update
+        verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).setNetwork(eq(TEST_NETWORK),
+                eq(ESP_IP_VERSION_AUTO) /* ipVersion */,
+                eq(ESP_ENCAP_TYPE_AUTO) /* encapType */,
+                eq(DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT) /* keepaliveDelay */);
+        verify(mMockNetworkAgent, never())
+                .doSetUnderlyingNetworks(any());
+
+        vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
+        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2,
+                new NetworkCapabilities.Builder().build());
+
+        // A new network should trigger both setNetwork() and a underlying network update.
+        verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).setNetwork(eq(TEST_NETWORK_2),
+                eq(ESP_IP_VERSION_AUTO) /* ipVersion */,
+                eq(ESP_ENCAP_TYPE_AUTO) /* encapType */,
+                eq(DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT) /* keepaliveDelay */);
+        verify(mMockNetworkAgent).doSetUnderlyingNetworks(
+                Collections.singletonList(TEST_NETWORK_2));
+
+        vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
+    }
+
+    @Test
+    public void testStartPlatformVpnMobility_mobikeEnabled() throws Exception {
+        final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
+                createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
+
+        // Set new MTU on a different network
+        final int newMtu = IPV6_MIN_MTU + 1;
+        doReturn(newMtu).when(mTestDeps).calculateVpnMtu(any(), anyInt(), anyInt(), anyBoolean());
+
+        // Mock network loss and verify a cleanup task is scheduled
+        vpnSnapShot.nwCb.onLost(TEST_NETWORK);
+        verify(mExecutor, atLeastOnce()).schedule(any(Runnable.class), anyLong(), any());
+
+        // Mock new network comes up and the cleanup task is cancelled
+        vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
+        verify(mIkeSessionWrapper, never()).setNetwork(any(), anyInt(), anyInt(), anyInt());
+
+        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2,
+                new NetworkCapabilities.Builder().build());
+        // Verify MOBIKE is triggered
+        verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).setNetwork(eq(TEST_NETWORK_2),
+                eq(ESP_IP_VERSION_AUTO) /* ipVersion */,
+                eq(ESP_ENCAP_TYPE_AUTO) /* encapType */,
+                eq(DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT) /* keepaliveDelay */);
+        // Verify mNetworkCapabilities is updated
+        assertEquals(
+                Collections.singletonList(TEST_NETWORK_2),
+                vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks());
+        verify(mMockNetworkAgent)
+                .doSetUnderlyingNetworks(Collections.singletonList(TEST_NETWORK_2));
+
+        // Mock the MOBIKE procedure
+        vpnSnapShot.ikeCb.onIkeSessionConnectionInfoChanged(createIkeConnectInfo_2());
+        vpnSnapShot.childCb.onIpSecTransformsMigrated(
+                createIpSecTransform(), createIpSecTransform());
+
+        verify(mIpSecService).setNetworkForTunnelInterface(
+                eq(TEST_TUNNEL_RESOURCE_ID), eq(TEST_NETWORK_2), anyString());
+
+        // Expect 2 times: one for initial setup and one for MOBIKE
+        verifyApplyTunnelModeTransforms(2);
+
+        // Verify mNetworkAgent is updated
+        verify(mMockNetworkAgent).doSendLinkProperties(argThat(lp -> lp.getMtu() == newMtu));
+        verify(mMockNetworkAgent, never()).unregister();
+        // No further doSetUnderlyingNetworks interaction. The interaction count should stay one.
+        verify(mMockNetworkAgent, times(1)).doSetUnderlyingNetworks(any());
+        vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
+    }
+
+    @Test
+    public void testStartPlatformVpnMobility_mobikeEnabledMtuDoesNotSupportIpv6() throws Exception {
+        final PlatformVpnSnapshot vpnSnapShot =
+                verifySetupPlatformVpn(
+                        createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
+
+        // Set MTU below 1280
+        final int newMtu = IPV6_MIN_MTU - 1;
+        doReturn(newMtu).when(mTestDeps).calculateVpnMtu(any(), anyInt(), anyInt(), anyBoolean());
+
+        // Mock new network available & MOBIKE procedures
+        vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
+        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2,
+                new NetworkCapabilities.Builder().build());
+        // Verify mNetworkCapabilities is updated
+        verify(mMockNetworkAgent, timeout(TEST_TIMEOUT_MS))
+                .doSetUnderlyingNetworks(Collections.singletonList(TEST_NETWORK_2));
+        assertEquals(
+                Collections.singletonList(TEST_NETWORK_2),
+                vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks());
+
+        vpnSnapShot.ikeCb.onIkeSessionConnectionInfoChanged(createIkeConnectInfo_2());
+        vpnSnapShot.childCb.onIpSecTransformsMigrated(
+                createIpSecTransform(), createIpSecTransform());
+
+        // Verify removal of IPv6 addresses and routes triggers a network agent restart
+        final ArgumentCaptor<LinkProperties> lpCaptor =
+                ArgumentCaptor.forClass(LinkProperties.class);
+        verify(mTestDeps, times(2))
+                .newNetworkAgent(any(), any(), anyString(), any(), lpCaptor.capture(), any(), any(),
+                        any(), any());
+        verify(mMockNetworkAgent).unregister();
+        // mMockNetworkAgent is an old NetworkAgent, so it won't update LinkProperties after
+        // unregistering.
+        verify(mMockNetworkAgent, never()).doSendLinkProperties(any());
+
+        final LinkProperties lp = lpCaptor.getValue();
+
+        for (LinkAddress addr : lp.getLinkAddresses()) {
+            if (addr.isIpv6()) {
+                fail("IPv6 address found on VPN with MTU < IPv6 minimum MTU");
+            }
+        }
+
+        for (InetAddress dnsAddr : lp.getDnsServers()) {
+            if (dnsAddr instanceof Inet6Address) {
+                fail("IPv6 DNS server found on VPN with MTU < IPv6 minimum MTU");
+            }
+        }
+
+        for (RouteInfo routeInfo : lp.getRoutes()) {
+            if (routeInfo.getDestinationLinkAddress().isIpv6()
+                    && !routeInfo.isIPv6UnreachableDefault()) {
+                fail("IPv6 route found on VPN with MTU < IPv6 minimum MTU");
+            }
+        }
+
+        assertEquals(newMtu, lp.getMtu());
+
+        vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
+    }
+
+    @Test
+    public void testStartPlatformVpnReestablishes_mobikeDisabled() throws Exception {
+        final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
+                createIkeConfig(createIkeConnectInfo(), false /* isMobikeEnabled */));
+
+        // Forget the first IKE creation to be prepared to capture callbacks of the second
+        // IKE session
+        resetIkev2SessionCreator(mock(Vpn.IkeSessionWrapper.class));
+
+        // Mock network switch
+        vpnSnapShot.nwCb.onLost(TEST_NETWORK);
+        vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
+        // The old IKE Session will not be killed until receiving network capabilities change.
+        verify(mIkeSessionWrapper, never()).kill();
+
+        vpnSnapShot.nwCb.onCapabilitiesChanged(
+                TEST_NETWORK_2, new NetworkCapabilities.Builder().build());
+        // Verify the old IKE Session is killed
+        verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).kill();
+
+        // Capture callbacks of the new IKE Session
+        final Pair<IkeSessionCallback, ChildSessionCallback> cbPair =
+                verifyCreateIkeAndCaptureCbs();
+        final IkeSessionCallback ikeCb = cbPair.first;
+        final ChildSessionCallback childCb = cbPair.second;
+
+        // Mock the IKE Session setup
+        ikeCb.onOpened(createIkeConfig(createIkeConnectInfo_2(), false /* isMobikeEnabled */));
+
+        childCb.onIpSecTransformCreated(createIpSecTransform(), IpSecManager.DIRECTION_IN);
+        childCb.onIpSecTransformCreated(createIpSecTransform(), IpSecManager.DIRECTION_OUT);
+        childCb.onOpened(createChildConfig());
+
+        // Expect 2 times since there have been two Session setups
+        verifyApplyTunnelModeTransforms(2);
+
+        // Verify mNetworkCapabilities and mNetworkAgent are updated
+        assertEquals(
+                Collections.singletonList(TEST_NETWORK_2),
+                vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks());
+        verify(mMockNetworkAgent)
+                .doSetUnderlyingNetworks(Collections.singletonList(TEST_NETWORK_2));
+
+        vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
+    }
+
+    private String getDump(@NonNull final Vpn vpn) {
+        final StringWriter sw = new StringWriter();
+        final IndentingPrintWriter writer = new IndentingPrintWriter(sw, "");
+        vpn.dump(writer);
+        writer.flush();
+        return sw.toString();
+    }
+
+    private int countMatches(@NonNull final Pattern regexp, @NonNull final String string) {
+        final Matcher m = regexp.matcher(string);
+        int i = 0;
+        while (m.find()) ++i;
+        return i;
+    }
+
+    @Test
+    public void testNCEventChanges() throws Exception {
+        final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                .setLinkDownstreamBandwidthKbps(1000)
+                .setLinkUpstreamBandwidthKbps(500);
+
+        final Ikev2VpnProfile ikeProfile =
+                new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY)
+                        .setAuthPsk(TEST_VPN_PSK)
+                        .setBypassable(true /* isBypassable */)
+                        .setAutomaticNattKeepaliveTimerEnabled(true)
+                        .setAutomaticIpVersionSelectionEnabled(true)
+                        .build();
+
+        final PlatformVpnSnapshot vpnSnapShot =
+                verifySetupPlatformVpn(ikeProfile.toVpnProfile(),
+                        createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */),
+                        ncBuilder.build(), false /* mtuSupportsIpv6 */,
+                        true /* areLongLivedTcpConnectionsExpensive */);
+
+        // Calls to onCapabilitiesChanged will be thrown to the executor for execution ; by
+        // default this will incur a 10ms delay before it's executed, messing with the timing
+        // of the log and having the checks for counts in equals() below flake.
+        mExecutor.executeDirect = true;
+
+        // First nc changed triggered by verifySetupPlatformVpn
+        final Pattern pattern = Pattern.compile("Cap changed from", Pattern.MULTILINE);
+        final String stage1 = getDump(vpnSnapShot.vpn);
+        assertEquals(1, countMatches(pattern, stage1));
+
+        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK, ncBuilder.build());
+        final String stage2 = getDump(vpnSnapShot.vpn);
+        // Was the same caps, there should still be only 1 match
+        assertEquals(1, countMatches(pattern, stage2));
+
+        ncBuilder.setLinkDownstreamBandwidthKbps(1200)
+                .setLinkUpstreamBandwidthKbps(300);
+        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK, ncBuilder.build());
+        final String stage3 = getDump(vpnSnapShot.vpn);
+        // Was not an important change, should not be logged, still only 1 match
+        assertEquals(1, countMatches(pattern, stage3));
+
+        ncBuilder.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK, ncBuilder.build());
+        final String stage4 = getDump(vpnSnapShot.vpn);
+        // Change to caps is important, should cause a new match
+        assertEquals(2, countMatches(pattern, stage4));
+
+        ncBuilder.removeCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+        ncBuilder.setLinkDownstreamBandwidthKbps(600);
+        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK, ncBuilder.build());
+        final String stage5 = getDump(vpnSnapShot.vpn);
+        // Change to caps is important, should cause a new match even with the unimportant change
+        assertEquals(3, countMatches(pattern, stage5));
+    }
+    // TODO : beef up event logs tests
+
+    private void verifyHandlingNetworkLoss(PlatformVpnSnapshot vpnSnapShot) throws Exception {
+        // Forget the #sendLinkProperties during first setup.
+        reset(mMockNetworkAgent);
+
+        // Mock network loss
+        vpnSnapShot.nwCb.onLost(TEST_NETWORK);
+
+        // Mock the grace period expires
+        verify(mExecutor, atLeastOnce()).schedule(any(Runnable.class), anyLong(), any());
+
+        final ArgumentCaptor<LinkProperties> lpCaptor =
+                ArgumentCaptor.forClass(LinkProperties.class);
+        verify(mMockNetworkAgent, timeout(TEST_TIMEOUT_MS))
+                .doSendLinkProperties(lpCaptor.capture());
+        final LinkProperties lp = lpCaptor.getValue();
+
+        assertNull(lp.getInterfaceName());
+        final List<RouteInfo> expectedRoutes = Arrays.asList(
+                new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null /* gateway */,
+                        null /* iface */, RTN_UNREACHABLE),
+                new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null /* gateway */,
+                        null /* iface */, RTN_UNREACHABLE));
+        assertEquals(expectedRoutes, lp.getRoutes());
+
+        verify(mMockNetworkAgent, timeout(TEST_TIMEOUT_MS)).unregister();
+    }
+
+    @Test
+    public void testStartPlatformVpnHandlesNetworkLoss_mobikeEnabled() throws Exception {
+        final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
+                createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
+        verifyHandlingNetworkLoss(vpnSnapShot);
+    }
+
+    @Test
+    public void testStartPlatformVpnHandlesNetworkLoss_mobikeDisabled() throws Exception {
+        final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
+                createIkeConfig(createIkeConnectInfo(), false /* isMobikeEnabled */));
+        verifyHandlingNetworkLoss(vpnSnapShot);
+    }
+
+    private ConnectivityDiagnosticsCallback getConnectivityDiagCallback() {
+        final ArgumentCaptor<ConnectivityDiagnosticsCallback> cdcCaptor =
+                ArgumentCaptor.forClass(ConnectivityDiagnosticsCallback.class);
+        verify(mCdm).registerConnectivityDiagnosticsCallback(
+                any(), any(), cdcCaptor.capture());
+        return cdcCaptor.getValue();
+    }
+
+    private DataStallReport createDataStallReport() {
+        return new DataStallReport(TEST_NETWORK, 1234 /* reportTimestamp */,
+                1 /* detectionMethod */, new LinkProperties(), new NetworkCapabilities(),
+                new PersistableBundle());
+    }
+
+    private void verifyMobikeTriggered(List<Network> expected, int retryIndex) {
+        // Verify retry is scheduled
+        final long expectedDelayMs = mTestDeps.getValidationFailRecoveryMs(retryIndex);
+        final ArgumentCaptor<Long> delayCaptor = ArgumentCaptor.forClass(Long.class);
+        verify(mExecutor, times(retryIndex + 1)).schedule(
+                any(Runnable.class), delayCaptor.capture(), eq(TimeUnit.MILLISECONDS));
+        final List<Long> delays = delayCaptor.getAllValues();
+        assertEquals(expectedDelayMs, (long) delays.get(delays.size() - 1));
+
+        final ArgumentCaptor<Network> networkCaptor = ArgumentCaptor.forClass(Network.class);
+        verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS + expectedDelayMs))
+                .setNetwork(networkCaptor.capture(), anyInt() /* ipVersion */,
+                        anyInt() /* encapType */, anyInt() /* keepaliveDelay */);
+        assertEquals(expected, Collections.singletonList(networkCaptor.getValue()));
+    }
+
+    @Test
+    public void testDataStallInIkev2VpnMobikeDisabled() throws Exception {
+        final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
+                createIkeConfig(createIkeConnectInfo(), false /* isMobikeEnabled */));
+
+        doReturn(TEST_NETWORK).when(mMockNetworkAgent).getNetwork();
+        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+
+        // Should not trigger MOBIKE if MOBIKE is not enabled
+        verify(mIkeSessionWrapper, never()).setNetwork(any() /* network */,
+                anyInt() /* ipVersion */, anyInt() /* encapType */, anyInt() /* keepaliveDelay */);
+    }
+
+    @Test
+    public void testDataStallInIkev2VpnRecoveredByMobike() throws Exception {
+        final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
+                createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
+
+        doReturn(TEST_NETWORK).when(mMockNetworkAgent).getNetwork();
+        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+        // Verify MOBIKE is triggered
+        verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks(),
+                0 /* retryIndex */);
+        // Validation failure on VPN network should trigger a re-evaluation request for the
+        // underlying network.
+        verify(mConnectivityManager).reportNetworkConnectivity(TEST_NETWORK, false);
+
+        reset(mIkev2SessionCreator);
+        reset(mExecutor);
+
+        // Send validation status update.
+        // Recovered and get network validated. It should not trigger the ike session reset.
+        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_VALID);
+        // Verify that the retry count is reset. The mValidationFailRetryCount will not be reset
+        // until the executor finishes the execute() call, so wait until the all tasks are executed.
+        waitForIdleSerialExecutor(mExecutor, TEST_TIMEOUT_MS);
+        assertEquals(0,
+                ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).mValidationFailRetryCount);
+        verify(mIkev2SessionCreator, never()).createIkeSession(
+                any(), any(), any(), any(), any(), any());
+
+        reset(mIkeSessionWrapper);
+        reset(mExecutor);
+
+        // Another validation fail should trigger another reportNetworkConnectivity
+        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+        verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks(),
+                0 /* retryIndex */);
+        verify(mConnectivityManager, times(2)).reportNetworkConnectivity(TEST_NETWORK, false);
+    }
+
+    @Test
+    public void testDataStallInIkev2VpnNotRecoveredByMobike() throws Exception {
+        final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
+                createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
+
+        int retry = 0;
+        doReturn(TEST_NETWORK).when(mMockNetworkAgent).getNetwork();
+        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+        verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks(),
+                retry++);
+        // Validation failure on VPN network should trigger a re-evaluation request for the
+        // underlying network.
+        verify(mConnectivityManager).reportNetworkConnectivity(TEST_NETWORK, false);
+        reset(mIkev2SessionCreator);
+
+        // Second validation status update.
+        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+        verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks(),
+                retry++);
+        // Call to reportNetworkConnectivity should only happen once. No further interaction.
+        verify(mConnectivityManager, times(1)).reportNetworkConnectivity(TEST_NETWORK, false);
+
+        // Use real delay to verify reset session will not be performed if there is an existing
+        // recovery for resetting the session.
+        mExecutor.delayMs = TestExecutor.REAL_DELAY;
+        mExecutor.executeDirect = true;
+        // Send validation status update should result in ike session reset.
+        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+
+        // Verify session reset is scheduled
+        long expectedDelay = mTestDeps.getValidationFailRecoveryMs(retry++);
+        final ArgumentCaptor<Long> delayCaptor = ArgumentCaptor.forClass(Long.class);
+        verify(mExecutor, times(retry)).schedule(any(Runnable.class), delayCaptor.capture(),
+                eq(TimeUnit.MILLISECONDS));
+        final List<Long> delays = delayCaptor.getAllValues();
+        assertEquals(expectedDelay, (long) delays.get(delays.size() - 1));
+        // Call to reportNetworkConnectivity should only happen once. No further interaction.
+        verify(mConnectivityManager, times(1)).reportNetworkConnectivity(TEST_NETWORK, false);
+
+        // Another invalid status reported should not trigger other scheduled recovery.
+        expectedDelay = mTestDeps.getValidationFailRecoveryMs(retry++);
+        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+        verify(mExecutor, never()).schedule(
+                any(Runnable.class), eq(expectedDelay), eq(TimeUnit.MILLISECONDS));
+
+        // Verify that session being reset
+        verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS + expectedDelay))
+                .createIkeSession(any(), any(), any(), any(), any(), any());
+        // Call to reportNetworkConnectivity should only happen once. No further interaction.
+        verify(mConnectivityManager, times(1)).reportNetworkConnectivity(TEST_NETWORK, false);
+    }
+
+    @Test
+    public void testStartLegacyVpnType() throws Exception {
+        setMockedUsers(PRIMARY_USER);
+        final Vpn vpn = createVpn(PRIMARY_USER.id);
+        final VpnProfile profile = new VpnProfile("testProfile" /* key */);
+
+        profile.type = VpnProfile.TYPE_PPTP;
+        assertThrows(UnsupportedOperationException.class, () -> startLegacyVpn(vpn, profile));
+        profile.type = VpnProfile.TYPE_L2TP_IPSEC_PSK;
+        assertThrows(UnsupportedOperationException.class, () -> startLegacyVpn(vpn, profile));
+    }
+
+    @Test
+    public void testStartLegacyVpnModifyProfile_TypePSK() throws Exception {
+        setMockedUsers(PRIMARY_USER);
+        final Vpn vpn = createVpn(PRIMARY_USER.id);
+        final Ikev2VpnProfile ikev2VpnProfile =
+                new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY)
+                        .setAuthPsk(TEST_VPN_PSK)
+                        .build();
+        final VpnProfile profile = ikev2VpnProfile.toVpnProfile();
+
+        startLegacyVpn(vpn, profile);
+        assertEquals(profile, ikev2VpnProfile.toVpnProfile());
+    }
+
+    private void assertTransportInfoMatches(NetworkCapabilities nc, int type) {
+        assertNotNull(nc);
+        VpnTransportInfo ti = (VpnTransportInfo) nc.getTransportInfo();
+        assertNotNull(ti);
+        assertEquals(type, ti.getType());
+    }
+
+    // Make it public and un-final so as to spy it
+    public class TestDeps extends Vpn.Dependencies {
+        TestDeps() {}
+
+        @Override
+        public boolean isCallerSystem() {
+            return true;
+        }
+
+        @Override
+        public PendingIntent getIntentForStatusPanel(Context context) {
+            return null;
+        }
+
+        @Override
+        public ParcelFileDescriptor adoptFd(Vpn vpn, int mtu) {
+            return new ParcelFileDescriptor(new FileDescriptor());
+        }
+
+        @Override
+        public int jniCreate(Vpn vpn, int mtu) {
+            // Pick a random positive number as fd to return.
+            return 345;
+        }
+
+        @Override
+        public String jniGetName(Vpn vpn, int fd) {
+            return TEST_IFACE_NAME;
+        }
+
+        @Override
+        public int jniSetAddresses(Vpn vpn, String interfaze, String addresses) {
+            if (addresses == null) return 0;
+            // Return the number of addresses.
+            return addresses.split(" ").length;
+        }
+
+        @Override
+        public void setBlocking(FileDescriptor fd, boolean blocking) {}
+
+        @Override
+        public DeviceIdleInternal getDeviceIdleInternal() {
+            return mDeviceIdleInternal;
+        }
+
+        @Override
+        public long getValidationFailRecoveryMs(int retryCount) {
+            // Simply return retryCount as the delay seconds for retrying.
+            return retryCount * 100L;
+        }
+
+        @Override
+        public ScheduledThreadPoolExecutor newScheduledThreadPoolExecutor() {
+            return mExecutor;
+        }
+
+        public boolean mIgnoreCallingUidChecks = true;
+        @Override
+        public void verifyCallingUidAndPackage(Context context, String packageName, int userId) {
+            if (!mIgnoreCallingUidChecks) {
+                super.verifyCallingUidAndPackage(context, packageName, userId);
+            }
+        }
+    }
+
+    /**
+     * Mock some methods of vpn object.
+     */
+    private Vpn createVpn(@UserIdInt int userId) {
+        final Context asUserContext = mock(Context.class, AdditionalAnswers.delegatesTo(mContext));
+        doReturn(UserHandle.of(userId)).when(asUserContext).getUser();
+        when(mContext.createContextAsUser(eq(UserHandle.of(userId)), anyInt()))
+                .thenReturn(asUserContext);
+        final TestLooper testLooper = new TestLooper();
+        final Vpn vpn = new Vpn(testLooper.getLooper(), mContext, mTestDeps, mNetService,
+                mNetd, userId, mVpnProfileStore, mSystemServices, mIkev2SessionCreator);
+        verify(mConnectivityManager, times(1)).registerNetworkProvider(argThat(
+                provider -> provider.getName().contains("VpnNetworkProvider")
+        ));
+        return vpn;
+    }
+
+    /**
+     * Populate {@link #mUserManager} with a list of fake users.
+     */
+    private void setMockedUsers(UserInfo... users) {
+        final Map<Integer, UserInfo> userMap = new ArrayMap<>();
+        for (UserInfo user : users) {
+            userMap.put(user.id, user);
+        }
+
+        /**
+         * @see UserManagerService#getUsers(boolean)
+         */
+        doAnswer(invocation -> {
+            final ArrayList<UserInfo> result = new ArrayList<>(users.length);
+            for (UserInfo ui : users) {
+                if (ui.isEnabled() && !ui.partial) {
+                    result.add(ui);
+                }
+            }
+            return result;
+        }).when(mUserManager).getAliveUsers();
+
+        doAnswer(invocation -> {
+            final int id = (int) invocation.getArguments()[0];
+            return userMap.get(id);
+        }).when(mUserManager).getUserInfo(anyInt());
+    }
+
+    /**
+     * Populate {@link #mPackageManager} with a fake packageName-to-UID mapping.
+     */
+    private void setMockedPackages(final Map<String, Integer> packages) {
+        try {
+            doAnswer(invocation -> {
+                final String appName = (String) invocation.getArguments()[0];
+                final int userId = (int) invocation.getArguments()[1];
+                Integer appId = packages.get(appName);
+                if (appId == null) throw new PackageManager.NameNotFoundException(appName);
+                return UserHandle.getUid(userId, appId);
+            }).when(mPackageManager).getPackageUidAsUser(anyString(), anyInt());
+        } catch (Exception e) {
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java b/services/tests/VpnTests/java/com/android/server/net/LockdownVpnTrackerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java
rename to services/tests/VpnTests/java/com/android/server/net/LockdownVpnTrackerTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 9c9aeea..705dac5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -19,6 +19,7 @@
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -72,7 +73,7 @@
     private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG = 4000;
     private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG_IDLE = 1000;
     private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG_IDLE = 2000;
-    private static final float DOZE_SCALE_FACTOR = 0.0f;
+    private static final float DOZE_SCALE_FACTOR = 0.54f;
     private static final boolean RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG = false;
     private static final int LIGHT_SENSOR_WARMUP_TIME = 0;
     private static final int AMBIENT_LIGHT_HORIZON_SHORT = 1000;
@@ -87,6 +88,7 @@
     @Mock SensorManager mSensorManager;
     @Mock BrightnessMappingStrategy mBrightnessMappingStrategy;
     @Mock BrightnessMappingStrategy mIdleBrightnessMappingStrategy;
+    @Mock BrightnessMappingStrategy mDozeBrightnessMappingStrategy;
     @Mock HysteresisLevels mAmbientBrightnessThresholds;
     @Mock HysteresisLevels mScreenBrightnessThresholds;
     @Mock HysteresisLevels mAmbientBrightnessThresholdsIdle;
@@ -124,12 +126,15 @@
 
         when(mBrightnessMappingStrategy.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_DEFAULT);
         when(mIdleBrightnessMappingStrategy.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_IDLE);
+        when(mDozeBrightnessMappingStrategy.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_DOZE);
 
         SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap = new SparseArray<>();
         brightnessMappingStrategyMap.append(AUTO_BRIGHTNESS_MODE_DEFAULT,
                 mBrightnessMappingStrategy);
         brightnessMappingStrategyMap.append(AUTO_BRIGHTNESS_MODE_IDLE,
                 mIdleBrightnessMappingStrategy);
+        brightnessMappingStrategyMap.append(AUTO_BRIGHTNESS_MODE_DOZE,
+                mDozeBrightnessMappingStrategy);
         mController = new AutomaticBrightnessController(
                 new AutomaticBrightnessController.Injector() {
                     @Override
@@ -1032,10 +1037,9 @@
         float normalizedBrightness = 0.3f;
         when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
         when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
-        when(mBrightnessMappingStrategy.getBrightness(eq(lux), eq(null), anyInt()))
-                .thenReturn(normalizedBrightness);
+        when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
+                /* category= */ anyInt())).thenReturn(normalizedBrightness);
         when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
-        when(mBrightnessThrottler.isThrottled()).thenReturn(true);
 
         // Send a new sensor value, disable the sensor and verify
         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
@@ -1047,4 +1051,79 @@
                 mController.getAutomaticScreenBrightnessBasedOnLastObservedLux(
                         /* brightnessEvent= */ null), EPSILON);
     }
+
+    @Test
+    public void testAutoBrightnessInDoze_ShouldScaleIfNotUsingDozeCurve() throws Exception {
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        // Set up system to return 0.3f as a brightness value
+        float lux = 100.0f;
+        // Brightness as float (from 0.0f to 1.0f)
+        float normalizedBrightness = 0.3f;
+        when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
+        when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
+        when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
+                /* category= */ anyInt())).thenReturn(normalizedBrightness);
+        when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
+
+        // Set policy to DOZE
+        mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
+                /* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChanged= */ false, DisplayPowerRequest.POLICY_DOZE,
+                /* shouldResetShortTermModel= */ true);
+
+        // Send a new sensor value
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
+
+        // The brightness should be scaled by the doze factor
+        assertEquals(normalizedBrightness * DOZE_SCALE_FACTOR,
+                mController.getAutomaticScreenBrightness(
+                        /* brightnessEvent= */ null), EPSILON);
+        assertEquals(normalizedBrightness * DOZE_SCALE_FACTOR,
+                mController.getAutomaticScreenBrightnessBasedOnLastObservedLux(
+                        /* brightnessEvent= */ null), EPSILON);
+    }
+
+    @Test
+    public void testAutoBrightnessInDoze_ShouldNotScaleIfUsingDozeCurve() throws Exception {
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        // Set up system to return 0.3f as a brightness value
+        float lux = 100.0f;
+        // Brightness as float (from 0.0f to 1.0f)
+        float normalizedBrightness = 0.3f;
+        when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
+        when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
+        when(mDozeBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
+                /* category= */ anyInt())).thenReturn(normalizedBrightness);
+        when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
+
+        // Switch mode to DOZE
+        mController.switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+
+        // Set policy to DOZE
+        mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
+                /* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChanged= */ false, DisplayPowerRequest.POLICY_DOZE,
+                /* shouldResetShortTermModel= */ true);
+
+        // Send a new sensor value
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
+
+        // The brightness should not be scaled by the doze factor
+        assertEquals(normalizedBrightness,
+                mController.getAutomaticScreenBrightness(
+                        /* brightnessEvent= */ null), EPSILON);
+        assertEquals(normalizedBrightness,
+                mController.getAutomaticScreenBrightnessBasedOnLastObservedLux(
+                        /* brightnessEvent= */ null), EPSILON);
+    }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
index dc6abf1..1c71abc 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
@@ -52,7 +52,9 @@
     private static final int WIDTH = 500;
     private static final int HEIGHT = 900;
     private static final Point PORTRAIT_SIZE = new Point(WIDTH, HEIGHT);
+    private static final Point PORTRAIT_DOUBLE_WIDTH = new Point(2 * WIDTH, HEIGHT);
     private static final Point LANDSCAPE_SIZE = new Point(HEIGHT, WIDTH);
+    private static final Point LANDSCAPE_DOUBLE_HEIGHT = new Point(HEIGHT, 2 * WIDTH);
 
     @Mock
     private SurfaceControl.Transaction mMockTransaction;
@@ -69,6 +71,16 @@
     }
 
     @Test
+    public void testGetDisplaySurfaceDefaultSizeLocked_notRotated_anisotropyCorrection() {
+        mDisplayDeviceInfo.xDpi = 0.5f;
+        mDisplayDeviceInfo.yDpi = 1.0f;
+        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+                mMockDisplayAdapter, /*isAnisotropyCorrectionEnabled=*/ true);
+        assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(
+                PORTRAIT_DOUBLE_WIDTH);
+    }
+
+    @Test
     public void testGetDisplaySurfaceDefaultSizeLocked_notRotated() {
         DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
                 mMockDisplayAdapter);
@@ -84,6 +96,17 @@
     }
 
     @Test
+    public void testGetDisplaySurfaceDefaultSizeLocked_rotation90_anisotropyCorrection() {
+        mDisplayDeviceInfo.xDpi = 0.5f;
+        mDisplayDeviceInfo.yDpi = 1.0f;
+        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+                mMockDisplayAdapter, /*isAnisotropyCorrectionEnabled=*/ true);
+        displayDevice.setProjectionLocked(mMockTransaction, ROTATION_90, new Rect(), new Rect());
+        assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(
+                LANDSCAPE_DOUBLE_HEIGHT);
+    }
+
+    @Test
     public void testGetDisplaySurfaceDefaultSizeLocked_rotation90() {
         DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
                 mMockDisplayAdapter);
@@ -111,8 +134,14 @@
         private final DisplayDeviceInfo mDisplayDeviceInfo;
 
         FakeDisplayDevice(DisplayDeviceInfo displayDeviceInfo, DisplayAdapter displayAdapter) {
+            this(displayDeviceInfo, displayAdapter, /*isAnisotropyCorrectionEnabled=*/ false);
+        }
+
+        FakeDisplayDevice(DisplayDeviceInfo displayDeviceInfo, DisplayAdapter displayAdapter,
+                boolean isAnisotropyCorrectionEnabled) {
             super(displayAdapter, /* displayToken= */ null, /* uniqueId= */ "",
-                    InstrumentationRegistry.getInstrumentation().getContext());
+                    InstrumentationRegistry.getInstrumentation().getContext(),
+                    isAnisotropyCorrectionEnabled);
             mDisplayDeviceInfo = displayDeviceInfo;
         }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
index fbb14c3..4409051 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
@@ -26,7 +26,6 @@
 import static org.mockito.Mockito.when;
 
 import android.hardware.display.DisplayManagerInternal;
-import android.os.PowerManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -66,8 +65,6 @@
         mSession.stopOffload();
 
         assertFalse(mSession.isActive());
-        verify(mDisplayPowerController).setBrightnessFromOffload(
-                PowerManager.BRIGHTNESS_INVALID_FLOAT);
 
         // An inactive session shouldn't be stopped again
         mSession.stopOffload();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index acddc9d..76b7780 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -22,6 +22,7 @@
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -77,6 +78,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.BrightnessReason;
 import com.android.server.display.brightness.clamper.BrightnessClamperController;
 import com.android.server.display.brightness.clamper.HdrClamper;
 import com.android.server.display.color.ColorDisplayService;
@@ -112,6 +114,8 @@
     private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
     private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
     private static final float PROX_SENSOR_MAX_RANGE = 5;
+    private static final float DOZE_SCALE_FACTOR = 0.34f;
+
     private static final float BRIGHTNESS_RAMP_RATE_MINIMUM = 0.0f;
     private static final float BRIGHTNESS_RAMP_RATE_FAST_DECREASE = 0.3f;
     private static final float BRIGHTNESS_RAMP_RATE_FAST_INCREASE = 0.4f;
@@ -191,6 +195,9 @@
 
         mContext.getOrCreateTestableResources().addOverride(
                 com.android.internal.R.bool.config_displayColorFadeDisabled, false);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
+                DOZE_SCALE_FACTOR);
 
         doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
                 SystemProperties.set(anyString(), any()));
@@ -1559,6 +1566,43 @@
 
         verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
                 eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+        assertEquals(BrightnessReason.REASON_OFFLOAD, mHolder.dpc.mBrightnessReason.getReason());
+    }
+
+    @Test
+    public void testBrightness_AutomaticHigherPriorityThanOffload() {
+        when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        float brightness = 0.34f;
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+
+        mHolder.dpc.setBrightnessFromOffload(brightness);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+        assertEquals(BrightnessReason.REASON_OFFLOAD, mHolder.dpc.mBrightnessReason.getReason());
+
+        // Now automatic brightness becomes available
+        brightness = 0.22f;
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+                any(BrightnessEvent.class))).thenReturn(brightness);
+
+        mHolder.dpc.updateBrightness();
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+        assertEquals(BrightnessReason.REASON_AUTOMATIC, mHolder.dpc.mBrightnessReason.getReason());
     }
 
     @Test
@@ -1666,7 +1710,7 @@
     }
 
     @Test
-    public void testInitialDozeBrightness() {
+    public void testInitialDozeBrightness_AutoBrightnessEnabled() {
         when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
         when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
         mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
@@ -1694,6 +1738,93 @@
                 /* ignoreAnimationLimits= */ anyBoolean());
     }
 
+    @Test
+    public void testInitialDozeBrightness_AutoBrightnessDisabled() {
+        when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+        mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+        float brightness = 0.277f;
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        when(mHolder.hbmController.getCurrentBrightnessMax())
+                .thenReturn(PowerManager.BRIGHTNESS_MAX);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState, initialize
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(brightness * DOZE_SCALE_FACTOR),
+                /* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(),
+                /* ignoreAnimationLimits= */ anyBoolean());
+        assertEquals(brightness * DOZE_SCALE_FACTOR, mHolder.dpc.getDozeBrightnessForOffload(),
+                /* delta= */ 0);
+    }
+
+    @Test
+    public void testInitialDozeBrightness_AbcIsNull() {
+        when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
+        when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true,
+                /* isAutoBrightnessAvailable= */ false);
+        mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+        float brightness = 0.277f;
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.automaticBrightnessController
+                .getAutomaticScreenBrightnessBasedOnLastObservedLux(any(BrightnessEvent.class)))
+                .thenReturn(brightness);
+        when(mHolder.hbmController.getCurrentBrightnessMax())
+                .thenReturn(PowerManager.BRIGHTNESS_MAX);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        // Automatic Brightness Controller is null so no initial doze brightness should be set and
+        // we should not crash
+        verify(mHolder.animator, never()).animateTo(eq(brightness),
+                /* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(),
+                /* ignoreAnimationLimits= */ anyBoolean());
+    }
+
+    @Test
+    public void testDefaultDozeBrightness() {
+        float brightness = 0.121f;
+        when(mPowerManagerMock.getBrightnessConstraint(
+                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE)).thenReturn(brightness);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        when(mHolder.hbmController.getCurrentBrightnessMax())
+                .thenReturn(PowerManager.BRIGHTNESS_MAX);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(brightness), /* linearSecondTarget= */ anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+    }
+
     /**
      * Creates a mock and registers it to {@link LocalServices}.
      */
@@ -1778,6 +1909,12 @@
 
     private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
             String uniqueId, boolean isEnabled) {
+        return createDisplayPowerController(displayId, uniqueId, isEnabled,
+                /* isAutoBrightnessAvailable= */ true);
+    }
+
+    private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
+            String uniqueId, boolean isEnabled, boolean isAutoBrightnessAvailable) {
         final DisplayPowerState displayPowerState = mock(DisplayPowerState.class);
         final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class);
         final AutomaticBrightnessController automaticBrightnessController =
@@ -1812,6 +1949,7 @@
         final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class);
 
         setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
+        when(config.isAutoBrightnessAvailable()).thenReturn(isAutoBrightnessAvailable);
 
         final DisplayPowerController dpc = new DisplayPowerController(
                 mContext, injector, mDisplayPowerCallbacksMock, mHandler,
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 b99ecf3..14de527 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -46,7 +46,6 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
-import android.os.PowerManager;
 import android.view.Display;
 import android.view.DisplayAddress;
 import android.view.SurfaceControl;
@@ -1229,8 +1228,6 @@
 
         verify(mDisplayOffloader).stopOffload();
         assertFalse(mDisplayOffloadSession.isActive());
-        verify(mMockedDisplayPowerController).setBrightnessFromOffload(
-                PowerManager.BRIGHTNESS_INVALID_FLOAT);
     }
 
     private void initDisplayOffloadSession() {
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 bed6f92..2939192 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -16,7 +16,7 @@
 
 package com.android.server.display;
 
-import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.DEFAULT_DISPLAY_GROUP;
 import static android.view.Display.FLAG_REAR;
@@ -674,7 +674,7 @@
                 /* isInteractive= */true,
                 /* isBootCompleted= */true));
         assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
-                INVALID_DEVICE_STATE,
+                INVALID_DEVICE_STATE_IDENTIFIER,
                 /* isOverrideActive= */false,
                 /* isInteractive= */true,
                 /* isBootCompleted= */true));
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
index 1c43418..549f0d7 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
@@ -106,8 +106,184 @@
     }
 
     @Test
+    public void testLetterbox() {
+        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+                /*isAnisotropyCorrectionEnabled=*/ false);
+        mDisplayDeviceInfo.xDpi = 0.5f;
+        mDisplayDeviceInfo.yDpi = 1.0f;
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        var originalDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+        assertEquals(DISPLAY_WIDTH, originalDisplayInfo.logicalWidth);
+        assertEquals(DISPLAY_HEIGHT, originalDisplayInfo.logicalHeight);
+
+        SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        assertEquals(new Point(0, 0), mLogicalDisplay.getDisplayPosition());
+
+        /*
+         * Content is too wide, should become letterboxed
+         *  ______DISPLAY_WIDTH________
+         * |                        |
+         * |________________________|
+         * |                        |
+         * |       CONTENT          |
+         * |                        |
+         * |________________________|
+         * |                        |
+         * |________________________|
+         */
+        // Make a wide application content, by reducing its height.
+        DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = DISPLAY_WIDTH;
+        displayInfo.logicalHeight = DISPLAY_HEIGHT / 2;
+        mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        assertEquals(new Point(0, DISPLAY_HEIGHT / 4), mLogicalDisplay.getDisplayPosition());
+    }
+
+    @Test
+    public void testNoLetterbox_anisotropyCorrection() {
+        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+                /*isAnisotropyCorrectionEnabled=*/ true);
+
+        // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
+        // to using the whole screen. This is because display will rescale it back to fill the
+        // screen (in case the display menu setting is set to stretch the pixels across the display)
+        mDisplayDeviceInfo.xDpi = 0.5f;
+        mDisplayDeviceInfo.yDpi = 1.0f;
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        var originalDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+        // Content width re-scaled
+        assertEquals(DISPLAY_WIDTH * 2, originalDisplayInfo.logicalWidth);
+        assertEquals(DISPLAY_HEIGHT, originalDisplayInfo.logicalHeight);
+
+        SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+        // Applications need to think that they are shown on a display with square pixels.
+        // as applications can be displayed on multiple displays simultaneously (mirrored).
+        // Content is too wide, should have become letterboxed - but it won't because of anisotropy
+        // correction
+        assertEquals(new Point(0, 0), mLogicalDisplay.getDisplayPosition());
+    }
+
+    @Test
+    public void testLetterbox_anisotropyCorrectionYDpi() {
+        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+                /*isAnisotropyCorrectionEnabled=*/ true);
+
+        DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = DISPLAY_WIDTH;
+        displayInfo.logicalHeight = DISPLAY_HEIGHT / 2;
+        mDisplayDeviceInfo.xDpi = 1.0f;
+        mDisplayDeviceInfo.yDpi = 0.5f;
+        mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+
+        SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+        assertEquals(new Point(0, 75), mLogicalDisplay.getDisplayPosition());
+    }
+
+    @Test
+    public void testPillarbox() {
+        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+                /*isAnisotropyCorrectionEnabled=*/ false);
+        mDisplayDeviceInfo.xDpi = 0.5f;
+        mDisplayDeviceInfo.yDpi = 1.0f;
+
+        DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.rotation = Surface.ROTATION_90;
+        displayInfo.logicalWidth = DISPLAY_WIDTH;
+        displayInfo.logicalHeight = DISPLAY_HEIGHT;
+        mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+        mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+
+        var updatedDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+        assertEquals(Surface.ROTATION_90, updatedDisplayInfo.rotation);
+        assertEquals(DISPLAY_WIDTH, updatedDisplayInfo.logicalWidth);
+        assertEquals(DISPLAY_HEIGHT, updatedDisplayInfo.logicalHeight);
+
+        /*
+         * Content is too tall, should become pillarboxed
+         *  ______DISPLAY_WIDTH________
+         * |    |                |    |
+         * |    |                |    |
+         * |    |                |    |
+         * |    |   CONTENT      |    |
+         * |    |                |    |
+         * |    |                |    |
+         * |____|________________|____|
+         */
+
+        SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+        assertEquals(new Point(75, 0), mLogicalDisplay.getDisplayPosition());
+    }
+
+    @Test
+    public void testPillarbox_anisotropyCorrection() {
+        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+                /*isAnisotropyCorrectionEnabled=*/ true);
+
+        DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = DISPLAY_WIDTH;
+        displayInfo.logicalHeight = DISPLAY_HEIGHT;
+        displayInfo.rotation = Surface.ROTATION_90;
+        mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+        // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
+        // to using the whole screen. This is because display will rescale it back to fill the
+        // screen (in case the display menu setting is set to stretch the pixels across the display)
+        mDisplayDeviceInfo.xDpi = 0.5f;
+        mDisplayDeviceInfo.yDpi = 1.0f;
+        mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+
+        SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+        // Applications need to think that they are shown on a display with square pixels.
+        // as applications can be displayed on multiple displays simultaneously (mirrored).
+        // Content is a bit wider than in #testPillarbox, due to content added stretching
+        assertEquals(new Point(50, 0), mLogicalDisplay.getDisplayPosition());
+    }
+
+    @Test
+    public void testNoPillarbox_anisotropyCorrectionYDpi() {
+        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+                /*isAnisotropyCorrectionEnabled=*/ true);
+
+        // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
+        // to using the whole screen. This is because display will rescale it back to fill the
+        // screen (in case the display menu setting is set to stretch the pixels across the display)
+        mDisplayDeviceInfo.xDpi = 1.0f;
+        mDisplayDeviceInfo.yDpi = 0.5f;
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        var originalDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+        // Content width re-scaled
+        assertEquals(DISPLAY_WIDTH, originalDisplayInfo.logicalWidth);
+        assertEquals(DISPLAY_HEIGHT * 2, originalDisplayInfo.logicalHeight);
+
+        SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+        // Applications need to think that they are shown on a display with square pixels.
+        // as applications can be displayed on multiple displays simultaneously (mirrored).
+        // Content is too tall, should have occupy the whole screen - but it won't because of
+        // anisotropy correction
+        assertEquals(new Point(0, 0), mLogicalDisplay.getDisplayPosition());
+    }
+
+    @Test
     public void testGetDisplayPosition() {
-        Point expectedPosition = new Point();
+        Point expectedPosition = new Point(0, 0);
 
         SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
         mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
index 060f99b..397d77c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display.brightness;
 
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
 
 import static org.junit.Assert.assertEquals;
@@ -43,6 +45,7 @@
                 getReason(BrightnessReason.REASON_DOZE, BrightnessReason.MODIFIER_LOW_POWER));
         mBrightnessEvent.setPhysicalDisplayId("test");
         mBrightnessEvent.setDisplayState(Display.STATE_ON);
+        mBrightnessEvent.setDisplayPolicy(POLICY_BRIGHT);
         mBrightnessEvent.setLux(100.0f);
         mBrightnessEvent.setPreThresholdLux(150.0f);
         mBrightnessEvent.setTime(System.currentTimeMillis());
@@ -74,11 +77,12 @@
     public void testToStringWorksAsExpected() {
         String actualString = mBrightnessEvent.toString(false);
         String expectedString =
-                "BrightnessEvent: disp=1, physDisp=test, displayState=ON, brt=0.6, initBrt=25.0,"
-                + " rcmdBrt=0.6, preBrt=NaN, lux=100.0, preLux=150.0, hbmMax=0.62, hbmMode=off,"
-                + " rbcStrength=-1, thrmMax=0.65, powerFactor=0.2, wasShortTermModelActive=true,"
-                + " flags=, reason=doze [ low_pwr ], autoBrightness=true, strategy="
-                        + DISPLAY_BRIGHTNESS_STRATEGY_NAME + ", autoBrightnessMode=idle";
+                "BrightnessEvent: disp=1, physDisp=test, displayState=ON, displayPolicy=BRIGHT,"
+                + " brt=0.6, initBrt=25.0, rcmdBrt=0.6, preBrt=NaN, lux=100.0, preLux=150.0,"
+                + " hbmMax=0.62, hbmMode=off, rbcStrength=-1, thrmMax=0.65, powerFactor=0.2,"
+                + " wasShortTermModelActive=true, flags=, reason=doze [ low_pwr ],"
+                + " autoBrightness=true, strategy=" + DISPLAY_BRIGHTNESS_STRATEGY_NAME
+                + ", autoBrightnessMode=idle";
         assertEquals(expectedString, actualString);
     }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index 289d54b..9b6cc0a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -393,7 +393,31 @@
         OffloadBrightnessStrategy offloadBrightnessStrategy = mock(OffloadBrightnessStrategy.class);
         when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(
                 offloadBrightnessStrategy);
-        mDisplayBrightnessController.setBrightnessFromOffload(brightness);
+        boolean brightnessUpdated =
+                mDisplayBrightnessController.setBrightnessFromOffload(brightness);
         verify(offloadBrightnessStrategy).setOffloadScreenBrightness(brightness);
+        assertTrue(brightnessUpdated);
+    }
+
+    @Test
+    public void setBrightnessFromOffload_OffloadStrategyNull() {
+        float brightness = 0.4f;
+        when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(null);
+        boolean brightnessUpdated =
+                mDisplayBrightnessController.setBrightnessFromOffload(brightness);
+        assertFalse(brightnessUpdated);
+    }
+
+    @Test
+    public void setBrightnessFromOffload_BrightnessUnchanged() {
+        float brightness = 0.4f;
+        OffloadBrightnessStrategy offloadBrightnessStrategy = mock(OffloadBrightnessStrategy.class);
+        when(offloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(brightness);
+        when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(
+                offloadBrightnessStrategy);
+        boolean brightnessUpdated =
+                mDisplayBrightnessController.setBrightnessFromOffload(brightness);
+        verify(offloadBrightnessStrategy, never()).setOffloadScreenBrightness(brightness);
+        assertFalse(brightnessUpdated);
     }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index 37958fa..a3728c8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -18,8 +18,10 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.ContentResolver;
@@ -27,6 +29,7 @@
 import android.content.ContextWrapper;
 import android.content.res.Resources;
 import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
 import android.view.Display;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -155,6 +158,7 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
                 DisplayManagerInternal.DisplayPowerRequest.class);
         displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+        displayPowerRequest.dozeScreenBrightness = 0.2f;
         when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn(
                 DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING);
         assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
@@ -162,6 +166,18 @@
     }
 
     @Test
+    public void selectStrategyDoesNotSelectDozeStrategyWhenInvalidBrightness() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+        displayPowerRequest.dozeScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn(
+                DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING);
+        assertNotEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                Display.STATE_DOZE), mDozeBrightnessModeStrategy);
+    }
+
+    @Test
     public void selectStrategySelectsScreenOffStrategyWhenValid() {
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
                 DisplayManagerInternal.DisplayPowerRequest.class);
@@ -233,6 +249,7 @@
         displayPowerRequest.screenBrightnessOverride = Float.NaN;
         when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
         when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+        when(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()).thenReturn(true);
         when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(0.3f);
         assertEquals(mOffloadBrightnessStrategy, mDisplayBrightnessStrategySelector.selectStrategy(
                 displayPowerRequest, Display.STATE_ON));
@@ -253,4 +270,33 @@
                 mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
                         Display.STATE_ON));
     }
+
+    @Test
+    public void selectStrategyCallsPostProcessorForAllStrategies() {
+        when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true);
+        mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
+                mInjector, DISPLAY_ID, mDisplayManagerFlags);
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(0.3f);
+
+        mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest, Display.STATE_ON);
+
+        StrategySelectionNotifyRequest strategySelectionNotifyRequest =
+                new StrategySelectionNotifyRequest(mFollowerBrightnessStrategy);
+        verify(mInvalidBrightnessStrategy).strategySelectionPostProcessor(
+                eq(strategySelectionNotifyRequest));
+        verify(mScreenOffBrightnessModeStrategy).strategySelectionPostProcessor(
+                eq(strategySelectionNotifyRequest));
+        verify(mDozeBrightnessModeStrategy).strategySelectionPostProcessor(
+                eq(strategySelectionNotifyRequest));
+        verify(mFollowerBrightnessStrategy).strategySelectionPostProcessor(
+                eq(strategySelectionNotifyRequest));
+        verify(mBoostBrightnessStrategy).strategySelectionPostProcessor(
+                eq(strategySelectionNotifyRequest));
+        verify(mOverrideBrightnessStrategy).strategySelectionPostProcessor(
+                eq(strategySelectionNotifyRequest));
+        verify(mTemporaryBrightnessStrategy).strategySelectionPostProcessor(
+                eq(strategySelectionNotifyRequest));
+    }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index 8867806..a5dc668 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -188,34 +188,11 @@
     }
 
     @Test
-    public void testAutoBrightnessState_BrightnessReasonIsOffload() {
-        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
-        int targetDisplayState = Display.STATE_ON;
-        boolean allowAutoBrightnessWhileDozing = false;
-        int brightnessReason = BrightnessReason.REASON_OFFLOAD;
-        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
-        float lastUserSetBrightness = 0.2f;
-        boolean userSetBrightnessChanged = true;
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
-        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
-                userSetBrightnessChanged);
-        verify(mAutomaticBrightnessController)
-                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
-                        mBrightnessConfiguration,
-                        lastUserSetBrightness,
-                        userSetBrightnessChanged, 0.5f,
-                        false, policy, true);
-        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
-        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
-    }
-
-    @Test
     public void testAutoBrightnessState_DisplayIsInDoze_ConfigDoesAllow() {
         mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
         int targetDisplayState = Display.STATE_DOZE;
         boolean allowAutoBrightnessWhileDozing = true;
-        int brightnessReason = BrightnessReason.REASON_DOZE;
+        int brightnessReason = BrightnessReason.REASON_UNKNOWN;
         int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
         float lastUserSetBrightness = 0.2f;
         boolean userSetBrightnessChanged = 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 64076e6..3eced7f 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
@@ -1631,12 +1631,25 @@
         director.start(sensorManager);
         director.injectSupportedModesByDisplay(supportedModesByDisplay);
 
-        setPeakRefreshRate(Float.POSITIVE_INFINITY);
+        // Disable Smooth Display
+        setPeakRefreshRate(RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
 
         Vote vote1 = director.getVote(DISPLAY_ID,
                 Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
         Vote vote2 = director.getVote(DISPLAY_ID_2,
                 Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+        assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+
+        // Enable Smooth Display
+        setPeakRefreshRate(Float.POSITIVE_INFINITY);
+
+        vote1 = director.getVote(DISPLAY_ID,
+                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        vote2 = director.getVote(DISPLAY_ID_2,
+                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
         assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0, /* frameRateHigh= */ 130);
         assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0, /* frameRateHigh= */ 140);
     }
@@ -1654,10 +1667,18 @@
         SensorManager sensorManager = createMockSensorManager(lightSensor);
         director.start(sensorManager);
 
-        setPeakRefreshRate(peakRefreshRate);
+        // Disable Smooth Display
+        setPeakRefreshRate(RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
 
         Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
         assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+
+        // Enable Smooth Display
+        setPeakRefreshRate(peakRefreshRate);
+
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0,
                 /* frameRateHigh= */ peakRefreshRate);
     }
 
@@ -1759,11 +1780,23 @@
         director.start(sensorManager);
         director.injectSupportedModesByDisplay(supportedModesByDisplay);
 
-        setMinRefreshRate(Float.POSITIVE_INFINITY);
+        // Disable Force Peak Refresh Rate
+        setMinRefreshRate(0);
 
         Vote vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
         Vote vote2 = director.getVote(DISPLAY_ID_2,
                 Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+        assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+
+        // Enable Force Peak Refresh Rate
+        setMinRefreshRate(Float.POSITIVE_INFINITY);
+
+        vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        vote2 = director.getVote(DISPLAY_ID_2,
+                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
         assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 130,
                 /* frameRateHigh= */ Float.POSITIVE_INFINITY);
         assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 140,
@@ -1783,9 +1816,17 @@
         SensorManager sensorManager = createMockSensorManager(lightSensor);
         director.start(sensorManager);
 
-        setMinRefreshRate(minRefreshRate);
+        // Disable Force Peak Refresh Rate
+        setMinRefreshRate(0);
 
         Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+
+        // Enable Force Peak Refresh Rate
+        setMinRefreshRate(minRefreshRate);
+
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
         assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ minRefreshRate,
                 /* frameRateHigh= */ Float.POSITIVE_INFINITY);
     }
@@ -1829,6 +1870,58 @@
     }
 
     @Test
+    public void testPeakAndMinRefreshRate_FlagEnabled_DisplayWithOneMode() {
+        when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
+                .thenReturn(true);
+        DisplayModeDirector director =
+                new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        Display.Mode[] modes1 = new Display.Mode[] {
+                new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 60),
+                new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 130),
+        };
+        Display.Mode[] modes2 = new Display.Mode[] {
+                new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 60),
+        };
+        SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
+        supportedModesByDisplay.put(DISPLAY_ID, modes1);
+        supportedModesByDisplay.put(DISPLAY_ID_2, modes2);
+
+        Sensor lightSensor = createLightSensor();
+        SensorManager sensorManager = createMockSensorManager(lightSensor);
+        director.start(sensorManager);
+        director.injectSupportedModesByDisplay(supportedModesByDisplay);
+
+        // Disable Force Peak Refresh Rate and Smooth Display
+        setMinRefreshRate(0);
+        setPeakRefreshRate(RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+
+        // Even though the highest refresh rate of the second display == the current min refresh
+        // rate == 60, Force Peak Refresh Rate should remain disabled
+        Vote vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        Vote vote2 = director.getVote(DISPLAY_ID_2,
+                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+        assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+
+        // Even though the highest refresh rate of the second display == the current peak refresh
+        // rate == 60, Smooth Display should remain disabled
+        vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        vote2 = director.getVote(DISPLAY_ID_2,
+                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+        assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+    }
+
+    @Test
     public void testSensorRegistration() {
         // First, configure brightness zones or DMD won't register for sensor data.
         final FakeDeviceConfig config = mInjector.getDeviceConfig();
diff --git a/services/tests/dreamservicetests/Android.bp b/services/tests/dreamservicetests/Android.bp
index 5aa5d61..86b3a6c 100644
--- a/services/tests/dreamservicetests/Android.bp
+++ b/services/tests/dreamservicetests/Android.bp
@@ -18,6 +18,11 @@
         "mockito-target-minus-junit4",
         "services.core",
         "mockingservicestests-utils-mockito",
+        "servicestests-utils",
+    ],
+
+    defaults: [
+        "modules-utils-testable-device-config-defaults",
     ],
 
     platform_apis: true,
diff --git a/services/tests/dreamservicetests/AndroidManifest.xml b/services/tests/dreamservicetests/AndroidManifest.xml
index f4b88d9..6092ef6 100644
--- a/services/tests/dreamservicetests/AndroidManifest.xml
+++ b/services/tests/dreamservicetests/AndroidManifest.xml
@@ -21,6 +21,7 @@
     Insert permissions here. eg:
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
     -->
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
 
     <application android:debuggable="true"
                  android:testOnly="true">
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java
index 32d4e75..992b853 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java
@@ -36,13 +36,14 @@
 import android.provider.Settings;
 
 import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
 
-import com.android.server.LocalServices;
+import com.android.internal.util.test.LocalServiceKeeperRule;
 import com.android.server.SystemService;
+import com.android.server.testutils.TestHandler;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -65,23 +66,24 @@
     @Mock
     private UserManager mUserManagerMock;
 
-    private MockitoSession mMockitoSession;
+    @Rule
+    public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule();
 
-    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
-        LocalServices.removeServiceForTest(clazz);
-        LocalServices.addService(clazz, mock);
-    }
+    private TestHandler mTestHandler;
+    private MockitoSession mMockitoSession;
 
     @Before
     public void setUp() throws Exception {
+        mTestHandler = new TestHandler(/* callback= */ null);
         MockitoAnnotations.initMocks(this);
-
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
         mResourcesSpy = spy(mContextSpy.getResources());
         when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
 
-        addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
-        addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
+        mLocalServiceKeeperRule.overrideLocalService(
+                ActivityManagerInternal.class, mActivityManagerInternalMock);
+        mLocalServiceKeeperRule.overrideLocalService(
+                PowerManagerInternal.class, mPowerManagerInternalMock);
 
         when(mContextSpy.getSystemService(UserManager.class)).thenReturn(mUserManagerMock);
         mMockitoSession = mockitoSession()
@@ -94,26 +96,20 @@
     @After
     public void tearDown() throws Exception {
         mMockitoSession.finishMocking();
-        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
-        LocalServices.removeServiceForTest(PowerManagerInternal.class);
     }
 
     private DreamManagerService createService() {
-        return new DreamManagerService(mContextSpy);
+        return new DreamManagerService(mContextSpy, mTestHandler);
     }
 
     @Test
-    @FlakyTest(bugId = 293443309)
     public void testSettingsQueryUserChange() {
         final DreamManagerService service = createService();
-
         final SystemService.TargetUser from =
                 new SystemService.TargetUser(mock(UserInfo.class));
         final SystemService.TargetUser to =
                 new SystemService.TargetUser(mock(UserInfo.class));
-
         service.onUserSwitching(from, to);
-
         verify(() -> Settings.Secure.getIntForUser(any(),
                 eq(Settings.Secure.SCREENSAVER_ENABLED),
                 anyInt(),
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 caa0864..a8b792e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -213,7 +213,7 @@
                 any(), any(), any(),
                 any(), any(),
                 any(), any(),
-                any(),
+                any(), any(),
                 anyLong(), anyLong());
 
         final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
@@ -277,7 +277,7 @@
                 null, null,
                 null,
                 null, null, null,
-                null, null,
+                null, null, null,
                 0, 0);
 
         // Sleep until timeout should have triggered
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index fb47aa8..bd20ae2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -98,8 +98,6 @@
     @Rule
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
-    final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[1];
-
     @Mock
     AppOpsService mAppOpsService;
     @Mock
@@ -120,6 +118,7 @@
     HandlerThread mHandlerThread;
     TestLooperManager mLooper;
     AtomicInteger mNextPid;
+    BroadcastHistory mEmptyHistory;
 
     /**
      * Map from PID to registered registered runtime receivers.
@@ -137,6 +136,13 @@
                 .acquireLooperManager(mHandlerThread.getLooper()));
         mNextPid = new AtomicInteger(100);
 
+        mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
+        mEmptyHistory = new BroadcastHistory(mConstants) {
+            public void addBroadcastToHistoryLocked(BroadcastRecord original) {
+                // Ignored
+            }
+        };
+
         LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
         LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
@@ -164,8 +170,6 @@
         mSkipPolicy = spy(new BroadcastSkipPolicy(mAms));
         doReturn(null).when(mSkipPolicy).shouldSkipMessage(any(), any());
         doReturn(false).when(mSkipPolicy).disallowBackgroundStart(any());
-
-        mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
     }
 
     public void tearDown() throws Exception {
@@ -213,8 +217,8 @@
         }
 
         @Override
-        public BroadcastQueue[] getBroadcastQueues(ActivityManagerService service) {
-            return mBroadcastQueues;
+        public BroadcastQueue getBroadcastQueue(ActivityManagerService service) {
+            return null;
         }
     }
 
@@ -279,4 +283,9 @@
         receiverList.add(res);
         return res;
     }
+
+    void setProcessFreezable(ProcessRecord app, boolean pendingFreeze, boolean frozen) {
+        app.mOptRecord.setPendingFreeze(pendingFreeze);
+        app.mOptRecord.setFrozen(frozen);
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index cc6fc80..0ba74c6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -42,6 +42,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -99,7 +100,7 @@
     private static final int TEST_UID = android.os.Process.FIRST_APPLICATION_UID;
     private static final int TEST_UID2 = android.os.Process.FIRST_APPLICATION_UID + 1;
 
-    @Mock ProcessRecord mProcess;
+    ProcessRecord mProcess;
 
     @Mock BroadcastProcessQueue mQueue1;
     @Mock BroadcastProcessQueue mQueue2;
@@ -118,20 +119,17 @@
         mConstants.DELAY_NORMAL_MILLIS = 10_000;
         mConstants.DELAY_CACHED_MILLIS = 120_000;
 
-        final BroadcastHistory emptyHistory = new BroadcastHistory(mConstants) {
-            public void addBroadcastToHistoryLocked(BroadcastRecord original) {
-                // Ignored
-            }
-        };
-
         mImpl = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
-            mConstants, mConstants, mSkipPolicy, emptyHistory);
-        mBroadcastQueues[0] = mImpl;
+                mConstants, mConstants, mSkipPolicy, mEmptyHistory);
+        mAms.setBroadcastQueueForTest(mImpl);
 
         doReturn(1L).when(mQueue1).getRunnableAt();
         doReturn(2L).when(mQueue2).getRunnableAt();
         doReturn(3L).when(mQueue3).getRunnableAt();
         doReturn(4L).when(mQueue4).getRunnableAt();
+
+        final ApplicationInfo ai = makeApplicationInfo(PACKAGE_ORANGE);
+        mProcess = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
     }
 
     @After
@@ -1415,6 +1413,9 @@
         final BroadcastRecord userPresentRecord2 = makeBroadcastRecord(userPresent);
 
         mImpl.enqueueBroadcastLocked(userPresentRecord1);
+        // Wait for a few ms before sending another broadcast to allow comparing the
+        // enqueue timestamps of these broadcasts.
+        SystemClock.sleep(5);
         mImpl.enqueueBroadcastLocked(userPresentRecord2);
 
         final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
@@ -1481,7 +1482,8 @@
                 eq(BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST),
                 eq(BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD),
                 anyLong(), anyLong(), anyLong(), anyInt(), nullable(String.class),
-                anyString(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt()),
+                anyString(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(),
+                anyBoolean(), anyLong()),
                 times(1));
     }
 
@@ -1752,6 +1754,31 @@
         }, false /* andRemove */);
     }
 
+    @Test
+    public void testIsProcessFreezable() throws Exception {
+        final ProcessRecord greenProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
+
+        setProcessFreezable(greenProcess, true /* pendingFreeze */, true /* frozen */);
+        mImpl.onProcessFreezableChangedLocked(greenProcess);
+        waitForIdle();
+        assertTrue(mImpl.isProcessFreezable(greenProcess));
+
+        setProcessFreezable(greenProcess, true /* pendingFreeze */, false /* frozen */);
+        mImpl.onProcessFreezableChangedLocked(greenProcess);
+        waitForIdle();
+        assertTrue(mImpl.isProcessFreezable(greenProcess));
+
+        setProcessFreezable(greenProcess, false /* pendingFreeze */, true /* frozen */);
+        mImpl.onProcessFreezableChangedLocked(greenProcess);
+        waitForIdle();
+        assertTrue(mImpl.isProcessFreezable(greenProcess));
+
+        setProcessFreezable(greenProcess, false /* pendingFreeze */, false /* frozen */);
+        mImpl.onProcessFreezableChangedLocked(greenProcess);
+        waitForIdle();
+        assertFalse(mImpl.isProcessFreezable(greenProcess));
+    }
+
     // TODO: Reuse BroadcastQueueTest.makeActiveProcessRecord()
     private ProcessRecord makeProcessRecord(ApplicationInfo info) {
         final ProcessRecord r = spy(new ProcessRecord(mAms, info, info.processName, info.uid));
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 3f6117b..66ab807 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -85,12 +85,9 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.After;
-import org.junit.Assume;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 import org.mockito.ArgumentMatcher;
 import org.mockito.InOrder;
 import org.mockito.verification.VerificationMode;
@@ -100,7 +97,6 @@
 import java.io.Writer;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -114,18 +110,10 @@
  * Common tests for {@link BroadcastQueue} implementations.
  */
 @MediumTest
-@RunWith(Parameterized.class)
 @SuppressWarnings("GuardedBy")
 public class BroadcastQueueTest extends BaseBroadcastQueueTest {
     private static final String TAG = "BroadcastQueueTest";
 
-    private final Impl mImpl;
-
-    private enum Impl {
-        DEFAULT,
-        MODERN,
-    }
-
     private BroadcastQueue mQueue;
     private UidObserver mUidObserver;
 
@@ -157,15 +145,6 @@
      */
     private List<Pair<Integer, String>> mScheduledBroadcasts = new ArrayList<>();
 
-    @Parameters(name = "impl={0}")
-    public static Collection<Object[]> data() {
-        return Arrays.asList(new Object[][] { {Impl.DEFAULT}, {Impl.MODERN} });
-    }
-
-    public BroadcastQueueTest(Impl impl) {
-        mImpl = impl;
-    }
-
     @Before
     public void setUp() throws Exception {
         super.setUp();
@@ -251,24 +230,9 @@
         }).when(mAms).registerUidObserver(any(), anyInt(),
                 eq(ActivityManager.PROCESS_STATE_TOP), any());
 
-        final BroadcastHistory emptyHistory = new BroadcastHistory(mConstants) {
-            public void addBroadcastToHistoryLocked(BroadcastRecord original) {
-                // Ignored
-            }
-        };
-
-        if (mImpl == Impl.DEFAULT) {
-            mQueue = new BroadcastQueueImpl(mAms, mHandlerThread.getThreadHandler(), TAG,
-                    mConstants, mSkipPolicy, emptyHistory, false,
-                    ProcessList.SCHED_GROUP_DEFAULT);
-        } else if (mImpl == Impl.MODERN) {
-            mQueue = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
-                    mConstants, mConstants, mSkipPolicy, emptyHistory);
-        } else {
-            throw new UnsupportedOperationException();
-        }
-        mBroadcastQueues[0] = mQueue;
-
+        mQueue = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
+                mConstants, mConstants, mSkipPolicy, mEmptyHistory);
+        mAms.setBroadcastQueueForTest(mQueue);
         mQueue.start(mContext.getContentResolver());
 
         // Set the constants after invoking BroadcastQueue.start() to ensure they don't
@@ -483,16 +447,9 @@
                 BackgroundStartPrivileges.NONE, false, null, PROCESS_STATE_UNKNOWN);
     }
 
-    private void setProcessFreezable(ProcessRecord app, boolean pendingFreeze, boolean frozen) {
-        app.mOptRecord.setPendingFreeze(pendingFreeze);
-        app.mOptRecord.setFrozen(frozen);
-    }
-
     private void assertHealth() {
-        if (mImpl == Impl.MODERN) {
-            // If this fails, it'll throw a clear reason message
-            ((BroadcastQueueModernImpl) mQueue).assertHealthLocked();
-        }
+        // If this fails, it'll throw a clear reason message
+        ((BroadcastQueueModernImpl) mQueue).assertHealthLocked();
     }
 
     private static Map<String, Object> asMap(Bundle bundle) {
@@ -814,18 +771,13 @@
                     .setReportedProcState(ActivityManager.PROCESS_STATE_RECEIVER);
             verify(mAms, times(2)).enqueueOomAdjTargetLocked(eq(receiverApp));
 
-            if ((mImpl == Impl.DEFAULT) && (receiverApp == receiverBlueApp)) {
-                // Nuance: the default implementation doesn't ask for manifest
-                // cold-started apps to be thawed, but the modern stack does
-            } else {
-                // Confirm that app was thawed
-                verify(mAms.mOomAdjuster, atLeastOnce()).unfreezeTemporarily(
-                        eq(receiverApp), eq(OOM_ADJ_REASON_START_RECEIVER));
+            // Confirm that app was thawed
+            verify(mAms.mOomAdjuster, atLeastOnce()).unfreezeTemporarily(
+                    eq(receiverApp), eq(OOM_ADJ_REASON_START_RECEIVER));
 
-                // Confirm that we added package to process
-                verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName),
-                        anyLong(), any());
-            }
+            // Confirm that we added package to process
+            verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName),
+                    anyLong(), any());
 
             // Confirm that we've reported package as being used
             verify(mAms, atLeastOnce()).notifyPackageUse(eq(receiverApp.info.packageName),
@@ -868,9 +820,6 @@
      */
     @Test
     public void testWedged_Registered_Ordered() throws Exception {
-        // Legacy stack doesn't detect these ANRs; likely an oversight
-        Assume.assumeTrue(mImpl == Impl.MODERN);
-
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
                 ProcessBehavior.WEDGE);
@@ -891,9 +840,6 @@
      */
     @Test
     public void testWedged_Registered_ResultTo() throws Exception {
-        // Legacy stack doesn't detect these ANRs; likely an oversight
-        Assume.assumeTrue(mImpl == Impl.MODERN);
-
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
                 ProcessBehavior.WEDGE);
@@ -994,9 +940,7 @@
                 getUidForPackage(PACKAGE_GREEN));
         // Modern queue always kills the target process when broadcast delivery fails, where as
         // the legacy queue leaves the process killing task to AMS
-        if (mImpl == Impl.MODERN) {
-            assertNull(receiverGreenApp);
-        }
+        assertNull(receiverGreenApp);
         final ProcessRecord receiverBlueApp = mAms.getProcessRecordLocked(PACKAGE_BLUE,
                 getUidForPackage(PACKAGE_BLUE));
         verifyScheduleReceiver(receiverBlueApp, airplane);
@@ -1110,10 +1054,8 @@
         waitForIdle();
         // Legacy stack does not remove registered receivers as part of
         // cleanUpDisabledPackageReceiversLocked() call, so verify this only on modern queue.
-        if (mImpl == Impl.MODERN) {
-            verifyScheduleReceiver(never(), callerApp, USER_GUEST);
-            verifyScheduleRegisteredReceiver(never(), callerApp, USER_GUEST);
-        }
+        verifyScheduleReceiver(never(), callerApp, USER_GUEST);
+        verifyScheduleRegisteredReceiver(never(), callerApp, USER_GUEST);
         for (String pkg : new String[] {
                 PACKAGE_GREEN, PACKAGE_BLUE, PACKAGE_YELLOW
         }) {
@@ -1199,9 +1141,6 @@
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_AVOID_REPEATED_BCAST_RE_ENQUEUES)
     public void testRepeatedKillWithoutNotify() throws Exception {
-        // Legacy queue does not handle repeated kills that don't get notified.
-        Assume.assumeTrue(mImpl == Impl.MODERN);
-
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
 
@@ -1227,9 +1166,7 @@
 
         // Modern queue always kills the target process when broadcast delivery fails, where as
         // the legacy queue leaves the process killing task to AMS
-        if (mImpl == Impl.MODERN) {
-            assertNull(receiverGreenApp);
-        }
+        assertNull(receiverGreenApp);
         verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
         verifyScheduleReceiver(times(1), receiverYellowApp, airplane);
         verifyScheduleReceiver(times(1), receiverOrangeApp, timezone);
@@ -1273,11 +1210,7 @@
         assertNotEquals(receiverBlueApp, restartedReceiverBlueApp);
         // Legacy queue will always try delivering the broadcast even if the process
         // has been killed.
-        if (mImpl == Impl.MODERN) {
-            verifyScheduleReceiver(never(), receiverBlueApp, airplane);
-        } else {
-            verifyScheduleReceiver(times(1), receiverBlueApp, airplane);
-        }
+        verifyScheduleReceiver(never(), receiverBlueApp, airplane);
         // Verify that the new process receives the broadcast.
         verifyScheduleReceiver(times(1), restartedReceiverBlueApp, airplane);
     }
@@ -1671,9 +1604,6 @@
      */
     @Test
     public void testPrioritized_withDeferrableBroadcasts() throws Exception {
-        // Legacy stack doesn't support deferral
-        Assume.assumeTrue(mImpl == Impl.MODERN);
-
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
         final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
@@ -1834,10 +1764,6 @@
 
     @Test
     public void testReplacePending_withUrgentBroadcast() throws Exception {
-        // The behavior is same with the legacy queue but AMS takes care of finding
-        // the right queue and replacing the broadcast.
-        Assume.assumeTrue(mImpl == Impl.MODERN);
-
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
 
         final Intent timeTickFirst = new Intent(Intent.ACTION_TIME_TICK);
@@ -1903,15 +1829,9 @@
 
         waitForIdle();
 
-        if (mImpl == Impl.MODERN) {
-            verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
-            verifyScheduleRegisteredReceiver(times(2), receiverBlueApp, airplane);
-            verifyScheduleRegisteredReceiver(times(1), receiverYellowApp, airplane);
-        } else {
-            verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
-            verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
-            verifyScheduleRegisteredReceiver(never(), receiverYellowApp, airplane);
-        }
+        verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
+        verifyScheduleRegisteredReceiver(times(2), receiverBlueApp, airplane);
+        verifyScheduleRegisteredReceiver(times(1), receiverYellowApp, airplane);
     }
 
     @Test
@@ -1931,14 +1851,10 @@
                 withPriority(receiverGreenA, 5))));
 
         waitForIdle();
-        if (mImpl == Impl.DEFAULT) {
-            verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
-        } else {
-            // In the modern queue, we don't end up replacing the old broadcast to
-            // avoid creating priority inversion and so the process will receive
-            // both the old and new broadcasts.
-            verifyScheduleRegisteredReceiver(times(3), receiverGreenApp, airplane);
-        }
+        // In the modern queue, we don't end up replacing the old broadcast to
+        // avoid creating priority inversion and so the process will receive
+        // both the old and new broadcasts.
+        verifyScheduleRegisteredReceiver(times(3), receiverGreenApp, airplane);
     }
 
     @Test
@@ -1966,11 +1882,7 @@
 
         verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, timeTick);
         verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, timeTick);
-        if (mImpl == Impl.MODERN) {
-            verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
-        } else {
-            verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
-        }
+        verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
         verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
     }
 
@@ -2001,6 +1913,56 @@
     }
 
     @Test
+    public void testReplacePendingToCachedProcess_withDeferrableBroadcast() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+        final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+        final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
+
+        setProcessFreezable(receiverGreenApp, true, false);
+        mQueue.onProcessFreezableChangedLocked(receiverGreenApp);
+        waitForIdle();
+
+        final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK)
+                .addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+        final BroadcastOptions opts = BroadcastOptions.makeBasic()
+                .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
+
+        final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp, 10);
+        final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp, 5);
+        final BroadcastFilter receiverYellow = makeRegisteredReceiver(receiverYellowApp, 0);
+        enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp, opts, List.of(
+                receiverGreen, receiverBlue, receiverYellow)));
+
+        // Enqueue the broadcast again to replace the earlier one
+        enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp, opts, List.of(
+                receiverGreen, receiverBlue, receiverYellow)));
+
+        waitForIdle();
+        // Green should still be in the cached state and shouldn't receive the broadcast
+        verifyScheduleRegisteredReceiver(never(), receiverGreenApp, timeTick);
+
+        final IApplicationThread blueThread = receiverBlueApp.getThread();
+        final IApplicationThread yellowThread = receiverYellowApp.getThread();
+        final InOrder inOrder = inOrder(blueThread, yellowThread);
+        inOrder.verify(blueThread).scheduleRegisteredReceiver(
+                any(), argThat(filterEqualsIgnoringComponent(timeTick)),
+                anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(),
+                eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any());
+        inOrder.verify(yellowThread).scheduleRegisteredReceiver(
+                any(), argThat(filterEqualsIgnoringComponent(timeTick)),
+                anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(),
+                eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any());
+
+        setProcessFreezable(receiverGreenApp, false, false);
+        mQueue.onProcessFreezableChangedLocked(receiverGreenApp);
+        waitForIdle();
+
+        // Confirm that green receives the broadcast once it comes out of the cached state
+        verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, timeTick);
+    }
+
+    @Test
     public void testIdleAndBarrier() throws Exception {
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
@@ -2171,16 +2133,9 @@
         }
         waitForIdle();
 
-        final int expectedTimes;
-        switch (mImpl) {
-            // Original stack requested for every single receiver; yikes
-            case DEFAULT: expectedTimes = 64; break;
-            // Modern stack requests once each time we promote a process to
-            // running; we promote "green" twice, and "blue" and "yellow" once
-            case MODERN: expectedTimes = 4; break;
-            default: throw new UnsupportedOperationException();
-        }
-
+        // Modern stack requests once each time we promote a process to
+        // running; we promote "green" twice, and "blue" and "yellow" once
+        final int expectedTimes = 4;
         verify(mAms, times(expectedTimes))
                 .updateOomAdjPendingTargetsLocked(eq(OOM_ADJ_REASON_START_RECEIVER));
     }
@@ -2249,9 +2204,6 @@
      */
     @Test
     public void testDeferralPolicy_UntilActive() throws Exception {
-        // Legacy stack doesn't support deferral
-        Assume.assumeTrue(mImpl == Impl.MODERN);
-
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
         final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
@@ -2297,9 +2249,6 @@
      */
     @Test
     public void testDeferralPolicy_UntilActive_WithMultiProcessUid() throws Exception {
-        // Legacy stack doesn't support deferral
-        Assume.assumeTrue(mImpl == Impl.MODERN);
-
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         final ProcessRecord receiverGreenApp1 = makeActiveProcessRecord(PACKAGE_GREEN);
         final ProcessRecord receiverGreenApp2 = makeActiveProcessRecord(PACKAGE_GREEN,
@@ -2331,9 +2280,6 @@
 
     @Test
     public void testBroadcastDelivery_uidForeground() throws Exception {
-        // Legacy stack doesn't support prioritization to foreground app.
-        Assume.assumeTrue(mImpl == Impl.MODERN);
-
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
         final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
@@ -2365,9 +2311,6 @@
 
     @Test
     public void testPrioritizedBroadcastDelivery_uidForeground() throws Exception {
-        // Legacy stack doesn't support prioritization to foreground app.
-        Assume.assumeTrue(mImpl == Impl.MODERN);
-
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
         final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
@@ -2392,6 +2335,38 @@
                 .isGreaterThan(getReceiverScheduledTime(prioritizedRecord, receiverBlue));
     }
 
+    @Ignore
+    @Test
+    public void testDeferOutgoingBroadcasts() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        setProcessFreezable(callerApp, true /* pendingFreeze */, false /* frozen */);
+        mQueue.onProcessFreezableChangedLocked(callerApp);
+        waitForIdle();
+
+        final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+        final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+        final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+        enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp, List.of(
+                makeRegisteredReceiver(receiverGreenApp),
+                makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+                makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
+
+        waitForIdle();
+        verifyScheduleRegisteredReceiver(never(), receiverGreenApp, timeTick);
+        verifyScheduleReceiver(never(), receiverBlueApp, timeTick);
+        assertNull(mAms.getProcessRecordLocked(PACKAGE_YELLOW, getUidForPackage(PACKAGE_GREEN)));
+
+        setProcessFreezable(callerApp, false /* pendingFreeze */, false /* frozen */);
+        mQueue.onProcessFreezableChangedLocked(callerApp);
+        waitForIdle();
+
+        verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, timeTick);
+        verifyScheduleReceiver(times(1), receiverBlueApp, timeTick);
+        final ProcessRecord receiverYellowApp = mAms.getProcessRecordLocked(PACKAGE_YELLOW,
+                getUidForPackage(PACKAGE_YELLOW));
+        verifyScheduleReceiver(times(1), receiverYellowApp, timeTick);
+    }
+
     private long getReceiverScheduledTime(@NonNull BroadcastRecord r, @NonNull Object receiver) {
         for (int i = 0; i < r.receivers.size(); ++i) {
             if (isReceiverEquals(receiver, r.receivers.get(i))) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index f0efb79..878b945 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -61,8 +61,6 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.server.am.BroadcastDispatcher.DeferredBootCompletedBroadcastPerUser;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -747,68 +745,6 @@
         }
     }
 
-    /**
-     * Test the class {@link BroadcastDispatcher#DeferredBootCompletedBroadcastPerUser}
-     */
-    @Test
-    public void testDeferBootCompletedBroadcast_dispatcher() {
-        testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_LOCKED_BOOT_COMPLETED, false);
-        testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_BOOT_COMPLETED, false);
-        testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_LOCKED_BOOT_COMPLETED, true);
-        testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_BOOT_COMPLETED, true);
-    }
-
-    private void testDeferBootCompletedBroadcast_dispatcher_internal(String action,
-            boolean isAllUidReady) {
-        final List<ResolveInfo> receivers = createReceiverInfos(PACKAGE_LIST, new int[] {USER0});
-        final BroadcastRecord br = createBroadcastRecord(receivers, USER0, new Intent(action));
-        assertEquals(PACKAGE_LIST.length, br.receivers.size());
-
-        SparseArray<BroadcastRecord> deferred = br.splitDeferredBootCompletedBroadcastLocked(
-                mActivityManagerInternal, DEFER_BOOT_COMPLETED_BROADCAST_ALL);
-        // original BroadcastRecord receivers list is empty now.
-        assertTrue(br.receivers.isEmpty());
-        assertEquals(PACKAGE_LIST.length, deferred.size());
-
-        DeferredBootCompletedBroadcastPerUser deferredPerUser =
-                new DeferredBootCompletedBroadcastPerUser(USER0);
-        deferredPerUser.enqueueBootCompletedBroadcasts(action, deferred);
-
-        if (action.equals(ACTION_LOCKED_BOOT_COMPLETED)) {
-            assertEquals(PACKAGE_LIST.length,
-                    deferredPerUser.mDeferredLockedBootCompletedBroadcasts.size());
-            assertTrue(deferredPerUser.mLockedBootCompletedBroadcastReceived);
-            for (int i = 0; i < PACKAGE_LIST.length; i++) {
-                final int uid = UserHandle.getUid(USER0, getAppId(i));
-                if (!isAllUidReady) {
-                    deferredPerUser.updateUidReady(uid);
-                }
-                BroadcastRecord d = deferredPerUser.dequeueDeferredBootCompletedBroadcast(
-                        isAllUidReady);
-                final ResolveInfo info = (ResolveInfo) d.receivers.get(0);
-                assertEquals(PACKAGE_LIST[i], info.activityInfo.applicationInfo.packageName);
-                assertEquals(uid, info.activityInfo.applicationInfo.uid);
-            }
-            assertEquals(0, deferredPerUser.mUidReadyForLockedBootCompletedBroadcast.size());
-        } else if (action.equals(ACTION_BOOT_COMPLETED)) {
-            assertEquals(PACKAGE_LIST.length,
-                    deferredPerUser.mDeferredBootCompletedBroadcasts.size());
-            assertTrue(deferredPerUser.mBootCompletedBroadcastReceived);
-            for (int i = 0; i < PACKAGE_LIST.length; i++) {
-                final int uid = UserHandle.getUid(USER0, getAppId(i));
-                if (!isAllUidReady) {
-                    deferredPerUser.updateUidReady(uid);
-                }
-                BroadcastRecord d = deferredPerUser.dequeueDeferredBootCompletedBroadcast(
-                        isAllUidReady);
-                final ResolveInfo info = (ResolveInfo) d.receivers.get(0);
-                assertEquals(PACKAGE_LIST[i], info.activityInfo.applicationInfo.packageName);
-                assertEquals(uid, info.activityInfo.applicationInfo.uid);
-            }
-            assertEquals(0, deferredPerUser.mUidReadyForBootCompletedBroadcast.size());
-        }
-    }
-
     private static void cleanupDisabledPackageReceivers(BroadcastRecord record,
             String packageName, int userId) {
         record.cleanupDisabledPackageReceiversLocked(packageName, null /* filterByClasses */,
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 1226f0c..872ac40 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -297,6 +297,10 @@
         } else {
             updateProcessRecordNodes(Arrays.asList(apps));
             if (apps.length == 1) {
+                final ProcessRecord app = apps[0];
+                if (!sService.mProcessList.getLruProcessesLOSP().contains(app)) {
+                    sService.mProcessList.getLruProcessesLOSP().add(app);
+                }
                 sService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE);
             } else {
                 setProcessesToLru(apps);
@@ -475,7 +479,16 @@
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         updateOomAdj(app);
 
-        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, CACHED_APP_MIN_ADJ,
+        final int expectedAdj;
+        if (sService.mConstants.ENABLE_NEW_OOMADJ) {
+            // A cached empty process can be at best a level higher than the min cached adj.
+            expectedAdj = sFirstCachedAdj;
+        } else {
+            // This is wrong but legacy behavior is going to be removed and not worth fixing.
+            expectedAdj = CACHED_APP_MIN_ADJ;
+        }
+
+        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, expectedAdj,
                 SCHED_GROUP_BACKGROUND);
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
index fcf761f..67be93b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
@@ -215,7 +215,7 @@
                 any(), any(), any(),
                 any(), any(),
                 any(), any(),
-                any(),
+                any(), any(),
                 anyLong(), anyLong());
         final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
         r.setPid(myPid());
@@ -263,7 +263,7 @@
                 null, null,
                 null,
                 null, null, null,
-                null, null,
+                null, null, null,
                 0, 0);
         return app;
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
index 709a804..97b7af8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
@@ -186,7 +186,7 @@
                 any());
         doReturn(true).when(mAms.mOomAdjuster.mCachedAppOptimizer).useFreezer();
         doNothing().when(mAms.mOomAdjuster.mCachedAppOptimizer).freezeAppAsyncInternalLSP(
-                any(), anyLong(), anyBoolean());
+                any(), anyLong(), anyBoolean(), anyBoolean());
         doReturn(false).when(mAms.mAppProfiler).updateLowMemStateLSP(anyInt(), anyInt(),
                 anyInt(), anyLong());
 
@@ -503,7 +503,7 @@
         if (clientApp.isFreezable()) {
             verify(mAms.mOomAdjuster.mCachedAppOptimizer,
                     times(Flags.serviceBindingOomAdjPolicy() ? 1 : 0))
-                    .freezeAppAsyncInternalLSP(eq(clientApp), eq(0L), anyBoolean());
+                    .freezeAppAsyncInternalLSP(eq(clientApp), eq(0L), anyBoolean(), anyBoolean());
             clearInvocations(mAms.mOomAdjuster.mCachedAppOptimizer);
         }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/PackageManagerBackupAgentTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/PackageManagerBackupAgentTest.java
new file mode 100644
index 0000000..d1b6de0
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/PackageManagerBackupAgentTest.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.nio.ByteBuffer;
+import java.util.Optional;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class PackageManagerBackupAgentTest {
+
+    private static final String EXISTING_PACKAGE_NAME = "com.android.wallpaperbackup";
+    private static final int USER_ID = 0;
+
+    @Rule public TemporaryFolder folder = new TemporaryFolder();
+
+    private PackageManagerBackupAgent mPackageManagerBackupAgent;
+    private ImmutableList<PackageInfo> mPackages;
+    private File mBackupData, mOldState, mNewState;
+
+    @Before
+    public void setUp() throws Exception {
+        PackageManager packageManager = getApplicationContext().getPackageManager();
+
+        PackageInfo existingPackageInfo =
+                packageManager.getPackageInfoAsUser(
+                        EXISTING_PACKAGE_NAME, PackageManager.GET_SIGNING_CERTIFICATES, USER_ID);
+        mPackages = ImmutableList.of(existingPackageInfo);
+        mPackageManagerBackupAgent =
+                new PackageManagerBackupAgent(packageManager, mPackages, USER_ID);
+
+        mBackupData = folder.newFile("backup_data");
+        mOldState = folder.newFile("old_state");
+        mNewState = folder.newFile("new_state");
+    }
+
+    @Test
+    public void onBackup_noState_backsUpEverything() throws Exception {
+        // no setup needed
+
+        runBackupAgentOnBackup();
+
+        // key/values should be written to backup data
+        ImmutableMap<String, Optional<ByteBuffer>> keyValues = getKeyValues(mBackupData);
+        assertThat(keyValues.keySet())
+                .containsExactly(
+                        PackageManagerBackupAgent.ANCESTRAL_RECORD_KEY,
+                        PackageManagerBackupAgent.GLOBAL_METADATA_KEY,
+                        EXISTING_PACKAGE_NAME)
+                .inOrder();
+        // new state must not be empty
+        assertThat(mNewState.length()).isGreaterThan(0);
+    }
+
+    @Test
+    public void onBackup_recentState_backsUpNothing() throws Exception {
+        try (ParcelFileDescriptor oldStateDescriptor = openForWriting(mOldState)) {
+            PackageManagerBackupAgent.writeStateFile(mPackages, oldStateDescriptor);
+        }
+
+        runBackupAgentOnBackup();
+
+        // We shouldn't have written anything, but a known issue is that we always write the
+        // ancestral record version.
+        ImmutableMap<String, Optional<ByteBuffer>> keyValues = getKeyValues(mBackupData);
+        assertThat(keyValues.keySet())
+                .containsExactly(PackageManagerBackupAgent.ANCESTRAL_RECORD_KEY);
+        assertThat(mNewState.length()).isGreaterThan(0);
+        assertThat(mNewState.length()).isEqualTo(mOldState.length());
+    }
+
+    @Test
+    public void onBackup_oldState_backsUpChanges() throws Exception {
+        String uninstalledPackageName = "does.not.exist";
+        try (ParcelFileDescriptor oldStateDescriptor = openForWriting(mOldState)) {
+            PackageManagerBackupAgent.writeStateFile(
+                    ImmutableList.of(createPackage(uninstalledPackageName, 1)), oldStateDescriptor);
+        }
+
+        runBackupAgentOnBackup();
+
+        // Note that uninstalledPackageName should not exist, i.e. it did not get deleted.
+        ImmutableMap<String, Optional<ByteBuffer>> keyValues = getKeyValues(mBackupData);
+        assertThat(keyValues.keySet())
+                .containsExactly(
+                        PackageManagerBackupAgent.ANCESTRAL_RECORD_KEY, EXISTING_PACKAGE_NAME);
+        assertThat(mNewState.length()).isGreaterThan(0);
+    }
+
+    @Test
+    public void onBackup_legacyState_backsUpEverything() throws Exception {
+        String uninstalledPackageName = "does.not.exist";
+        writeLegacyStateFile(
+                mOldState,
+                ImmutableList.of(createPackage(uninstalledPackageName, 1), mPackages.getFirst()));
+
+        runBackupAgentOnBackup();
+
+        ImmutableMap<String, Optional<ByteBuffer>> keyValues = getKeyValues(mBackupData);
+        assertThat(keyValues.keySet())
+                .containsExactly(
+                        PackageManagerBackupAgent.ANCESTRAL_RECORD_KEY,
+                        PackageManagerBackupAgent.GLOBAL_METADATA_KEY,
+                        EXISTING_PACKAGE_NAME);
+        assertThat(mNewState.length()).isGreaterThan(0);
+    }
+
+    @Test
+    public void onRestore_recentBackup_restoresBackup() throws Exception {
+        runBackupAgentOnBackup();
+
+        runBackupAgentOnRestore();
+
+        assertThat(mPackageManagerBackupAgent.getRestoredPackages())
+                .containsExactly(EXISTING_PACKAGE_NAME);
+        // onRestore does not write to newState
+        assertThat(mNewState.length()).isEqualTo(0);
+    }
+
+    @Test
+    public void onRestore_legacyBackup_restoresBackup() throws Exception {
+        // A legacy backup is one without an ancestral record version. Ancestral record versions
+        // are always written however, so we'll need to delete it from the backup data before
+        // restoring.
+        runBackupAgentOnBackup();
+        deleteKeyFromBackupData(mBackupData, PackageManagerBackupAgent.ANCESTRAL_RECORD_KEY);
+
+        runBackupAgentOnRestore();
+
+        assertThat(mPackageManagerBackupAgent.getRestoredPackages())
+                .containsExactly(EXISTING_PACKAGE_NAME);
+        // onRestore does not write to newState
+        assertThat(mNewState.length()).isEqualTo(0);
+    }
+
+    private void runBackupAgentOnBackup() throws Exception {
+        try (ParcelFileDescriptor oldStateDescriptor = openForReading(mOldState);
+                ParcelFileDescriptor backupDataDescriptor = openForWriting(mBackupData);
+                ParcelFileDescriptor newStateDescriptor = openForWriting(mNewState)) {
+            mPackageManagerBackupAgent.onBackup(
+                    oldStateDescriptor,
+                    new BackupDataOutput(backupDataDescriptor.getFileDescriptor()),
+                    newStateDescriptor);
+        }
+    }
+
+    private void runBackupAgentOnRestore() throws Exception {
+        try (ParcelFileDescriptor backupDataDescriptor = openForReading(mBackupData);
+                ParcelFileDescriptor newStateDescriptor = openForWriting(mNewState)) {
+            mPackageManagerBackupAgent.onRestore(
+                    new BackupDataInput(backupDataDescriptor.getFileDescriptor()),
+                    /* appVersionCode= */ 0,
+                    newStateDescriptor);
+        }
+    }
+
+    private void deleteKeyFromBackupData(File backupData, String key) throws Exception {
+        File temporaryBackupData = folder.newFile("backup_data.tmp");
+        try (ParcelFileDescriptor inputDescriptor = openForReading(backupData);
+                ParcelFileDescriptor outputDescriptor = openForWriting(temporaryBackupData); ) {
+            BackupDataInput input = new BackupDataInput(inputDescriptor.getFileDescriptor());
+            BackupDataOutput output = new BackupDataOutput(outputDescriptor.getFileDescriptor());
+            while (input.readNextHeader()) {
+                if (input.getKey().equals(key)) {
+                    if (input.getDataSize() > 0) {
+                        input.skipEntityData();
+                    }
+                    continue;
+                }
+                output.writeEntityHeader(input.getKey(), input.getDataSize());
+                if (input.getDataSize() < 0) {
+                    input.skipEntityData();
+                } else {
+                    byte[] buf = new byte[input.getDataSize()];
+                    input.readEntityData(buf, 0, buf.length);
+                    output.writeEntityData(buf, buf.length);
+                }
+            }
+        }
+        assertThat(temporaryBackupData.renameTo(backupData)).isTrue();
+    }
+
+    private static PackageInfo createPackage(String name, int versionCode) {
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = name;
+        packageInfo.versionCodeMajor = versionCode;
+        return packageInfo;
+    }
+
+    /** This creates a legacy state file in which {@code STATE_FILE_HEADER} was not yet present. */
+    private static void writeLegacyStateFile(File stateFile, ImmutableList<PackageInfo> packages)
+            throws Exception {
+        try (ParcelFileDescriptor stateFileDescriptor = openForWriting(stateFile);
+                DataOutputStream out =
+                        new DataOutputStream(
+                                new BufferedOutputStream(
+                                        new FileOutputStream(
+                                                stateFileDescriptor.getFileDescriptor())))) {
+            out.writeUTF(PackageManagerBackupAgent.GLOBAL_METADATA_KEY);
+            out.writeInt(Build.VERSION.SDK_INT);
+            out.writeUTF(Build.VERSION.INCREMENTAL);
+
+            // now write all the app names + versions
+            for (PackageInfo pkg : packages) {
+                out.writeUTF(pkg.packageName);
+                out.writeInt(pkg.versionCode);
+            }
+            out.flush();
+        }
+    }
+
+    /**
+     * Reads the given backup data file and returns a map of key-value pairs. The value is a {@link
+     * ByteBuffer} wrapped in an {@link Optional}, where the empty {@link Optional} represents a key
+     * deletion.
+     */
+    private static ImmutableMap<String, Optional<ByteBuffer>> getKeyValues(File backupData)
+            throws Exception {
+        ImmutableMap.Builder<String, Optional<ByteBuffer>> builder = ImmutableMap.builder();
+        try (ParcelFileDescriptor backupDataDescriptor = openForReading(backupData)) {
+            BackupDataInput backupDataInput =
+                    new BackupDataInput(backupDataDescriptor.getFileDescriptor());
+            while (backupDataInput.readNextHeader()) {
+                ByteBuffer value = null;
+                if (backupDataInput.getDataSize() >= 0) {
+                    byte[] val = new byte[backupDataInput.getDataSize()];
+                    backupDataInput.readEntityData(val, 0, val.length);
+                    value = ByteBuffer.wrap(val);
+                }
+                builder.put(backupDataInput.getKey(), Optional.ofNullable(value));
+            }
+        }
+        return builder.build();
+    }
+
+    private static ParcelFileDescriptor openForWriting(File file) throws Exception {
+        return ParcelFileDescriptor.open(
+                file,
+                ParcelFileDescriptor.MODE_CREATE
+                        | ParcelFileDescriptor.MODE_TRUNCATE
+                        | ParcelFileDescriptor.MODE_WRITE_ONLY);
+    }
+
+    private static ParcelFileDescriptor openForReading(File file) throws Exception {
+        return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 6bcd778..c6a6865 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -60,10 +60,13 @@
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.Nullable;
 import android.app.AlarmManager;
 import android.app.AppGlobals;
 import android.app.job.JobInfo;
@@ -71,12 +74,15 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
 import android.net.NetworkRequest;
 import android.os.Looper;
 import android.os.PowerManager;
+import android.os.UserHandle;
 import android.provider.DeviceConfig;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccSlotMapping;
 import android.util.ArraySet;
 import android.util.EmptyArray;
 import android.util.SparseArray;
@@ -104,6 +110,9 @@
 import java.time.Instant;
 import java.time.ZoneOffset;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.Executor;
 
 public class FlexibilityControllerTest {
@@ -113,6 +122,9 @@
 
     private MockitoSession mMockingSession;
     private BroadcastReceiver mBroadcastReceiver;
+    private final SparseArray<ArraySet<String>> mCarrierPrivilegedApps = new SparseArray<>();
+    private final SparseArray<TelephonyManager.CarrierPrivilegesCallback>
+            mCarrierPrivilegedCallbacks = new SparseArray<>();
     private FlexibilityController mFlexibilityController;
     private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
     private JobStore mJobStore;
@@ -130,6 +142,10 @@
     @Mock
     private PrefetchController mPrefetchController;
     @Mock
+    private TelephonyManager mTelephonyManager;
+    @Mock
+    private IPackageManager mIPackageManager;
+    @Mock
     private PackageManager mPackageManager;
 
     @Before
@@ -138,6 +154,7 @@
                 .initMocks(this)
                 .strictness(Strictness.LENIENT)
                 .spyStatic(DeviceConfig.class)
+                .mockStatic(AppGlobals.class)
                 .mockStatic(LocalServices.class)
                 .startMocking();
         // Called in StateController constructor.
@@ -167,17 +184,23 @@
                         -> mDeviceConfigPropertiesBuilder.build())
                 .when(() -> DeviceConfig.getProperties(
                         eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER), ArgumentMatchers.<String>any()));
+        // Used in FlexibilityController.SpecialAppTracker.
+        when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION))
+                .thenReturn(true);
         //used to get jobs by UID
         mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir());
         doReturn(mJobStore).when(mJobSchedulerService).getJobStore();
         // Used in JobStatus.
-        doReturn(mock(PackageManagerInternal.class))
-                .when(() -> LocalServices.getService(PackageManagerInternal.class));
+        doReturn(mIPackageManager).when(AppGlobals::getPackageManager);
         // Freeze the clocks at a moment in time
         JobSchedulerService.sSystemClock =
                 Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
+        // Set empty set of privileged apps.
+        setSimSlotMappings(null);
+        setPowerWhitelistExceptIdle();
         // Initialize real objects.
         doReturn(Long.MAX_VALUE).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(any());
         ArgumentCaptor<BroadcastReceiver> receiverCaptor =
@@ -249,9 +272,13 @@
     }
 
     private JobStatus createJobStatus(String testTag, JobInfo.Builder job) {
+        return createJobStatus(testTag, job, SOURCE_PACKAGE);
+    }
+
+    private JobStatus createJobStatus(String testTag, JobInfo.Builder job, String sourcePackage) {
         JobInfo jobInfo = job.build();
         JobStatus js = JobStatus.createFromJobInfo(
-                jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, "FCTest", testTag);
+                jobInfo, 1000, sourcePackage, SOURCE_USER_ID, "FCTest", testTag);
         js.enqueueTime = FROZEN_TIME;
         js.setStandbyBucket(ACTIVE_INDEX);
         if (js.hasFlexibilityConstraint()) {
@@ -1084,7 +1111,6 @@
 
     @Test
     public void testAllowlistedAppBypass() {
-        setPowerWhitelistExceptIdle();
         mFlexibilityController.onSystemServicesReady();
 
         JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass",
@@ -1118,6 +1144,148 @@
     }
 
     @Test
+    public void testCarrierPrivilegedAppBypass() throws Exception {
+        mFlexibilityController.onSystemServicesReady();
+
+        final String carrier1Pkg1 = "com.test.carrier.1.pkg.1";
+        final String carrier1Pkg2 = "com.test.carrier.1.pkg.2";
+        final String carrier2Pkg = "com.test.carrier.2.pkg";
+        final String nonCarrierPkg = "com.test.normal.pkg";
+
+        setPackageUid(carrier1Pkg1, 1);
+        setPackageUid(carrier1Pkg2, 11);
+        setPackageUid(carrier2Pkg, 2);
+        setPackageUid(nonCarrierPkg, 3);
+
+        // Set the second carrier's privileged list before SIM configuration is sent to test
+        // initialization.
+        setCarrierPrivilegedAppList(2, carrier2Pkg);
+
+        UiccSlotMapping sim1 = mock(UiccSlotMapping.class);
+        UiccSlotMapping sim2 = mock(UiccSlotMapping.class);
+        doReturn(1).when(sim1).getLogicalSlotIndex();
+        doReturn(2).when(sim2).getLogicalSlotIndex();
+        setSimSlotMappings(List.of(sim1, sim2));
+
+        JobStatus jsHighC1P1 = createJobStatus("testCarrierPrivilegedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_HIGH), carrier1Pkg1);
+        JobStatus jsDefaultC1P1 = createJobStatus("testCarrierPrivilegedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), carrier1Pkg1);
+        JobStatus jsLowC1P1 = createJobStatus("testCarrierPrivilegedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_LOW), carrier1Pkg1);
+        JobStatus jsMinC1P1 = createJobStatus("testCarrierPrivilegedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_MIN), carrier1Pkg1);
+        JobStatus jsHighC1P2 = createJobStatus("testCarrierPrivilegedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_HIGH), carrier1Pkg2);
+        JobStatus jsDefaultC1P2 = createJobStatus("testCarrierPrivilegedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), carrier1Pkg2);
+        JobStatus jsLowC1P2 = createJobStatus("testCarrierPrivilegedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_LOW), carrier1Pkg2);
+        JobStatus jsMinC1P2 = createJobStatus("testCarrierPrivilegedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_MIN), carrier1Pkg2);
+        JobStatus jsHighC2P = createJobStatus("testCarrierPrivilegedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_HIGH), carrier2Pkg);
+        JobStatus jsDefaultC2P = createJobStatus("testCarrierPrivilegedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), carrier2Pkg);
+        JobStatus jsLowC2P = createJobStatus("testCarrierPrivilegedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_LOW), carrier2Pkg);
+        JobStatus jsMinC2P = createJobStatus("testCarrierPrivilegedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_MIN), carrier2Pkg);
+        JobStatus jsHighNCP = createJobStatus("testCarrierPrivilegedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_HIGH), nonCarrierPkg);
+        JobStatus jsDefaultNCP = createJobStatus("testCarrierPrivilegedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), nonCarrierPkg);
+        JobStatus jsLowNCP = createJobStatus("testCarrierPrivilegedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_LOW), nonCarrierPkg);
+        JobStatus jsMinNCP = createJobStatus("testCarrierPrivilegedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_MIN), nonCarrierPkg);
+
+        setCarrierPrivilegedAppList(1);
+        synchronized (mFlexibilityController.mLock) {
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2));
+            assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P));
+            assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP));
+        }
+
+        // Only mark the first package of carrier 1 as privileged. Only that app's jobs should
+        // be exempted.
+        setCarrierPrivilegedAppList(1, carrier1Pkg1);
+        synchronized (mFlexibilityController.mLock) {
+            assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1));
+            assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2));
+            assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P));
+            assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP));
+        }
+
+        // Add the second package of carrier 1. Both apps' jobs should be exempted.
+        setCarrierPrivilegedAppList(1, carrier1Pkg1, carrier1Pkg2);
+        synchronized (mFlexibilityController.mLock) {
+            assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1));
+            assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1));
+            assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2));
+            assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2));
+            assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P));
+            assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP));
+        }
+
+        // Remove a SIM slot. The relevant app's should no longer have exempted jobs.
+        setSimSlotMappings(List.of(sim1));
+        synchronized (mFlexibilityController.mLock) {
+            assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1));
+            assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1));
+            assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2));
+            assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP));
+        }
+    }
+
+    @Test
     public void testForegroundAppBypass() {
         JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass",
                 createJob(0).setPriority(JobInfo.PRIORITY_HIGH));
@@ -1753,6 +1921,24 @@
         }
     }
 
+    private void setCarrierPrivilegedAppList(int logicalIndex, String... packages) {
+        final ArraySet<String> packageSet = packages == null
+                ? new ArraySet<>() : new ArraySet<>(packages);
+        mCarrierPrivilegedApps.put(logicalIndex, packageSet);
+
+        TelephonyManager.CarrierPrivilegesCallback callback =
+                mCarrierPrivilegedCallbacks.get(logicalIndex);
+        if (callback != null) {
+            callback.onCarrierPrivilegesChanged(packageSet, Collections.emptySet());
+            waitForQuietModuleThread();
+        }
+    }
+
+    private void setPackageUid(final String pkgName, final int uid) throws Exception {
+        doReturn(uid).when(mIPackageManager)
+                .getPackageUid(eq(pkgName), anyLong(), eq(UserHandle.getUserId(uid)));
+    }
+
     private void setPowerWhitelistExceptIdle(String... packages) {
         doReturn(packages == null ? EmptyArray.STRING : packages)
                 .when(mDeviceIdleInternal).getFullPowerWhitelistExceptIdle();
@@ -1763,6 +1949,47 @@
         }
     }
 
+    private void setSimSlotMappings(@Nullable Collection<UiccSlotMapping> simSlotMapping) {
+        clearInvocations(mTelephonyManager);
+        final Collection<UiccSlotMapping> returnedMapping = simSlotMapping == null
+                ? Collections.emptyList() : simSlotMapping;
+        doReturn(returnedMapping).when(mTelephonyManager).getSimSlotMapping();
+        if (mBroadcastReceiver != null) {
+            final Intent intent = new Intent(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
+            mBroadcastReceiver.onReceive(mContext, intent);
+            waitForQuietModuleThread();
+        }
+        if (returnedMapping.size() > 0) {
+            ArgumentCaptor<TelephonyManager.CarrierPrivilegesCallback> callbackCaptor =
+                    ArgumentCaptor.forClass(TelephonyManager.CarrierPrivilegesCallback.class);
+            ArgumentCaptor<Integer> logicalIndexCaptor = ArgumentCaptor.forClass(Integer.class);
+
+            final int minExpectedNewRegistrations = Math.max(0,
+                    returnedMapping.size() - mCarrierPrivilegedCallbacks.size());
+            verify(mTelephonyManager, atLeast(minExpectedNewRegistrations))
+                    .registerCarrierPrivilegesCallback(
+                            logicalIndexCaptor.capture(), any(), callbackCaptor.capture());
+
+            final List<Integer> registeredIndices = logicalIndexCaptor.getAllValues();
+            final List<TelephonyManager.CarrierPrivilegesCallback> registeredCallbacks =
+                    callbackCaptor.getAllValues();
+            for (int i = 0; i < registeredIndices.size(); ++i) {
+                final int logicalIndex = registeredIndices.get(i);
+                final TelephonyManager.CarrierPrivilegesCallback callback =
+                        registeredCallbacks.get(i);
+
+                mCarrierPrivilegedCallbacks.put(logicalIndex, callback);
+
+                // The API contract promises a callback upon registration with the current list.
+                final ArraySet<String> cpApps = mCarrierPrivilegedApps.get(logicalIndex);
+                callback.onCarrierPrivilegesChanged(
+                        cpApps == null ? Collections.emptySet() : cpApps,
+                        Collections.emptySet());
+            }
+            waitForQuietModuleThread();
+        }
+    }
+
     private void setUidBias(int uid, int bias) {
         int prevBias = mJobSchedulerService.getUidBias(uid);
         doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 7bbcd50..bc7c9a5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -15,6 +15,10 @@
  */
 package com.android.server.pm;
 
+import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
+import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
+import static android.content.pm.PackageManager.FEATURE_LEANBACK;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
 import static android.os.UserManager.DISALLOW_OUTGOING_CALLS;
 import static android.os.UserManager.DISALLOW_SMS;
 import static android.os.UserManager.DISALLOW_USER_SWITCH;
@@ -41,6 +45,7 @@
 import static org.mockito.Mockito.when;
 
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.KeyguardManager;
 import android.content.Context;
@@ -48,6 +53,7 @@
 import android.content.pm.UserInfo;
 import android.multiuser.Flags;
 import android.os.PowerManager;
+import android.os.ServiceSpecificException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -76,6 +82,7 @@
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -120,11 +127,14 @@
 
     private static final String TAG_RESTRICTIONS = "restrictions";
 
+    private static final String PRIVATE_PROFILE_NAME = "TestPrivateProfile";
+
     @Rule
     public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
             .spyStatic(UserManager.class)
             .spyStatic(LocalServices.class)
             .spyStatic(SystemProperties.class)
+            .spyStatic(ActivityManager.class)
             .mockStatic(Settings.Global.class)
             .mockStatic(Settings.Secure.class)
             .build();
@@ -163,6 +173,7 @@
     @Before
     @UiThreadTest // Needed to initialize main handler
     public void setFixtures() {
+        MockitoAnnotations.initMocks(this);
         mSpiedContext = spy(mRealContext);
 
         // Called when WatchedUserStates is constructed
@@ -172,11 +183,12 @@
         when(mDeviceStorageMonitorInternal.isMemoryLow()).thenReturn(false);
         mockGetLocalService(DeviceStorageMonitorInternal.class, mDeviceStorageMonitorInternal);
         when(mSpiedContext.getSystemService(StorageManager.class)).thenReturn(mStorageManager);
-        when(mSpiedContext.getSystemService(KeyguardManager.class)).thenReturn(mKeyguardManager);
+        doReturn(mKeyguardManager).when(mSpiedContext).getSystemService(KeyguardManager.class);
         when(mSpiedContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
         mockGetLocalService(LockSettingsInternal.class, mLockSettingsInternal);
         mockGetLocalService(PackageManagerInternal.class, mPackageManagerInternal);
         doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
+        mockIsLowRamDevice(false);
 
         // Must construct UserManagerService in the UiThread
         mTestDir = new File(mRealContext.getDataDir(), "umstest");
@@ -570,9 +582,10 @@
 
     @Test
     public void testAutoLockPrivateProfile() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
         UserManagerService mSpiedUms = spy(mUms);
         UserInfo privateProfileUser =
-                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
                         USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
         Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync(
                 eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true), any(),
@@ -587,10 +600,11 @@
 
     @Test
     public void testAutoLockOnDeviceLockForPrivateProfile() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
         mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
         UserManagerService mSpiedUms = spy(mUms);
         UserInfo privateProfileUser =
-                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
                 USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
         mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
         Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync(
@@ -606,10 +620,11 @@
 
     @Test
     public void testAutoLockOnDeviceLockForPrivateProfile_keyguardUnlocked() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
         mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
         UserManagerService mSpiedUms = spy(mUms);
         UserInfo privateProfileUser =
-                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
                 USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
         mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
 
@@ -623,10 +638,11 @@
 
     @Test
     public void testAutoLockOnDeviceLockForPrivateProfile_flagDisabled() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
         mSetFlagsRule.disableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
         UserManagerService mSpiedUms = spy(mUms);
         UserInfo privateProfileUser =
-                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
                 USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
 
         mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(true);
@@ -641,13 +657,14 @@
 
     @Test
     public void testAutoLockAfterInactityForPrivateProfile() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
         mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
         UserManagerService mSpiedUms = spy(mUms);
         mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
         when(mPowerManager.isInteractive()).thenReturn(false);
 
         UserInfo privateProfileUser =
-                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
                         USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
         Mockito.doNothing().when(mSpiedUms).scheduleMessageToAutoLockPrivateSpace(
                 eq(privateProfileUser.getUserHandle().getIdentifier()), any(),
@@ -662,6 +679,7 @@
 
     @Test
     public void testSetOrUpdateAutoLockPreference_noPrivateProfile() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
         mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
 
         mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
@@ -675,8 +693,9 @@
 
     @Test
     public void testSetOrUpdateAutoLockPreference() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
         mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
-        mUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+        mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
                         USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
 
         // Set the preference to auto lock on device lock
@@ -733,6 +752,77 @@
         }
     }
 
+    @Test
+    public void testCreatePrivateProfileOnHeadlessSystemUser_shouldAllowCreation() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+        UserManagerService mSpiedUms = spy(mUms);
+        int mainUser = mSpiedUms.getMainUserId();
+        doReturn(true).when(mSpiedUms).isHeadlessSystemUserMode();
+        assertThat(mSpiedUms.canAddPrivateProfile(mainUser)).isTrue();
+        assertThat(mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(
+                PRIVATE_PROFILE_NAME, USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null)).isNotNull();
+    }
+
+    @Test
+    public void testCreatePrivateProfileOnSecondaryUser_shouldNotAllowCreation() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+        UserInfo user = mUms.createUserWithThrow(generateLongString(), USER_TYPE_FULL_SECONDARY, 0);
+        assertThat(mUms.canAddPrivateProfile(user.id)).isFalse();
+        assertThrows(ServiceSpecificException.class,
+                () -> mUms.createProfileForUserWithThrow(PRIVATE_PROFILE_NAME,
+                        USER_TYPE_PROFILE_PRIVATE, 0, user.id, null));
+    }
+
+    @Test
+    public void testCreatePrivateProfileOnAutoDevices_shouldNotAllowCreation() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+        doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_AUTOMOTIVE), anyInt());
+        int mainUser = mUms.getMainUserId();
+        assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+        assertThrows(ServiceSpecificException.class,
+                () -> mUms.createProfileForUserWithThrow(PRIVATE_PROFILE_NAME,
+                        USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
+    }
+
+    @Test
+    public void testCreatePrivateProfileOnTV_shouldNotAllowCreation() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+        doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_LEANBACK), anyInt());
+        int mainUser = mUms.getMainUserId();
+        assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+        assertThrows(ServiceSpecificException.class,
+                () -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
+                        USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
+    }
+
+    @Test
+    public void testCreatePrivateProfileOnEmbedded_shouldNotAllowCreation() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+        doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_EMBEDDED), anyInt());
+        int mainUser = mUms.getMainUserId();
+        assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+        assertThrows(ServiceSpecificException.class,
+                () -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
+                        USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
+    }
+
+    @Test
+    public void testCreatePrivateProfileOnWatch_shouldNotAllowCreation() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+        doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_WATCH), anyInt());
+        int mainUser = mUms.getMainUserId();
+        assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+        assertThrows(ServiceSpecificException.class,
+                () -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
+                        USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
+    }
+
     /**
      * Returns true if the user's XML file has Default restrictions
      * @param userId Id of the user.
@@ -800,6 +890,10 @@
                 any(), eq(android.provider.Settings.Global.USER_SWITCHER_ENABLED), anyInt()));
     }
 
+    private void mockIsLowRamDevice(boolean isLowRamDevice) {
+        doReturn(isLowRamDevice).when(ActivityManager::isLowRamDeviceStatic);
+    }
+
     private void mockDeviceDemoMode(boolean enabled) {
         doReturn(enabled ? 1 : 0).when(() -> Settings.Global.getInt(
                 any(), eq(android.provider.Settings.Global.DEVICE_DEMO_MODE), anyInt()));
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
new file mode 100644
index 0000000..e94b8ad
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
@@ -0,0 +1,56 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "RollbackPackageHealthObserverTests",
+
+    srcs: [
+        "*.java",
+    ],
+
+    static_libs: [
+        "androidx.test.runner",
+        "mockito-target-extended-minus-junit4",
+        "services.core",
+        "truth",
+        "flag-junit",
+    ],
+
+    libs: [
+        "android.test.mock",
+        "android.test.base",
+        "android.test.runner",
+    ],
+
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
+
+    certificate: "platform",
+    platform_apis: true,
+    test_suites: [
+        "device-tests",
+        "automotive-tests",
+    ],
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidManifest.xml b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidManifest.xml
new file mode 100644
index 0000000..c52dbde
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.server.rollback">
+
+    <uses-sdk android:targetSdkVersion="35" />
+
+    <application android:testOnly="true"
+                 android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.server.rollback"
+        android:label="Frameworks Rollback Package Health Observer test" />
+</manifest>
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml
new file mode 100644
index 0000000..635183c
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Runs Rollback Package Health Observer Tests.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="RollbackPackageHealthObserverTests.apk" />
+    </target_preparer>
+
+    <option name="test-tag" value="RollbackPackageHealthObserverTests" />
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.server.rollback" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
index d6e246f..6e41685 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
@@ -25,6 +25,8 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -42,6 +44,7 @@
 import android.crashrecovery.flags.Flags;
 import android.os.Handler;
 import android.os.MessageQueue;
+import android.os.SystemProperties;
 import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -65,6 +68,7 @@
 import org.mockito.stubbing.Answer;
 
 import java.time.Duration;
+import java.util.HashMap;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -90,7 +94,7 @@
 
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
+    private HashMap<String, String> mSystemSettingsMap;
     private MockitoSession mSession;
     private static final String APP_A = "com.package.a";
     private static final String APP_B = "com.package.b";
@@ -99,6 +103,9 @@
     private static final long VERSION_CODE_2 = 2L;
     private static final String LOG_TAG = "RollbackPackageHealthObserverTest";
 
+    private static final String PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG =
+            "persist.device_config.configuration.disable_high_impact_rollback";
+
     private SystemConfig mSysConfig;
 
     @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
@@ -111,11 +118,34 @@
                 .initMocks(this)
                 .strictness(Strictness.LENIENT)
                 .spyStatic(PackageWatchdog.class)
+                .spyStatic(SystemProperties.class)
                 .startMocking();
+        mSystemSettingsMap = new HashMap<>();
 
         // Mock PackageWatchdog
         doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog)
                 .when(() -> PackageWatchdog.getInstance(mMockContext));
+
+        // Mock SystemProperties setter and various getters
+        doAnswer((Answer<Void>) invocationOnMock -> {
+                    String key = invocationOnMock.getArgument(0);
+                    String value = invocationOnMock.getArgument(1);
+
+                    mSystemSettingsMap.put(key, value);
+                    return null;
+                }
+        ).when(() -> SystemProperties.set(anyString(), anyString()));
+
+        doAnswer((Answer<Boolean>) invocationOnMock -> {
+                    String key = invocationOnMock.getArgument(0);
+                    boolean defaultValue = invocationOnMock.getArgument(1);
+
+                    String storedValue = mSystemSettingsMap.get(key);
+                    return storedValue == null ? defaultValue : Boolean.parseBoolean(storedValue);
+                }
+        ).when(() -> SystemProperties.getBoolean(anyString(), anyBoolean()));
+
+        SystemProperties.set(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, Boolean.toString(false));
     }
 
     @After
@@ -609,6 +639,32 @@
                 observer.onBootLoop(1));
     }
 
+    @Test
+    public void onBootLoop_impactLevelHighDisableHighImpactRollback_onePackage()
+            throws PackageManager.NameNotFoundException {
+        mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+        SystemProperties.set(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, Boolean.toString(true));
+        VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+        VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+        PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+                null, null, false, false,
+                null);
+        RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+                false, null, 111,
+                PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+        RollbackPackageHealthObserver observer =
+                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+
+        when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+        // Make the rollbacks available
+        when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+        assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+                observer.onBootLoop(1));
+    }
+
     /**
      * When the rollback impact level is manual only return user impact level 0. (User impact level
      * 0 is ignored by package watchdog)
@@ -924,6 +980,50 @@
         assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId2));
     }
 
+    /**
+     * Don't roll back if kill switch is enabled.
+     */
+    @Test
+    public void executeBootLoopMitigation_impactLevelHighKillSwitchTrue_rollbackHigh()
+            throws PackageManager.NameNotFoundException {
+        mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+        SystemProperties.set(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, Boolean.toString(true));
+        int rollbackId1 = 1;
+        VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+        VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+        PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+                null, null , false, false,
+                null);
+        RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoB),
+                false, null, 111,
+                PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+        int rollbackId2 = 2;
+        VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+        VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+        PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+                null, null , false, false,
+                null);
+        RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoA),
+                false, null, 111,
+                PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+        RollbackPackageHealthObserver observer =
+                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+        ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+        when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+        // Make the rollbacks available
+        when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+                List.of(rollbackInfo1, rollbackInfo2));
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+        observer.executeBootLoopMitigation(1);
+        waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+        verify(mRollbackManager, never()).commitRollback(
+                argument.capture(), any(), any());
+    }
+
     private void waitForIdleHandler(Handler handler, Duration timeout) {
         final MessageQueue queue = handler.getLooper().getQueue();
         final CountDownLatch latch = new CountDownLatch(1);
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
index e42bdad..4ac4484 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
@@ -1,7 +1,7 @@
 {
-  "postsubmit": [
+  "presubmit": [
     {
-      "name": "FrameworksMockingServicesTests",
+      "name": "RollbackPackageHealthObserverTests",
       "options": [
         {
           "include-filter": "com.android.server.rollback"
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
new file mode 100644
index 0000000..7ecc7fd
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
@@ -0,0 +1,640 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wallpaper;
+
+import static android.app.WallpaperManager.LANDSCAPE;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
+import static android.app.WallpaperManager.PORTRAIT;
+import static android.app.WallpaperManager.SQUARE_LANDSCAPE;
+import static android.app.WallpaperManager.SQUARE_PORTRAIT;
+import static android.app.WallpaperManager.getOrientation;
+import static android.app.WallpaperManager.getRotatedOrientation;
+
+import static com.android.window.flags.Flags.FLAG_MULTI_CROP;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.util.SparseArray;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Unit tests for the most important helpers of {@link WallpaperCropper}, in particular
+ * {@link WallpaperCropper#getCrop(Point, Point, SparseArray, boolean)}.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@RequiresFlagsEnabled(FLAG_MULTI_CROP)
+public class WallpaperCropperTest {
+
+    @Mock
+    private WallpaperDisplayHelper mWallpaperDisplayHelper;
+    private WallpaperCropper mWallpaperCropper;
+
+    private static final Point PORTRAIT_ONE = new Point(500, 800);
+    private static final Point PORTRAIT_TWO = new Point(400, 1000);
+    private static final Point PORTRAIT_THREE = new Point(2000, 800);
+    private static final Point PORTRAIT_FOUR = new Point(1600, 1000);
+
+    private static final Point SQUARE_PORTRAIT_ONE = new Point(1000, 800);
+    private static final Point SQUARE_LANDSCAPE_ONE = new Point(800, 1000);
+
+    /**
+     * Common device: a single screen of portrait/landscape orientation
+     */
+    private static final List<Point> STANDARD_DISPLAY = List.of(PORTRAIT_ONE);
+
+    /** 1: folded: portrait, unfolded: square with w < h */
+    private static final List<Point> FOLDABLE_ONE = List.of(PORTRAIT_ONE, SQUARE_PORTRAIT_ONE);
+
+    /** 2: folded: portrait, unfolded: square with w > h */
+    private static final List<Point> FOLDABLE_TWO = List.of(PORTRAIT_TWO, SQUARE_LANDSCAPE_ONE);
+
+    /** 3: folded: square with w < h, unfolded: portrait */
+    private static final List<Point> FOLDABLE_THREE = List.of(SQUARE_PORTRAIT_ONE, PORTRAIT_THREE);
+
+    /** 4: folded: square with w > h, unfolded: portrait */
+    private static final List<Point> FOLDABLE_FOUR = List.of(SQUARE_LANDSCAPE_ONE, PORTRAIT_FOUR);
+
+    /**
+     * List of different sets of displays for foldable devices. Foldable devices have two displays:
+     * a folded (smaller) unfolded (larger).
+     */
+    private static final List<List<Point>> ALL_FOLDABLE_DISPLAYS = List.of(
+            FOLDABLE_ONE, FOLDABLE_TWO, FOLDABLE_THREE, FOLDABLE_FOUR);
+
+    private SparseArray<Point> mDisplaySizes = new SparseArray<>();
+    private int mFolded = ORIENTATION_UNKNOWN;
+    private int mFoldedRotated = ORIENTATION_UNKNOWN;
+    private int mUnfolded = ORIENTATION_UNKNOWN;
+    private int mUnfoldedRotated = ORIENTATION_UNKNOWN;
+
+    private static final List<Integer> ALL_MODES = List.of(
+            WallpaperCropper.ADD, WallpaperCropper.REMOVE, WallpaperCropper.BALANCE);
+
+    @Before
+    public void setUp() {
+        initMocks(this);
+        mWallpaperCropper = new WallpaperCropper(mWallpaperDisplayHelper);
+    }
+
+    private void setUpWithDisplays(List<Point> displaySizes) {
+        mDisplaySizes = new SparseArray<>();
+        displaySizes.forEach(size -> {
+            mDisplaySizes.put(getOrientation(size), size);
+            Point rotated = new Point(size.y, size.x);
+            mDisplaySizes.put(getOrientation(rotated), rotated);
+        });
+        when(mWallpaperDisplayHelper.getDefaultDisplaySizes()).thenReturn(mDisplaySizes);
+        if (displaySizes.size() == 2) {
+            Point largestDisplay = displaySizes.stream().max(
+                    Comparator.comparingInt(p -> p.x * p.y)).get();
+            Point smallestDisplay = displaySizes.stream().min(
+                    Comparator.comparingInt(p -> p.x * p.y)).get();
+            mUnfolded = getOrientation(largestDisplay);
+            mFolded = getOrientation(smallestDisplay);
+            mUnfoldedRotated = getRotatedOrientation(mUnfolded);
+            mFoldedRotated = getRotatedOrientation(mFolded);
+        }
+        doAnswer(invocation -> getFoldedOrientation(invocation.getArgument(0)))
+                .when(mWallpaperDisplayHelper).getFoldedOrientation(anyInt());
+        doAnswer(invocation -> getUnfoldedOrientation(invocation.getArgument(0)))
+                .when(mWallpaperDisplayHelper).getUnfoldedOrientation(anyInt());
+    }
+
+    private int getFoldedOrientation(int orientation) {
+        if (orientation == ORIENTATION_UNKNOWN) return ORIENTATION_UNKNOWN;
+        if (orientation == mUnfolded) return mFolded;
+        if (orientation == mUnfoldedRotated) return mFoldedRotated;
+        return ORIENTATION_UNKNOWN;
+    }
+
+    private int getUnfoldedOrientation(int orientation) {
+        if (orientation == ORIENTATION_UNKNOWN) return ORIENTATION_UNKNOWN;
+        if (orientation == mFolded) return mUnfolded;
+        if (orientation == mFoldedRotated) return mUnfoldedRotated;
+        return ORIENTATION_UNKNOWN;
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#noParallax} successfully removes the parallax in a simple
+     * case, removing the right or left part depending on the "rtl" argument.
+     */
+    @Test
+    public void testNoParallax_noScale() {
+        Point displaySize = new Point(1000, 1000);
+        Point bitmapSize = new Point(1200, 1000);
+        Point expectedCropSize = new Point(1000, 1000);
+        Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+        assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ false))
+                .isEqualTo(leftOf(crop, expectedCropSize));
+        assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ true))
+                .isEqualTo(rightOf(crop, expectedCropSize));
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#noParallax} correctly takes zooming into account.
+     */
+    @Test
+    public void testNoParallax_withScale() {
+        Point displaySize = new Point(1000, 1000);
+        Point bitmapSize = new Point(600, 500);
+        Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+        Point expectedCropSize = new Point(500, 500);
+        assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ false))
+                .isEqualTo(leftOf(crop, expectedCropSize));
+        assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ true))
+                .isEqualTo(rightOf(crop, expectedCropSize));
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#noParallax} correctly removes parallax when the image is
+     * cropped, i.e. when the crop rectangle is not the full bitmap.
+     */
+    @Test
+    public void testNoParallax_withScaleAndCrop() {
+        Point displaySize = new Point(1000, 1000);
+        Point bitmapSize = new Point(2000, 2000);
+        Rect crop = new Rect(300, 1000, 900, 1500);
+        Point expectedCropSize = new Point(500, 500);
+        assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ false))
+                .isEqualTo(leftOf(crop, expectedCropSize));
+        assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ true))
+                .isEqualTo(rightOf(crop, expectedCropSize));
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getAdjustedCrop} does nothing when the crop has the same
+     * width/height ratio than the screen.
+     */
+    @Test
+    public void testGetAdjustedCrop_noOp() {
+        Point displaySize = new Point(1000, 1000);
+
+        for (Point bitmapSize: List.of(
+                new Point(1000, 1000),
+                new Point(2000, 2000),
+                new Point(500, 500))) {
+            for (Rect crop: List.of(
+                    new Rect(0, 0, bitmapSize.x, bitmapSize.y),
+                    new Rect(100, 200, bitmapSize.x - 100, bitmapSize.y))) {
+                for (int mode: ALL_MODES) {
+                    for (boolean rtl: List.of(true, false)) {
+                        for (boolean parallax: List.of(true, false)) {
+                            assertThat(WallpaperCropper.getAdjustedCrop(
+                                    crop, bitmapSize, displaySize, parallax, rtl, mode))
+                                    .isEqualTo(crop);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with parallax = true,
+     * does not keep more width than needed for {@link WallpaperCropper#MAX_PARALLAX}.
+     */
+    @Test
+    public void testGetAdjustedCrop_tooMuchParallax() {
+        Point displaySize = new Point(1000, 1000);
+        int tooLargeWidth = (int) (displaySize.x * (1 + 2 * WallpaperCropper.MAX_PARALLAX));
+        Point bitmapSize = new Point(tooLargeWidth, 1000);
+        Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+        int expectedWidth = (int) (displaySize.x * (1 + WallpaperCropper.MAX_PARALLAX));
+        Point expectedCropSize = new Point(expectedWidth, 1000);
+        for (int mode: ALL_MODES) {
+            assertThat(WallpaperCropper.getAdjustedCrop(
+                    crop, bitmapSize, displaySize, true, false, mode))
+                    .isEqualTo(leftOf(crop, expectedCropSize));
+            assertThat(WallpaperCropper.getAdjustedCrop(
+                    crop, bitmapSize, displaySize, true, true, mode))
+                    .isEqualTo(rightOf(crop, expectedCropSize));
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with parallax = true,
+     * does not remove parallax if the parallax is below {@link WallpaperCropper#MAX_PARALLAX}.
+     */
+    @Test
+    public void testGetAdjustedCrop_acceptableParallax() {
+        Point displaySize = new Point(1000, 1000);
+        List<Integer> acceptableWidths = List.of(displaySize.x,
+                (int) (displaySize.x * (1 + 0.5 * WallpaperCropper.MAX_PARALLAX)),
+                (int) (displaySize.x * (1 + 0.9 * WallpaperCropper.MAX_PARALLAX)),
+                (int) (displaySize.x * (1 + 1.0 * WallpaperCropper.MAX_PARALLAX)));
+        for (int acceptableWidth: acceptableWidths) {
+            Point bitmapSize = new Point(acceptableWidth, 1000);
+            Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+            for (int mode : ALL_MODES) {
+                for (boolean rtl : List.of(false, true)) {
+                    assertThat(WallpaperCropper.getAdjustedCrop(
+                            crop, bitmapSize, displaySize, true, rtl, mode))
+                            .isEqualTo(crop);
+                }
+            }
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with
+     * {@link WallpaperCropper#ADD}, correctly enlarges the crop to match the display dimensions,
+     * and adds content to the crop by an equal amount on both sides when possible.
+     */
+    @Test
+    public void testGetAdjustedCrop_add() {
+        Point displaySize = new Point(1000, 1000);
+        Point bitmapSize = new Point(1000, 1000);
+
+        List<Rect> crops = List.of(
+                new Rect(0, 0, 900, 1000),
+                new Rect(0, 0, 1000, 900),
+                new Rect(0, 0, 400, 500),
+                new Rect(500, 600, 1000, 1000));
+
+        List<Rect> expectedAdjustedCrops = List.of(
+                new Rect(0, 0, 1000, 1000),
+                new Rect(0, 0, 1000, 1000),
+                new Rect(0, 0, 500, 500),
+                new Rect(500, 500, 1000, 1000));
+
+        for (int i = 0; i < crops.size(); i++) {
+            Rect crop = crops.get(i);
+            Rect expectedCrop = expectedAdjustedCrops.get(i);
+            for (boolean rtl: List.of(false, true)) {
+                assertThat(WallpaperCropper.getAdjustedCrop(
+                        crop, bitmapSize, displaySize, false, rtl, WallpaperCropper.ADD))
+                        .isEqualTo(expectedCrop);
+            }
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with
+     * {@link WallpaperCropper#REMOVE}, correctly shrinks the crop to match the display dimensions,
+     * and removes content by an equal amount on both sides.
+     */
+    @Test
+    public void testGetAdjustedCrop_remove() {
+        Point displaySize = new Point(1000, 1000);
+        Point bitmapSize = new Point(1500, 1500);
+
+        List<Rect> crops = List.of(
+                new Rect(50, 0, 1150, 1000),
+                new Rect(0, 50, 1000, 1150));
+
+        Point expectedCropSize = new Point(1000, 1000);
+
+        for (Rect crop: crops) {
+            for (boolean rtl : List.of(false, true)) {
+                assertThat(WallpaperCropper.getAdjustedCrop(
+                        crop, bitmapSize, displaySize, false, rtl, WallpaperCropper.REMOVE))
+                        .isEqualTo(centerOf(crop, expectedCropSize));
+            }
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with
+     * {@link WallpaperCropper#BALANCE}, gives an adjusted crop with the same center and same number
+     * of pixels when possible.
+     */
+    @Test
+    public void testGetAdjustedCrop_balance() {
+        Point displaySize = new Point(500, 1000);
+        Point transposedDisplaySize = new Point(1000, 500);
+        Point bitmapSize = new Point(1000, 1000);
+
+        List<Rect> crops = List.of(
+                new Rect(0, 250, 1000, 750),
+                new Rect(100, 0, 300, 100));
+
+        List<Rect> expectedAdjustedCrops = List.of(
+                new Rect(250, 0, 750, 1000),
+                new Rect(150, 0, 250, 200));
+
+        for (int i = 0; i < crops.size(); i++) {
+            Rect crop = crops.get(i);
+            Rect expected = expectedAdjustedCrops.get(i);
+            assertThat(WallpaperCropper.getAdjustedCrop(
+                    crop, bitmapSize, displaySize, false, false, WallpaperCropper.BALANCE))
+                    .isEqualTo(expected);
+
+            Rect transposedCrop = new Rect(crop.top, crop.left, crop.bottom, crop.right);
+            Rect expectedTransposed = new Rect(
+                    expected.top, expected.left, expected.bottom, expected.right);
+            assertThat(WallpaperCropper.getAdjustedCrop(transposedCrop, bitmapSize,
+                    transposedDisplaySize, false, false, WallpaperCropper.BALANCE))
+                    .isEqualTo(expectedTransposed);
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getCrop} follows a simple centre-align strategy when
+     * no suggested crops are provided.
+     */
+    @Test
+    public void testGetCrop_noSuggestedCrops_centersWallpaper() {
+        setUpWithDisplays(STANDARD_DISPLAY);
+        Point bitmapSize = new Point(800, 1000);
+        Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+        SparseArray<Rect> suggestedCrops = new SparseArray<>();
+
+        List<Point> displaySizes = List.of(
+                new Point(500, 1000),
+                new Point(1000, 500));
+        List<Point> expectedCropSizes = List.of(
+                new Point(500, 1000),
+                new Point(800, 400));
+
+        for (int i = 0; i < displaySizes.size(); i++) {
+            Point displaySize = displaySizes.get(i);
+            Point expectedCropSize = expectedCropSizes.get(i);
+            for (boolean rtl : List.of(false, true)) {
+                assertThat(mWallpaperCropper.getCrop(
+                        displaySize, bitmapSize, suggestedCrops, rtl))
+                        .isEqualTo(centerOf(bitmapRect, expectedCropSize));
+            }
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getCrop} reuses a suggested crop of the same orientation
+     * as the display if possible, and does not remove additional width for parallax,
+     * but adds width if necessary.
+     */
+    @Test
+    public void testGetCrop_hasSuggestedCrop() {
+        setUpWithDisplays(STANDARD_DISPLAY);
+        Point bitmapSize = new Point(800, 1000);
+        SparseArray<Rect> suggestedCrops = new SparseArray<>();
+        suggestedCrops.put(PORTRAIT, new Rect(0, 0, 400, 800));
+        for (int otherOrientation: List.of(LANDSCAPE, SQUARE_LANDSCAPE, SQUARE_PORTRAIT)) {
+            suggestedCrops.put(otherOrientation, new Rect(0, 0, 10, 10));
+        }
+
+        for (boolean rtl : List.of(false, true)) {
+            assertThat(mWallpaperCropper.getCrop(
+                    new Point(300, 800), bitmapSize, suggestedCrops, rtl))
+                    .isEqualTo(suggestedCrops.get(PORTRAIT));
+            assertThat(mWallpaperCropper.getCrop(
+                    new Point(500, 800), bitmapSize, suggestedCrops, rtl))
+                    .isEqualTo(new Rect(0, 0, 500, 800));
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getCrop}, if there is no suggested crop of the same
+     * orientation as the display, reuses a suggested crop of the rotated orientation if possible,
+     * and preserves the center and number of pixels of the crop if possible.
+     * <p>
+     * To simplify, in this test case all crops have the same size as the display (no zoom)
+     * and are at the center of the image. Also the image is large enough to preserver the number
+     * of pixels (no additional zoom required).
+     */
+    @Test
+    public void testGetCrop_hasRotatedSuggestedCrop() {
+        setUpWithDisplays(STANDARD_DISPLAY);
+        Point bitmapSize = new Point(2000, 1800);
+        Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+        SparseArray<Rect> suggestedCrops = new SparseArray<>();
+        Point portrait = PORTRAIT_ONE;
+        Point landscape = new Point(PORTRAIT_ONE.y, PORTRAIT_ONE.x);
+        Point squarePortrait = SQUARE_PORTRAIT_ONE;
+        Point squareLandscape = new Point(SQUARE_PORTRAIT_ONE.y, SQUARE_PORTRAIT_ONE.y);
+        suggestedCrops.put(PORTRAIT, centerOf(bitmapRect, portrait));
+        suggestedCrops.put(SQUARE_LANDSCAPE, centerOf(bitmapRect, squareLandscape));
+        for (boolean rtl : List.of(false, true)) {
+            assertThat(mWallpaperCropper.getCrop(
+                    landscape, bitmapSize, suggestedCrops, rtl))
+                    .isEqualTo(centerOf(bitmapRect, landscape));
+
+            assertThat(mWallpaperCropper.getCrop(
+                    squarePortrait, bitmapSize, suggestedCrops, rtl))
+                    .isEqualTo(centerOf(bitmapRect, squarePortrait));
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getCrop}, when asked for a folded crop with a suggested
+     * crop only for the relative unfolded orientation, creates the folded crop at the center of the
+     * unfolded crop, by removing content on two sides to match the folded screen dimensions.
+     * <p>
+     * To simplify, in this test case all crops have the same size as the display (no zoom)
+     * and are at the center of the image.
+     */
+    @Test
+    public void testGetCrop_hasUnfoldedSuggestedCrop() {
+        for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+            setUpWithDisplays(displaySizes);
+            Point bitmapSize = new Point(2000, 2400);
+            Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+
+            Point largestDisplay = displaySizes.stream().max(
+                    Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+            int unfoldedOne = getOrientation(largestDisplay);
+            int unfoldedTwo = getRotatedOrientation(unfoldedOne);
+            Rect unfoldedCropOne = centerOf(bitmapRect, mDisplaySizes.get(unfoldedOne));
+            Rect unfoldedCropTwo = centerOf(bitmapRect, mDisplaySizes.get(unfoldedTwo));
+            SparseArray<Rect> suggestedCrops = new SparseArray<>();
+            suggestedCrops.put(unfoldedOne, unfoldedCropOne);
+            suggestedCrops.put(unfoldedTwo, unfoldedCropTwo);
+
+            int foldedOne = getFoldedOrientation(unfoldedOne);
+            int foldedTwo = getFoldedOrientation(unfoldedTwo);
+            Point foldedDisplayOne = mDisplaySizes.get(foldedOne);
+            Point foldedDisplayTwo = mDisplaySizes.get(foldedTwo);
+
+            for (boolean rtl : List.of(false, true)) {
+                assertThat(mWallpaperCropper.getCrop(
+                        foldedDisplayOne, bitmapSize, suggestedCrops, rtl))
+                        .isEqualTo(centerOf(unfoldedCropOne, foldedDisplayOne));
+
+                assertThat(mWallpaperCropper.getCrop(
+                        foldedDisplayTwo, bitmapSize, suggestedCrops, rtl))
+                        .isEqualTo(centerOf(unfoldedCropTwo, foldedDisplayTwo));
+            }
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getCrop}, when asked for an unfolded crop with a suggested
+     * crop only for the relative folded orientation, creates the unfolded crop with the same center
+     * as the folded crop, by adding content on two sides to match the unfolded screen dimensions.
+     * <p>
+     * To simplify, in this test case all crops have the same size as the display (no zoom) and are
+     * at the center of the image. Also the image is large enough to add content.
+     */
+    @Test
+    public void testGetCrop_hasFoldedSuggestedCrop() {
+        for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+            setUpWithDisplays(displaySizes);
+            Point bitmapSize = new Point(2000, 2000);
+            Rect bitmapRect = new Rect(0, 0, 2000, 2000);
+
+            Point smallestDisplay = displaySizes.stream().min(
+                    Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+            int foldedOne = getOrientation(smallestDisplay);
+            int foldedTwo = getRotatedOrientation(foldedOne);
+            Point foldedDisplayOne = mDisplaySizes.get(foldedOne);
+            Point foldedDisplayTwo = mDisplaySizes.get(foldedTwo);
+            Rect foldedCropOne = centerOf(bitmapRect, foldedDisplayOne);
+            Rect foldedCropTwo = centerOf(bitmapRect, foldedDisplayTwo);
+            SparseArray<Rect> suggestedCrops = new SparseArray<>();
+            suggestedCrops.put(foldedOne, foldedCropOne);
+            suggestedCrops.put(foldedTwo, foldedCropTwo);
+
+            int unfoldedOne = getUnfoldedOrientation(foldedOne);
+            int unfoldedTwo = getUnfoldedOrientation(foldedTwo);
+            Point unfoldedDisplayOne = mDisplaySizes.get(unfoldedOne);
+            Point unfoldedDisplayTwo = mDisplaySizes.get(unfoldedTwo);
+
+            for (boolean rtl : List.of(false, true)) {
+                assertThat(centerOf(mWallpaperCropper.getCrop(
+                        unfoldedDisplayOne, bitmapSize, suggestedCrops, rtl), foldedDisplayOne))
+                        .isEqualTo(foldedCropOne);
+
+                assertThat(centerOf(mWallpaperCropper.getCrop(
+                        unfoldedDisplayTwo, bitmapSize, suggestedCrops, rtl), foldedDisplayTwo))
+                        .isEqualTo(foldedCropTwo);
+            }
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getCrop}, when asked for an folded crop with a suggested
+     * crop only for the rotated unfolded orientation, creates the folded crop from that crop by
+     * combining a rotate + fold operation. The folded crop should have less pixels than the
+     * unfolded crop due to the fold operation which removes content on both sides of the image.
+     * <p>
+     * To simplify, in this test case all crops have the same size as the display (no zoom)
+     * and are at the center of the image.
+     */
+    @Test
+    public void testGetCrop_hasRotatedUnfoldedSuggestedCrop() {
+        for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+            setUpWithDisplays(displaySizes);
+            Point bitmapSize = new Point(2000, 2000);
+            Rect bitmapRect = new Rect(0, 0, 2000, 2000);
+            Point largestDisplay = displaySizes.stream().max(
+                    Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+            int unfoldedOne = getOrientation(largestDisplay);
+            int unfoldedTwo = getRotatedOrientation(unfoldedOne);
+            for (int unfolded: List.of(unfoldedOne, unfoldedTwo)) {
+                Rect unfoldedCrop = centerOf(bitmapRect, mDisplaySizes.get(unfolded));
+                int rotatedUnfolded = getRotatedOrientation(unfolded);
+                Rect rotatedUnfoldedCrop = centerOf(bitmapRect, mDisplaySizes.get(rotatedUnfolded));
+                SparseArray<Rect> suggestedCrops = new SparseArray<>();
+                suggestedCrops.put(unfolded, unfoldedCrop);
+                int rotatedFolded = getFoldedOrientation(rotatedUnfolded);
+                Point rotatedFoldedDisplay = mDisplaySizes.get(rotatedFolded);
+
+                for (boolean rtl : List.of(false, true)) {
+                    assertThat(mWallpaperCropper.getCrop(
+                            rotatedFoldedDisplay, bitmapSize, suggestedCrops, rtl))
+                            .isEqualTo(centerOf(rotatedUnfoldedCrop, rotatedFoldedDisplay));
+                }
+            }
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getCrop}, when asked for an unfolded crop with a suggested
+     * crop only for the rotated folded orientation, creates the unfolded crop from that crop by
+     * combining a rotate + unfold operation. The unfolded crop should have more pixels than the
+     * folded crop due to the unfold operation which adds content on two sides of the image.
+     * <p>
+     * To simplify, in this test case all crops have the same size as the display (no zoom)
+     * and are centered inside the image. Also the image is large enough to add content.
+     */
+    @Test
+    public void testGetCrop_hasRotatedFoldedSuggestedCrop() {
+        for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+            setUpWithDisplays(displaySizes);
+            Point bitmapSize = new Point(2000, 2000);
+            Rect bitmapRect = new Rect(0, 0, 2000, 2000);
+
+            Point smallestDisplay = displaySizes.stream().min(
+                    Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+            int foldedOne = getOrientation(smallestDisplay);
+            int foldedTwo = getRotatedOrientation(foldedOne);
+            for (int folded: List.of(foldedOne, foldedTwo)) {
+                Rect foldedCrop = centerOf(bitmapRect, mDisplaySizes.get(folded));
+                SparseArray<Rect> suggestedCrops = new SparseArray<>();
+                suggestedCrops.put(folded, foldedCrop);
+                int rotatedFolded = getRotatedOrientation(folded);
+                int rotatedUnfolded = getUnfoldedOrientation(rotatedFolded);
+                Point rotatedFoldedDisplay = mDisplaySizes.get(rotatedFolded);
+                Rect rotatedFoldedCrop = centerOf(bitmapRect, rotatedFoldedDisplay);
+                Point rotatedUnfoldedDisplay = mDisplaySizes.get(rotatedUnfolded);
+
+                for (boolean rtl : List.of(false, true)) {
+                    Rect rotatedUnfoldedCrop = mWallpaperCropper.getCrop(
+                            rotatedUnfoldedDisplay, bitmapSize, suggestedCrops, rtl);
+                    assertThat(centerOf(rotatedUnfoldedCrop, rotatedFoldedDisplay))
+                            .isEqualTo(rotatedFoldedCrop);
+                }
+            }
+        }
+    }
+
+    private static Rect centerOf(Rect container, Point point) {
+        checkSubset(container, point);
+        int diffWidth = container.width() - point.x;
+        int diffHeight = container.height() - point.y;
+        int startX = container.left + diffWidth / 2;
+        int startY = container.top + diffHeight / 2;
+        return new Rect(startX, startY, startX + point.x, startY + point.y);
+    }
+
+    private static Rect leftOf(Rect container, Point point) {
+        Rect result = centerOf(container, point);
+        result.offset(container.left - result.left, 0);
+        return result;
+    }
+
+    private static Rect rightOf(Rect container, Point point) {
+        checkSubset(container, point);
+        Rect result = centerOf(container, point);
+        result.offset(container.right - result.right, 0);
+        return result;
+    }
+
+    private static void checkSubset(Rect container, Point point) {
+        if (container.width() < point.x || container.height() < point.y) {
+            throw new IllegalArgumentException();
+        }
+    }
+}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 3743483..37967fa 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -85,6 +85,7 @@
         "ravenwood-junit",
         "net_flags_lib",
         "CtsVirtualDeviceCommonLib",
+        "com_android_server_accessibility_flags_lib",
     ],
 
     libs: [
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 5e5181b..0089d4c 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -109,7 +109,6 @@
     <uses-permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES" />
     <uses-permission android:name="android.permission.ACCESS_CONTEXT_HUB" />
     <uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" />
-    <uses-permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
     <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
     <uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
 
diff --git a/services/tests/servicestests/src/com/android/server/DynamicSystemServiceTest.java b/services/tests/servicestests/src/com/android/server/DynamicSystemServiceTest.java
index d367f71..4a8fe41 100644
--- a/services/tests/servicestests/src/com/android/server/DynamicSystemServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/DynamicSystemServiceTest.java
@@ -19,7 +19,8 @@
 import android.os.ServiceManager;
 import android.os.image.IDynamicSystemService;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.filters.LargeTest;
 
 public class DynamicSystemServiceTest extends AndroidTestCase {
     private static final String TAG = "DynamicSystemServiceTests";
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index e168596..16d05b1 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -214,7 +214,7 @@
                 .thenReturn(mA11yWindowInfos.get(0));
         when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(PIP_WINDOWID))
                 .thenReturn(mA11yWindowInfos.get(1));
-        when(mMockA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked(USER_ID,
+        when(mMockA11yWindowManager.getDisplayIdByUserIdAndWindowId(USER_ID,
             WINDOWID_ONSECONDDISPLAY)).thenReturn(SECONDARY_DISPLAY_ID);
         when(mMockA11yWindowManager.getWindowListLocked(SECONDARY_DISPLAY_ID))
             .thenReturn(mA11yWindowInfosOnSecondDisplay);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterInputTest.kt b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterInputTest.kt
index 5c8c6bb..e6c94c5 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterInputTest.kt
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterInputTest.kt
@@ -25,6 +25,7 @@
 import android.view.DisplayAdjustments
 import android.view.DisplayInfo
 import android.view.IInputFilterHost
+import android.view.InputDevice.SOURCE_JOYSTICK
 import android.view.InputDevice.SOURCE_STYLUS
 import android.view.InputDevice.SOURCE_TOUCHSCREEN
 import android.view.InputEvent
@@ -130,6 +131,8 @@
     private val fromTouchScreen = allOf(withDeviceId(touchDeviceId), withSource(SOURCE_TOUCHSCREEN))
     private val stylusDeviceId = 2
     private val fromStylus = allOf(withDeviceId(stylusDeviceId), withSource(STYLUS_SOURCE))
+    private val joystickDeviceId = 3
+    private val fromJoystick = allOf(withDeviceId(joystickDeviceId), withSource(SOURCE_JOYSTICK))
 
     @Before
     fun setUp() {
@@ -457,6 +460,23 @@
         verifier.assertNoEvents()
     }
 
+    /**
+     * Send some joystick events and ensure they pass through normally.
+     */
+    @Test
+    fun testJoystickEvents() {
+        enableFeatures(ALL_A11Y_FEATURES)
+
+        sendJoystickEvent()
+        verifier.assertReceivedMotion(fromJoystick)
+
+        sendJoystickEvent()
+        verifier.assertReceivedMotion(fromJoystick)
+
+        sendJoystickEvent()
+        verifier.assertReceivedMotion(fromJoystick)
+    }
+
     private fun createStubDisplay(displayId: Int, displayInfo: DisplayInfo): Display {
         val display = Display(DisplayManagerGlobal.getInstance(), displayId,
             displayInfo, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS)
@@ -477,6 +497,11 @@
         send(createMotionEvent(action, downTime, eventTime, STYLUS_SOURCE, stylusDeviceId))
     }
 
+    private fun sendJoystickEvent() {
+        val time = SystemClock.uptimeMillis()
+        send(createMotionEvent(ACTION_MOVE, time, time, SOURCE_JOYSTICK, joystickDeviceId))
+    }
+
     private fun send(event: InputEvent) {
         // We need to make a copy of the event before sending it to the filter, because the filter
         // will recycle it, but the caller of this function might want to still be able to use
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 95cfc2a..7f88b00 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -61,29 +61,38 @@
 import android.graphics.drawable.Icon;
 import android.hardware.display.DisplayManagerGlobal;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
+import android.os.Handler;
 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.AndroidTestingRunner;
 import android.testing.TestableContext;
+import android.testing.TestableLooper;
 import android.util.ArraySet;
 import android.view.Display;
 import android.view.DisplayAdjustments;
 import android.view.DisplayInfo;
 import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityWindowAttributes;
+import android.view.accessibility.IAccessibilityManager;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
 
 import com.android.compatibility.common.util.TestUtils;
 import com.android.internal.R;
+import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.internal.accessibility.common.ShortcutConstants.FloatingMenuSize;
+import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
+import com.android.internal.accessibility.util.AccessibilityUtils;
+import com.android.internal.accessibility.util.ShortcutUtils;
 import com.android.internal.compat.IPlatformCompat;
 import com.android.server.LocalServices;
 import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener;
@@ -91,31 +100,38 @@
 import com.android.server.accessibility.magnification.MagnificationConnectionManager;
 import com.android.server.accessibility.magnification.MagnificationController;
 import com.android.server.accessibility.magnification.MagnificationProcessor;
-import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.pm.UserManagerInternal;
+import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
+import org.mockito.internal.util.reflection.FieldReader;
+import org.mockito.internal.util.reflection.FieldSetter;
 import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * APCT tests for {@link AccessibilityManagerService}.
  */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class AccessibilityManagerServiceTest {
     @Rule
     public final A11yTestableContext mTestableContext = new A11yTestableContext(
@@ -140,6 +156,12 @@
             TEST_PENDING_INTENT);
 
     private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY + 1;
+    private static final String TARGET_MAGNIFICATION = MAGNIFICATION_CONTROLLER_NAME;
+    private static final ComponentName TARGET_ALWAYS_ON_A11Y_SERVICE =
+            new ComponentName("FakePackage", "AlwaysOnA11yService");
+    private static final String TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS = "TileService";
+    private static final ComponentName TARGET_STANDARD_A11Y_SERVICE =
+            new ComponentName("FakePackage", "StandardA11yService");
 
     static final ComponentName COMPONENT_NAME = new ComponentName(
             "com.android.server.accessibility", "AccessibilityManagerServiceTest");
@@ -163,24 +185,33 @@
     @Mock private MagnificationController mMockMagnificationController;
     @Mock private FullScreenMagnificationController mMockFullScreenMagnificationController;
     @Mock private ProxyManager mProxyManager;
+    @Mock private StatusBarManagerInternal mStatusBarManagerInternal;
     @Captor private ArgumentCaptor<Intent> mIntentArgumentCaptor;
-    private MessageCapturingHandler mHandler = new MessageCapturingHandler(null);
+    private IAccessibilityManager mA11yManagerServiceOnDevice;
     private AccessibilityServiceConnection mAccessibilityServiceConnection;
     private AccessibilityInputFilter mInputFilter;
     private AccessibilityManagerService mA11yms;
+    private TestableLooper mTestableLooper;
+    private Handler mHandler;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        mTestableLooper = TestableLooper.get(this);
+        mHandler = new Handler(mTestableLooper.getLooper());
+
         LocalServices.removeServiceForTest(WindowManagerInternal.class);
         LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
         LocalServices.removeServiceForTest(UserManagerInternal.class);
+        LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
         LocalServices.addService(
                 WindowManagerInternal.class, mMockWindowManagerService);
         LocalServices.addService(
                 ActivityTaskManagerInternal.class, mMockActivityTaskManagerInternal);
         LocalServices.addService(
                 UserManagerInternal.class, mMockUserManagerInternal);
+        LocalServices.addService(
+                StatusBarManagerInternal.class, mStatusBarManagerInternal);
         mInputFilter = Mockito.mock(FakeInputFilter.class);
 
         when(mMockMagnificationController.getMagnificationConnectionManager()).thenReturn(
@@ -218,6 +249,19 @@
         final AccessibilityUserState userState = new AccessibilityUserState(
                 mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
         mA11yms.mUserStates.put(mA11yms.getCurrentUserIdLocked(), userState);
+        AccessibilityManager am = mTestableContext.getSystemService(AccessibilityManager.class);
+        mA11yManagerServiceOnDevice = (IAccessibilityManager) new FieldReader(am,
+                AccessibilityManager.class.getDeclaredField("mService")).read();
+        FieldSetter.setField(am, AccessibilityManager.class.getDeclaredField("mService"), mA11yms);
+    }
+
+    @After
+    public void cleanUp() throws Exception {
+        mTestableLooper.processAllMessages();
+        AccessibilityManager am = mTestableContext.getSystemService(AccessibilityManager.class);
+        FieldSetter.setField(
+                am, AccessibilityManager.class.getDeclaredField("mService"),
+                mA11yManagerServiceOnDevice);
     }
 
     private void setupAccessibilityServiceConnection(int serviceInfoFlag) {
@@ -297,7 +341,8 @@
                 mA11yms.getCurrentUserIdLocked());
 
         mA11yms.notifySystemActionsChangedLocked(userState);
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        mTestableLooper.processAllMessages();
+
         verify(mMockServiceClient).onSystemActionsChanged();
     }
 
@@ -415,7 +460,7 @@
         );
 
         mA11yms.onMagnificationTransitionEndedLocked(Display.DEFAULT_DISPLAY, true);
-        mHandler.sendAllMessages();
+        mTestableLooper.processAllMessages();
 
         ArgumentCaptor<Display> displayCaptor = ArgumentCaptor.forClass(Display.class);
         verify(mInputFilter, timeout(100)).refreshMagnificationMode(displayCaptor.capture());
@@ -730,7 +775,7 @@
 
         mA11yms.performAccessibilityShortcut(
                 ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
-        mHandler.sendAllMessages();
+        mTestableLooper.processAllMessages();
 
         assertStartActivityWithExpectedComponentName(mTestableContext.getMockContext(),
                 ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
@@ -805,26 +850,6 @@
     }
 
     @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();
@@ -842,7 +867,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK)
     public void testSwitchUserScanPackages_scansWithoutHoldingLock() {
         setupAccessibilityServiceConnection(0);
         final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning();
@@ -907,11 +931,14 @@
     public void testIsAccessibilityServiceWarningRequired_notRequiredIfAllowlisted() {
         mockManageAccessibilityGranted(mTestableContext);
         final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo(
-                new ComponentName("package_a", "class_a"), true);
+                new ComponentName("package_a", "class_a"),
+                /* isSystemApp= */ true, /* isAlwaysOnService= */ false);
         final AccessibilityServiceInfo info_b = mockAccessibilityServiceInfo(
-                new ComponentName("package_b", "class_b"), false);
+                new ComponentName("package_b", "class_b"),
+                /* isSystemApp= */ false, /* isAlwaysOnService= */ false);
         final AccessibilityServiceInfo info_c = mockAccessibilityServiceInfo(
-                new ComponentName("package_c", "class_c"), true);
+                new ComponentName("package_c", "class_c"),
+                /* isSystemApp= */ true, /* isAlwaysOnService= */ false);
         mTestableContext.getOrCreateTestableResources().addOverride(
                 R.array.config_trustedAccessibilityServices,
                 new String[]{
@@ -926,14 +953,380 @@
         assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_c)).isFalse();
     }
 
+    @Test
+    public void enableShortcutsForTargets_permissionNotGranted_throwsException() {
+        mTestableContext.getTestablePermissions().setPermission(
+                Manifest.permission.MANAGE_ACCESSIBILITY, PackageManager.PERMISSION_DENIED);
+
+        assertThrows(SecurityException.class,
+                () -> mA11yms.enableShortcutsForTargets(
+                        /* enable= */true,
+                        UserShortcutType.SOFTWARE,
+                        List.of(TARGET_MAGNIFICATION),
+                        mA11yms.getCurrentUserIdLocked()));
+    }
+
+    @Test
+    public void enableShortcutsForTargets_enableSoftwareShortcut_shortcutTurnedOn()
+            throws Exception {
+        mockManageAccessibilityGranted(mTestableContext);
+        setupShortcutTargetServices();
+        String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
+
+        mA11yms.enableShortcutsForTargets(
+                /* enable= */ true,
+                UserShortcutType.SOFTWARE,
+                List.of(target),
+                mA11yms.getCurrentUserIdLocked());
+        mTestableLooper.processAllMessages();
+
+        assertThat(ShortcutUtils.isComponentIdExistingInSettings(
+                mTestableContext, UserShortcutType.SOFTWARE, target
+        )).isTrue();
+    }
+
+    @Test
+    public void enableShortcutsForTargets_disableSoftwareShortcut_shortcutTurnedOff()
+            throws Exception {
+        String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
+        enableShortcutsForTargets_enableSoftwareShortcut_shortcutTurnedOn();
+
+        mA11yms.enableShortcutsForTargets(
+                /* enable= */ false,
+                UserShortcutType.SOFTWARE,
+                List.of(target),
+                mA11yms.getCurrentUserIdLocked());
+        mTestableLooper.processAllMessages();
+
+        assertThat(ShortcutUtils.isComponentIdExistingInSettings(
+                mTestableContext, UserShortcutType.SOFTWARE, target
+        )).isFalse();
+    }
+
+    @Test
+    public void enableShortcutsForTargets_enableSoftwareShortcutWithMagnification_menuSizeIncreased() {
+        mockManageAccessibilityGranted(mTestableContext);
+
+        mA11yms.enableShortcutsForTargets(
+                /* enable= */ true,
+                UserShortcutType.SOFTWARE,
+                List.of(MAGNIFICATION_CONTROLLER_NAME),
+                mA11yms.getCurrentUserIdLocked());
+        mTestableLooper.processAllMessages();
+
+        assertThat(
+                Settings.Secure.getInt(
+                        mTestableContext.getContentResolver(),
+                        Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
+                        FloatingMenuSize.UNKNOWN))
+                .isEqualTo(FloatingMenuSize.LARGE);
+    }
+
+    @Test
+    public void enableShortcutsForTargets_enableSoftwareShortcutWithMagnification_userConfigureSmallMenuSize_menuSizeNotChanged() {
+        mockManageAccessibilityGranted(mTestableContext);
+        Settings.Secure.putInt(
+                mTestableContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
+                FloatingMenuSize.SMALL);
+
+        mA11yms.enableShortcutsForTargets(
+                /* enable= */ true,
+                UserShortcutType.SOFTWARE,
+                List.of(MAGNIFICATION_CONTROLLER_NAME),
+                mA11yms.getCurrentUserIdLocked());
+        mTestableLooper.processAllMessages();
+
+        assertThat(
+                Settings.Secure.getInt(
+                        mTestableContext.getContentResolver(),
+                        Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
+                        FloatingMenuSize.UNKNOWN))
+                .isEqualTo(FloatingMenuSize.SMALL);
+    }
+
+    @Test
+    public void enableShortcutsForTargets_enableAlwaysOnServiceSoftwareShortcut_turnsOnAlwaysOnService()
+            throws Exception {
+        mockManageAccessibilityGranted(mTestableContext);
+        setupShortcutTargetServices();
+
+        mA11yms.enableShortcutsForTargets(
+                /* enable= */ true,
+                UserShortcutType.SOFTWARE,
+                List.of(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString()),
+                mA11yms.getCurrentUserIdLocked());
+        mTestableLooper.processAllMessages();
+
+        assertThat(
+                AccessibilityUtils.getEnabledServicesFromSettings(
+                        mTestableContext,
+                        mA11yms.getCurrentUserIdLocked())
+        ).contains(TARGET_ALWAYS_ON_A11Y_SERVICE);
+    }
+
+    @Test
+    public void enableShortcutsForTargets_disableAlwaysOnServiceSoftwareShortcut_turnsOffAlwaysOnService()
+            throws Exception {
+        enableShortcutsForTargets_enableAlwaysOnServiceSoftwareShortcut_turnsOnAlwaysOnService();
+
+        mA11yms.enableShortcutsForTargets(
+                /* enable= */ false,
+                UserShortcutType.SOFTWARE,
+                List.of(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString()),
+                mA11yms.getCurrentUserIdLocked());
+        mTestableLooper.processAllMessages();
+
+        assertThat(
+                AccessibilityUtils.getEnabledServicesFromSettings(
+                        mTestableContext,
+                        mA11yms.getCurrentUserIdLocked())
+        ).doesNotContain(TARGET_ALWAYS_ON_A11Y_SERVICE);
+    }
+
+    @Test
+    public void enableShortcutsForTargets_enableStandardServiceSoftwareShortcut_wontTurnOnService()
+            throws Exception {
+        mockManageAccessibilityGranted(mTestableContext);
+        setupShortcutTargetServices();
+
+        mA11yms.enableShortcutsForTargets(
+                /* enable= */ true,
+                UserShortcutType.SOFTWARE,
+                List.of(TARGET_STANDARD_A11Y_SERVICE.flattenToString()),
+                mA11yms.getCurrentUserIdLocked());
+        mTestableLooper.processAllMessages();
+
+        assertThat(
+                AccessibilityUtils.getEnabledServicesFromSettings(
+                        mTestableContext,
+                        mA11yms.getCurrentUserIdLocked())
+        ).doesNotContain(TARGET_STANDARD_A11Y_SERVICE);
+    }
+
+    @Test
+    public void enableShortcutsForTargets_disableStandardServiceSoftwareShortcutWithServiceOn_wontTurnOffService()
+            throws Exception {
+        enableShortcutsForTargets_enableStandardServiceSoftwareShortcut_wontTurnOnService();
+        AccessibilityUtils.setAccessibilityServiceState(
+                mTestableContext, TARGET_STANDARD_A11Y_SERVICE, /* enabled= */ true);
+
+        mA11yms.enableShortcutsForTargets(
+                /* enable= */ false,
+                UserShortcutType.SOFTWARE,
+                List.of(TARGET_STANDARD_A11Y_SERVICE.flattenToString()),
+                mA11yms.getCurrentUserIdLocked());
+        mTestableLooper.processAllMessages();
+
+        assertThat(
+                AccessibilityUtils.getEnabledServicesFromSettings(
+                        mTestableContext,
+                        mA11yms.getCurrentUserIdLocked())
+        ).contains(TARGET_STANDARD_A11Y_SERVICE);
+    }
+
+    @Test
+    public void enableShortcutsForTargets_enableTripleTapShortcut_settingUpdated() {
+        mockManageAccessibilityGranted(mTestableContext);
+
+        mA11yms.enableShortcutsForTargets(
+                /* enable= */ true,
+                UserShortcutType.TRIPLETAP,
+                List.of(TARGET_MAGNIFICATION),
+                mA11yms.getCurrentUserIdLocked());
+        mTestableLooper.processAllMessages();
+
+        assertThat(
+                Settings.Secure.getInt(
+                        mTestableContext.getContentResolver(),
+                        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
+                        AccessibilityUtils.State.OFF)
+        ).isEqualTo(AccessibilityUtils.State.ON);
+    }
+
+    @Test
+    public void enableShortcutsForTargets_disableTripleTapShortcut_settingUpdated() {
+        enableShortcutsForTargets_enableTripleTapShortcut_settingUpdated();
+
+        mA11yms.enableShortcutsForTargets(
+                /* enable= */ false,
+                UserShortcutType.TRIPLETAP,
+                List.of(TARGET_MAGNIFICATION),
+                mA11yms.getCurrentUserIdLocked());
+
+        assertThat(
+                Settings.Secure.getInt(
+                        mTestableContext.getContentResolver(),
+                        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
+                        AccessibilityUtils.State.OFF)
+        ).isEqualTo(AccessibilityUtils.State.OFF);
+    }
+
+    @Test
+    public void enableShortcutsForTargets_enableMultiFingerMultiTapsShortcut_settingUpdated() {
+        mockManageAccessibilityGranted(mTestableContext);
+
+        mA11yms.enableShortcutsForTargets(
+                /* enable= */ true,
+                UserShortcutType.TWOFINGER_DOUBLETAP,
+                List.of(TARGET_MAGNIFICATION),
+                mA11yms.getCurrentUserIdLocked());
+        mTestableLooper.processAllMessages();
+
+        assertThat(
+                Settings.Secure.getInt(
+                        mTestableContext.getContentResolver(),
+                        Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
+                        AccessibilityUtils.State.OFF)
+        ).isEqualTo(AccessibilityUtils.State.ON);
+    }
+
+    @Test
+    public void enableShortcutsForTargets_disableMultiFingerMultiTapsShortcut_settingUpdated() {
+        enableShortcutsForTargets_enableMultiFingerMultiTapsShortcut_settingUpdated();
+
+        mA11yms.enableShortcutsForTargets(
+                /* enable= */ false,
+                UserShortcutType.TWOFINGER_DOUBLETAP,
+                List.of(TARGET_MAGNIFICATION),
+                mA11yms.getCurrentUserIdLocked());
+        mTestableLooper.processAllMessages();
+
+        assertThat(
+                Settings.Secure.getInt(
+                        mTestableContext.getContentResolver(),
+                        Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
+                        AccessibilityUtils.State.OFF)
+        ).isEqualTo(AccessibilityUtils.State.OFF);
+    }
+
+    @Test
+    public void enableShortcutsForTargets_enableVolumeKeysShortcut_shortcutSet() {
+        mockManageAccessibilityGranted(mTestableContext);
+        setupShortcutTargetServices();
+
+        mA11yms.enableShortcutsForTargets(
+                /* enable= */ true,
+                UserShortcutType.HARDWARE,
+                List.of(TARGET_STANDARD_A11Y_SERVICE.flattenToString()),
+                mA11yms.getCurrentUserIdLocked());
+        mTestableLooper.processAllMessages();
+
+        assertThat(
+                ShortcutUtils.isComponentIdExistingInSettings(
+                        mTestableContext, ShortcutConstants.UserShortcutType.HARDWARE,
+                        TARGET_STANDARD_A11Y_SERVICE.flattenToString())
+        ).isTrue();
+    }
+
+    @Test
+    public void enableShortcutsForTargets_disableVolumeKeysShortcut_shortcutNotSet() {
+        enableShortcutsForTargets_enableVolumeKeysShortcut_shortcutSet();
+
+        mA11yms.enableShortcutsForTargets(
+                /* enable= */ false,
+                UserShortcutType.HARDWARE,
+                List.of(TARGET_STANDARD_A11Y_SERVICE.flattenToString()),
+                mA11yms.getCurrentUserIdLocked());
+        mTestableLooper.processAllMessages();
+
+        assertThat(
+                ShortcutUtils.isComponentIdExistingInSettings(
+                        mTestableContext, ShortcutConstants.UserShortcutType.HARDWARE,
+                        TARGET_STANDARD_A11Y_SERVICE.flattenToString())
+        ).isFalse();
+    }
+
+    @Test
+    public void enableShortcutsForTargets_enableQuickSettings_shortcutSet() {
+        mockManageAccessibilityGranted(mTestableContext);
+        setupShortcutTargetServices();
+
+        mA11yms.enableShortcutsForTargets(
+                /* enable= */ true,
+                UserShortcutType.QUICK_SETTINGS,
+                List.of(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString()),
+                mA11yms.getCurrentUserIdLocked());
+        mTestableLooper.processAllMessages();
+
+        assertThat(
+                ShortcutUtils.isComponentIdExistingInSettings(
+                        mTestableContext, UserShortcutType.QUICK_SETTINGS,
+                        TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString())
+        ).isTrue();
+        verify(mStatusBarManagerInternal)
+                .addQsTileToFrontOrEnd(
+                        new ComponentName(
+                                TARGET_ALWAYS_ON_A11Y_SERVICE.getPackageName(),
+                                TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS),
+                        /* end= */ true);
+    }
+
+    @Test
+    public void enableShortcutsForTargets_disableQuickSettings_shortcutNotSet() {
+        enableShortcutsForTargets_enableQuickSettings_shortcutSet();
+
+        mA11yms.enableShortcutsForTargets(
+                /* enable= */ false,
+                UserShortcutType.QUICK_SETTINGS,
+                List.of(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString()),
+                mA11yms.getCurrentUserIdLocked());
+        mTestableLooper.processAllMessages();
+
+        assertThat(
+                ShortcutUtils.isComponentIdExistingInSettings(
+                        mTestableContext, UserShortcutType.QUICK_SETTINGS,
+                        TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString())
+        ).isFalse();
+        verify(mStatusBarManagerInternal)
+                .removeQsTile(
+                        new ComponentName(
+                                TARGET_ALWAYS_ON_A11Y_SERVICE.getPackageName(),
+                                TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS));
+    }
+
+    @Test
+    public void getA11yFeatureToTileMap_permissionNotGranted_throwsException() {
+        mTestableContext.getTestablePermissions().setPermission(
+                Manifest.permission.MANAGE_ACCESSIBILITY, PackageManager.PERMISSION_DENIED);
+
+        assertThrows(SecurityException.class,
+                () -> mA11yms.getA11yFeatureToTileMap(mA11yms.getCurrentUserIdLocked()));
+    }
+
+    @Test
+    public void getA11yFeatureToTileMap() {
+        mockManageAccessibilityGranted(mTestableContext);
+        setupShortcutTargetServices();
+
+        Bundle bundle = mA11yms.getA11yFeatureToTileMap(mA11yms.getCurrentUserIdLocked());
+
+        // Framework tile size + TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS
+        assertThat(bundle.size())
+                .isEqualTo(ShortcutConstants.A11Y_FEATURE_TO_FRAMEWORK_TILE.size() + 1);
+        assertThat(
+                bundle.getParcelable(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString(),
+                        ComponentName.class)
+        ).isEqualTo(
+                new ComponentName(
+                        TARGET_ALWAYS_ON_A11Y_SERVICE.getPackageName(),
+                        TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS));
+        for (Map.Entry<ComponentName, ComponentName> entry :
+                ShortcutConstants.A11Y_FEATURE_TO_FRAMEWORK_TILE.entrySet()) {
+            assertThat(bundle.getParcelable(entry.getKey().flattenToString(), ComponentName.class))
+                    .isEqualTo(entry.getValue());
+        }
+    }
+
     private static AccessibilityServiceInfo mockAccessibilityServiceInfo(
             ComponentName componentName) {
-        return mockAccessibilityServiceInfo(componentName, false);
+        return mockAccessibilityServiceInfo(
+                componentName, /* isSystemApp= */ false, /* isAlwaysOnService=*/ false);
     }
 
     private static AccessibilityServiceInfo mockAccessibilityServiceInfo(
             ComponentName componentName,
-            boolean isSystemApp) {
+            boolean isSystemApp, boolean isAlwaysOnService) {
         AccessibilityServiceInfo accessibilityServiceInfo =
                 Mockito.spy(new AccessibilityServiceInfo());
         accessibilityServiceInfo.setComponentName(componentName);
@@ -941,7 +1334,15 @@
         when(accessibilityServiceInfo.getResolveInfo()).thenReturn(mockResolveInfo);
         mockResolveInfo.serviceInfo = Mockito.mock(ServiceInfo.class);
         mockResolveInfo.serviceInfo.applicationInfo = Mockito.mock(ApplicationInfo.class);
+        mockResolveInfo.serviceInfo.packageName = componentName.getPackageName();
+        mockResolveInfo.serviceInfo.name = componentName.getClassName();
         when(mockResolveInfo.serviceInfo.applicationInfo.isSystemApp()).thenReturn(isSystemApp);
+        if (isAlwaysOnService) {
+            accessibilityServiceInfo.flags |=
+                    AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
+            mockResolveInfo.serviceInfo.applicationInfo.targetSdkVersion =
+                    Build.VERSION_CODES.R;
+        }
         return accessibilityServiceInfo;
     }
 
@@ -974,6 +1375,22 @@
                 Intent.EXTRA_COMPONENT_NAME)).isEqualTo(componentName);
     }
 
+    private void setupShortcutTargetServices() {
+        AccessibilityServiceInfo alwaysOnServiceInfo = mockAccessibilityServiceInfo(
+                TARGET_ALWAYS_ON_A11Y_SERVICE,
+                /* isSystemApp= */ false,
+                /* isAlwaysOnService= */ true);
+        when(alwaysOnServiceInfo.getTileServiceName())
+                .thenReturn(TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS);
+        AccessibilityServiceInfo standardServiceInfo = mockAccessibilityServiceInfo(
+                TARGET_STANDARD_A11Y_SERVICE,
+                /* isSystemApp= */ false,
+                /* isAlwaysOnService= */ false);
+        mA11yms.getCurrentUserState().mInstalledServices.addAll(
+                List.of(alwaysOnServiceInfo, standardServiceInfo));
+        mA11yms.getCurrentUserState().updateTileServiceMapForAccessibilityServiceLocked();
+    }
+
     public static class FakeInputFilter extends AccessibilityInputFilter {
         FakeInputFilter(Context context,
                 AccessibilityManagerService service) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
index 89d146d..8717a05 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -47,6 +47,9 @@
 import android.os.LocaleList;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.IWindow;
@@ -67,6 +70,7 @@
 import org.hamcrest.TypeSafeMatcher;
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
@@ -77,9 +81,16 @@
 import java.util.Arrays;
 import java.util.List;
 
+// This test verifies deprecated codepath. Probably changing this file means
+// AccessibilityWindowManagerWithAccessibilityWindowTest also needs to be updated.
+// LINT.IfChange
+
 /**
- * Tests for the AccessibilityWindowManager
+ * Tests for the AccessibilityWindowManager with Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y enabled.
+ * TODO(b/322444245): Merge with AccessibilityWindowManagerWithAccessibilityWindowTest
+ *  after completing the flag migration.
  */
+@RequiresFlagsDisabled(Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y)
 public class AccessibilityWindowManagerTest {
     private static final String PACKAGE_NAME = "com.android.server.accessibility";
     private static final boolean FORCE_SEND = true;
@@ -132,6 +143,9 @@
     @Mock private IBinder mMockEmbeddedToken;
     @Mock private IBinder mMockInvalidToken;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Before
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
@@ -1227,3 +1241,4 @@
         }
     }
 }
+// LINT.ThenChange(/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java)
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
new file mode 100644
index 0000000..dc26e6e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
@@ -0,0 +1,1283 @@
+/*
+ * 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.accessibility;
+
+import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.DisplayIdMatcher.displayId;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowChangesMatcher.a11yWindowChanges;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowIdMatcher.a11yWindowId;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.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.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.graphics.Point;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.os.LocaleList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.IWindow;
+import android.view.WindowInfo;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowAttributes;
+import android.view.accessibility.AccessibilityWindowInfo;
+import android.view.accessibility.IAccessibilityInteractionConnection;
+
+import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
+import com.android.server.accessibility.test.MessageCapturingHandler;
+import com.android.server.wm.AccessibilityWindowsPopulator.AccessibilityWindow;
+import com.android.server.wm.WindowManagerInternal;
+import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.After;
+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;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for the AccessibilityWindowManager with Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y
+ * TODO(b/322444245): Merge with AccessibilityWindowManagerTest
+ *  after completing the flag migration.
+ */
+@RequiresFlagsEnabled(Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y)
+public class AccessibilityWindowManagerWithAccessibilityWindowTest {
+    private static final String PACKAGE_NAME = "com.android.server.accessibility";
+    private static final boolean FORCE_SEND = true;
+    private static final boolean SEND_ON_WINDOW_CHANGES = false;
+    private static final int USER_SYSTEM_ID = UserHandle.USER_SYSTEM;
+    private static final int USER_PROFILE = 11;
+    private static final int USER_PROFILE_PARENT = 1;
+    private static final int SECONDARY_DISPLAY_ID = Display.DEFAULT_DISPLAY + 1;
+    private static final int NUM_GLOBAL_WINDOWS = 4;
+    private static final int NUM_APP_WINDOWS = 4;
+    private static final int NUM_OF_WINDOWS = (NUM_GLOBAL_WINDOWS + NUM_APP_WINDOWS);
+    private static final int DEFAULT_FOCUSED_INDEX = 1;
+    private static final int SCREEN_WIDTH = 1080;
+    private static final int SCREEN_HEIGHT = 1920;
+    private static final int INVALID_ID = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+    private static final int HOST_WINDOW_ID = 10;
+    private static final int EMBEDDED_WINDOW_ID = 11;
+    private static final int OTHER_WINDOW_ID = 12;
+
+    private AccessibilityWindowManager mA11yWindowManager;
+    // Window manager will support multiple focused window if config_perDisplayFocusEnabled is true,
+    // i.e., each display would have its current focused window, and one of all focused windows
+    // would be top focused window. Otherwise, window manager only supports one focused window
+    // at all displays, and that focused window would be top focused window.
+    private boolean mSupportPerDisplayFocus = false;
+    private int mTopFocusedDisplayId = Display.INVALID_DISPLAY;
+    private IBinder mTopFocusedWindowToken = null;
+
+    // List of window token, mapping from windowId -> window token.
+    private final SparseArray<IWindow> mA11yWindowTokens = new SparseArray<>();
+    // List of window info lists, mapping from displayId -> a11y window lists.
+    private final SparseArray<ArrayList<AccessibilityWindow>> mWindows = new SparseArray<>();
+    // List of callback, mapping from displayId -> callback.
+    private final SparseArray<WindowsForAccessibilityCallback> mCallbackOfWindows =
+            new SparseArray<>();
+    // List of display ID.
+    private final ArrayList<Integer> mExpectedDisplayList = new ArrayList<>(Arrays.asList(
+            Display.DEFAULT_DISPLAY, SECONDARY_DISPLAY_ID));
+
+    private final MessageCapturingHandler mHandler = new MessageCapturingHandler(null);
+
+    // This maps displayId -> next region offset.
+    // Touchable region must have un-occluded area so that it's exposed to a11y services.
+    // This offset can be used as left and top of new region so that top-left of each region are
+    // kept visible.
+    // It's expected to be incremented by some amount everytime the value is used.
+    private final SparseArray<Integer> mNextRegionOffsets = new SparseArray<>();
+
+    @Mock
+    private WindowManagerInternal mMockWindowManagerInternal;
+    @Mock
+    private AccessibilityWindowManager.AccessibilityEventSender mMockA11yEventSender;
+    @Mock
+    private AccessibilitySecurityPolicy mMockA11ySecurityPolicy;
+    @Mock
+    private AccessibilitySecurityPolicy.AccessibilityUserManager mMockA11yUserManager;
+    @Mock
+    private AccessibilityTraceManager mMockA11yTraceManager;
+
+    @Mock
+    private IBinder mMockHostToken;
+    @Mock
+    private IBinder mMockEmbeddedToken;
+    @Mock
+    private IBinder mMockInvalidToken;
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Before
+    public void setUp() throws RemoteException {
+        MockitoAnnotations.initMocks(this);
+        when(mMockA11yUserManager.getCurrentUserIdLocked()).thenReturn(USER_SYSTEM_ID);
+        when(mMockA11ySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(
+                USER_PROFILE)).thenReturn(USER_PROFILE_PARENT);
+        when(mMockA11ySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(
+                USER_SYSTEM_ID)).thenReturn(USER_SYSTEM_ID);
+        when(mMockA11ySecurityPolicy.resolveValidReportedPackageLocked(
+                anyString(), anyInt(), anyInt(), anyInt())).thenReturn(PACKAGE_NAME);
+
+        doAnswer((invocation) -> {
+            onAccessibilityWindowsChanged(invocation.getArgument(0), false);
+            return null;
+        }).when(mMockWindowManagerInternal).computeWindowsForAccessibility(anyInt());
+
+        mA11yWindowManager = new AccessibilityWindowManager(new Object(), mHandler,
+                mMockWindowManagerInternal,
+                mMockA11yEventSender,
+                mMockA11ySecurityPolicy,
+                mMockA11yUserManager,
+                mMockA11yTraceManager);
+        // Starts tracking window of default display and sets the default display
+        // as top focused display before each testing starts.
+        startTrackingPerDisplay(Display.DEFAULT_DISPLAY);
+
+        // AccessibilityEventSender is invoked during onAccessibilityWindowsChanged.
+        // Resets it for mockito verify of further test case.
+        Mockito.reset(mMockA11yEventSender);
+
+        registerLeashedTokenAndWindowId();
+    }
+
+    @After
+    public void tearDown() {
+        mHandler.removeAllMessages();
+    }
+
+    @Test
+    public void startTrackingWindows_shouldEnableWindowManagerCallback() {
+        // AccessibilityWindowManager#startTrackingWindows already invoked in setup.
+        assertTrue(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY));
+        final WindowsForAccessibilityCallback callbacks =
+                mCallbackOfWindows.get(Display.DEFAULT_DISPLAY);
+        verify(mMockWindowManagerInternal).setWindowsForAccessibilityCallback(
+                eq(Display.DEFAULT_DISPLAY), eq(callbacks));
+    }
+
+    @Test
+    public void stopTrackingWindows_shouldDisableWindowManagerCallback() {
+        assertTrue(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY));
+        Mockito.reset(mMockWindowManagerInternal);
+
+        mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY);
+        assertFalse(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY));
+        verify(mMockWindowManagerInternal).setWindowsForAccessibilityCallback(
+                eq(Display.DEFAULT_DISPLAY), isNull());
+
+    }
+
+    @Test
+    public void stopTrackingWindows_shouldClearWindows() {
+        assertTrue(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY));
+        final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
+
+        mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY);
+        assertNull(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY));
+        assertEquals(mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT),
+                AccessibilityWindowInfo.UNDEFINED_WINDOW_ID);
+        assertEquals(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID),
+                activeWindowId);
+    }
+
+    @Test
+    public void stopTrackingWindows_onNonTopFocusedDisplay_shouldNotResetTopFocusWindow()
+            throws RemoteException {
+        // At setup, the default display sets be the top focused display and
+        // its current focused window sets be the top focused window.
+        // Starts tracking window of second display.
+        startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
+        assertTrue(mA11yWindowManager.isTrackingWindowsLocked(SECONDARY_DISPLAY_ID));
+        // Stops tracking windows of second display.
+        mA11yWindowManager.stopTrackingWindows(SECONDARY_DISPLAY_ID);
+        assertNotEquals(mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT),
+                AccessibilityWindowInfo.UNDEFINED_WINDOW_ID);
+    }
+
+    @Test
+    public void onWindowsChanged_duringTouchInteractAndFocusChange_shouldChangeActiveWindow() {
+        final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
+        final WindowInfo focusedWindowInfo =
+                mWindows.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX).getWindowInfo();
+        assertEquals(activeWindowId, mA11yWindowManager.findWindowIdLocked(
+                USER_SYSTEM_ID, focusedWindowInfo.token));
+
+        focusedWindowInfo.focused = false;
+        mWindows.get(Display.DEFAULT_DISPLAY).get(
+                DEFAULT_FOCUSED_INDEX + 1).getWindowInfo().focused = true;
+
+        mA11yWindowManager.onTouchInteractionStart();
+        setTopFocusedWindowAndDisplay(Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX + 1);
+        onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+        assertNotEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
+    }
+
+    @Test
+    public void
+            onWindowsChanged_focusChangeOnNonTopFocusedDisplay_perDisplayFocusOn_notChangeWindow()
+            throws RemoteException {
+        // At setup, the default display sets be the top focused display and
+        // its current focused window sets be the top focused window.
+        // Sets supporting multiple focused window, i.e., config_perDisplayFocusEnabled is true.
+        mSupportPerDisplayFocus = true;
+        // Starts tracking window of second display.
+        startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
+        // Gets the active window.
+        final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
+        // Gets the top focused window.
+        final int topFocusedWindowId =
+                mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT);
+        // Changes the current focused window at second display.
+        changeFocusedWindowOnDisplayPerDisplayFocusConfig(SECONDARY_DISPLAY_ID,
+                DEFAULT_FOCUSED_INDEX + 1, Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX);
+
+        onAccessibilityWindowsChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
+        // The active window should not be changed.
+        assertEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
+        // The top focused window should not be changed.
+        assertEquals(topFocusedWindowId,
+                mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT));
+    }
+
+    @Test
+    public void
+            onWindowChange_focusChangeToNonTopFocusedDisplay_perDisplayFocusOff_shouldChangeWindow()
+            throws RemoteException {
+        // At setup, the default display sets be the top focused display and
+        // its current focused window sets be the top focused window.
+        // Sets not supporting multiple focused window, i.e., config_perDisplayFocusEnabled is
+        // false.
+        mSupportPerDisplayFocus = false;
+        // Starts tracking window of second display.
+        startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
+        // Gets the active window.
+        final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
+        // Gets the top focused window.
+        final int topFocusedWindowId =
+                mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT);
+        // Changes the current focused window from default display to second display.
+        changeFocusedWindowOnDisplayPerDisplayFocusConfig(SECONDARY_DISPLAY_ID,
+                DEFAULT_FOCUSED_INDEX, Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX);
+
+        onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+        onAccessibilityWindowsChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
+        // The active window should be changed.
+        assertNotEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
+        // The top focused window should be changed.
+        assertNotEquals(topFocusedWindowId,
+                mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT));
+    }
+
+    @Test
+    public void onWindowsChanged_shouldReportCorrectLayer() {
+        // AccessibilityWindowManager#onAccessibilityWindowsChanged already invoked in setup.
+        List<AccessibilityWindowInfo> a11yWindows =
+                mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+        for (int i = 0; i < a11yWindows.size(); i++) {
+            final AccessibilityWindowInfo a11yWindow = a11yWindows.get(i);
+            assertThat(mWindows.get(Display.DEFAULT_DISPLAY).size() - i - 1,
+                    is(a11yWindow.getLayer()));
+        }
+    }
+
+    @Test
+    public void onWindowsChanged_shouldReportCorrectOrder() {
+        // AccessibilityWindowManager#onAccessibilityWindowsChanged already invoked in setup.
+        List<AccessibilityWindowInfo> a11yWindows =
+                mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+        for (int i = 0; i < a11yWindows.size(); i++) {
+            final AccessibilityWindowInfo a11yWindow = a11yWindows.get(i);
+            final IBinder windowToken = mA11yWindowManager
+                    .getWindowTokenForUserAndWindowIdLocked(USER_SYSTEM_ID, a11yWindow.getId());
+            final WindowInfo windowInfo = mWindows.get(Display.DEFAULT_DISPLAY)
+                    .get(i).getWindowInfo();
+            assertThat(windowToken, is(windowInfo.token));
+        }
+    }
+
+    @Test
+    public void onWindowsChangedAndForceSend_shouldUpdateWindows() {
+        assertNotEquals("new title",
+                toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)
+                        .get(0).getTitle()));
+
+        mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo().title = "new title";
+
+        onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+        assertEquals("new title",
+                toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)
+                        .get(0).getTitle()));
+    }
+
+    @Test
+    public void onWindowsChangedNoForceSend_windowChanged_shouldUpdateWindows()
+            throws RemoteException {
+        final AccessibilityWindowInfo oldWindow =
+                mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0);
+        final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+                true, USER_SYSTEM_ID);
+        mWindows.get(Display.DEFAULT_DISPLAY).set(0,
+                createMockAccessibilityWindow(token, Display.DEFAULT_DISPLAY));
+
+        onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+        assertNotEquals(oldWindow,
+                mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0));
+    }
+
+    @Test
+    public void onWindowsChangedNoForceSend_focusChanged_shouldUpdateWindows() {
+        final WindowInfo focusedWindowInfo =
+                mWindows.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX).getWindowInfo();
+        final WindowInfo windowInfo = mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo();
+        focusedWindowInfo.focused = false;
+        windowInfo.focused = true;
+
+        onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+        assertTrue(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0)
+                .isFocused());
+    }
+
+    @Test
+    public void removeAccessibilityInteractionConnection_byWindowToken_shouldRemoved() {
+        for (int i = 0; i < NUM_OF_WINDOWS; i++) {
+            final int windowId = mA11yWindowTokens.keyAt(i);
+            final IWindow windowToken = mA11yWindowTokens.valueAt(i);
+            assertNotNull(mA11yWindowManager.getConnectionLocked(USER_SYSTEM_ID, windowId));
+
+            mA11yWindowManager.removeAccessibilityInteractionConnection(windowToken);
+            assertNull(mA11yWindowManager.getConnectionLocked(USER_SYSTEM_ID, windowId));
+        }
+    }
+
+    @Test
+    public void remoteAccessibilityConnection_binderDied_shouldRemoveConnection() {
+        for (int i = 0; i < NUM_OF_WINDOWS; i++) {
+            final int windowId = mA11yWindowTokens.keyAt(i);
+            final RemoteAccessibilityConnection remoteA11yConnection =
+                    mA11yWindowManager.getConnectionLocked(USER_SYSTEM_ID, windowId);
+            assertNotNull(remoteA11yConnection);
+
+            remoteA11yConnection.binderDied();
+            assertNull(mA11yWindowManager.getConnectionLocked(USER_SYSTEM_ID, windowId));
+        }
+    }
+
+    @Test
+    public void getWindowTokenForUserAndWindowId_shouldNotNull() {
+        final List<AccessibilityWindowInfo> windows =
+                mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+        for (int i = 0; i < windows.size(); i++) {
+            final int windowId = windows.get(i).getId();
+
+            assertNotNull(mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked(
+                    USER_SYSTEM_ID, windowId));
+        }
+    }
+
+    @Test
+    public void findWindowId() {
+        final List<AccessibilityWindowInfo> windows =
+                mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+        for (int i = 0; i < windows.size(); i++) {
+            final int windowId = windows.get(i).getId();
+            final IBinder windowToken = mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked(
+                    USER_SYSTEM_ID, windowId);
+
+            assertEquals(mA11yWindowManager.findWindowIdLocked(
+                    USER_SYSTEM_ID, windowToken), windowId);
+        }
+    }
+
+    @Test
+    public void resolveParentWindowId_windowIsNotEmbedded_shouldReturnGivenId()
+            throws RemoteException {
+        final int windowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, false,
+                Mockito.mock(IBinder.class), USER_SYSTEM_ID);
+        assertEquals(windowId, mA11yWindowManager.resolveParentWindowIdLocked(windowId));
+    }
+
+    @Test
+    public void resolveParentWindowId_windowIsNotRegistered_shouldReturnGivenId() {
+        final int windowId = -1;
+        assertEquals(windowId, mA11yWindowManager.resolveParentWindowIdLocked(windowId));
+    }
+
+    @Test
+    public void resolveParentWindowId_windowIsAssociated_shouldReturnParentWindowId()
+            throws RemoteException {
+        final IBinder mockHostToken = Mockito.mock(IBinder.class);
+        final IBinder mockEmbeddedToken = Mockito.mock(IBinder.class);
+        final int hostWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+                false, mockHostToken, USER_SYSTEM_ID);
+        final int embeddedWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+                false, mockEmbeddedToken, USER_SYSTEM_ID);
+
+        mA11yWindowManager.associateEmbeddedHierarchyLocked(mockHostToken, mockEmbeddedToken);
+
+        final int resolvedWindowId = mA11yWindowManager.resolveParentWindowIdLocked(
+                embeddedWindowId);
+        assertEquals(hostWindowId, resolvedWindowId);
+    }
+
+    @Test
+    public void resolveParentWindowId_windowIsDisassociated_shouldReturnGivenId()
+            throws RemoteException {
+        final IBinder mockHostToken = Mockito.mock(IBinder.class);
+        final IBinder mockEmbeddedToken = Mockito.mock(IBinder.class);
+        final int hostWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+                false, mockHostToken, USER_SYSTEM_ID);
+        final int embeddedWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+                false, mockEmbeddedToken, USER_SYSTEM_ID);
+
+        mA11yWindowManager.associateEmbeddedHierarchyLocked(mockHostToken, mockEmbeddedToken);
+        mA11yWindowManager.disassociateEmbeddedHierarchyLocked(mockEmbeddedToken);
+
+        final int resolvedWindowId = mA11yWindowManager.resolveParentWindowIdLocked(
+                embeddedWindowId);
+        assertNotEquals(hostWindowId, resolvedWindowId);
+        assertEquals(embeddedWindowId, resolvedWindowId);
+    }
+
+    @Test
+    public void computePartialInteractiveRegionForWindow_wholeVisible_returnWholeRegion() {
+        // Updates top 2 z-order WindowInfo are whole visible.
+        final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+        setRegionForMockAccessibilityWindow(firstWindow,
+                new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2));
+        final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
+        setRegionForMockAccessibilityWindow(secondWindow,
+                new Region(0, SCREEN_HEIGHT / 2, SCREEN_WIDTH, SCREEN_HEIGHT));
+
+        onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+        final List<AccessibilityWindowInfo> a11yWindows =
+                mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+        assertThat(a11yWindows, hasSize(2));
+        final Region outBounds = new Region();
+        int windowId = a11yWindows.get(0).getId();
+
+        mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
+        assertThat(outBounds.getBounds().width(), is(SCREEN_WIDTH));
+        assertThat(outBounds.getBounds().height(), is(SCREEN_HEIGHT / 2));
+
+        windowId = a11yWindows.get(1).getId();
+
+        mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
+        assertThat(outBounds.getBounds().width(), is(SCREEN_WIDTH));
+        assertThat(outBounds.getBounds().height(), is(SCREEN_HEIGHT / 2));
+    }
+
+    @Test
+    public void computePartialInteractiveRegionForWindow_halfVisible_returnHalfRegion() {
+        // Updates z-order #1 WindowInfo is half visible.
+        final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+        setRegionForMockAccessibilityWindow(firstWindow,
+                new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2));
+        final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
+        setRegionForMockAccessibilityWindow(secondWindow,
+                new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+
+        onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+        final List<AccessibilityWindowInfo> a11yWindows =
+                mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+        assertThat(a11yWindows, hasSize(2));
+        final Region outBounds = new Region();
+        int windowId = a11yWindows.get(1).getId();
+
+        mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
+        assertThat(outBounds.getBounds().width(), is(SCREEN_WIDTH));
+        assertThat(outBounds.getBounds().height(), is(SCREEN_HEIGHT / 2));
+    }
+
+    @Test
+    public void computePartialInteractiveRegionForWindow_notVisible_returnEmptyRegion() {
+        // z-order #0 WindowInfo is full screen, z-order #1 WindowInfo should be invisible.
+        final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+        setRegionForMockAccessibilityWindow(firstWindow,
+                new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+
+        onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+        final List<AccessibilityWindowInfo> a11yWindows =
+                mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+        // Note that the second window is also exposed even if region is empty because it's focused.
+        assertThat(a11yWindows, hasSize(2));
+        final Region outBounds = new Region();
+        int windowId = a11yWindows.get(1).getId();
+
+        mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
+        assertTrue(outBounds.getBounds().isEmpty());
+    }
+
+    @Test
+    public void computePartialInteractiveRegionForWindow_partialVisible_returnVisibleRegion() {
+        // Updates z-order #0 WindowInfo to have two interact-able areas.
+        final Region region = new Region(0, 0, SCREEN_WIDTH, 200);
+        region.op(0, SCREEN_HEIGHT - 200, SCREEN_WIDTH, SCREEN_HEIGHT, Region.Op.UNION);
+        final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+        setRegionForMockAccessibilityWindow(firstWindow, region);
+        final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
+        setRegionForMockAccessibilityWindow(secondWindow,
+                new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+
+        onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+        final List<AccessibilityWindowInfo> a11yWindows =
+                mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+        assertThat(a11yWindows, hasSize(2));
+        final Region outBounds = new Region();
+        final int windowId = a11yWindows.get(1).getId();
+
+        mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
+        assertFalse(outBounds.getBounds().isEmpty());
+        assertThat(outBounds.getBounds().width(), is(SCREEN_WIDTH));
+        assertThat(outBounds.getBounds().height(), is(SCREEN_HEIGHT - 400));
+    }
+
+    @Test
+    public void updateActiveAndA11yFocusedWindow_windowStateChangedEvent_noTracking_shouldUpdate() {
+        final IBinder eventWindowToken =
+                mWindows.get(Display.DEFAULT_DISPLAY)
+                        .get(DEFAULT_FOCUSED_INDEX + 1).getWindowInfo().token;
+        final int eventWindowId = mA11yWindowManager.findWindowIdLocked(
+                USER_SYSTEM_ID, eventWindowToken);
+        when(mMockWindowManagerInternal.getFocusedWindowTokenFromWindowStates())
+                .thenReturn(eventWindowToken);
+
+        final int noUse = 0;
+        mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY);
+        mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
+                eventWindowId,
+                noUse,
+                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
+                noUse);
+        assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(eventWindowId));
+        assertThat(mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT),
+                is(eventWindowId));
+    }
+
+    @Test
+    public void updateActiveAndA11yFocusedWindow_hoverEvent_touchInteract_shouldSetActiveWindow() {
+        final int eventWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY,
+                DEFAULT_FOCUSED_INDEX + 1);
+        final int currentActiveWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
+        assertThat(currentActiveWindowId, is(not(eventWindowId)));
+
+        final int noUse = 0;
+        mA11yWindowManager.onTouchInteractionStart();
+        mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
+                eventWindowId,
+                noUse,
+                AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
+                noUse);
+        assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(eventWindowId));
+        final ArgumentCaptor<AccessibilityEvent> captor =
+                ArgumentCaptor.forClass(AccessibilityEvent.class);
+        verify(mMockA11yEventSender, times(2))
+                .sendAccessibilityEventForCurrentUserLocked(captor.capture());
+        assertThat(captor.getAllValues().get(0),
+                allOf(displayId(Display.DEFAULT_DISPLAY),
+                        a11yWindowId(currentActiveWindowId),
+                        a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
+        assertThat(captor.getAllValues().get(1),
+                allOf(displayId(Display.DEFAULT_DISPLAY),
+                        a11yWindowId(eventWindowId),
+                        a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
+    }
+
+    @Test
+    public void updateActiveAndA11yFocusedWindow_a11yFocusEvent_shouldUpdateA11yFocus() {
+        final int eventWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY,
+                DEFAULT_FOCUSED_INDEX);
+        final int currentA11yFocusedWindowId = mA11yWindowManager.getFocusedWindowId(
+                AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
+        assertThat(currentA11yFocusedWindowId, is(AccessibilityWindowInfo.UNDEFINED_WINDOW_ID));
+
+        final int noUse = 0;
+        mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
+                eventWindowId,
+                AccessibilityNodeInfo.ROOT_NODE_ID,
+                AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                noUse);
+        assertThat(mA11yWindowManager.getFocusedWindowId(
+                AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(eventWindowId));
+        final ArgumentCaptor<AccessibilityEvent> captor =
+                ArgumentCaptor.forClass(AccessibilityEvent.class);
+        verify(mMockA11yEventSender, times(1))
+                .sendAccessibilityEventForCurrentUserLocked(captor.capture());
+        assertThat(captor.getAllValues().get(0),
+                allOf(displayId(Display.DEFAULT_DISPLAY),
+                        a11yWindowId(eventWindowId),
+                        a11yWindowChanges(
+                                AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
+    }
+
+    @Test
+    public void updateActiveAndA11yFocusedWindow_a11yFocusEvent_multiDisplay_defaultToSecondary()
+            throws RemoteException {
+        runUpdateActiveAndA11yFocusedWindow_MultiDisplayTest(
+                Display.DEFAULT_DISPLAY, SECONDARY_DISPLAY_ID);
+    }
+
+    @Test
+    public void updateActiveAndA11yFocusedWindow_a11yFocusEvent_multiDisplay_SecondaryToDefault()
+            throws RemoteException {
+        runUpdateActiveAndA11yFocusedWindow_MultiDisplayTest(
+                SECONDARY_DISPLAY_ID, Display.DEFAULT_DISPLAY);
+    }
+
+    private void runUpdateActiveAndA11yFocusedWindow_MultiDisplayTest(
+            int initialDisplayId, int eventDisplayId) throws RemoteException {
+        startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
+        final int initialWindowId = getWindowIdFromWindowInfosForDisplay(
+                initialDisplayId, DEFAULT_FOCUSED_INDEX);
+        final int noUse = 0;
+        mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
+                initialWindowId,
+                AccessibilityNodeInfo.ROOT_NODE_ID,
+                AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                noUse);
+        assertThat(mA11yWindowManager.getFocusedWindowId(
+                AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(initialWindowId));
+        Mockito.reset(mMockA11yEventSender);
+
+        final int eventWindowId = getWindowIdFromWindowInfosForDisplay(
+                eventDisplayId, DEFAULT_FOCUSED_INDEX);
+        mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
+                eventWindowId,
+                AccessibilityNodeInfo.ROOT_NODE_ID,
+                AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                noUse);
+        assertThat(mA11yWindowManager.getFocusedWindowId(
+                AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(eventWindowId));
+        final ArgumentCaptor<AccessibilityEvent> captor =
+                ArgumentCaptor.forClass(AccessibilityEvent.class);
+        verify(mMockA11yEventSender, times(2))
+                .sendAccessibilityEventForCurrentUserLocked(captor.capture());
+        assertThat(captor.getAllValues().get(0),
+                allOf(displayId(initialDisplayId),
+                        a11yWindowId(initialWindowId),
+                        a11yWindowChanges(
+                                AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
+        assertThat(captor.getAllValues().get(1),
+                allOf(displayId(eventDisplayId),
+                        a11yWindowId(eventWindowId),
+                        a11yWindowChanges(
+                                AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
+    }
+
+    @Test
+    public void updateActiveAndA11yFocusedWindow_clearA11yFocusEvent_shouldClearA11yFocus() {
+        final int eventWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY,
+                DEFAULT_FOCUSED_INDEX);
+        final int currentA11yFocusedWindowId = mA11yWindowManager.getFocusedWindowId(
+                AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
+        assertThat(currentA11yFocusedWindowId, is(not(eventWindowId)));
+
+        final int noUse = 0;
+        mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
+                eventWindowId,
+                AccessibilityNodeInfo.ROOT_NODE_ID,
+                AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                noUse);
+        assertThat(mA11yWindowManager.getFocusedWindowId(
+                AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(eventWindowId));
+        mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
+                eventWindowId,
+                AccessibilityNodeInfo.ROOT_NODE_ID,
+                AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
+                noUse);
+        assertThat(mA11yWindowManager.getFocusedWindowId(
+                        AccessibilityNodeInfo.FOCUS_ACCESSIBILITY),
+                is(AccessibilityWindowInfo.UNDEFINED_WINDOW_ID));
+    }
+
+    @Test
+    public void onTouchInteractionEnd_shouldRollbackActiveWindow() {
+        final int eventWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY,
+                DEFAULT_FOCUSED_INDEX + 1);
+        final int currentActiveWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
+        assertThat(currentActiveWindowId, is(not(eventWindowId)));
+
+        final int noUse = 0;
+        mA11yWindowManager.onTouchInteractionStart();
+        mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
+                eventWindowId,
+                noUse,
+                AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
+                noUse);
+        assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(eventWindowId));
+        // AccessibilityEventSender is invoked after active window changed. Reset it.
+        Mockito.reset(mMockA11yEventSender);
+
+        mA11yWindowManager.onTouchInteractionEnd();
+        final ArgumentCaptor<AccessibilityEvent> captor =
+                ArgumentCaptor.forClass(AccessibilityEvent.class);
+        verify(mMockA11yEventSender, times(2))
+                .sendAccessibilityEventForCurrentUserLocked(captor.capture());
+        assertThat(captor.getAllValues().get(0),
+                allOf(displayId(Display.DEFAULT_DISPLAY),
+                        a11yWindowId(eventWindowId),
+                        a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
+        assertThat(captor.getAllValues().get(1),
+                allOf(displayId(Display.DEFAULT_DISPLAY),
+                        a11yWindowId(currentActiveWindowId),
+                        a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
+    }
+
+    @Test
+    public void onTouchInteractionEnd_noServiceInteractiveWindow_shouldClearA11yFocus()
+            throws RemoteException {
+        final IBinder defaultFocusWinToken =
+                mWindows.get(Display.DEFAULT_DISPLAY).get(
+                        DEFAULT_FOCUSED_INDEX).getWindowInfo().token;
+        final int defaultFocusWindowId = mA11yWindowManager.findWindowIdLocked(
+                USER_SYSTEM_ID, defaultFocusWinToken);
+        when(mMockWindowManagerInternal.getFocusedWindowTokenFromWindowStates())
+                .thenReturn(defaultFocusWinToken);
+        final int newFocusWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY,
+                DEFAULT_FOCUSED_INDEX + 1);
+        final IAccessibilityInteractionConnection mockNewFocusConnection =
+                mA11yWindowManager.getConnectionLocked(
+                        USER_SYSTEM_ID, newFocusWindowId).getRemote();
+
+        mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY);
+        final int noUse = 0;
+        mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
+                defaultFocusWindowId,
+                noUse,
+                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
+                noUse);
+        assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(defaultFocusWindowId));
+        assertThat(mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT),
+                is(defaultFocusWindowId));
+
+        mA11yWindowManager.onTouchInteractionStart();
+        mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
+                newFocusWindowId,
+                noUse,
+                AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
+                noUse);
+        mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
+                newFocusWindowId,
+                AccessibilityNodeInfo.ROOT_NODE_ID,
+                AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                noUse);
+        assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(newFocusWindowId));
+        assertThat(mA11yWindowManager.getFocusedWindowId(
+                AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(newFocusWindowId));
+
+        mA11yWindowManager.onTouchInteractionEnd();
+        mHandler.sendLastMessage();
+        verify(mockNewFocusConnection).clearAccessibilityFocus();
+    }
+
+    @Test
+    public void getPictureInPictureWindow_shouldNotNull() {
+        assertNull(mA11yWindowManager.getPictureInPictureWindowLocked());
+        mWindows.get(Display.DEFAULT_DISPLAY).get(1).getWindowInfo().inPictureInPicture = true;
+        onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+        assertNotNull(mA11yWindowManager.getPictureInPictureWindowLocked());
+    }
+
+    @Test
+    public void notifyOutsideTouch() throws RemoteException {
+        final int targetWindowId =
+                getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 1);
+        final int outsideWindowId =
+                getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
+        final IAccessibilityInteractionConnection mockRemoteConnection =
+                mA11yWindowManager.getConnectionLocked(
+                        USER_SYSTEM_ID, outsideWindowId).getRemote();
+        mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo().hasFlagWatchOutsideTouch =
+                true;
+        onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+        mA11yWindowManager.notifyOutsideTouch(USER_SYSTEM_ID, targetWindowId);
+        verify(mockRemoteConnection).notifyOutsideTouch();
+    }
+
+    @Test
+    public void addAccessibilityInteractionConnection_profileUser_findInParentUser()
+            throws RemoteException {
+        final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+                false, USER_PROFILE);
+        final int windowId = mA11yWindowManager.findWindowIdLocked(
+                USER_PROFILE_PARENT, token.asBinder());
+        assertTrue(windowId >= 0);
+    }
+
+    @Test
+    public void getDisplayList() throws RemoteException {
+        // Starts tracking window of second display.
+        startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
+
+        final ArrayList<Integer> displayList = mA11yWindowManager.getDisplayListLocked(
+                DISPLAY_TYPE_DEFAULT);
+        assertTrue(displayList.equals(mExpectedDisplayList));
+    }
+
+    @Test
+    public void setAccessibilityWindowIdToSurfaceMetadata()
+            throws RemoteException {
+        final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+                true, USER_SYSTEM_ID);
+        int windowId = -1;
+        for (int i = 0; i < mA11yWindowTokens.size(); i++) {
+            if (mA11yWindowTokens.valueAt(i).equals(token)) {
+                windowId = mA11yWindowTokens.keyAt(i);
+            }
+        }
+        assertNotEquals("Returned token is not found in mA11yWindowTokens", -1, windowId);
+        verify(mMockWindowManagerInternal, times(1)).setAccessibilityIdToSurfaceMetadata(
+                token.asBinder(), windowId);
+
+        mA11yWindowManager.removeAccessibilityInteractionConnection(token);
+        verify(mMockWindowManagerInternal, times(1)).setAccessibilityIdToSurfaceMetadata(
+                token.asBinder(), -1);
+    }
+
+    @Test
+    public void getHostTokenLocked_hierarchiesAreAssociated_shouldReturnHostToken() {
+        mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken);
+        final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken);
+        assertEquals(hostToken, mMockHostToken);
+    }
+
+    @Test
+    public void getHostTokenLocked_hierarchiesAreNotAssociated_shouldReturnNull() {
+        final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken);
+        assertNull(hostToken);
+    }
+
+    @Test
+    public void getHostTokenLocked_embeddedHierarchiesAreDisassociated_shouldReturnNull() {
+        mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken);
+        mA11yWindowManager.disassociateLocked(mMockEmbeddedToken);
+        final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken);
+        assertNull(hostToken);
+    }
+
+    @Test
+    public void getHostTokenLocked_hostHierarchiesAreDisassociated_shouldReturnNull() {
+        mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken);
+        mA11yWindowManager.disassociateLocked(mMockHostToken);
+        final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockHostToken);
+        assertNull(hostToken);
+    }
+
+    @Test
+    public void getWindowIdLocked_windowIsRegistered_shouldReturnWindowId() {
+        final int windowId = mA11yWindowManager.getWindowIdLocked(mMockHostToken);
+        assertEquals(windowId, HOST_WINDOW_ID);
+    }
+
+    @Test
+    public void getWindowIdLocked_windowIsNotRegistered_shouldReturnInvalidWindowId() {
+        final int windowId = mA11yWindowManager.getWindowIdLocked(mMockInvalidToken);
+        assertEquals(windowId, INVALID_ID);
+    }
+
+    @Test
+    public void getTokenLocked_windowIsRegistered_shouldReturnToken() {
+        final IBinder token = mA11yWindowManager.getLeashTokenLocked(HOST_WINDOW_ID);
+        assertEquals(token, mMockHostToken);
+    }
+
+    @Test
+    public void getTokenLocked_windowIsNotRegistered_shouldReturnNull() {
+        final IBinder token = mA11yWindowManager.getLeashTokenLocked(OTHER_WINDOW_ID);
+        assertNull(token);
+    }
+
+    @Test
+    public void setAccessibilityWindowAttributes_windowIsNotRegistered_titleIsChanged() {
+        final int windowId =
+                getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
+        final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
+        layoutParams.accessibilityTitle = "accessibility window title";
+        final AccessibilityWindowAttributes attributes = new AccessibilityWindowAttributes(
+                layoutParams, new LocaleList());
+
+        mA11yWindowManager.setAccessibilityWindowAttributes(Display.DEFAULT_DISPLAY, windowId,
+                USER_SYSTEM_ID, attributes);
+
+        final AccessibilityWindowInfo a11yWindow = mA11yWindowManager.findA11yWindowInfoByIdLocked(
+                windowId);
+        assertEquals(toString(layoutParams.accessibilityTitle), toString(a11yWindow.getTitle()));
+    }
+
+    @Test
+    public void sendAccessibilityEventOnWindowRemoval() {
+        final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
+
+        // Removing index 0 because it's not focused, and avoids unnecessary layer change.
+        final int windowId =
+                getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
+        windows.remove(0);
+
+        onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+
+        final ArgumentCaptor<AccessibilityEvent> captor =
+                ArgumentCaptor.forClass(AccessibilityEvent.class);
+        verify(mMockA11yEventSender, times(1))
+                .sendAccessibilityEventForCurrentUserLocked(captor.capture());
+        assertThat(captor.getAllValues().get(0),
+                allOf(displayId(Display.DEFAULT_DISPLAY),
+                        a11yWindowId(windowId),
+                        a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_REMOVED)));
+    }
+
+    @Test
+    public void sendAccessibilityEventOnWindowAddition() throws RemoteException {
+        final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
+
+        final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+                false, USER_SYSTEM_ID);
+        // Adding window to the front so that other windows' layer won't change.
+        windows.add(0, createMockAccessibilityWindow(token, Display.DEFAULT_DISPLAY));
+        final int windowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
+
+        onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+
+        final ArgumentCaptor<AccessibilityEvent> captor =
+                ArgumentCaptor.forClass(AccessibilityEvent.class);
+        verify(mMockA11yEventSender, times(1))
+                .sendAccessibilityEventForCurrentUserLocked(captor.capture());
+        assertThat(captor.getAllValues().get(0),
+                allOf(displayId(Display.DEFAULT_DISPLAY),
+                        a11yWindowId(windowId),
+                        a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ADDED)));
+    }
+
+    @Test
+    public void sendAccessibilityEventOnWindowChange() {
+        final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
+        windows.get(0).getWindowInfo().title = "new title";
+        final int windowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
+
+        onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+
+        final ArgumentCaptor<AccessibilityEvent> captor =
+                ArgumentCaptor.forClass(AccessibilityEvent.class);
+        verify(mMockA11yEventSender, times(1))
+                .sendAccessibilityEventForCurrentUserLocked(captor.capture());
+        assertThat(captor.getAllValues().get(0),
+                allOf(displayId(Display.DEFAULT_DISPLAY),
+                        a11yWindowId(windowId),
+                        a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_TITLE)));
+    }
+
+    private void registerLeashedTokenAndWindowId() {
+        mA11yWindowManager.registerIdLocked(mMockHostToken, HOST_WINDOW_ID);
+        mA11yWindowManager.registerIdLocked(mMockEmbeddedToken, EMBEDDED_WINDOW_ID);
+    }
+
+    private void startTrackingPerDisplay(int displayId) throws RemoteException {
+        ArrayList<AccessibilityWindow> windowsForDisplay = new ArrayList<>();
+        // Adds RemoteAccessibilityConnection into AccessibilityWindowManager, and copy
+        // mock window token into mA11yWindowTokens. Also, preparing WindowInfo mWindowInfos
+        // for the test.
+        for (int i = 0; i < NUM_GLOBAL_WINDOWS; i++) {
+            final IWindow token = addAccessibilityInteractionConnection(displayId,
+                    true, USER_SYSTEM_ID);
+            windowsForDisplay.add(createMockAccessibilityWindow(token, displayId));
+
+        }
+        for (int i = 0; i < NUM_APP_WINDOWS; i++) {
+            final IWindow token = addAccessibilityInteractionConnection(displayId,
+                    false, USER_SYSTEM_ID);
+            windowsForDisplay.add(createMockAccessibilityWindow(token, displayId));
+        }
+        // Sets up current focused window of display.
+        // Each display has its own current focused window if config_perDisplayFocusEnabled is true.
+        // Otherwise only default display needs to current focused window.
+        if (mSupportPerDisplayFocus || displayId == Display.DEFAULT_DISPLAY) {
+            windowsForDisplay.get(DEFAULT_FOCUSED_INDEX).getWindowInfo().focused = true;
+        }
+        // Turns on windows tracking, and update window info.
+        mA11yWindowManager.startTrackingWindows(displayId, false);
+        // Puts window lists into array.
+        mWindows.put(displayId, windowsForDisplay);
+        // Sets the default display is the top focused display and
+        // its current focused window is the top focused window.
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            setTopFocusedWindowAndDisplay(displayId, DEFAULT_FOCUSED_INDEX);
+        }
+        // Invokes callback for sending window lists to A11y framework.
+        onAccessibilityWindowsChanged(displayId, FORCE_SEND);
+
+        assertEquals(mA11yWindowManager.getWindowListLocked(displayId).size(),
+                windowsForDisplay.size());
+    }
+
+    private WindowsForAccessibilityCallback getWindowsForAccessibilityCallbacks(int displayId) {
+        ArgumentCaptor<WindowsForAccessibilityCallback> windowsForAccessibilityCallbacksCaptor =
+                ArgumentCaptor.forClass(
+                        WindowsForAccessibilityCallback.class);
+        verify(mMockWindowManagerInternal)
+                .setWindowsForAccessibilityCallback(eq(displayId),
+                        windowsForAccessibilityCallbacksCaptor.capture());
+        return windowsForAccessibilityCallbacksCaptor.getValue();
+    }
+
+    private IWindow addAccessibilityInteractionConnection(int displayId, boolean bGlobal,
+            int userId) throws RemoteException {
+        final IWindow mockWindowToken = Mockito.mock(IWindow.class);
+        final IAccessibilityInteractionConnection mockA11yConnection = Mockito.mock(
+                IAccessibilityInteractionConnection.class);
+        final IBinder mockConnectionBinder = Mockito.mock(IBinder.class);
+        final IBinder mockWindowBinder = Mockito.mock(IBinder.class);
+        final IBinder mockLeashToken = Mockito.mock(IBinder.class);
+        when(mockA11yConnection.asBinder()).thenReturn(mockConnectionBinder);
+        when(mockWindowToken.asBinder()).thenReturn(mockWindowBinder);
+        when(mMockA11ySecurityPolicy.isCallerInteractingAcrossUsers(userId))
+                .thenReturn(bGlobal);
+        when(mMockWindowManagerInternal.getDisplayIdForWindow(mockWindowBinder))
+                .thenReturn(displayId);
+
+        int windowId = mA11yWindowManager.addAccessibilityInteractionConnection(
+                mockWindowToken, mockLeashToken, mockA11yConnection, PACKAGE_NAME, userId);
+        mA11yWindowTokens.put(windowId, mockWindowToken);
+        return mockWindowToken;
+    }
+
+    private int addAccessibilityInteractionConnection(int displayId, boolean bGlobal,
+            IBinder leashToken, int userId) throws RemoteException {
+        final IWindow mockWindowToken = Mockito.mock(IWindow.class);
+        final IAccessibilityInteractionConnection mockA11yConnection = Mockito.mock(
+                IAccessibilityInteractionConnection.class);
+        final IBinder mockConnectionBinder = Mockito.mock(IBinder.class);
+        final IBinder mockWindowBinder = Mockito.mock(IBinder.class);
+        when(mockA11yConnection.asBinder()).thenReturn(mockConnectionBinder);
+        when(mockWindowToken.asBinder()).thenReturn(mockWindowBinder);
+        when(mMockA11ySecurityPolicy.isCallerInteractingAcrossUsers(userId))
+                .thenReturn(bGlobal);
+        when(mMockWindowManagerInternal.getDisplayIdForWindow(mockWindowBinder))
+                .thenReturn(displayId);
+
+        int windowId = mA11yWindowManager.addAccessibilityInteractionConnection(
+                mockWindowToken, leashToken, mockA11yConnection, PACKAGE_NAME, userId);
+        mA11yWindowTokens.put(windowId, mockWindowToken);
+        return windowId;
+    }
+
+    private int getWindowIdFromWindowInfosForDisplay(int displayId, int index) {
+        final IBinder windowToken = mWindows.get(displayId).get(index).getWindowInfo().token;
+        return mA11yWindowManager.findWindowIdLocked(
+                USER_SYSTEM_ID, windowToken);
+    }
+
+    private void setTopFocusedWindowAndDisplay(int displayId, int index) {
+        // Sets the top focus window.
+        mTopFocusedWindowToken = mWindows.get(displayId).get(index).getWindowInfo().token;
+        // Sets the top focused display.
+        mTopFocusedDisplayId = displayId;
+    }
+
+    private void onAccessibilityWindowsChanged(int displayId, boolean forceSend) {
+        WindowsForAccessibilityCallback callbacks = mCallbackOfWindows.get(displayId);
+        if (callbacks == null) {
+            callbacks = getWindowsForAccessibilityCallbacks(displayId);
+            mCallbackOfWindows.put(displayId, callbacks);
+        }
+        callbacks.onAccessibilityWindowsChanged(forceSend, mTopFocusedDisplayId,
+                mTopFocusedWindowToken, new Point(SCREEN_WIDTH, SCREEN_HEIGHT),
+                mWindows.get(displayId));
+    }
+
+    private void changeFocusedWindowOnDisplayPerDisplayFocusConfig(
+            int changeFocusedDisplayId, int newFocusedWindowIndex, int oldTopFocusedDisplayId,
+            int oldFocusedWindowIndex) {
+        if (mSupportPerDisplayFocus) {
+            // Gets the old focused window of display which wants to change focused window.
+            WindowInfo focusedWindowInfo =
+                    mWindows.get(changeFocusedDisplayId).get(oldFocusedWindowIndex).getWindowInfo();
+            // Resets the focus of old focused window.
+            focusedWindowInfo.focused = false;
+            // Gets the new window of display which wants to change focused window.
+            focusedWindowInfo =
+                    mWindows.get(changeFocusedDisplayId).get(newFocusedWindowIndex).getWindowInfo();
+            // Sets the focus of new focused window.
+            focusedWindowInfo.focused = true;
+        } else {
+            // Gets the window of display which wants to change focused window.
+            WindowInfo focusedWindowInfo =
+                    mWindows.get(changeFocusedDisplayId).get(newFocusedWindowIndex).getWindowInfo();
+            // Sets the focus of new focused window.
+            focusedWindowInfo.focused = true;
+            // Gets the old focused window of old top focused display.
+            focusedWindowInfo =
+                    mWindows.get(oldTopFocusedDisplayId).get(oldFocusedWindowIndex).getWindowInfo();
+            // Resets the focus of old focused window.
+            focusedWindowInfo.focused = false;
+            // Changes the top focused display and window.
+            setTopFocusedWindowAndDisplay(changeFocusedDisplayId, newFocusedWindowIndex);
+        }
+    }
+
+    private AccessibilityWindow createMockAccessibilityWindow(IWindow windowToken, int displayId) {
+        final WindowInfo windowInfo = WindowInfo.obtain();
+        // TODO(b/325341171): add tests with various kinds of windows such as
+        //  changing window types, touchable or not, trusted or not, etc.
+        windowInfo.type = WindowManager.LayoutParams.TYPE_APPLICATION;
+        windowInfo.token = windowToken.asBinder();
+
+        final AccessibilityWindow window = Mockito.mock(AccessibilityWindow.class);
+        when(window.getWindowInfo()).thenReturn(windowInfo);
+        when(window.ignoreRecentsAnimationForAccessibility()).thenReturn(false);
+        when(window.isFocused()).thenAnswer(invocation -> windowInfo.focused);
+        when(window.isTouchable()).thenReturn(true);
+        when(window.getType()).thenReturn(windowInfo.type);
+
+        setRegionForMockAccessibilityWindow(window, nextToucableRegion(displayId));
+        return window;
+    }
+
+    private void setRegionForMockAccessibilityWindow(AccessibilityWindow window, Region region) {
+        doAnswer(invocation -> {
+            ((Region) invocation.getArgument(0)).set(region);
+            return null;
+        }).when(window).getTouchableRegionInScreen(any(Region.class));
+        doAnswer(invocation -> {
+            ((Region) invocation.getArgument(0)).set(region);
+            return null;
+        }).when(window).getTouchableRegionInWindow(any(Region.class));
+    }
+
+    private Region nextToucableRegion(int displayId) {
+        final int topLeft = mNextRegionOffsets.get(displayId, 0);
+        final int bottomRight = topLeft + 100;
+        mNextRegionOffsets.put(displayId, topLeft + 10);
+        return new Region(topLeft, topLeft, bottomRight, bottomRight);
+    }
+
+    @Nullable
+    private static String toString(@Nullable CharSequence cs) {
+        return cs == null ? null : cs.toString();
+    }
+
+    static class DisplayIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
+        private final int mDisplayId;
+
+        DisplayIdMatcher(int displayId) {
+            super();
+            mDisplayId = displayId;
+        }
+
+        static DisplayIdMatcher displayId(int displayId) {
+            return new DisplayIdMatcher(displayId);
+        }
+
+        @Override
+        protected boolean matchesSafely(AccessibilityEvent event) {
+            return event.getDisplayId() == mDisplayId;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText("Matching to displayId " + mDisplayId);
+        }
+    }
+
+    static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
+        private int mWindowId;
+
+        WindowIdMatcher(int windowId) {
+            super();
+            mWindowId = windowId;
+        }
+
+        static WindowIdMatcher a11yWindowId(int windowId) {
+            return new WindowIdMatcher(windowId);
+        }
+
+        @Override
+        protected boolean matchesSafely(AccessibilityEvent event) {
+            return event.getWindowId() == mWindowId;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText("Matching to windowId " + mWindowId);
+        }
+    }
+
+    static class WindowChangesMatcher extends TypeSafeMatcher<AccessibilityEvent> {
+        private int mWindowChanges;
+
+        WindowChangesMatcher(int windowChanges) {
+            super();
+            mWindowChanges = windowChanges;
+        }
+
+        static WindowChangesMatcher a11yWindowChanges(int windowChanges) {
+            return new WindowChangesMatcher(windowChanges);
+        }
+
+        @Override
+        protected boolean matchesSafely(AccessibilityEvent event) {
+            return event.getWindowChanges() == mWindowChanges;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText("Matching to window changes " + mWindowChanges);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java
index aec3f45..344e2c2 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java
@@ -103,7 +103,7 @@
                 }
 
                 BrailleDisplayConnection.BrailleDisplayScanner scanner =
-                        mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
+                        BrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
 
                 assertThat(scanner.getHidrawNodePaths(testDir.toPath()))
                         .containsExactly(hidrawNode0, hidrawNode1);
@@ -123,7 +123,7 @@
                     descriptor);
 
             BrailleDisplayConnection.BrailleDisplayScanner scanner =
-                    mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
+                    BrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
 
             assertThat(scanner.getDeviceReportDescriptor(NULL_PATH)).isEqualTo(descriptor);
         }
@@ -133,7 +133,7 @@
             when(mNativeInterface.getHidrawDescSize(anyInt())).thenReturn(0);
 
             BrailleDisplayConnection.BrailleDisplayScanner scanner =
-                    mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
+                    BrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
 
             assertThat(scanner.getDeviceReportDescriptor(NULL_PATH)).isNull();
         }
@@ -144,7 +144,7 @@
             when(mNativeInterface.getHidrawUniq(anyInt())).thenReturn(macAddress);
 
             BrailleDisplayConnection.BrailleDisplayScanner scanner =
-                    mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
+                    BrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
 
             assertThat(scanner.getUniqueId(NULL_PATH)).isEqualTo(macAddress);
         }
@@ -155,7 +155,7 @@
                     .thenReturn(BrailleDisplayConnection.BUS_USB);
 
             BrailleDisplayConnection.BrailleDisplayScanner scanner =
-                    mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
+                    BrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
 
             assertThat(scanner.getDeviceBusType(NULL_PATH))
                     .isEqualTo(BrailleDisplayConnection.BUS_USB);
@@ -167,7 +167,7 @@
                     .thenReturn(BrailleDisplayConnection.BUS_BLUETOOTH);
 
             BrailleDisplayConnection.BrailleDisplayScanner scanner =
-                    mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
+                    BrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
 
             assertThat(scanner.getDeviceBusType(NULL_PATH))
                     .isEqualTo(BrailleDisplayConnection.BUS_BLUETOOTH);
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 8c0d44c..7fbd521 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
@@ -189,6 +189,8 @@
     FullScreenMagnificationVibrationHelper mMockFullScreenMagnificationVibrationHelper;
     @Mock
     FullScreenMagnificationGestureHandler.MagnificationLogger mMockMagnificationLogger;
+    @Mock
+    OneFingerPanningSettingsProvider mMockOneFingerPanningSettingsProvider;
 
     @Rule
     public final TestableContext mContext = new TestableContext(getInstrumentation().getContext());
@@ -266,6 +268,7 @@
         mMgh.onDestroy();
         mFullScreenMagnificationController.unregister(DISPLAY_0);
         verify(mWindowMagnificationPromptController).onDestroy();
+        verify(mMockOneFingerPanningSettingsProvider).unregister();
         Settings.Secure.putFloatForUser(mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
                 mOriginalMagnificationPersistedScale,
@@ -288,11 +291,10 @@
                         DISPLAY_0,
                         mMockFullScreenMagnificationVibrationHelper,
                         mMockMagnificationLogger,
-                        ViewConfiguration.get(mContext));
+                        ViewConfiguration.get(mContext),
+                        mMockOneFingerPanningSettingsProvider);
         if (isWatch()) {
-            h.setSinglePanningEnabled(true);
-        } else {
-            h.setSinglePanningEnabled(false);
+            enableOneFingerPanning(true);
         }
         mHandler = new TestHandler(h.mDetectingState, mClock) {
             @Override
@@ -607,8 +609,8 @@
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
     public void testTwoFingerTap_StateIsActivated_shouldInDelegating() {
-        assumeTrue(mMgh.mIsSinglePanningEnabled);
-        mMgh.setSinglePanningEnabled(false);
+        assumeTrue(isWatch());
+        enableOneFingerPanning(false);
         goFromStateIdleTo(STATE_ACTIVATED);
         allowEventDelegation();
 
@@ -623,8 +625,8 @@
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
     public void testTwoFingerTap_StateIsIdle_shouldInDelegating() {
-        assumeTrue(mMgh.mIsSinglePanningEnabled);
-        mMgh.setSinglePanningEnabled(false);
+        assumeTrue(isWatch());
+        enableOneFingerPanning(false);
         goFromStateIdleTo(STATE_IDLE);
         allowEventDelegation();
 
@@ -830,7 +832,7 @@
 
     @Test
     public void testActionUpNotAtEdge_singlePanningState_detectingState() {
-        assumeTrue(mMgh.mIsSinglePanningEnabled);
+        assumeTrue(isWatch());
         goFromStateIdleTo(STATE_SINGLE_PANNING);
 
         send(upEvent());
@@ -841,8 +843,8 @@
 
     @Test
     public void testScroll_SinglePanningDisabled_delegatingState() {
-        assumeTrue(mMgh.mIsSinglePanningEnabled);
-        mMgh.setSinglePanningEnabled(false);
+        assumeTrue(isWatch());
+        enableOneFingerPanning(false);
 
         goFromStateIdleTo(STATE_ACTIVATED);
         allowEventDelegation();
@@ -854,7 +856,7 @@
     @Test
     @FlakyTest
     public void testScroll_singleHorizontalPanningAndAtEdge_leftEdgeOverscroll() {
-        assumeTrue(mMgh.mIsSinglePanningEnabled);
+        assumeTrue(isWatch());
         goFromStateIdleTo(STATE_SINGLE_PANNING);
         float centerY =
                 (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.bottom) / 2.0f;
@@ -878,7 +880,7 @@
     @Test
     @FlakyTest
     public void testScroll_singleHorizontalPanningAndAtEdge_rightEdgeOverscroll() {
-        assumeTrue(mMgh.mIsSinglePanningEnabled);
+        assumeTrue(isWatch());
         goFromStateIdleTo(STATE_SINGLE_PANNING);
         float centerY =
                 (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.bottom) / 2.0f;
@@ -902,7 +904,7 @@
     @Test
     @FlakyTest
     public void testScroll_singleVerticalPanningAndAtEdge_verticalOverscroll() {
-        assumeTrue(mMgh.mIsSinglePanningEnabled);
+        assumeTrue(isWatch());
         goFromStateIdleTo(STATE_SINGLE_PANNING);
         float centerX =
                 (INITIAL_MAGNIFICATION_BOUNDS.right + INITIAL_MAGNIFICATION_BOUNDS.left) / 2.0f;
@@ -924,7 +926,7 @@
 
     @Test
     public void testScroll_singlePanningAndAtEdge_noOverscroll() {
-        assumeTrue(mMgh.mIsSinglePanningEnabled);
+        assumeTrue(isWatch());
         goFromStateIdleTo(STATE_SINGLE_PANNING);
         float centerY =
                 (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.bottom) / 2.0f;
@@ -946,7 +948,7 @@
 
     @Test
     public void testScroll_singleHorizontalPanningAndAtEdge_vibrate() {
-        assumeTrue(mMgh.mIsSinglePanningEnabled);
+        assumeTrue(isWatch());
         goFromStateIdleTo(STATE_SINGLE_PANNING);
         mFullScreenMagnificationController.setCenter(
                 DISPLAY_0,
@@ -970,7 +972,7 @@
 
     @Test
     public void testScroll_singleVerticalPanningAndAtEdge_doNotVibrate() {
-        assumeTrue(mMgh.mIsSinglePanningEnabled);
+        assumeTrue(isWatch());
         goFromStateIdleTo(STATE_SINGLE_PANNING);
         mFullScreenMagnificationController.setCenter(
                 DISPLAY_0,
@@ -993,8 +995,9 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE)
     public void singleFinger_testScrollAfterMagnified_startsFling() {
-        assumeTrue(mMgh.mIsSinglePanningEnabled);
+        assumeTrue(isWatch());
         goFromStateIdleTo(STATE_ACTIVATED);
 
         swipeAndHold();
@@ -1274,6 +1277,10 @@
         mFullScreenMagnificationController.reset(DISPLAY_0, /* animate= */ false);
     }
 
+    private void enableOneFingerPanning(boolean enable) {
+        when(mMockOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()).thenReturn(enable);
+    }
+
     private void assertActionsInOrder(List<MotionEvent> actualEvents,
             List<Integer> expectedActions) {
         assertTrue(actualEvents.size() == expectedActions.size());
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/OneFingerPanningSettingsProviderTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/OneFingerPanningSettingsProviderTest.java
new file mode 100644
index 0000000..ac46ef9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/OneFingerPanningSettingsProviderTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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.accessibility.magnification;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.provider.Settings;
+import android.testing.TestableContext;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.accessibility.magnification.OneFingerPanningSettingsProvider.State;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class OneFingerPanningSettingsProviderTest {
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(getInstrumentation().getContext());
+
+    private boolean mDefaultValue;
+    private boolean mOriginalIsOneFingerPanningEnabled;
+
+    private OneFingerPanningSettingsProvider mProvider;
+
+    @Before
+    public void setup() {
+        mDefaultValue = OneFingerPanningSettingsProvider.isOneFingerPanningEnabledDefault(mContext);
+        mOriginalIsOneFingerPanningEnabled = isSecureSettingsEnabled();
+    }
+
+    @After
+    public void tearDown() {
+        enableSecureSettings(mOriginalIsOneFingerPanningEnabled);
+        if (mProvider != null) {
+            mProvider.unregister();
+        }
+    }
+
+    @Test
+    public void isOneFingerPanningEnabled_flagDisabled_matchesDefault() {
+        mProvider = new OneFingerPanningSettingsProvider(mContext, /* featureFlagEnabled */ false);
+
+        assertThat(mProvider.isOneFingerPanningEnabled()).isEqualTo(mDefaultValue);
+    }
+
+    @Test
+    public void isOneFingerPanningEnabled_flagEnabledSettingEnabled_true() {
+        enableSecureSettings(true);
+        mProvider = new OneFingerPanningSettingsProvider(mContext, /* featureFlagEnabled */ true);
+
+        assertTrue(mProvider.isOneFingerPanningEnabled());
+    }
+
+    @Test
+    public void isOneFingerPanningEnabled_flagEnabledSettingDisabled_false() {
+        enableSecureSettings(false);
+        mProvider = new OneFingerPanningSettingsProvider(mContext, /* featureFlagEnabled */ true);
+
+        assertFalse(mProvider.isOneFingerPanningEnabled());
+    }
+
+    @Test
+    public void isOneFingerPanningEnabled_flagEnabledSettingsFalse_false() {
+        mProvider = new OneFingerPanningSettingsProvider(mContext, /* featureFlagEnabled */ true);
+
+        // Simulate observer triggered.
+        enableSecureSettings(false);
+        mProvider.mObserver.onChange(/* selfChange= */ false);
+
+        assertFalse(mProvider.isOneFingerPanningEnabled());
+    }
+
+    @Test
+    public void isOneFingerPanningEnabled_flagEnabledSettingsTrue_true() {
+        mProvider = new OneFingerPanningSettingsProvider(mContext, /* featureFlagEnabled */ true);
+
+        // Simulate observer triggered.
+        enableSecureSettings(true);
+        mProvider.mObserver.onChange(/* selfChange= */ false);
+
+        assertTrue(mProvider.isOneFingerPanningEnabled());
+    }
+
+    @Test
+    public void isOneFingerPanningEnabled_flagDisabledSettingsChanges_valueUnchanged() {
+        mProvider = new OneFingerPanningSettingsProvider(mContext, /* featureFlagEnabled */ false);
+        var previousValue = mProvider.isOneFingerPanningEnabled();
+
+        enableSecureSettings(!previousValue);
+
+        assertThat(mProvider.isOneFingerPanningEnabled()).isEqualTo(previousValue);
+        assertThat(mProvider.isOneFingerPanningEnabled()).isEqualTo(mDefaultValue);
+    }
+
+    @Test
+    public void unregister_featureEnabled_contentResolverNull() {
+        var provider = new OneFingerPanningSettingsProvider(
+                mContext, /* featureFlagEnabled */ true);
+
+        provider.unregister();
+
+        assertThat(provider.mContentResolver).isNull();
+    }
+
+    @Test
+    public void unregister_featureDisabled_noError() {
+        var provider = new OneFingerPanningSettingsProvider(
+                mContext, /* featureFlagEnabled */ false);
+
+        provider.unregister();
+    }
+
+    private void enableSecureSettings(boolean enable) {
+        Settings.Secure.putIntForUser(
+                mContext.getContentResolver(),
+                OneFingerPanningSettingsProvider.KEY,
+                enable ? State.ON : State.OFF,
+                mContext.getUserId());
+    }
+
+    private boolean isSecureSettingsEnabled() {
+        return State.ON == Settings.Secure.getIntForUser(
+                mContext.getContentResolver(),
+                OneFingerPanningSettingsProvider.KEY,
+                mDefaultValue ? State.ON : State.OFF,
+                mContext.getUserId());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index caa2e36..e0a99b0 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -70,9 +70,10 @@
 import android.os.UserManager;
 import android.test.AndroidTestCase;
 import android.test.mock.MockContext;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.server.LocalServices;
 
 import org.mockito.ArgumentCaptor;
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
index 29a920a..61ac74c 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
@@ -21,7 +21,6 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -31,17 +30,16 @@
 import android.database.Cursor;
 import android.database.sqlite.SQLiteStatement;
 import android.os.Build;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Pair;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
diff --git a/services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java b/services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java
index 08a6529..1acb8ac 100644
--- a/services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java
@@ -24,6 +24,7 @@
 import static com.android.server.adaptiveauth.AdaptiveAuthService.MAX_ALLOWED_FAILED_AUTH_ATTEMPTS;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -32,9 +33,11 @@
 
 import android.app.KeyguardManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.hardware.biometrics.AuthenticationStateListener;
 import android.hardware.biometrics.BiometricManager;
 import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
 import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.InstrumentationRegistry;
@@ -62,6 +65,7 @@
 /**
  * atest FrameworksServicesTests:AdaptiveAuthServiceTest
  */
+@Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class AdaptiveAuthServiceTest {
@@ -103,6 +107,10 @@
         mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
 
         mContext = spy(ApplicationProvider.getApplicationContext());
+
+        assumeTrue("Adaptive auth is disabled on device",
+                !mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE));
+
         when(mContext.getSystemService(BiometricManager.class)).thenReturn(mBiometricManager);
         when(mContext.getSystemService(KeyguardManager.class)).thenReturn(mKeyguardManager);
 
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
index 99eb047..0bf419e 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
@@ -52,7 +52,6 @@
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.server.wm.settings.SettingsSession;
-import android.test.suitebuilder.annotation.LargeTest;
 import android.text.TextUtils;
 import android.util.KeyValueListParser;
 import android.util.Log;
@@ -60,6 +59,7 @@
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.FlakyTest;
+import androidx.test.filters.LargeTest;
 import androidx.test.uiautomator.UiDevice;
 
 import org.junit.Before;
diff --git a/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java b/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java
index 4b359eb..cfcb3dd 100644
--- a/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java
@@ -38,10 +38,10 @@
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.server.wm.settings.SettingsSession;
-import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.SystemUtil;
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 cea10ea..7891661 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -825,7 +825,8 @@
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
         assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* expectLocking= */ true);
         verifyUserUnassignedFromDisplay(TEST_USER_ID1);
@@ -842,7 +843,8 @@
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
         assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback */ null, /* expectLocking= */ false);
@@ -852,19 +854,28 @@
     public void testStopPrivateProfileWithDelayedLocking_flagDisabled() throws Exception {
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         mSetFlagsRule.disableFlags(
                 android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
         setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
         assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback */ null, /* expectLocking= */ true);
 
-        mSetFlagsRule.disableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.disableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         mSetFlagsRule.enableFlags(
                 android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
         setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_PRIVATE);
         assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback */ null, /* expectLocking= */ true);
+
+        mSetFlagsRule.disableFlags(android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+        setUpAndStartProfileInBackground(TEST_USER_ID3, UserManager.USER_TYPE_PROFILE_PRIVATE);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ true,
+                /* keyEvictedCallback */ null, /* expectLocking= */ true);
     }
 
     /** Delayed-locking users (as opposed to devices) have no limits on how many can be unlocked. */
@@ -874,7 +885,8 @@
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
                 /* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false);
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
         setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_MANAGED);
         assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
@@ -890,7 +902,8 @@
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED);
         assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback */ null, /* expectLocking= */ true);
@@ -1329,7 +1342,7 @@
         @Override
         protected int broadcastIntent(Intent intent, String resolvedType,
                 IIntentReceiver resultTo, int resultCode, String resultData, Bundle resultExtras,
-                String[] requiredPermissions, int appOp, Bundle bOptions, boolean ordered,
+                String[] requiredPermissions, int appOp, Bundle bOptions,
                 boolean sticky, int callingPid, int callingUid, int realCallingUid,
                 int realCallingPid, int userId) {
             Log.i(TAG, "broadcastIntentLocked " + intent);
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/ApiCounterTest.kt b/services/tests/servicestests/src/com/android/server/appwidget/ApiCounterTest.kt
new file mode 100644
index 0000000..79766f8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appwidget/ApiCounterTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.appwidget
+
+import android.content.ComponentName
+import com.android.server.appwidget.AppWidgetServiceImpl.ApiCounter
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class ApiCounterTest {
+    private companion object {
+        const val RESET_INTERVAL_MS = 10L
+        const val MAX_CALLS_PER_INTERVAL = 2
+    }
+
+    private var currentTime = 0L
+
+    private val id =
+        AppWidgetServiceImpl.ProviderId(
+            /* uid= */ 123,
+            ComponentName("com.android.server.appwidget", "FakeProviderClass")
+        )
+    private val counter = ApiCounter(RESET_INTERVAL_MS, MAX_CALLS_PER_INTERVAL) { currentTime }
+
+    @Test
+    fun tryApiCall() {
+        for (i in 0 until MAX_CALLS_PER_INTERVAL) {
+            assertThat(counter.tryApiCall(id)).isTrue()
+        }
+        assertThat(counter.tryApiCall(id)).isFalse()
+        currentTime = 5L
+        assertThat(counter.tryApiCall(id)).isFalse()
+        currentTime = 11L
+        assertThat(counter.tryApiCall(id)).isTrue()
+    }
+
+    @Test
+    fun remove() {
+        for (i in 0 until MAX_CALLS_PER_INTERVAL) {
+            assertThat(counter.tryApiCall(id)).isTrue()
+        }
+        assertThat(counter.tryApiCall(id)).isFalse()
+        // remove should cause the call count to be 0 on the next tryApiCall
+        counter.remove(id)
+        assertThat(counter.tryApiCall(id)).isTrue()
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
index 9acc4bd..8a7815e 100644
--- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
@@ -47,11 +47,12 @@
 import android.os.Handler;
 import android.os.UserHandle;
 import android.test.InstrumentationTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.AtomicFile;
 import android.util.Xml;
 import android.widget.RemoteViews;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.frameworks.servicestests.R;
 import com.android.internal.appwidget.IAppWidgetHost;
 import com.android.modules.utils.TypedXmlPullParser;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
index bb00634..fa1fd90 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
@@ -344,6 +344,21 @@
         verifyNoMoreInteractions(mSensorManager);
     }
 
+    @Test
+    public void testAwaitLuxWhenNoLightSensor() {
+        when(mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(null);
+        mProbe = new ALSProbe(mSensorManager, new Handler(mLooper.getLooper()), TIMEOUT_MS - 1);
+
+        AtomicInteger lux = new AtomicInteger(-5);
+        mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+
+        // Verify that no light sensor will be registered.
+        verify(mSensorManager, times(0)).registerListener(
+                mSensorEventListenerCaptor.capture(), any(), anyInt());
+
+        assertThat(lux.get()).isEqualTo(-1);
+    }
+
     private void moveTimeBy(long millis) {
         mLooper.moveTimeForward(millis);
         mLooper.processAllMessages();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
index c8a5583d..3aaac2e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
@@ -16,7 +16,6 @@
 
 package com.android.server.biometrics.sensors.face;
 
-import static android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
 import static android.hardware.face.FaceSensorProperties.TYPE_UNKNOWN;
@@ -235,26 +234,6 @@
     }
 
     @Test
-    public void testAuthenticateInBackground() throws Exception {
-        FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
-                .build();
-        initService();
-        mFaceService.mServiceWrapper.registerAuthenticators(List.of());
-        waitForRegistration();
-
-        mContext.getTestablePermissions().setPermission(
-                USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_DENIED);
-        mContext.getTestablePermissions().setPermission(
-                USE_BACKGROUND_FACE_AUTHENTICATION, PackageManager.PERMISSION_GRANTED);
-
-        final long operationId = 5;
-        mFaceService.mServiceWrapper.authenticateInBackground(mToken, operationId,
-                mFaceServiceReceiver, faceAuthenticateOptions);
-
-        assertThat(faceAuthenticateOptions.getSensorId()).isEqualTo(ID_DEFAULT);
-    }
-
-    @Test
     public void testOptionsForDetect() throws Exception {
         FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
                 .setOpPackageName(ComponentName.unflattenFromString(OP_PACKAGE_NAME)
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java
index 5b81277..c12f13e 100644
--- 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
@@ -25,7 +25,8 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
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
index 9d0c84e..5d4b04f 100644
--- 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
@@ -28,9 +28,9 @@
 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.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.server.biometrics.log.BiometricContext;
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
index 1b4c017..a1e43fb 100644
--- 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
@@ -31,9 +31,9 @@
 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 androidx.test.filters.SmallTest;
 
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
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
index 8d74fd1..9845b58 100644
--- 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
@@ -31,7 +31,8 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
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
index dbbd69b..d6bc73e 100644
--- 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
@@ -28,7 +28,8 @@
 import android.hardware.biometrics.face.ISession;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
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
index fb5502a..e8cb5ad 100644
--- 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
@@ -25,7 +25,8 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
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
index eb8cc9c..b60c845 100644
--- 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
@@ -27,9 +27,9 @@
 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.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.server.biometrics.log.BiometricContext;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
index b9a4fb4..b5d73d2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
@@ -16,8 +16,8 @@
 
 package com.android.server.biometrics.sensors.face.hidl;
 
-import static com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter.ENROLL_TIMEOUT_SEC;
 import static com.android.server.biometrics.sensors.face.hidl.FaceGenerateChallengeClient.CHALLENGE_TIMEOUT_SEC;
+import static com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter.ENROLL_TIMEOUT_SEC;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -45,9 +45,9 @@
 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.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.server.biometrics.HardwareAuthTokenUtils;
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
index 8409619..72b44d4 100644
--- 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
@@ -25,7 +25,8 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
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
index 723f916..b5df836 100644
--- 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
@@ -31,7 +31,8 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapterTest.java
index d723e87..cdf1266 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapterTest.java
@@ -28,7 +28,8 @@
 import android.hardware.keymaster.Timestamp;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
 
diff --git a/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java b/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java
index 01159b1..bcb4877 100644
--- a/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java
@@ -32,7 +32,6 @@
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.server.companion.PackageUtils;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.Test;
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index ef15f60..36b163e 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -173,6 +173,25 @@
     }
 
     @Test
+    public void testGetLoggableChanges() throws Exception {
+        final long disabledChangeId = 1234L;
+        final long enabledLatestChangeId = 2345L;
+        final long enabledOlderChangeId = 3456L;
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                // Disabled changes should not be logged.
+                .addDisabledChangeWithId(disabledChangeId)
+                // A change targeting the latest sdk should be logged.
+                .addEnableSinceSdkChangeWithId(3, enabledLatestChangeId)
+                // A change targeting an old sdk should not be logged.
+                .addEnableSinceSdkChangeWithId(1, enabledOlderChangeId)
+                .build();
+
+        assertThat(compatConfig.getLoggableChanges(
+                ApplicationInfoBuilder.create().withTargetSdk(3).build()))
+                    .asList().containsExactly(enabledLatestChangeId);
+    }
+
+    @Test
     public void testPackageOverrideEnabled() throws Exception {
         CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
                 .addDisabledChangeWithId(1234L)
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
index 0344663..28eee66 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
@@ -36,9 +36,9 @@
 import android.os.Bundle;
 import android.os.UserManager;
 import android.provider.ContactsContract;
-import android.test.suitebuilder.annotation.SmallTest;
 
 import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
 
 import junit.framework.TestCase;
 
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
index a694d5e..95f893b 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
@@ -21,7 +21,8 @@
 import android.os.Bundle;
 import android.os.PersistableBundle;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 /**
  * Test for SyncOperation.
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java
index 5fe60d7..b012aaa 100644
--- a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java
@@ -16,18 +16,26 @@
 
 package com.android.server.contentprotection;
 
+import static android.app.admin.DevicePolicyManager.CONTENT_PROTECTION_DISABLED;
+import static android.app.admin.DevicePolicyManager.CONTENT_PROTECTION_ENABLED;
+import static android.app.admin.DevicePolicyManager.CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY;
+import static android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED;
+
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+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 android.app.admin.DevicePolicyCache;
 import android.app.admin.DevicePolicyManagerInternal;
-import android.content.ContentResolver;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.UserHandle;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.testing.TestableContentResolver;
 import android.testing.TestableContext;
@@ -36,6 +44,9 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.LocalServices;
+
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -78,29 +89,23 @@
     public final TestableContext mTestableContext =
             new TestableContext(ApplicationProvider.getApplicationContext());
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private final TestableContentResolver mTestableContentResolver =
             mTestableContext.getContentResolver();
 
-    @Mock private ContentResolver mMockContentResolver;
-
     @Mock private DevicePolicyManagerInternal mMockDevicePolicyManagerInternal;
 
-    @Test
-    public void constructor_registersContentObserver() {
-        ContentProtectionConsentManager manager =
-                createContentProtectionConsentManager(mMockContentResolver);
+    @Mock private DevicePolicyCache mMockDevicePolicyCache;
 
-        assertThat(manager.mContentObserver).isNotNull();
-        verify(mMockContentResolver)
-                .registerContentObserver(
-                        URI_PACKAGE_VERIFIER_USER_CONSENT,
-                        /* notifyForDescendants= */ false,
-                        manager.mContentObserver,
-                        UserHandle.USER_ALL);
+    @Before
+    public void setup() {
+        setupLocalService(DevicePolicyManagerInternal.class, mMockDevicePolicyManagerInternal);
     }
 
     @Test
-    public void isConsentGranted_packageVerifierNotGranted() {
+    public void isConsentGranted_policyFlagDisabled_packageVerifierNotGranted() {
+        mSetFlagsRule.disableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED);
         ContentProtectionConsentManager manager =
                 createContentProtectionConsentManager(VALUE_FALSE, VALUE_TRUE);
 
@@ -108,10 +113,25 @@
 
         assertThat(actual).isFalse();
         verifyZeroInteractions(mMockDevicePolicyManagerInternal);
+        verifyZeroInteractions(mMockDevicePolicyCache);
     }
 
     @Test
-    public void isConsentGranted_contentProtectionNotGranted() {
+    public void isConsentGranted_policyFlagEnabled_packageVerifierNotGranted() {
+        mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED);
+        ContentProtectionConsentManager manager =
+                createContentProtectionConsentManager(VALUE_FALSE, VALUE_TRUE);
+
+        boolean actual = manager.isConsentGranted(TEST_USER_ID);
+
+        assertThat(actual).isFalse();
+        verifyZeroInteractions(mMockDevicePolicyManagerInternal);
+        verifyZeroInteractions(mMockDevicePolicyCache);
+    }
+
+    @Test
+    public void isConsentGranted_policyFlagDisabled_contentProtectionNotGranted() {
+        mSetFlagsRule.disableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED);
         ContentProtectionConsentManager manager =
                 createContentProtectionConsentManager(VALUE_TRUE, VALUE_FALSE);
 
@@ -119,10 +139,12 @@
 
         assertThat(actual).isFalse();
         verifyZeroInteractions(mMockDevicePolicyManagerInternal);
+        verifyZeroInteractions(mMockDevicePolicyCache);
     }
 
     @Test
-    public void isConsentGranted_packageVerifierGranted_userNotManaged() {
+    public void isConsentGranted_policyFlagDisabled_packageVerifierGranted_userNotManaged() {
+        mSetFlagsRule.disableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED);
         ContentProtectionConsentManager manager =
                 createContentProtectionConsentManager(VALUE_TRUE, VALUE_TRUE);
 
@@ -130,10 +152,12 @@
 
         assertThat(actual).isTrue();
         verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
+        verifyZeroInteractions(mMockDevicePolicyCache);
     }
 
     @Test
-    public void isConsentGranted_packageVerifierGranted_userManaged() {
+    public void isConsentGranted_policyFlagDisabled_packageVerifierGranted_userManaged() {
+        mSetFlagsRule.disableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED);
         when(mMockDevicePolicyManagerInternal.isUserOrganizationManaged(TEST_USER_ID))
                 .thenReturn(true);
         ContentProtectionConsentManager manager =
@@ -142,21 +166,110 @@
         boolean actual = manager.isConsentGranted(TEST_USER_ID);
 
         assertThat(actual).isFalse();
+        verifyZeroInteractions(mMockDevicePolicyCache);
     }
 
     @Test
-    public void isConsentGranted_packageVerifierDefault() {
+    public void isConsentGranted_policyFlagEnabled_packageVerifierGranted_userNotManaged_contentProtectionNotGranted() {
+        mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED);
         ContentProtectionConsentManager manager =
-                createContentProtectionConsentManager(VALUE_DEFAULT, VALUE_TRUE);
+                createContentProtectionConsentManager(VALUE_TRUE, VALUE_FALSE);
 
         boolean actual = manager.isConsentGranted(TEST_USER_ID);
 
         assertThat(actual).isFalse();
-        verifyZeroInteractions(mMockDevicePolicyManagerInternal);
+        verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
+        verifyZeroInteractions(mMockDevicePolicyCache);
     }
 
     @Test
-    public void isConsentGranted_contentProtectionDefault() {
+    public void isConsentGranted_policyFlagEnabled_packageVerifierGranted_userNotManaged_contentProtectionGranted() {
+        mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED);
+        ContentProtectionConsentManager manager =
+                createContentProtectionConsentManager(VALUE_TRUE, VALUE_TRUE);
+
+        boolean actual = manager.isConsentGranted(TEST_USER_ID);
+
+        assertThat(actual).isTrue();
+        verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
+        verifyZeroInteractions(mMockDevicePolicyCache);
+    }
+
+    @Test
+    public void isConsentGranted_policyFlagEnabled_packageVerifierGranted_userManaged_policyDisabled() {
+        mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED);
+        when(mMockDevicePolicyManagerInternal.isUserOrganizationManaged(TEST_USER_ID))
+                .thenReturn(true);
+        when(mMockDevicePolicyCache.getContentProtectionPolicy(TEST_USER_ID))
+                .thenReturn(CONTENT_PROTECTION_DISABLED);
+        ContentProtectionConsentManager manager =
+                createContentProtectionConsentManager(VALUE_TRUE, VALUE_TRUE);
+
+        boolean actual = manager.isConsentGranted(TEST_USER_ID);
+
+        assertThat(actual).isFalse();
+        verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
+        verify(mMockDevicePolicyCache).getContentProtectionPolicy(TEST_USER_ID);
+    }
+
+    @Test
+    public void isConsentGranted_policyFlagEnabled_packageVerifierGranted_userManaged_policyEnabled() {
+        mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED);
+        when(mMockDevicePolicyManagerInternal.isUserOrganizationManaged(TEST_USER_ID))
+                .thenReturn(true);
+        when(mMockDevicePolicyCache.getContentProtectionPolicy(TEST_USER_ID))
+                .thenReturn(CONTENT_PROTECTION_ENABLED);
+        ContentProtectionConsentManager manager =
+                createContentProtectionConsentManager(VALUE_TRUE, VALUE_FALSE);
+
+        boolean actual = manager.isConsentGranted(TEST_USER_ID);
+
+        assertThat(actual).isTrue();
+        verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
+        verify(mMockDevicePolicyCache).getContentProtectionPolicy(TEST_USER_ID);
+    }
+
+    @Test
+    public void isConsentGranted_policyFlagEnabled_packageVerifierGranted_userManaged_policyNotControlled_contentProtectionGranted() {
+        mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED);
+        when(mMockDevicePolicyManagerInternal.isUserOrganizationManaged(TEST_USER_ID))
+                .thenReturn(true);
+        when(mMockDevicePolicyCache.getContentProtectionPolicy(TEST_USER_ID))
+                .thenReturn(CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY);
+        ContentProtectionConsentManager manager =
+                createContentProtectionConsentManager(VALUE_TRUE, VALUE_TRUE);
+
+        boolean actual = manager.isConsentGranted(TEST_USER_ID);
+
+        assertThat(actual).isTrue();
+        verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
+        verify(mMockDevicePolicyCache).getContentProtectionPolicy(TEST_USER_ID);
+    }
+
+    @Test
+    public void isConsentGranted_policyFlagEnabled_packageVerifierGranted_userManaged_policyNotControlled_contentProtectionNotGranted() {
+        mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED);
+        when(mMockDevicePolicyManagerInternal.isUserOrganizationManaged(TEST_USER_ID))
+                .thenReturn(true);
+        when(mMockDevicePolicyCache.getContentProtectionPolicy(TEST_USER_ID))
+                .thenReturn(CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY);
+        ContentProtectionConsentManager manager =
+                createContentProtectionConsentManager(VALUE_TRUE, VALUE_FALSE);
+
+        boolean actual = manager.isConsentGranted(TEST_USER_ID);
+
+        assertThat(actual).isFalse();
+        verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
+        verify(mMockDevicePolicyCache).getContentProtectionPolicy(TEST_USER_ID);
+    }
+
+    @Test
+    public void isConsentGranted_policyFlagEnabled_packageVerifierGranted_userManaged_policyNotControlled_contentProtectionDefault() {
+        mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED);
+        when(mMockDevicePolicyManagerInternal.isUserOrganizationManaged(TEST_USER_ID))
+                .thenReturn(true);
+        when(mMockDevicePolicyCache.getContentProtectionPolicy(TEST_USER_ID))
+                .thenReturn(CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY);
         ContentProtectionConsentManager manager =
                 createContentProtectionConsentManager(VALUE_TRUE, VALUE_DEFAULT);
 
@@ -164,60 +277,150 @@
 
         assertThat(actual).isTrue();
         verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
+        verify(mMockDevicePolicyCache).getContentProtectionPolicy(TEST_USER_ID);
     }
 
     @Test
-    public void contentObserver_packageVerifier() {
+    public void isConsentGranted_policyFlagDisabled_packageVerifierDefault() {
+        mSetFlagsRule.disableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED);
         ContentProtectionConsentManager manager =
-                createContentProtectionConsentManager(VALUE_TRUE, VALUE_DEFAULT);
-        boolean firstActual = manager.isConsentGranted(TEST_USER_ID);
+                createContentProtectionConsentManager(VALUE_DEFAULT, VALUE_TRUE);
 
-        notifyContentObserver(
-                manager,
-                URI_PACKAGE_VERIFIER_USER_CONSENT,
-                KEY_PACKAGE_VERIFIER_USER_CONSENT,
-                VALUE_FALSE);
-        boolean secondActual = manager.isConsentGranted(TEST_USER_ID);
+        boolean actual = manager.isConsentGranted(TEST_USER_ID);
 
-        assertThat(firstActual).isTrue();
-        assertThat(secondActual).isFalse();
-        verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
+        assertThat(actual).isFalse();
+        verifyZeroInteractions(mMockDevicePolicyManagerInternal);
+        verifyZeroInteractions(mMockDevicePolicyCache);
     }
 
     @Test
-    public void contentObserver_contentProtection() {
+    public void isConsentGranted_policyFlagEnabled_packageVerifierDefault() {
+        mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED);
         ContentProtectionConsentManager manager =
-                createContentProtectionConsentManager(VALUE_TRUE, VALUE_DEFAULT);
-        boolean firstActual = manager.isConsentGranted(TEST_USER_ID);
+                createContentProtectionConsentManager(VALUE_DEFAULT, VALUE_TRUE);
 
-        notifyContentObserver(
-                manager,
-                URI_CONTENT_PROTECTION_USER_CONSENT,
-                KEY_CONTENT_PROTECTION_USER_CONSENT,
-                VALUE_FALSE);
-        boolean secondActual = manager.isConsentGranted(TEST_USER_ID);
+        boolean actual = manager.isConsentGranted(TEST_USER_ID);
 
-        assertThat(firstActual).isTrue();
-        assertThat(secondActual).isFalse();
-        verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
+        assertThat(actual).isFalse();
+        verifyZeroInteractions(mMockDevicePolicyManagerInternal);
+        verifyZeroInteractions(mMockDevicePolicyCache);
     }
 
-    private void notifyContentObserver(
-            ContentProtectionConsentManager manager, Uri uri, String key, int value) {
+    @Test
+    public void isConsentGranted_policyFlagDisabled_contentProtectionDefault() {
+        mSetFlagsRule.disableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED);
+        ContentProtectionConsentManager manager =
+                createContentProtectionConsentManager(VALUE_TRUE, VALUE_DEFAULT);
+
+        boolean actual = manager.isConsentGranted(TEST_USER_ID);
+
+        assertThat(actual).isTrue();
+        verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
+        verifyZeroInteractions(mMockDevicePolicyCache);
+    }
+
+    @Test
+    public void contentObserver_policyFlagDisabled_packageVerifier() {
+        mSetFlagsRule.disableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED);
+        ContentProtectionConsentManager manager =
+                createContentProtectionConsentManager(VALUE_FALSE, VALUE_TRUE);
+
+        boolean firstActual = manager.isConsentGranted(TEST_USER_ID);
+        assertThat(firstActual).isFalse();
+        verify(mMockDevicePolicyManagerInternal, never()).isUserOrganizationManaged(anyInt());
+
+        putGlobalSettings(KEY_PACKAGE_VERIFIER_USER_CONSENT, VALUE_TRUE);
+        boolean secondActual = manager.isConsentGranted(TEST_USER_ID);
+        assertThat(secondActual).isFalse();
+        verify(mMockDevicePolicyManagerInternal, never()).isUserOrganizationManaged(anyInt());
+
+        notifyContentObserver(manager, URI_PACKAGE_VERIFIER_USER_CONSENT);
+        boolean thirdActual = manager.isConsentGranted(TEST_USER_ID);
+        assertThat(thirdActual).isTrue();
+        verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
+
+        verifyZeroInteractions(mMockDevicePolicyCache);
+    }
+
+    @Test
+    public void contentObserver_policyFlagEnabled_packageVerifier() {
+        mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED);
+        ContentProtectionConsentManager manager =
+                createContentProtectionConsentManager(VALUE_FALSE, VALUE_TRUE);
+
+        boolean firstActual = manager.isConsentGranted(TEST_USER_ID);
+        assertThat(firstActual).isFalse();
+        verify(mMockDevicePolicyManagerInternal, never()).isUserOrganizationManaged(anyInt());
+
+        putGlobalSettings(KEY_PACKAGE_VERIFIER_USER_CONSENT, VALUE_TRUE);
+        boolean secondActual = manager.isConsentGranted(TEST_USER_ID);
+        assertThat(secondActual).isFalse();
+        verify(mMockDevicePolicyManagerInternal, never()).isUserOrganizationManaged(anyInt());
+
+        notifyContentObserver(manager, URI_PACKAGE_VERIFIER_USER_CONSENT);
+        boolean thirdActual = manager.isConsentGranted(TEST_USER_ID);
+        assertThat(thirdActual).isTrue();
+        verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
+
+        verifyZeroInteractions(mMockDevicePolicyCache);
+    }
+
+    @Test
+    public void contentObserver_policyFlagDisabled_contentProtection() {
+        mSetFlagsRule.disableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED);
+        ContentProtectionConsentManager manager =
+                createContentProtectionConsentManager(VALUE_TRUE, VALUE_FALSE);
+
+        boolean firstActual = manager.isConsentGranted(TEST_USER_ID);
+        assertThat(firstActual).isFalse();
+        verify(mMockDevicePolicyManagerInternal, never()).isUserOrganizationManaged(anyInt());
+
+        putGlobalSettings(KEY_CONTENT_PROTECTION_USER_CONSENT, VALUE_TRUE);
+        boolean secondActual = manager.isConsentGranted(TEST_USER_ID);
+        assertThat(secondActual).isFalse();
+        verify(mMockDevicePolicyManagerInternal, never()).isUserOrganizationManaged(anyInt());
+
+        notifyContentObserver(manager, URI_CONTENT_PROTECTION_USER_CONSENT);
+        boolean thirdActual = manager.isConsentGranted(TEST_USER_ID);
+        assertThat(thirdActual).isTrue();
+        verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
+
+        verifyZeroInteractions(mMockDevicePolicyCache);
+    }
+
+    @Test
+    public void contentObserver_policyFlagEnabled_contentProtection() {
+        mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED);
+        ContentProtectionConsentManager manager =
+                createContentProtectionConsentManager(VALUE_TRUE, VALUE_FALSE);
+
+        boolean firstActual = manager.isConsentGranted(TEST_USER_ID);
+        assertThat(firstActual).isFalse();
+        verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
+
+        putGlobalSettings(KEY_CONTENT_PROTECTION_USER_CONSENT, VALUE_TRUE);
+        boolean secondActual = manager.isConsentGranted(TEST_USER_ID);
+        assertThat(secondActual).isFalse();
+        verify(mMockDevicePolicyManagerInternal, times(2)).isUserOrganizationManaged(TEST_USER_ID);
+
+        notifyContentObserver(manager, URI_CONTENT_PROTECTION_USER_CONSENT);
+        boolean thirdActual = manager.isConsentGranted(TEST_USER_ID);
+        assertThat(thirdActual).isTrue();
+        verify(mMockDevicePolicyManagerInternal, times(3)).isUserOrganizationManaged(TEST_USER_ID);
+
+        verifyZeroInteractions(mMockDevicePolicyCache);
+    }
+
+    private void putGlobalSettings(String key, int value) {
         Settings.Global.putInt(mTestableContentResolver, key, value);
+    }
+
+    private void notifyContentObserver(ContentProtectionConsentManager manager, Uri uri) {
         // Observer has to be called manually, mTestableContentResolver is not propagating
         manager.mContentObserver.onChange(/* selfChange= */ false, uri, TEST_USER_ID);
     }
 
     private ContentProtectionConsentManager createContentProtectionConsentManager(
-            ContentResolver contentResolver) {
-        return new ContentProtectionConsentManager(
-                new Handler(Looper.getMainLooper()),
-                contentResolver,
-                mMockDevicePolicyManagerInternal);
-    }
-
-    private ContentProtectionConsentManager createContentProtectionConsentManager(
             int valuePackageVerifierUserConsent, int valueContentProtectionUserConsent) {
         Settings.Global.putInt(
                 mTestableContentResolver,
@@ -227,6 +430,14 @@
                 mTestableContentResolver,
                 KEY_CONTENT_PROTECTION_USER_CONSENT,
                 valueContentProtectionUserConsent);
-        return createContentProtectionConsentManager(mTestableContentResolver);
+        return new ContentProtectionConsentManager(
+                new Handler(Looper.getMainLooper()),
+                mTestableContentResolver,
+                mMockDevicePolicyCache);
+    }
+
+    private <T> void setupLocalService(Class<T> clazz, T service) {
+        LocalServices.removeServiceForTest(clazz);
+        LocalServices.addService(clazz, service);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyConstantsTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyConstantsTest.java
index 9660d6b..5f60ad9 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyConstantsTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyConstantsTest.java
@@ -17,7 +17,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index 1dd64ff..5582e13 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -145,6 +145,7 @@
 
     @SmallTest
     @Test
+    @Ignore("b/277916462")
     public void testCompMigrationUnAffiliated_skipped() throws Exception {
         prepareAdmin1AsDo();
         prepareAdminAnotherPackageAsPo(COPE_PROFILE_USER_ID);
@@ -216,6 +217,7 @@
 
     @SmallTest
     @Test
+    @Ignore("b/277916462")
     public void testCompMigration_keepSuspendedAppsWhenDpcIsRPlus() throws Exception {
         prepareAdmin1AsDo();
         prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.R);
@@ -249,6 +251,7 @@
 
     @SmallTest
     @Test
+    @Ignore("b/277916462")
     public void testCompMigration_unsuspendAppsWhenDpcNotRPlus() throws Exception {
         prepareAdmin1AsDo();
         prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.Q);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
index d55f379..8a9538f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
@@ -37,7 +37,8 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.test.TestLooper;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
index 375b52d..e81231a 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
@@ -25,8 +25,8 @@
 
 import android.content.ComponentName;
 import android.os.IpcDataCache;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
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 b705077..733f056 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -16,7 +16,7 @@
 
 package com.android.server.devicestate;
 
-import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
 
 import static com.android.compatibility.common.util.PollingCheck.waitFor;
 
@@ -48,8 +48,6 @@
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowProcessController;
 
-import junit.framework.Assert;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -57,6 +55,8 @@
 import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Optional;
 
 import javax.annotation.Nullable;
@@ -69,21 +69,25 @@
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public final class DeviceStateManagerServiceTest {
-    private static final DeviceState DEFAULT_DEVICE_STATE =
-            new DeviceState(0, "DEFAULT", 0 /* flags */);
-    private static final DeviceState OTHER_DEVICE_STATE =
-            new DeviceState(1, "OTHER", 0 /* flags */);
-    private static final DeviceState
-            DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
-            new DeviceState(2, "DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP",
-                    DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP /* flags */);
-    // A device state that is not reported as being supported for the default test provider.
-    private static final DeviceState UNSUPPORTED_DEVICE_STATE =
-            new DeviceState(255, "UNSUPPORTED", 0 /* flags */);
+    private static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(
+            new DeviceState.Configuration.Builder(0, "DEFAULT").build());
+    private static final DeviceState OTHER_DEVICE_STATE = new DeviceState(
+            new DeviceState.Configuration.Builder(1, "DEFAULT").build());
+    private static final DeviceState DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
+            new DeviceState(new DeviceState.Configuration.Builder(2,
+                    "DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP")
+                    .setSystemProperties(new HashSet<>(List.of(
+                            DeviceState.PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP)))
+                    .build());
 
-    private static final int[] SUPPORTED_DEVICE_STATE_IDENTIFIERS =
-            new int[]{DEFAULT_DEVICE_STATE.getIdentifier(), OTHER_DEVICE_STATE.getIdentifier(),
-                    DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP.getIdentifier()};
+    // A device state that is not reported as being supported for the default test provider.
+    private static final DeviceState UNSUPPORTED_DEVICE_STATE = new DeviceState(
+            new DeviceState.Configuration.Builder(255, "UNSUPPORTED")
+                    .build());
+
+    private static final List<DeviceState> SUPPORTED_DEVICE_STATES = Arrays.asList(
+            DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE,
+            DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
 
     private static final int FAKE_PROCESS_ID = 100;
 
@@ -201,9 +205,8 @@
 
     @Test
     public void baseStateChanged_invalidState() {
-        assertThrows(IllegalArgumentException.class, () -> {
-            mProvider.setState(INVALID_DEVICE_STATE);
-        });
+        assertThrows(IllegalArgumentException.class,
+                () -> mProvider.setState(INVALID_DEVICE_STATE_IDENTIFIER));
 
         assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
         assertEquals(mService.getPendingState(), Optional.empty());
@@ -224,7 +227,7 @@
         assertEquals(mSysPropSetter.getValue(),
                 DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
         assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
-        assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
+        assertThat(mService.getSupportedStates()).containsExactly(DEFAULT_DEVICE_STATE,
                 OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
 
         mProvider.notifySupportedDeviceStates(new DeviceState[]{DEFAULT_DEVICE_STATE});
@@ -237,10 +240,9 @@
         assertEquals(mSysPropSetter.getValue(),
                 DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
         assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
-        assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE);
+        assertThat(mService.getSupportedStates()).containsExactly(DEFAULT_DEVICE_STATE);
 
-        assertArrayEquals(callback.getLastNotifiedInfo().supportedStates,
-                new int[]{DEFAULT_DEVICE_STATE.getIdentifier()});
+        assertEquals(callback.getLastNotifiedInfo().supportedStates, List.of(DEFAULT_DEVICE_STATE));
     }
 
     @Test
@@ -257,7 +259,7 @@
         assertEquals(mSysPropSetter.getValue(),
                 DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
         assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
-        assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
+        assertThat(mService.getSupportedStates()).containsExactly(DEFAULT_DEVICE_STATE,
                 OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
 
         mProvider.notifySupportedDeviceStates(new DeviceState[]{DEFAULT_DEVICE_STATE,
@@ -271,7 +273,7 @@
         assertEquals(mSysPropSetter.getValue(),
                 DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
         assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
-        assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
+        assertThat(mService.getSupportedStates()).containsExactly(DEFAULT_DEVICE_STATE,
                 OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
 
         // The callback wasn't notified about a change in supported states as the states have not
@@ -283,9 +285,9 @@
     public void getDeviceStateInfo() throws RemoteException {
         DeviceStateInfo info = mService.getBinderService().getDeviceStateInfo();
         assertNotNull(info);
-        assertArrayEquals(info.supportedStates, SUPPORTED_DEVICE_STATE_IDENTIFIERS);
-        assertEquals(info.baseState, DEFAULT_DEVICE_STATE.getIdentifier());
-        assertEquals(info.currentState, DEFAULT_DEVICE_STATE.getIdentifier());
+        assertEquals(info.supportedStates, SUPPORTED_DEVICE_STATES);
+        assertEquals(info.baseState, DEFAULT_DEVICE_STATE);
+        assertEquals(info.currentState, DEFAULT_DEVICE_STATE);
     }
 
     @FlakyTest(bugId = 297949293)
@@ -299,9 +301,9 @@
 
         DeviceStateInfo info = mService.getBinderService().getDeviceStateInfo();
 
-        assertArrayEquals(info.supportedStates, SUPPORTED_DEVICE_STATE_IDENTIFIERS);
-        assertEquals(info.baseState, INVALID_DEVICE_STATE);
-        assertEquals(info.currentState, INVALID_DEVICE_STATE);
+        assertEquals(info.supportedStates, SUPPORTED_DEVICE_STATES);
+        assertEquals(info.baseState.getIdentifier(), INVALID_DEVICE_STATE_IDENTIFIER);
+        assertEquals(info.currentState.getIdentifier(), INVALID_DEVICE_STATE_IDENTIFIER);
     }
 
     @Test
@@ -310,33 +312,33 @@
         mService.getBinderService().registerCallback(callback);
 
         mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
-        waitAndAssert(() -> callback.getLastNotifiedInfo().baseState
+        waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
                 == OTHER_DEVICE_STATE.getIdentifier());
-        waitAndAssert(() -> callback.getLastNotifiedInfo().currentState
+        waitAndAssert(() -> callback.getLastNotifiedInfo().currentState.getIdentifier()
                 == OTHER_DEVICE_STATE.getIdentifier());
 
         mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier());
-        waitAndAssert(() -> callback.getLastNotifiedInfo().baseState
+        waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
                 == DEFAULT_DEVICE_STATE.getIdentifier());
 
-        waitAndAssert(() -> callback.getLastNotifiedInfo().currentState
+        waitAndAssert(() -> callback.getLastNotifiedInfo().currentState.getIdentifier()
                 == DEFAULT_DEVICE_STATE.getIdentifier());
 
         mPolicy.blockConfigure();
         mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
         // The callback should not have been notified of the state change as the policy is still
         // pending callback.
-        waitAndAssert(() -> callback.getLastNotifiedInfo().baseState
+        waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
                 == DEFAULT_DEVICE_STATE.getIdentifier());
-        waitAndAssert(() -> callback.getLastNotifiedInfo().currentState
+        waitAndAssert(() -> callback.getLastNotifiedInfo().currentState.getIdentifier()
                 == DEFAULT_DEVICE_STATE.getIdentifier());
 
         mPolicy.resumeConfigure();
         // Now that the policy is finished processing the callback should be notified of the state
         // change.
-        waitAndAssert(() -> callback.getLastNotifiedInfo().baseState
+        waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
                 == OTHER_DEVICE_STATE.getIdentifier());
-        waitAndAssert(() -> callback.getLastNotifiedInfo().currentState
+        waitAndAssert(() -> callback.getLastNotifiedInfo().currentState.getIdentifier()
                 == OTHER_DEVICE_STATE.getIdentifier());
     }
 
@@ -346,10 +348,8 @@
         mService.getBinderService().registerCallback(callback);
         flushHandler();
         assertNotNull(callback.getLastNotifiedInfo());
-        assertEquals(callback.getLastNotifiedInfo().baseState,
-                DEFAULT_DEVICE_STATE.getIdentifier());
-        assertEquals(callback.getLastNotifiedInfo().currentState,
-                DEFAULT_DEVICE_STATE.getIdentifier());
+        assertEquals(callback.getLastNotifiedInfo().baseState, DEFAULT_DEVICE_STATE);
+        assertEquals(callback.getLastNotifiedInfo().currentState, DEFAULT_DEVICE_STATE);
     }
 
     @Test
@@ -392,10 +392,8 @@
                 OTHER_DEVICE_STATE.getIdentifier());
 
         assertNotNull(callback.getLastNotifiedInfo());
-        assertEquals(callback.getLastNotifiedInfo().baseState,
-                DEFAULT_DEVICE_STATE.getIdentifier());
-        assertEquals(callback.getLastNotifiedInfo().currentState,
-                OTHER_DEVICE_STATE.getIdentifier());
+        assertEquals(callback.getLastNotifiedInfo().baseState, DEFAULT_DEVICE_STATE);
+        assertEquals(callback.getLastNotifiedInfo().currentState, OTHER_DEVICE_STATE);
 
         mService.getBinderService().cancelStateRequest();
 
@@ -410,10 +408,8 @@
         assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
                 DEFAULT_DEVICE_STATE.getIdentifier());
 
-        assertEquals(callback.getLastNotifiedInfo().baseState,
-                DEFAULT_DEVICE_STATE.getIdentifier());
-        assertEquals(callback.getLastNotifiedInfo().currentState,
-                DEFAULT_DEVICE_STATE.getIdentifier());
+        assertEquals(callback.getLastNotifiedInfo().baseState, DEFAULT_DEVICE_STATE);
+        assertEquals(callback.getLastNotifiedInfo().currentState, DEFAULT_DEVICE_STATE);
     }
 
     @FlakyTest(bugId = 200332057)
@@ -636,7 +632,8 @@
         assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
                 OTHER_DEVICE_STATE.getIdentifier());
 
-        mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE });
+        mProvider.notifySupportedDeviceStates(
+                new DeviceState[]{DEFAULT_DEVICE_STATE});
         flushHandler();
 
         // Request is canceled because the state is no longer supported.
@@ -672,7 +669,8 @@
 
         assertThrows(IllegalArgumentException.class, () -> {
             final IBinder token = new Binder();
-            mService.getBinderService().requestState(token, INVALID_DEVICE_STATE, 0 /* flags */);
+            mService.getBinderService().requestState(token, INVALID_DEVICE_STATE_IDENTIFIER,
+                    0 /* flags */);
         });
     }
 
@@ -712,10 +710,8 @@
                 OTHER_DEVICE_STATE.getIdentifier());
 
         assertNotNull(callback.getLastNotifiedInfo());
-        assertEquals(callback.getLastNotifiedInfo().baseState,
-                OTHER_DEVICE_STATE.getIdentifier());
-        assertEquals(callback.getLastNotifiedInfo().currentState,
-                OTHER_DEVICE_STATE.getIdentifier());
+        assertEquals(callback.getLastNotifiedInfo().baseState, OTHER_DEVICE_STATE);
+        assertEquals(callback.getLastNotifiedInfo().currentState, OTHER_DEVICE_STATE);
 
         mService.getBinderService().cancelBaseStateOverride();
 
@@ -731,19 +727,19 @@
         assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
                 DEFAULT_DEVICE_STATE.getIdentifier());
 
-        waitAndAssert(() -> callback.getLastNotifiedInfo().baseState
+        waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
                 == DEFAULT_DEVICE_STATE.getIdentifier());
-        assertEquals(callback.getLastNotifiedInfo().currentState,
-                DEFAULT_DEVICE_STATE.getIdentifier());
+        assertEquals(callback.getLastNotifiedInfo().currentState, DEFAULT_DEVICE_STATE);
     }
 
     @Test
     public void requestBaseStateOverride_cancelledByBaseStateUpdate() throws RemoteException {
-        final DeviceState testDeviceState = new DeviceState(2, "TEST", 0);
+        final DeviceState testDeviceState = new DeviceState(new DeviceState.Configuration.Builder(2,
+                "TEST").build());
         TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
         mService.getBinderService().registerCallback(callback);
-        mProvider.notifySupportedDeviceStates(
-                new DeviceState[]{DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE, testDeviceState });
+        mProvider.notifySupportedDeviceStates(new DeviceState[]{DEFAULT_DEVICE_STATE,
+                OTHER_DEVICE_STATE, testDeviceState});
         flushHandler();
 
         final IBinder token = new Binder();
@@ -767,10 +763,8 @@
                 OTHER_DEVICE_STATE.getIdentifier());
 
         assertNotNull(callback.getLastNotifiedInfo());
-        assertEquals(callback.getLastNotifiedInfo().baseState,
-                OTHER_DEVICE_STATE.getIdentifier());
-        assertEquals(callback.getLastNotifiedInfo().currentState,
-                OTHER_DEVICE_STATE.getIdentifier());
+        assertEquals(callback.getLastNotifiedInfo().baseState, OTHER_DEVICE_STATE);
+        assertEquals(callback.getLastNotifiedInfo().currentState, OTHER_DEVICE_STATE);
 
         mProvider.setState(testDeviceState.getIdentifier());
 
@@ -786,10 +780,9 @@
         assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
                 testDeviceState.getIdentifier());
 
-        waitAndAssert(() -> callback.getLastNotifiedInfo().baseState
+        waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
                 == testDeviceState.getIdentifier());
-        assertEquals(callback.getLastNotifiedInfo().currentState,
-                testDeviceState.getIdentifier());
+        assertEquals(callback.getLastNotifiedInfo().currentState, testDeviceState);
     }
 
     @Test
@@ -811,8 +804,8 @@
 
         assertThrows(IllegalArgumentException.class, () -> {
             final IBinder token = new Binder();
-            mService.getBinderService().requestBaseStateOverride(token, INVALID_DEVICE_STATE,
-                    0 /* flags */);
+            mService.getBinderService().requestBaseStateOverride(token,
+                    INVALID_DEVICE_STATE_IDENTIFIER, 0 /* flags */);
         });
     }
 
@@ -826,10 +819,6 @@
         });
     }
 
-    private static void assertArrayEquals(int[] expected, int[] actual) {
-        Assert.assertTrue(Arrays.equals(expected, actual));
-    }
-
     /**
      * Common code to verify the handling of FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP flag.
      *
@@ -892,7 +881,8 @@
      * @param isOverrideState whether a state override is active.
      */
     private void assertDeviceStateConditions(
-            DeviceState state, DeviceState baseState, boolean isOverrideState) {
+            DeviceState state, DeviceState baseState,
+            boolean isOverrideState) {
         assertEquals(mService.getCommittedState(), Optional.of(state));
         assertEquals(mService.getBaseState(), Optional.of(baseState));
         assertEquals(mSysPropSetter.getValue(),
@@ -910,7 +900,7 @@
 
     private static final class TestDeviceStatePolicy extends DeviceStatePolicy {
         private final DeviceStateProvider mProvider;
-        private int mLastDeviceStateRequestedToConfigure = INVALID_DEVICE_STATE;
+        private int mLastDeviceStateRequestedToConfigure = INVALID_DEVICE_STATE_IDENTIFIER;
         private boolean mConfigureBlocked = false;
         private Runnable mPendingConfigureCompleteRunnable;
 
@@ -970,10 +960,11 @@
     }
 
     private static final class TestDeviceStateProvider implements DeviceStateProvider {
-        private DeviceState[] mSupportedDeviceStates = new DeviceState[]{
-                DEFAULT_DEVICE_STATE,
-                OTHER_DEVICE_STATE,
-                DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP};
+        private DeviceState[] mSupportedDeviceStates =
+                new DeviceState[]{
+                        DEFAULT_DEVICE_STATE,
+                        OTHER_DEVICE_STATE,
+                        DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP};
 
         @Nullable private final DeviceState mInitialState;
         private Listener mListener;
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
index cfdb586..637bf03 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
@@ -49,10 +49,10 @@
 @RunWith(AndroidJUnit4.class)
 public final class OverrideRequestControllerTest {
 
-    private static final DeviceState
-            TEST_DEVICE_STATE_ZERO = new DeviceState(0, "TEST_STATE", 0);
-    private static final DeviceState
-            TEST_DEVICE_STATE_ONE = new DeviceState(1, "TEST_STATE", 0);
+    private static final DeviceState TEST_DEVICE_STATE_ZERO = new DeviceState(
+            new DeviceState.Configuration.Builder(0, "TEST_STATE").build());
+    private static final DeviceState TEST_DEVICE_STATE_ONE = new DeviceState(
+            new DeviceState.Configuration.Builder(1, "TEST_STATE").build());
 
     private TestStatusChangeListener mStatusListener;
     private OverrideRequestController mController;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
index 9ad2652..6731403 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
@@ -235,7 +235,7 @@
     }
 
     @Test
-    public void adjustOnlyAvbEnabled_audioDeviceVolumeChanged_doesNotSendSetAudioVolumeLevel() {
+    public void adjustOnlyAvbEnabled_audioDeviceVolumeChanged_requestsAndUpdatesAudioStatus() {
         enableAdjustOnlyAbsoluteVolumeBehavior();
 
         mNativeWrapper.clearResultMessages();
@@ -250,7 +250,22 @@
         );
         mTestLooper.dispatchAll();
 
-        assertThat(mNativeWrapper.getResultMessages()).isEmpty();
+        // We can't sent <Set Audio Volume Level> when using adjust-only AVB.
+        // Instead, we send <Give Audio Status>, to get the System Audio device's volume level.
+        // This ensures that we end up with a correct audio status in AudioService, even if it
+        // set it incorrectly because it assumed that we could send <Set Audio Volume Level>
+        assertThat(mNativeWrapper.getResultMessages().size()).isEqualTo(1);
+        assertThat(mNativeWrapper.getResultMessages()).contains(
+                HdmiCecMessageBuilder.buildGiveAudioStatus(getLogicalAddress(),
+                        getSystemAudioDeviceLogicalAddress())
+        );
+
+        // When we receive <Report Audio Status>, we notify AudioService of the volume level.
+        receiveReportAudioStatus(50,
+                true);
+        verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC),
+                eq(50 * STREAM_MUSIC_MAX_VOLUME / AudioStatus.MAX_VOLUME),
+                anyInt());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index a6e05dd..5520897 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -364,6 +364,30 @@
     }
 
     @Test
+    public void systemAudioControlOnPowerOn_singleActionStarted() throws Exception {
+        mHdmiCecLocalDeviceAudioSystem.removeAction(SystemAudioInitiationActionFromAvr.class);
+        mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
+                Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, true);
+        mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
+                Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, true);
+        assertThat(
+                mHdmiCecLocalDeviceAudioSystem.getActions(
+                        SystemAudioInitiationActionFromAvr.class))
+                .hasSize(1);
+    }
+
+    @Test
+    public void onSystemAudioControlFeatureSupportChanged_singleActionStarted() throws Exception {
+        mHdmiCecLocalDeviceAudioSystem.removeAction(SystemAudioInitiationActionFromAvr.class);
+        mHdmiCecLocalDeviceAudioSystem.onSystemAudioControlFeatureSupportChanged(true);
+        mHdmiCecLocalDeviceAudioSystem.onSystemAudioControlFeatureSupportChanged(true);
+        assertThat(
+                mHdmiCecLocalDeviceAudioSystem.getActions(
+                        SystemAudioInitiationActionFromAvr.class))
+                .hasSize(1);
+    }
+
+    @Test
     public void handleActiveSource_updateActiveSource() throws Exception {
         HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
         ActiveSource expectedActiveSource = new ActiveSource(ADDR_TV, 0x0000);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
index 0aa72d0..98e119c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
@@ -184,13 +184,13 @@
 
     @Test
     public void isValid_setMenuLanguage() {
-        assertMessageValidity("4F:32:53:50:41").isEqualTo(OK);
+        assertMessageValidity("0F:32:53:50:41").isEqualTo(OK);
         assertMessageValidity("0F:32:45:4E:47:8C:49:D3:48").isEqualTo(OK);
 
-        assertMessageValidity("40:32:53:50:41").isEqualTo(ERROR_DESTINATION);
-        assertMessageValidity("F0:32").isEqualTo(ERROR_SOURCE);
-        assertMessageValidity("4F:32:45:55").isEqualTo(ERROR_PARAMETER_SHORT);
-        assertMessageValidity("4F:32:19:7F:83").isEqualTo(ERROR_PARAMETER);
+        assertMessageValidity("04:32:53:50:41").isEqualTo(ERROR_DESTINATION);
+        assertMessageValidity("40:32").isEqualTo(ERROR_SOURCE);
+        assertMessageValidity("0F:32:45:55").isEqualTo(ERROR_PARAMETER_SHORT);
+        assertMessageValidity("0F:32:19:7F:83").isEqualTo(ERROR_PARAMETER);
     }
 
     @Test
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 e59b5ea..2ba3969 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -48,7 +48,6 @@
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
 import android.provider.Settings;
-import android.security.KeyStore;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -102,7 +101,6 @@
     IActivityManager mActivityManager;
     DevicePolicyManager mDevicePolicyManager;
     DevicePolicyManagerInternal mDevicePolicyManagerInternal;
-    KeyStore mKeyStore;
     MockSyntheticPasswordManager mSpManager;
     IAuthSecret mAuthSecretService;
     WindowManagerInternal mMockWindowManager;
@@ -165,7 +163,6 @@
                 new LockSettingsServiceTestable.MockInjector(
                         mContext,
                         mStorage,
-                        mKeyStore,
                         mActivityManager,
                         setUpStorageManagerMock(),
                         mSpManager,
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index 296d2cb..f9077c4 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -30,7 +30,6 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.storage.IStorageManager;
-import android.security.KeyStore;
 import android.security.keystore.KeyPermanentlyInvalidatedException;
 import android.service.gatekeeper.IGateKeeperService;
 
@@ -41,6 +40,7 @@
 import com.android.server.pm.UserManagerInternal;
 
 import java.io.FileNotFoundException;
+import java.security.KeyStore;
 
 public class LockSettingsServiceTestable extends LockSettingsService {
     private Intent mSavedFrpNotificationIntent = null;
@@ -50,7 +50,6 @@
     public static class MockInjector extends LockSettingsService.Injector {
 
         private LockSettingsStorage mLockSettingsStorage;
-        private KeyStore mKeyStore;
         private IActivityManager mActivityManager;
         private IStorageManager mStorageManager;
         private SyntheticPasswordManager mSpManager;
@@ -62,14 +61,13 @@
         public boolean mIsHeadlessSystemUserMode = false;
         public boolean mIsMainUserPermanentAdmin = false;
 
-        public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore,
-                IActivityManager activityManager,
-                IStorageManager storageManager, SyntheticPasswordManager spManager,
-                FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager,
+        public MockInjector(Context context, LockSettingsStorage storage,
+                IActivityManager activityManager, IStorageManager storageManager,
+                SyntheticPasswordManager spManager, FakeGsiService gsiService,
+                RecoverableKeyStoreManager recoverableKeyStoreManager,
                 UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) {
             super(context);
             mLockSettingsStorage = storage;
-            mKeyStore = keyStore;
             mActivityManager = activityManager;
             mStorageManager = storageManager;
             mSpManager = spManager;
@@ -110,11 +108,6 @@
         }
 
         @Override
-        public KeyStore getKeyStore() {
-            return mKeyStore;
-        }
-
-        @Override
         public IStorageManager getStorageManager() {
             return mStorageManager;
         }
@@ -145,8 +138,7 @@
         }
 
         @Override
-        public UnifiedProfilePasswordCache getUnifiedProfilePasswordCache(
-                java.security.KeyStore ks) {
+        public UnifiedProfilePasswordCache getUnifiedProfilePasswordCache(KeyStore ks) {
             return mock(UnifiedProfilePasswordCache.class);
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
index d6d2b6d..07fb9fc 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
@@ -52,9 +52,9 @@
 import android.os.RemoteException;
 import android.os.test.FakePermissionEnforcer;
 import android.platform.test.annotations.Presubmit;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArrayMap;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.app.IBatteryStats;
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index b1e62f9..67b131f 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -177,7 +177,6 @@
 import android.telephony.SubscriptionPlan;
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.MediumTest;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -191,6 +190,7 @@
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.FlakyTest;
+import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.test.BroadcastInterceptingContext;
@@ -2412,6 +2412,46 @@
     }
 
     @Test
+    public void testObsoleteHandleUidGone() throws Exception {
+        callAndWaitOnUidStateChanged(UID_A, PROCESS_STATE_TOP, 51);
+        assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+
+        clearInvocations(mNetworkManager);
+
+        // In the service, handleUidGone is only called from mUidEventHandler. Then a call to it may
+        // be rendered obsolete by a newer uid change posted on the handler. The latest uid state
+        // change is always reflected in the current UidStateChangeCallbackInfo for the uid, so to
+        // simulate an obsolete call for test, we directly call handleUidGone and leave the state in
+        // UidStateChangeCallbackInfo set by the previous call to onUidStateChanged(TOP). This call
+        // should then do nothing.
+        mService.handleUidGone(UID_A);
+
+        verify(mNetworkManager, times(0)).setFirewallUidRule(anyInt(), anyInt(), anyInt());
+        assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+    }
+
+    @Ignore("Temporarily disabled until the feature is enabled")
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
+    public void testObsoleteHandleUidChanged() throws Exception {
+        callAndWaitOnUidGone(UID_A);
+        assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+
+        clearInvocations(mNetworkManager);
+
+        // In the service, handleUidChanged is only called from mUidEventHandler. Then a call to it
+        // may be rendered obsolete by an immediate uid-gone posted on the handler. The latest uid
+        // state change is always reflected in the current UidStateChangeCallbackInfo for the uid,
+        // so to simulate an obsolete call for test, we directly call handleUidChanged and leave the
+        // state in UidStateChangeCallbackInfo as null as it would get removed by the previous call
+        // to onUidGone(). This call should then do nothing.
+        mService.handleUidChanged(UID_A);
+
+        verify(mNetworkManager, times(0)).setFirewallUidRule(anyInt(), anyInt(), anyInt());
+        assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+    }
+
+    @Test
     public void testLowPowerStandbyAllowlist() throws Exception {
         // Chain background is also enabled but these procstates are important enough to be exempt.
         callAndWaitOnUidStateChanged(UID_A, PROCESS_STATE_TOP, 0);
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 ea84eb2..a6f2196 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -16,8 +16,6 @@
 
 package com.android.server.os;
 
-import android.app.admin.flags.Flags;
-
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -27,15 +25,21 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.when;
 
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.flags.Flags;
 import android.app.role.RoleManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.os.Binder;
 import android.os.BugreportManager.BugreportCallback;
+import android.os.BugreportParams;
 import android.os.IBinder;
 import android.os.IDumpstateListener;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.platform.test.annotations.RequiresFlagsDisabled;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
@@ -65,6 +69,9 @@
 @RunWith(AndroidJUnit4.class)
 public class BugreportManagerServiceImplTest {
 
+    private static final UserInfo ADMIN_USER_INFO =
+            new UserInfo(/* id= */ 5678, "adminUser", UserInfo.FLAG_ADMIN);
+
     @Rule
     public final CheckFlagsRule mCheckFlagsRule =
             DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -75,6 +82,12 @@
 
     @Mock
     private PackageManager mPackageManager;
+    @Mock
+    private UserManager mMockUserManager;
+    @Mock
+    private DevicePolicyManager mMockDevicePolicyManager;
+
+    private TestInjector mInjector;
 
     private int mCallingUid = 1234;
     private String mCallingPackage  = "test.package";
@@ -90,11 +103,13 @@
         mMappingFile = new AtomicFile(mContext.getFilesDir(), "bugreport-mapping.xml");
         ArraySet<String> mAllowlistedPackages = new ArraySet<>();
         mAllowlistedPackages.add(mContext.getPackageName());
-        mService = new BugreportManagerServiceImpl(
-                new BugreportManagerServiceImpl.Injector(mContext, mAllowlistedPackages,
-                        mMappingFile));
+        mInjector = new TestInjector(mContext, mAllowlistedPackages, mMappingFile,
+                mMockUserManager, mMockDevicePolicyManager);
+        mService = new BugreportManagerServiceImpl(mInjector);
         mBugreportFileManager = new BugreportManagerServiceImpl.BugreportFileManager(mMappingFile);
         when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())).thenReturn(mCallingUid);
+        // The calling user is an admin user by default.
+        when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(true);
     }
 
     @After
@@ -182,6 +197,63 @@
     }
 
     @Test
+    public void testStartBugreport() throws Exception {
+        mService.startBugreport(mCallingUid, mContext.getPackageName(),
+                new FileDescriptor(), /* screenshotFd= */ null,
+                BugreportParams.BUGREPORT_MODE_FULL,
+                /* flags= */ 0, new Listener(new CountDownLatch(1)),
+                /* isScreenshotRequested= */ false);
+
+        assertThat(mInjector.isBugreportStarted()).isTrue();
+    }
+
+    @Test
+    public void testStartBugreport_nonAdminProfileOfAdminCurrentUser() throws Exception {
+        int callingUid = Binder.getCallingUid();
+        int callingUserId = UserHandle.getUserId(callingUid);
+        when(mMockUserManager.isUserAdmin(callingUserId)).thenReturn(false);
+        when(mMockUserManager.getProfileParent(callingUserId)).thenReturn(ADMIN_USER_INFO);
+
+        mService.startBugreport(mCallingUid, mContext.getPackageName(),
+                new FileDescriptor(), /* screenshotFd= */ null,
+                BugreportParams.BUGREPORT_MODE_FULL,
+                /* flags= */ 0, new Listener(new CountDownLatch(1)),
+                /* isScreenshotRequested= */ false);
+
+        assertThat(mInjector.isBugreportStarted()).isTrue();
+    }
+
+    @Test
+    public void testStartBugreport_throwsForNonAdminUser() throws Exception {
+        when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(false);
+
+        Exception thrown = assertThrows(IllegalArgumentException.class,
+                () -> mService.startBugreport(mCallingUid, mContext.getPackageName(),
+                        new FileDescriptor(), /* screenshotFd= */ null,
+                        BugreportParams.BUGREPORT_MODE_FULL,
+                        /* flags= */ 0, new Listener(new CountDownLatch(1)),
+                        /* isScreenshotRequested= */ false));
+
+        assertThat(thrown.getMessage()).contains("not an admin user");
+    }
+
+    @Test
+    public void testStartBugreport_throwsForNotAffiliatedUser() throws Exception {
+        when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(false);
+        when(mMockDevicePolicyManager.getDeviceOwnerUserId()).thenReturn(-1);
+        when(mMockDevicePolicyManager.isAffiliatedUser(anyInt())).thenReturn(false);
+
+        Exception thrown = assertThrows(IllegalArgumentException.class,
+                () -> mService.startBugreport(mCallingUid, mContext.getPackageName(),
+                        new FileDescriptor(), /* screenshotFd= */ null,
+                        BugreportParams.BUGREPORT_MODE_REMOTE,
+                        /* flags= */ 0, new Listener(new CountDownLatch(1)),
+                        /* isScreenshotRequested= */ false));
+
+        assertThat(thrown.getMessage()).contains("not affiliated to the device owner");
+    }
+
+    @Test
     public void testRetrieveBugreportWithoutFilesForCaller() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
         Listener listener = new Listener(latch);
@@ -224,7 +296,8 @@
 
     private void clearAllowlist() {
         mService = new BugreportManagerServiceImpl(
-                new BugreportManagerServiceImpl.Injector(mContext, new ArraySet<>(), mMappingFile));
+                new TestInjector(mContext, new ArraySet<>(), mMappingFile,
+                        mMockUserManager, mMockDevicePolicyManager));
     }
 
     private static class Listener implements IDumpstateListener {
@@ -275,4 +348,46 @@
             complete(successful);
         }
     }
+
+    private static class TestInjector extends BugreportManagerServiceImpl.Injector {
+
+        private static final String SYSTEM_PROPERTY_BUGREPORT_START = "ctl.start";
+        private static final String SYSTEM_PROPERTY_BUGREPORT_STOP = "ctl.stop";
+
+        private final UserManager mUserManager;
+        private final DevicePolicyManager mDevicePolicyManager;
+        private boolean mBugreportStarted = false;
+
+        TestInjector(Context context, ArraySet<String> allowlistedPackages, AtomicFile mappingFile,
+                UserManager um, DevicePolicyManager dpm) {
+            super(context, allowlistedPackages, mappingFile);
+            mUserManager = um;
+            mDevicePolicyManager = dpm;
+        }
+
+        @Override
+        public UserManager getUserManager() {
+            return mUserManager;
+        }
+
+        @Override
+        public DevicePolicyManager getDevicePolicyManager() {
+            return mDevicePolicyManager;
+        }
+
+        @Override
+        public void setSystemProperty(String key, String value) {
+            // Calling SystemProperties.set() will throw a RuntimeException due to permission error.
+            // Instead, we are just marking a flag to store the state for testing.
+            if (SYSTEM_PROPERTY_BUGREPORT_START.equals(key)) {
+                mBugreportStarted = true;
+            } else if (SYSTEM_PROPERTY_BUGREPORT_STOP.equals(key)) {
+                mBugreportStarted = false;
+            }
+        }
+
+        public boolean isBugreportStarted() {
+            return mBugreportStarted;
+        }
+    }
 }
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 44c464e..1591a96 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -40,13 +40,13 @@
 import android.platform.test.annotations.Postsubmit;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.provider.Settings;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
 import android.util.ArraySet;
 import android.util.Slog;
 
 import androidx.annotation.Nullable;
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.MediumTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -316,6 +316,10 @@
                 .that(userTypeDetails).isNotNull();
         final UserProperties typeProps = userTypeDetails.getDefaultUserPropertiesReference();
 
+        // Only run the test if private profile creation is enabled on the device
+        assumeTrue("Private profile not enabled on the device",
+                mUserManager.canAddPrivateProfile());
+
         // Test that only one private profile  can be created
         final int mainUserId = mainUser.getIdentifier();
         UserInfo userInfo = createProfileForUser("Private profile1",
@@ -1231,6 +1235,20 @@
 
     @MediumTest
     @Test
+    public void testPrivateProfileCreationRestrictions() {
+        assumeTrue(mUserManager.canAddPrivateProfile());
+        final int mainUserId = ActivityManager.getCurrentUser();
+        try {
+            UserInfo privateProfileInfo = createProfileForUser("Private",
+                            UserManager.USER_TYPE_PROFILE_PRIVATE, mainUserId);
+            assertThat(privateProfileInfo).isNotNull();
+        } catch (Exception e) {
+            fail("Creation of private profile failed due to " + e.getMessage());
+        }
+    }
+
+    @MediumTest
+    @Test
     public void testAddRestrictedProfile() throws Exception {
         if (isAutomotive() || UserManager.isHeadlessSystemUserMode()) return;
         assertWithMessage("There should be no associated restricted profiles before the test")
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 16909ab..fad10f7 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -60,7 +60,10 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.Constructor;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Unit tests for {@link DeviceStateProviderImpl}.
@@ -68,10 +71,15 @@
  * Run with <code>atest DeviceStateProviderImplTest</code>.
  */
 public final class DeviceStateProviderImplTest {
-    private final ArgumentCaptor<DeviceState[]> mDeviceStateArrayCaptor = ArgumentCaptor.forClass(
-            DeviceState[].class);
+    private final ArgumentCaptor<DeviceState[]> mDeviceStateArrayCaptor =
+            ArgumentCaptor.forClass(DeviceState[].class);
     private final ArgumentCaptor<Integer> mIntegerCaptor = ArgumentCaptor.forClass(Integer.class);
     private static final int MAX_HINGE_ANGLE_EXCLUSIVE = 360;
+    private static final Set<Integer> EMPTY_PROPERTY_SET = new HashSet<>();
+    private static final Set<Integer> THERMAL_TEST_PROPERTY_SET = new HashSet<>(
+            Arrays.asList(DeviceState.PROPERTY_EMULATED_ONLY,
+                    DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL,
+                    DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE));
 
     private Context mContext;
     private SensorManager mSensorManager;
@@ -160,8 +168,8 @@
         verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
                 eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         final DeviceState[] expectedStates = new DeviceState[]{
-                new DeviceState(1, "", 0 /* flags */),
-                new DeviceState(2, "", 0 /* flags */) };
+                createDeviceState(1, "", EMPTY_PROPERTY_SET),
+                createDeviceState(2, "", EMPTY_PROPERTY_SET)};
         assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
 
         verify(listener).onStateChanged(mIntegerCaptor.capture());
@@ -169,13 +177,13 @@
     }
 
     @Test
-    public void create_stateWithCancelOverrideRequestFlag() {
+    public void create_stateWithCancelOverrideRequestProperty() {
         String configString = "<device-state-config>\n"
                 + "    <device-state>\n"
                 + "        <identifier>1</identifier>\n"
-                + "        <flags>\n"
-                + "            <flag>FLAG_CANCEL_OVERRIDE_REQUESTS</flag>\n"
-                + "        </flags>\n"
+                + "        <properties>\n"
+                + "            <property>PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS</property>\n"
+                + "        </properties>\n"
                 + "        <conditions/>\n"
                 + "    </device-state>\n"
                 + "    <device-state>\n"
@@ -192,20 +200,22 @@
 
         verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
                 eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
+
         final DeviceState[] expectedStates = new DeviceState[]{
-                new DeviceState(1, "", DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS),
-                new DeviceState(2, "", 0 /* flags */) };
+                createDeviceState(1, "", new HashSet<>(
+                        List.of(DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS))),
+                createDeviceState(2, "", EMPTY_PROPERTY_SET)};
         assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
     }
 
     @Test
-    public void create_stateWithInvalidFlag() {
+    public void create_stateWithInvalidProperty() {
         String configString = "<device-state-config>\n"
                 + "    <device-state>\n"
                 + "        <identifier>1</identifier>\n"
-                + "        <flags>\n"
-                + "            <flag>INVALID_FLAG</flag>\n"
-                + "        </flags>\n"
+                + "        <properties>\n"
+                + "            <property>INVALID_PROPERTY</property>\n"
+                + "        </properties>\n"
                 + "        <conditions/>\n"
                 + "    </device-state>\n"
                 + "    <device-state>\n"
@@ -223,8 +233,8 @@
         verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
                 eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         final DeviceState[] expectedStates = new DeviceState[]{
-                new DeviceState(1, "", 0 /* flags */),
-                new DeviceState(2, "", 0 /* flags */) };
+                createDeviceState(1, "", EMPTY_PROPERTY_SET),
+                createDeviceState(2, "", EMPTY_PROPERTY_SET)};
         assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
     }
 
@@ -259,8 +269,8 @@
         verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
                 eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         final DeviceState[] expectedStates = new DeviceState[]{
-                new DeviceState(1, "", 0 /* flags */),
-                new DeviceState(2, "CLOSED", 0 /* flags */) };
+                createDeviceState(1, "", EMPTY_PROPERTY_SET),
+                createDeviceState(2, "CLOSED", EMPTY_PROPERTY_SET)};
         assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
 
         // onStateChanged() should not be called because the provider has not yet been notified of
@@ -327,11 +337,13 @@
                 + "    <device-state>\n"
                 + "        <identifier>4</identifier>\n"
                 + "        <name>THERMAL_TEST</name>\n"
-                + "        <flags>\n"
-                + "            <flag>FLAG_EMULATED_ONLY</flag>\n"
-                + "            <flag>FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL</flag>\n"
-                + "            <flag>FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE</flag>\n"
-                + "        </flags>\n"
+                + "        <properties>\n"
+                + "            <property>PROPERTY_EMULATED_ONLY</property>\n"
+                + "            <property>PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL"
+                + "</property>\n"
+                + "            <property>PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE"
+                + "</property>\n"
+                + "        </properties>\n"
                 + "    </device-state>\n"
                 + "</device-state-config>\n";
         DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
@@ -352,13 +364,11 @@
                 eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         assertArrayEquals(
                 new DeviceState[]{
-                        new DeviceState(1, "CLOSED", 0 /* flags */),
-                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
-                        new DeviceState(3, "OPENED", 0 /* flags */),
-                        new DeviceState(4, "THERMAL_TEST",
-                                DeviceState.FLAG_EMULATED_ONLY
-                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
-                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
+                        createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+                        createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(4, "THERMAL_TEST",
+                                THERMAL_TEST_PROPERTY_SET)},
                 mDeviceStateArrayCaptor.getValue());
         // onStateChanged() should not be called because the provider has not yet been notified of
         // the initial sensor state.
@@ -405,7 +415,7 @@
     }
 
     @Test
-    public void test_flagDisableWhenThermalStatusCritical() throws Exception {
+    public void test_propertyDisableWhenThermalStatusCritical() throws Exception {
         Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
         when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
         DeviceStateProviderImpl provider = create_sensorBasedProvider(sensor);
@@ -418,13 +428,11 @@
                 eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         assertArrayEquals(
                 new DeviceState[]{
-                        new DeviceState(1, "CLOSED", 0 /* flags */),
-                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
-                        new DeviceState(3, "OPENED", 0 /* flags */),
-                        new DeviceState(4, "THERMAL_TEST",
-                                DeviceState.FLAG_EMULATED_ONLY
-                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
-                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
+                        createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+                        createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(4, "THERMAL_TEST",
+                                THERMAL_TEST_PROPERTY_SET)},
                 mDeviceStateArrayCaptor.getValue());
         Mockito.clearInvocations(listener);
 
@@ -439,9 +447,9 @@
                 eq(SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL));
         assertArrayEquals(
                 new DeviceState[]{
-                        new DeviceState(1, "CLOSED", 0 /* flags */),
-                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
-                        new DeviceState(3, "OPENED", 0 /* flags */) },
+                        createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+                        createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET)},
                 mDeviceStateArrayCaptor.getValue());
         Mockito.clearInvocations(listener);
 
@@ -451,18 +459,16 @@
                 eq(SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL));
         assertArrayEquals(
                 new DeviceState[]{
-                        new DeviceState(1, "CLOSED", 0 /* flags */),
-                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
-                        new DeviceState(3, "OPENED", 0 /* flags */),
-                        new DeviceState(4, "THERMAL_TEST",
-                                DeviceState.FLAG_EMULATED_ONLY
-                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
-                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
+                        createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+                        createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(4, "THERMAL_TEST",
+                                THERMAL_TEST_PROPERTY_SET)},
                 mDeviceStateArrayCaptor.getValue());
     }
 
     @Test
-    public void test_flagDisableWhenPowerSaveEnabled() throws Exception {
+    public void test_propertyDisableWhenPowerSaveEnabled() throws Exception {
         Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
         when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
         DeviceStateProviderImpl provider = create_sensorBasedProvider(sensor);
@@ -475,13 +481,11 @@
                 eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         assertArrayEquals(
                 new DeviceState[]{
-                        new DeviceState(1, "CLOSED", 0 /* flags */),
-                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
-                        new DeviceState(3, "OPENED", 0 /* flags */),
-                        new DeviceState(4, "THERMAL_TEST",
-                                DeviceState.FLAG_EMULATED_ONLY
-                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
-                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
+                        createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+                        createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(4, "THERMAL_TEST",
+                                THERMAL_TEST_PROPERTY_SET)},
                 mDeviceStateArrayCaptor.getValue());
         Mockito.clearInvocations(listener);
 
@@ -496,9 +500,9 @@
                 eq(SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED));
         assertArrayEquals(
                 new DeviceState[]{
-                        new DeviceState(1, "CLOSED", 0 /* flags */),
-                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
-                        new DeviceState(3, "OPENED", 0 /* flags */) },
+                        createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+                        createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET)},
                 mDeviceStateArrayCaptor.getValue());
         Mockito.clearInvocations(listener);
 
@@ -508,13 +512,11 @@
                 eq(SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED));
         assertArrayEquals(
                 new DeviceState[]{
-                        new DeviceState(1, "CLOSED", 0 /* flags */),
-                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
-                        new DeviceState(3, "OPENED", 0 /* flags */),
-                        new DeviceState(4, "THERMAL_TEST",
-                                DeviceState.FLAG_EMULATED_ONLY
-                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
-                                        | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
+                        createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+                        createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+                        createDeviceState(4, "THERMAL_TEST",
+                                THERMAL_TEST_PROPERTY_SET)},
                 mDeviceStateArrayCaptor.getValue());
     }
 
@@ -598,13 +600,22 @@
                 eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         assertArrayEquals(
                 new DeviceState[]{
-                        new DeviceState(1, "CLOSED", 0 /* flags */),
-                        new DeviceState(2, "HALF_OPENED", 0 /* flags */)
+                        createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+                        createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET)
                 }, mDeviceStateArrayCaptor.getValue());
         // onStateChanged() should not be called because the provider could not find the sensor.
         verify(listener, never()).onStateChanged(mIntegerCaptor.capture());
     }
 
+    private DeviceState createDeviceState(int identifier, @NonNull String name,
+            @NonNull Set<@DeviceState.DeviceStateProperties Integer> systemProperties) {
+        DeviceState.Configuration configuration = new DeviceState.Configuration.Builder(identifier,
+                name)
+                .setSystemProperties(systemProperties)
+                .build();
+        return new DeviceState(configuration);
+    }
+
     private static Sensor newSensor(String name, String type) throws Exception {
         Constructor<Sensor> constructor = Sensor.class.getDeclaredConstructor();
         constructor.setAccessible(true);
diff --git a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
index 0635cc4..669eedf 100644
--- a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
@@ -32,13 +32,13 @@
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.test.TestLooper;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
 import android.testing.TestableResources;
 import android.view.Window;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
 
diff --git a/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java
index ca0270d..db7822d 100644
--- a/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java
@@ -47,7 +47,8 @@
 import android.provider.Settings;
 import android.service.attention.AttentionService;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.server.wm.WindowManagerInternal;
 
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
index 6fffd75..fe9a0e2 100644
--- a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
@@ -29,10 +29,11 @@
 import android.provider.DeviceConfig;
 import android.provider.Settings.Global;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.test.suitebuilder.annotation.Suppress;
 import android.util.ArrayMap;
 
+import androidx.test.filters.SmallTest;
+import androidx.test.filters.Suppress;
+
 import com.android.frameworks.servicestests.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.power.batterysaver.BatterySaverPolicy.Policy;
diff --git a/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java b/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java
index 517f483..cb5371b 100644
--- a/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java
@@ -26,8 +26,8 @@
 import android.app.usage.UsageStatsManager;
 import android.content.res.Configuration;
 import android.os.PersistableBundle;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.ArrayUtils;
diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
index cd29c80..f388528 100644
--- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
+++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
@@ -31,11 +31,11 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.os.PersistableBundle;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.AtomicFile;
 import android.util.LongSparseArray;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
index c8bef45..44d1161 100644
--- a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
@@ -147,7 +147,7 @@
             final int n = 4;
             StackTraceElement[] stack = Thread.currentThread().getStackTrace();
             if (stack.length < n+1) return "test";
-            return stack[n].getMethodName();
+            return stack[n].getClassName() + "." + stack[n].getMethodName();
         }
     }
 
@@ -312,12 +312,17 @@
     }
 
     /**
-     * Verify the dump output.
+     * Verify the dump output.  This only applies when native timers are supported.
      */
     @Test
     public void testDumpOutput() throws Exception {
+        if (!AnrTimer.nativeTimersSupported()) return;
+
+        // The timers in this class are named "class.method".
+        final String timerName = "timer: com.android.server.utils.AnrTimerTest";
+
         String r1 = getDumpOutput();
-        assertThat(r1).doesNotContain("timer:");
+        assertThat(r1).doesNotContain(timerName);
 
         Helper helper = new Helper(2);
         TestArg t1 = new TestArg(1, 1);
@@ -331,14 +336,14 @@
             String r2 = getDumpOutput();
             // There are timers in the list if and only if the feature is enabled.
             if (mEnabled) {
-              assertThat(r2).contains("timer:");
+                assertThat(r2).contains(timerName);
             } else {
-              assertThat(r2).doesNotContain("timer:");
+                assertThat(r2).doesNotContain(timerName);
             }
         }
 
         String r3 = getDumpOutput();
-        assertThat(r3).doesNotContain("timer:");
+        assertThat(r3).doesNotContain(timerName);
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
index 2039f93..54d1138 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
@@ -96,6 +96,9 @@
             return;
         }
         PackageInfo packageInfo = userPackages.get(userId);
+        if (packageInfo == null) {
+            return;
+        }
         packageInfo.applicationInfo.enabled = enable;
         setPackageInfoForUser(userId, packageInfo);
     }
@@ -106,6 +109,9 @@
             return;
         }
         PackageInfo packageInfo = userPackages.get(userId);
+        if (packageInfo == null) {
+            return;
+        }
         packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED;
         packageInfo.applicationInfo.privateFlags &= (~ApplicationInfo.PRIVATE_FLAG_HIDDEN);
         setPackageInfoForUser(userId, packageInfo);
diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
index 53c172a..e181a51 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
@@ -31,7 +31,6 @@
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Base64;
 import android.webkit.UserPackage;
 import android.webkit.WebViewFactory;
@@ -39,6 +38,7 @@
 import android.webkit.WebViewProviderResponse;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Rule;
@@ -1551,7 +1551,7 @@
 
     @Test
     @RequiresFlagsEnabled("android.webkit.update_service_v2")
-    public void testDefaultWebViewPackageInstalling() {
+    public void testDefaultWebViewPackageInstallingDuringStartUp() {
         String testPackage = "testDefault";
         WebViewProviderInfo[] packages =
                 new WebViewProviderInfo[] {
@@ -1574,6 +1574,68 @@
                         Matchers.anyObject(), Mockito.eq(testPackage));
     }
 
+    @Test
+    @RequiresFlagsEnabled("android.webkit.update_service_v2")
+    public void testDefaultWebViewPackageInstallingAfterStartUp() {
+        String testPackage = "testDefault";
+        WebViewProviderInfo[] packages =
+                new WebViewProviderInfo[] {
+                    new WebViewProviderInfo(
+                            testPackage,
+                            "",
+                            true /* default available */,
+                            false /* fallback */,
+                            null)
+                };
+        checkCertainPackageUsedAfterWebViewBootPreparation(testPackage, packages);
+
+        // uninstall the default package.
+        mTestSystemImpl.setPackageInfo(
+                createPackageInfo(
+                        testPackage, true /* enabled */, true /* valid */, false /* installed */));
+        mWebViewUpdateServiceImpl.packageStateChanged(testPackage,
+                WebViewUpdateService.PACKAGE_REMOVED, 0);
+
+        // Check that we try to re-install the default package.
+        Mockito.verify(mTestSystemImpl)
+                .installExistingPackageForAllUsers(
+                        Matchers.anyObject(), Mockito.eq(testPackage));
+    }
+
+    /**
+     * Ensures that adding a new user for which the current WebView package is uninstalled triggers
+     * the repair logic.
+     */
+    @Test
+    @RequiresFlagsEnabled("android.webkit.update_service_v2")
+    public void testAddingNewUserWithDefaultdPackageNotInstalled() {
+        String testPackage = "testDefault";
+        WebViewProviderInfo[] packages =
+                new WebViewProviderInfo[] {
+                    new WebViewProviderInfo(
+                            testPackage,
+                            "",
+                            true /* default available */,
+                            false /* fallback */,
+                            null)
+                };
+        checkCertainPackageUsedAfterWebViewBootPreparation(testPackage, packages);
+
+        // Add new user with the default package not installed.
+        int newUser = 100;
+        mTestSystemImpl.addUser(newUser);
+        mTestSystemImpl.setPackageInfoForUser(newUser,
+                createPackageInfo(testPackage, true /* enabled */, true /* valid */,
+                        false /* installed */));
+
+        mWebViewUpdateServiceImpl.handleNewUser(newUser);
+
+        // Check that we try to re-install the default package for all users.
+        Mockito.verify(mTestSystemImpl)
+                .installExistingPackageForAllUsers(
+                        Matchers.anyObject(), Mockito.eq(testPackage));
+    }
+
     private void testDefaultPackageChosen(PackageInfo packageInfo) {
         WebViewProviderInfo[] packages =
                 new WebViewProviderInfo[] {
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 2f29d10..515898a 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -48,6 +48,8 @@
         "notification_flags_lib",
         "platform-test-rules",
         "SettingsLib",
+        "libprotobuf-java-lite",
+        "platformprotoslite",
     ],
 
     libs: [
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/AlertRateLimiterTest.java b/services/tests/uiservicestests/src/com/android/server/notification/AlertRateLimiterTest.java
index dc7f118..f229fd9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/AlertRateLimiterTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/AlertRateLimiterTest.java
@@ -20,8 +20,7 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
-import android.test.suitebuilder.annotation.SmallTest;
-
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java
index 8bc027d..39ff9cc 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java
@@ -33,8 +33,8 @@
 import android.os.UserManager;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BadgeExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BadgeExtractorTest.java
index ce6939a9..2f5c96c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BadgeExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BadgeExtractorTest.java
@@ -35,8 +35,8 @@
 import android.media.session.MediaSession;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
index 9dffed2..b5bc610 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
@@ -48,8 +48,8 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
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 8622488..517dcb4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -74,12 +74,12 @@
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IAccessibilityManager;
 import android.view.accessibility.IAccessibilityManagerClient;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.logging.InstanceIdSequence;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
index 3797dbb..bfbc81c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
@@ -29,9 +29,11 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
@@ -103,8 +105,10 @@
         mContext.addMockSystemService(ColorDisplayManager.class, mColorDisplayManager);
         mContext.addMockSystemService(UiModeManager.class, mUiModeManager);
         mContext.addMockSystemService(WallpaperManager.class, mWallpaperManager);
+        when(mWallpaperManager.isWallpaperSupported()).thenReturn(true);
 
         mApplier = new DefaultDeviceEffectsApplier(mContext);
+        verify(mWallpaperManager).isWallpaperSupported();
     }
 
     @Test
@@ -187,6 +191,26 @@
     }
 
     @Test
+    public void apply_disabledWallpaperService_dimWallpaperNotApplied() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+        WallpaperManager disabledWallpaperService = mock(WallpaperManager.class);
+        when(mWallpaperManager.isWallpaperSupported()).thenReturn(false);
+        mContext.addMockSystemService(WallpaperManager.class, disabledWallpaperService);
+        mApplier = new DefaultDeviceEffectsApplier(mContext);
+        verify(mWallpaperManager).isWallpaperSupported();
+
+        ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
+                .setShouldSuppressAmbientDisplay(true)
+                .setShouldDimWallpaper(true)
+                .setShouldDisplayGrayscale(true)
+                .setShouldUseNightMode(true)
+                .build();
+        mApplier.apply(effects, UPDATE_ORIGIN_USER);
+
+        verifyNoMoreInteractions(mWallpaperManager);
+    }
+
+    @Test
     public void apply_someEffects_onlyThoseEffectsApplied() {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GlobalSortKeyComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GlobalSortKeyComparatorTest.java
index 5041779..d3e1b90 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GlobalSortKeyComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GlobalSortKeyComparatorTest.java
@@ -22,8 +22,8 @@
 import android.app.NotificationManager;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index f077914..1194973 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -57,9 +57,9 @@
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArrayMap;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.R;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ImportanceExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ImportanceExtractorTest.java
index ffc0dcd..a11d3f4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ImportanceExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ImportanceExtractorTest.java
@@ -16,9 +16,6 @@
 package com.android.server.notification;
 
 import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
 import android.app.Notification;
@@ -27,8 +24,8 @@
 import android.app.NotificationManager;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 4dded1d..05b6c90 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -37,6 +37,7 @@
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -885,6 +886,7 @@
             return true;
         });
 
+        mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
         service.addApprovedList("a", 0, true);
 
         service.reregisterService(cn, 0);
@@ -915,6 +917,7 @@
             return true;
         });
 
+        mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
         service.addApprovedList("a", 0, false);
 
         service.reregisterService(cn, 0);
@@ -945,6 +948,7 @@
             return true;
         });
 
+        mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
         service.addApprovedList("a/a", 0, true);
 
         service.reregisterService(cn, 0);
@@ -975,6 +979,7 @@
             return true;
         });
 
+        mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
         service.addApprovedList("a/a", 0, false);
 
         service.reregisterService(cn, 0);
@@ -1152,6 +1157,58 @@
     }
 
     @Test
+    public void testUpgradeAppNoPermissionNoRebind() throws Exception {
+        Context context = spy(getContext());
+        doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any());
+
+        ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles,
+                mIpm,
+                APPROVAL_BY_COMPONENT);
+
+        List<String> packages = new ArrayList<>();
+        packages.add("package");
+        addExpectedServices(service, packages, 0);
+
+        final ComponentName unapprovedComponent = ComponentName.unflattenFromString("package/C1");
+        final ComponentName approvedComponent = ComponentName.unflattenFromString("package/C2");
+
+        // Both components are approved initially
+        mExpectedPrimaryComponentNames.clear();
+        mExpectedPrimaryPackages.clear();
+        mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2");
+        mExpectedSecondaryComponentNames.clear();
+        mExpectedSecondaryPackages.clear();
+
+        loadXml(service);
+
+        //Component package/C1 loses bind permission
+        when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer(
+                (Answer<ServiceInfo>) invocation -> {
+                    ComponentName invocationCn = invocation.getArgument(0);
+                    if (invocationCn != null) {
+                        ServiceInfo serviceInfo = new ServiceInfo();
+                        serviceInfo.packageName = invocationCn.getPackageName();
+                        serviceInfo.name = invocationCn.getClassName();
+                        if (invocationCn.equals(unapprovedComponent)) {
+                            serviceInfo.permission = "none";
+                        } else {
+                            serviceInfo.permission = service.getConfig().bindPermission;
+                        }
+                        serviceInfo.metaData = null;
+                        return serviceInfo;
+                    }
+                    return null;
+                }
+        );
+
+        // Trigger package update
+        service.onPackagesChanged(false, new String[]{"package"}, new int[]{0});
+
+        assertFalse(service.isComponentEnabledForCurrentProfiles(unapprovedComponent));
+        assertTrue(service.isComponentEnabledForCurrentProfiles(approvedComponent));
+    }
+
+    @Test
     public void testSetPackageOrComponentEnabled() throws Exception {
         for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
             ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index 33ca5c2..a45b102 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -20,6 +20,7 @@
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
 
+import static junit.framework.Assert.fail;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
@@ -32,6 +33,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.app.INotificationManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -47,9 +49,12 @@
 import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Xml;
+import android.Manifest;
 
+import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.function.TriPredicate;
 import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.UiServiceTestCase;
 import com.android.server.notification.NotificationManagerService.NotificationAssistants;
 
@@ -59,7 +64,9 @@
 import org.mockito.MockitoAnnotations;
 
 import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -89,11 +96,15 @@
     UserInfo mZero = new UserInfo(0, "zero", 0);
     UserInfo mTen = new UserInfo(10, "ten", 0);
 
+    ComponentName mCn = new ComponentName("a", "b");
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mContext.setMockPackageManager(mPm);
         mContext.addMockSystemService(Context.USER_SERVICE, mUm);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.string.config_defaultAssistantAccessComponent, "a/a");
         mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm));
         when(mNm.getBinderService()).thenReturn(mINm);
         mContext.ensureTestableResources();
@@ -102,8 +113,9 @@
         ResolveInfo resolve = new ResolveInfo();
         approved.add(resolve);
         ServiceInfo info = new ServiceInfo();
-        info.packageName = "a";
-        info.name="a";
+        info.packageName = mCn.getPackageName();
+        info.name = mCn.getClassName();
+        info.permission = Manifest.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE;
         resolve.serviceInfo = info;
         when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
                 .thenReturn(approved);
@@ -137,6 +149,51 @@
     }
 
     @Test
+    public void testWriteXml_userTurnedOffNAS() throws Exception {
+        int userId = ActivityManager.getCurrentUser();
+
+        mAssistants.loadDefaultsFromConfig(true);
+
+        mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
+               true, true);
+
+        ComponentName current = CollectionUtils.firstOrNull(
+                mAssistants.getAllowedComponents(userId));
+        assertNotNull(current);
+        mAssistants.setUserSet(userId, true);
+        mAssistants.setPackageOrComponentEnabled(current.flattenToString(), userId, true, false,
+                true);
+
+        TypedXmlSerializer serializer = Xml.newFastSerializer();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+        serializer.startDocument(null, true);
+        mAssistants.writeXml(serializer, true, userId);
+        serializer.endDocument();
+        serializer.flush();
+
+        //fail(baos.toString("UTF-8"));
+
+        final TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(baos.toByteArray())), null);
+        TriPredicate<String, Integer, String> allowedManagedServicePackages =
+                mNm::canUseManagedServices;
+
+        parser.nextTag();
+        mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm));
+        mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL);
+
+        ArrayMap<Boolean, ArraySet<String>> approved = mAssistants.mApproved.get(0);
+        // approved should not be null
+        assertNotNull(approved);
+        assertEquals(new ArraySet<>(), approved.get(true));
+
+        // user set is maintained
+        assertTrue(mAssistants.mIsUserChanged.get(ActivityManager.getCurrentUser()));
+    }
+
+    @Test
     public void testReadXml_userDisabled() throws Exception {
         String xml = "<enabled_assistants version=\"4\" defaults=\"b/b\">"
                 + "<service_listing approved=\"\" user=\"0\" primary=\"true\""
@@ -160,6 +217,33 @@
     }
 
     @Test
+    public void testReadXml_userDisabled_restore() throws Exception {
+        String xml = "<enabled_assistants version=\"4\" defaults=\"b/b\">"
+                + "<service_listing approved=\"\" user=\"0\" primary=\"true\""
+                + "user_changed=\"true\"/>"
+                + "</enabled_assistants>";
+
+        final TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(xml.toString().getBytes())), null);
+        TriPredicate<String, Integer, String> allowedManagedServicePackages =
+                mNm::canUseManagedServices;
+
+        parser.nextTag();
+        mAssistants.readXml(parser, allowedManagedServicePackages, true,
+                ActivityManager.getCurrentUser());
+
+        ArrayMap<Boolean, ArraySet<String>> approved = mAssistants.mApproved.get(0);
+
+        // approved should not be null
+        assertNotNull(approved);
+        assertEquals(new ArraySet<>(), approved.get(true));
+
+        // user set is maintained
+        assertTrue(mAssistants.mIsUserChanged.get(ActivityManager.getCurrentUser()));
+    }
+
+    @Test
     public void testReadXml_upgradeUserSet() throws Exception {
         String xml = "<enabled_assistants version=\"3\" defaults=\"b/b\">"
                 + "<service_listing approved=\"\" user=\"0\" primary=\"true\""
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 e75afcc..a1c24f1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -34,10 +34,9 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-
-import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyObject;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -84,13 +83,13 @@
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Pair;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IAccessibilityManager;
 import android.view.accessibility.IAccessibilityManagerClient;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
@@ -103,10 +102,6 @@
 import com.android.server.lights.LogicalLight;
 import com.android.server.pm.PackageManagerService;
 
-import java.util.List;
-import java.util.Objects;
-
-import java.util.Set;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -118,6 +113,10 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.verification.VerificationMode;
 
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 @SuppressLint("GuardedBy")
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index 7b16500..5aecac2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -50,8 +50,8 @@
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.telecom.TelecomManager;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryFilterTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryFilterTest.java
index 10bfcf1..6faa899 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryFilterTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryFilterTest.java
@@ -20,9 +20,9 @@
 import android.app.NotificationHistory;
 import android.app.NotificationHistory.HistoricalNotification;
 import android.graphics.drawable.Icon;
-import android.test.suitebuilder.annotation.SmallTest;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
index 3499a12..bf8cfa5c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
@@ -20,8 +20,12 @@
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.TestCase.assertFalse;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -70,10 +74,11 @@
 
     @Before
     public void setUp() throws Exception {
-        mJobService = new NotificationHistoryJobService();
+        mJobService = spy(new NotificationHistoryJobService());
         mJobService.attachBaseContext(mContext);
         mJobService.onCreate();
         mJobService.onBind(/* intent= */ null);  // Create JobServiceEngine within JobService.
+        doNothing().when(mJobService).jobFinished(any(), eq(false));
 
         mContext.addMockSystemService(JobScheduler.class, mMockJobScheduler);
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryProtoHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryProtoHelperTest.java
index 6eaf546..b234c3e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryProtoHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryProtoHelperTest.java
@@ -20,10 +20,10 @@
 import android.app.NotificationHistory;
 import android.app.NotificationHistory.HistoricalNotification;
 import android.graphics.drawable.Icon;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.text.TextUtils;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
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 bf850cf..8fad01a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -56,8 +56,8 @@
 import android.service.notification.NotificationRankingUpdate;
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
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 715c9d4..26cda65 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -87,10 +87,10 @@
 import static android.service.notification.Adjustment.KEY_IMPORTANCE;
 import static android.service.notification.Adjustment.KEY_TEXT_REPLIES;
 import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
-import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS;
 import static android.service.notification.Condition.SOURCE_CONTEXT;
 import static android.service.notification.Condition.SOURCE_USER_ACTION;
 import static android.service.notification.Condition.STATE_TRUE;
+import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
@@ -247,7 +247,6 @@
 import android.service.notification.ZenPolicy;
 import android.telecom.TelecomManager;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
@@ -263,6 +262,7 @@
 import android.widget.RemoteViews;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
@@ -12008,7 +12008,7 @@
 
         // style + self managed call - bypasses block
         when(mTelecomManager.isInSelfManagedCall(
-                r.getSbn().getPackageName(), true)).thenReturn(true);
+                r.getSbn().getPackageName(), UserHandle.ALL)).thenReturn(true);
         assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
                 r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue();
 
@@ -12091,7 +12091,7 @@
         // style + self managed call - bypasses block
         mService.clearNotifications();
         reset(mUsageStats);
-        when(mTelecomManager.isInSelfManagedCall(r.getSbn().getPackageName(), true))
+        when(mTelecomManager.isInSelfManagedCall(r.getSbn().getPackageName(), UserHandle.ALL))
                 .thenReturn(true);
 
         mService.addEnqueuedNotification(r);
@@ -14092,7 +14092,8 @@
 
     @Test
     public void testProfileUnavailableIntent() throws RemoteException {
-        mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         simulateProfileAvailabilityActions(Intent.ACTION_PROFILE_UNAVAILABLE);
         verify(mWorkerHandler).post(any(Runnable.class));
         verify(mSnoozeHelper).clearData(anyInt());
@@ -14101,7 +14102,8 @@
 
     @Test
     public void testManagedProfileUnavailableIntent() throws RemoteException {
-        mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         simulateProfileAvailabilityActions(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
         verify(mWorkerHandler).post(any(Runnable.class));
         verify(mSnoozeHelper).clearData(anyInt());
@@ -14334,6 +14336,7 @@
 
     @Test
     @EnableFlags(android.app.Flags.FLAG_MODES_API)
+    @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
     public void requestInterruptionFilterFromListener_fromApp_doesNotSetGlobalZen()
             throws Exception {
         mService.setCallerIsNormalPackage();
@@ -14351,6 +14354,7 @@
 
     @Test
     @EnableFlags(android.app.Flags.FLAG_MODES_API)
+    @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
     public void requestInterruptionFilterFromListener_fromSystem_setsGlobalZen()
             throws Exception {
         mService.isSystemUid = true;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationShellCmdTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationShellCmdTest.java
index 0222bfbf..b42df77 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationShellCmdTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationShellCmdTest.java
@@ -22,7 +22,6 @@
 import static org.hamcrest.Matchers.instanceOf;
 import static org.junit.Assert.assertThat;
 import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -43,12 +42,12 @@
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.UserHandle;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableContext;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.server.UiServiceTestCase;
 
 import org.junit.Before;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotifyingAppTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotifyingAppTest.java
index 0c62831..30e851f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotifyingAppTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotifyingAppTest.java
@@ -20,8 +20,8 @@
 
 import android.os.Parcel;
 import android.service.notification.NotifyingApp;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
index 2f52d5c..2d591ba 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
@@ -43,9 +43,9 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.ParceledListSlice;
 import android.permission.IPermissionManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Pair;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 8b55778..bfc47fd 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -115,7 +115,6 @@
 import android.provider.Settings.Secure;
 import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.nano.RankingHelperProto;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.TestableContentResolver;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
@@ -128,6 +127,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
@@ -324,7 +324,11 @@
         when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
                 .thenReturn(appPermissions);
 
-        when(mUserProfiles.getCurrentProfileIds()).thenReturn(IntArray.wrap(new int[] {0}));
+        IntArray currentProfileIds = IntArray.wrap(new int[]{0});
+        if (UserManager.isHeadlessSystemUserMode()) {
+            currentProfileIds.add(UserHandle.getUserId(UID_HEADLESS));
+        }
+        when(mUserProfiles.getCurrentProfileIds()).thenReturn(currentProfileIds);
 
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index 4217881..d2c6028 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -43,10 +43,10 @@
 import android.os.UserHandle;
 import android.os.Vibrator;
 import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.TestableContentResolver;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RateEstimatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RateEstimatorTest.java
index 131aaa0..65ed7b6 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RateEstimatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RateEstimatorTest.java
@@ -19,8 +19,7 @@
 
 import static java.util.concurrent.TimeUnit.HOURS;
 
-import android.test.suitebuilder.annotation.SmallTest;
-
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 81c573d..70910b1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -54,7 +54,6 @@
 import android.permission.PermissionManager;
 import android.telecom.TelecomManager;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
@@ -63,6 +62,7 @@
 import android.util.Pair;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.config.sysui.TestableFlagResolver;
 import com.android.internal.logging.InstanceIdSequence;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java
index 9ad007d..7b4229f6 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java
@@ -23,9 +23,9 @@
 
 import android.service.notification.ScheduleCalendar;
 import android.service.notification.ZenModeConfig;
-import android.test.suitebuilder.annotation.SmallTest;
 
 import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
index 11cb150..a4fb16d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
@@ -36,9 +36,9 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
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 1e3b728..dcd56e0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -44,10 +44,10 @@
 import android.app.PendingIntent;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.IntArray;
 import android.util.Xml;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.modules.utils.TypedXmlPullParser;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java
index 0564a73..cb44222 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java
@@ -45,11 +45,11 @@
 import android.os.UserManager;
 import android.provider.ContactsContract;
 import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.text.SpannableString;
 import android.util.ArraySet;
 import android.util.LruCache;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
index f135d16..0993bec 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
@@ -24,8 +24,8 @@
 
 import android.os.VibrationEffect;
 import android.os.Vibrator;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/VisibilityExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/VisibilityExtractorTest.java
index 3998129..6084153 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/VisibilityExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/VisibilityExtractorTest.java
@@ -18,17 +18,11 @@
 import static android.app.Notification.VISIBILITY_PRIVATE;
 import static android.app.Notification.VISIBILITY_SECRET;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
 import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
 
 import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.Mockito.when;
 
@@ -36,15 +30,11 @@
 import android.app.Notification;
 import android.app.Notification.Builder;
 import android.app.NotificationChannel;
-import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
-import android.content.Intent;
-import android.graphics.drawable.Icon;
-import android.media.session.MediaSession;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
index 99d5a6d..75552bc 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
@@ -16,7 +16,7 @@
 
 package com.android.server.notification;
 
-import static com.android.server.notification.ZenAdapters.notificationPolicyToZenPolicy;
+import static android.service.notification.ZenAdapters.notificationPolicyToZenPolicy;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java
new file mode 100644
index 0000000..f724510
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AutomaticZenRule;
+import android.provider.Settings;
+import android.service.notification.ZenPolicy;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.os.dnd.ActiveRuleType;
+import com.android.os.dnd.ChannelPolicy;
+import com.android.os.dnd.ConversationType;
+import com.android.os.dnd.PeopleType;
+import com.android.os.dnd.State;
+import com.android.os.dnd.ZenMode;
+
+import com.google.protobuf.Internal;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/** Test to validate that logging enums used in Zen classes match their API definitions. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ZenEnumTest {
+
+    @Test
+    public void testEnum_zenMode() {
+        testEnum(Settings.Global.class, "ZEN_MODE", ZenMode.class, "ZEN_MODE");
+    }
+
+    @Test
+    public void testEnum_activeRuleType() {
+        testEnum(AutomaticZenRule.class, "TYPE", ActiveRuleType.class, "TYPE");
+    }
+
+    @Test
+    public void testEnum_zenPolicyState() {
+        testEnum(ZenPolicy.class, "STATE", State.class, "STATE");
+    }
+
+    @Test
+    public void testEnum_zenPolicyChannelPolicy() {
+        testEnum(ZenPolicy.class, "CHANNEL_POLICY", ChannelPolicy.class, "CHANNEL_POLICY");
+    }
+
+    @Test
+    public void testEnum_zenPolicyConversationType() {
+        testEnum(ZenPolicy.class, "CONVERSATION_SENDERS", ConversationType.class, "CONV");
+    }
+
+    @Test
+    public void testEnum_zenPolicyPeopleType() {
+        testEnum(ZenPolicy.class, "PEOPLE_TYPE", PeopleType.class, "PEOPLE");
+    }
+
+    /**
+     * Verifies that any constants (i.e. {@code public static final int} fields) named {@code
+     * <apiPrefix>_SOMETHING} in {@code apiClass} are present and have the same numerical value
+     * in the enum values defined in {@code loggingProtoEnumClass}.
+     *
+     * <p>Note that <em>extra</em> values in the logging enum are accepted (since we have one of
+     * those, and the main goal of this test is that we don't forget to update the logging enum
+     * if new API enum values are added).
+     */
+    private static void testEnum(Class<?> apiClass, String apiPrefix,
+            Class<? extends Internal.EnumLite> loggingProtoEnumClass,
+            String loggingPrefix) {
+        Map<String, Integer> apiConstants =
+                Arrays.stream(apiClass.getDeclaredFields())
+                        .filter(f -> Modifier.isPublic(f.getModifiers()))
+                        .filter(f -> Modifier.isStatic(f.getModifiers()))
+                        .filter(f -> Modifier.isFinal(f.getModifiers()))
+                        .filter(f -> f.getType().equals(int.class))
+                        .filter(f -> f.getName().startsWith(apiPrefix + "_"))
+                        .collect(Collectors.toMap(
+                                Field::getName,
+                                ZenEnumTest::getStaticFieldIntValue));
+
+        Map<String, Integer> loggingConstants =
+                Arrays.stream(loggingProtoEnumClass.getEnumConstants())
+                        .collect(Collectors.toMap(
+                                v -> v.toString(),
+                                v -> v.getNumber()));
+
+        Map<String, Integer> renamedApiConstants = apiConstants.entrySet().stream()
+                .collect(Collectors.toMap(
+                        entry -> entry.getKey().replace(apiPrefix + "_", loggingPrefix + "_"),
+                        Map.Entry::getValue));
+
+        assertThat(loggingConstants).containsAtLeastEntriesIn(renamedApiConstants);
+    }
+
+    private static int getStaticFieldIntValue(Field f) {
+        try {
+            return f.getInt(null);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
index 7d6e12c..b997f5d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
@@ -51,11 +51,12 @@
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenPolicy;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.ArraySet;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.util.NotificationMessagingUtil;
 import com.android.server.UiServiceTestCase;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 8c2fd10..c4d2460 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -22,6 +22,7 @@
 import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DEACTIVATED;
 import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DISABLED;
 import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ENABLED;
+import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_UNKNOWN;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
@@ -108,6 +109,7 @@
 import android.app.Flags;
 import android.app.NotificationManager;
 import android.app.NotificationManager.Policy;
+import android.app.compat.CompatChanges;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.pm.ActivityInfo;
@@ -134,6 +136,7 @@
 import android.provider.Settings.Global;
 import android.service.notification.Condition;
 import android.service.notification.DeviceEffectsApplier;
+import android.service.notification.ZenAdapters;
 import android.service.notification.ZenDeviceEffects;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
@@ -141,7 +144,6 @@
 import android.service.notification.ZenModeConfig.ZenRule;
 import android.service.notification.ZenModeDiff;
 import android.service.notification.ZenPolicy;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.TestWithLooperRule;
 import android.testing.TestableLooper;
 import android.util.ArrayMap;
@@ -150,6 +152,8 @@
 import android.util.StatsEventTestUtils;
 import android.util.Xml;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.R;
 import com.android.internal.config.sysui.TestableFlagResolver;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
@@ -4673,7 +4677,11 @@
                 UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
-        assertEquals(AUTOMATIC_RULE_STATUS_ACTIVATED, actualStatus[0]);
+        if (CompatChanges.isChangeEnabled(ZenModeHelper.SEND_ACTIVATION_AZR_STATUSES)) {
+            assertEquals(AUTOMATIC_RULE_STATUS_ACTIVATED, actualStatus[0]);
+        } else {
+            assertEquals(AUTOMATIC_RULE_STATUS_UNKNOWN, actualStatus[0]);
+        }
     }
 
     @Test
@@ -4714,7 +4722,11 @@
                 null, "", Process.SYSTEM_UID);
 
         assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
-        assertEquals(AUTOMATIC_RULE_STATUS_DEACTIVATED, actualStatus[1]);
+        if (CompatChanges.isChangeEnabled(ZenModeHelper.SEND_ACTIVATION_AZR_STATUSES)) {
+            assertEquals(AUTOMATIC_RULE_STATUS_DEACTIVATED, actualStatus[1]);
+        } else {
+            assertEquals(AUTOMATIC_RULE_STATUS_UNKNOWN, actualStatus[1]);
+        }
     }
 
     @Test
@@ -4753,10 +4765,14 @@
 
         mZenModeHelper.setAutomaticZenRuleState(createdId,
                 new Condition(zenRule.getConditionId(), "", STATE_FALSE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                UPDATE_ORIGIN_APP, Process.SYSTEM_UID);
 
         assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
-        assertEquals(AUTOMATIC_RULE_STATUS_DEACTIVATED, actualStatus[1]);
+        if (CompatChanges.isChangeEnabled(ZenModeHelper.SEND_ACTIVATION_AZR_STATUSES)) {
+            assertEquals(AUTOMATIC_RULE_STATUS_DEACTIVATED, actualStatus[1]);
+        } else {
+            assertEquals(AUTOMATIC_RULE_STATUS_UNKNOWN, actualStatus[1]);
+        }
     }
 
     @Test
@@ -4792,6 +4808,53 @@
 
     @Test
     @EnableFlags(Flags.FLAG_MODES_API)
+    public void updateAutomaticZenRule_ruleChanged_deactivatesRule() {
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", CONDITION_ID)
+                .setConfigurationActivity(new ComponentName(mPkg, "cls"))
+                .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, UPDATE_ORIGIN_APP, "reason",
+                CUSTOM_PKG_UID);
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+        AutomaticZenRule updateWithDiff = new AutomaticZenRule.Builder(rule)
+                .setTriggerDescription("Whenever")
+                .build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, UPDATE_ORIGIN_APP, "reason",
+                CUSTOM_PKG_UID);
+
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+        assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isNull();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void updateAutomaticZenRule_ruleNotChanged_doesNotDeactivateRule() {
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", CONDITION_ID)
+                .setConfigurationActivity(new ComponentName(mPkg, "cls"))
+                .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, UPDATE_ORIGIN_APP, "reason",
+                CUSTOM_PKG_UID);
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+        AutomaticZenRule updateUnchanged = new AutomaticZenRule.Builder(rule).build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId, updateUnchanged, UPDATE_ORIGIN_APP, "reason",
+                CUSTOM_PKG_UID);
+
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+        assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isEqualTo(
+                CONDITION_TRUE);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
     public void removeAutomaticZenRule_propagatesOriginToEffectsApplier() {
         mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
         reset(mDeviceEffectsApplier);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
index 3a88294..6433b76 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
@@ -26,8 +26,8 @@
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.notification.ZenPolicy;
 import android.service.notification.nano.DNDPolicyProto;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/GroupedAggregatedLogRecordsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/GroupedAggregatedLogRecordsTest.java
new file mode 100644
index 0000000..54ab367
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/GroupedAggregatedLogRecordsTest.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.util.IndentingPrintWriter;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.vibrator.GroupedAggregatedLogRecords.AggregatedLogRecord;
+import com.android.server.vibrator.GroupedAggregatedLogRecords.SingleLogRecord;
+
+import org.junit.Test;
+
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class GroupedAggregatedLogRecordsTest {
+
+    private static final int AGGREGATION_TIME_LIMIT = 1000;
+    private static final int NO_AGGREGATION_TIME_LIMIT = 0;
+    private static final long PROTO_FIELD_ID = 1;
+    private static final int GROUP_1 = 1;
+    private static final int GROUP_2 = 2;
+    private static final int KEY_1 = 1;
+    private static final int KEY_2 = 2;
+
+    private static final IndentingPrintWriter WRITER = new IndentingPrintWriter(new StringWriter());
+    private static final ProtoOutputStream PROTO_OUTPUT_STREAM = new ProtoOutputStream();
+
+    private final List<TestSingleLogRecord> mTestRecords = new ArrayList<>();
+
+    @Test
+    public void record_noAggregation_keepsIndividualRecords() {
+        int sizeLimit = 10;
+        long createTime = 100;
+        TestGroupedAggregatedLogRecords records = new TestGroupedAggregatedLogRecords(
+                sizeLimit, NO_AGGREGATION_TIME_LIMIT, PROTO_FIELD_ID);
+
+        for (int i = 0; i < sizeLimit; i++) {
+            assertThat(records.add(createRecord(GROUP_1, KEY_1, createTime++))).isNull();
+        }
+
+        dumpRecords(records);
+        assertGroupHeadersWrittenOnce(records, GROUP_1);
+        assertRecordsInRangeWrittenOnce(0, sizeLimit);
+    }
+
+    @Test
+    public void record_sizeLimit_dropsOldestEntriesForNewOnes() {
+        long createTime = 100;
+        TestGroupedAggregatedLogRecords records = new TestGroupedAggregatedLogRecords(
+                /* sizeLimit= */ 2, NO_AGGREGATION_TIME_LIMIT, PROTO_FIELD_ID);
+
+        TestSingleLogRecord firstRecord = createRecord(GROUP_1, KEY_1, createTime++);
+        assertThat(records.add(firstRecord)).isNull();
+        assertThat(records.add(createRecord(GROUP_1, KEY_1, createTime++))).isNull();
+
+        // Adding third record drops first record
+        AggregatedLogRecord<TestSingleLogRecord> droppedRecord =
+                records.add(createRecord(GROUP_1, KEY_1, createTime++));
+        assertThat(droppedRecord).isNotNull();
+        assertThat(droppedRecord.getLatest()).isEqualTo(firstRecord);
+
+        dumpRecords(records);
+        assertGroupHeadersWrittenOnce(records, GROUP_1);
+        assertRecordsInRangeNotWritten(0, 1);  // First record not written
+        assertRecordsInRangeWrittenOnce(1, 3); // All newest records written
+    }
+
+    @Test
+    public void record_timeAggregation_aggregatesCloseRecordAndPrintsOnlyFirstAndLast() {
+        long createTime = 100;
+        TestGroupedAggregatedLogRecords records = new TestGroupedAggregatedLogRecords(
+                /* sizeLimit= */ 1, AGGREGATION_TIME_LIMIT, PROTO_FIELD_ID);
+
+        // No record dropped, all aggregated in a single entry
+        assertThat(records.add(createRecord(GROUP_1, KEY_1, createTime))).isNull();
+        assertThat(records.add(createRecord(GROUP_1, KEY_1, createTime + 1))).isNull();
+        assertThat(records.add(createRecord(GROUP_1, KEY_1,
+                createTime + AGGREGATION_TIME_LIMIT - 2))).isNull();
+        assertThat(records.add(createRecord(GROUP_1, KEY_1,
+                createTime + AGGREGATION_TIME_LIMIT - 1))).isNull();
+
+        dumpRecords(records);
+        assertGroupHeadersWrittenOnce(records, GROUP_1);
+        assertRecordsInRangeWrittenOnce(0, 1); // Writes first record
+        assertRecordsInRangeNotWritten(1, 3);  // Skips aggregated records in between
+        assertRecordsInRangeWrittenOnce(3, 4); // Writes last record
+    }
+
+    @Test
+    public void record_differentGroups_recordsKeptSeparate() {
+        long createTime = 100;
+        TestGroupedAggregatedLogRecords records = new TestGroupedAggregatedLogRecords(
+                /* sizeLimit= */ 1, AGGREGATION_TIME_LIMIT, PROTO_FIELD_ID);
+
+        // No record dropped, all kept in separate aggregated lists
+        assertThat(records.add(createRecord(GROUP_1, KEY_1, createTime++))).isNull();
+        assertThat(records.add(createRecord(GROUP_1, KEY_1, createTime++))).isNull();
+        assertThat(records.add(createRecord(GROUP_2, KEY_2, createTime++))).isNull();
+        assertThat(records.add(createRecord(GROUP_2, KEY_2, createTime++))).isNull();
+
+        dumpRecords(records);
+        assertGroupHeadersWrittenOnce(records, GROUP_1, GROUP_2);
+        assertRecordsInRangeWrittenOnce(0, 4);
+    }
+
+    @Test
+    public void record_sameGroupDifferentAggregationKeys_recordsNotAggregated() {
+        long createTime = 100;
+        TestGroupedAggregatedLogRecords records = new TestGroupedAggregatedLogRecords(
+                /* sizeLimit= */ 1, AGGREGATION_TIME_LIMIT, PROTO_FIELD_ID);
+
+        assertThat(records.add(createRecord(GROUP_1, KEY_1, createTime++))).isNull();
+
+        // Second record on same group with different key not aggregated, drops first record
+        AggregatedLogRecord<TestSingleLogRecord> droppedRecord =
+                records.add(createRecord(GROUP_1, KEY_2, createTime++));
+        assertThat(droppedRecord).isNotNull();
+        assertThat(droppedRecord.getLatest()).isEqualTo(mTestRecords.get(0));
+
+        dumpRecords(records);
+        assertGroupHeadersWrittenOnce(records, GROUP_1);
+        assertRecordsInRangeNotWritten(0, 1);  // Skips first record that was dropped
+        assertRecordsInRangeWrittenOnce(1, 2); // Writes last record
+    }
+
+    @Test
+    public void record_sameGroupAndAggregationKeysDistantTimes_recordsNotAggregated() {
+        long createTime = 100;
+        TestGroupedAggregatedLogRecords records = new TestGroupedAggregatedLogRecords(
+                /* sizeLimit= */ 1, AGGREGATION_TIME_LIMIT, PROTO_FIELD_ID);
+
+        assertThat(records.add(createRecord(GROUP_1, KEY_1, createTime))).isNull();
+
+        // Second record after aggregation time limit not aggregated, drops first record
+        AggregatedLogRecord<TestSingleLogRecord> droppedRecord =
+                records.add(createRecord(GROUP_1, KEY_1, createTime + AGGREGATION_TIME_LIMIT));
+        assertThat(droppedRecord).isNotNull();
+        assertThat(droppedRecord.getLatest()).isEqualTo(mTestRecords.get(0));
+
+        dumpRecords(records);
+        assertGroupHeadersWrittenOnce(records, GROUP_1);
+        assertRecordsInRangeNotWritten(0, 1);  // Skips first record that was dropped
+        assertRecordsInRangeWrittenOnce(1, 2); // Writes last record
+    }
+
+    private TestSingleLogRecord createRecord(int groupKey, int aggregateKey, long createTime) {
+        TestSingleLogRecord record = new TestSingleLogRecord(groupKey, aggregateKey, createTime);
+        mTestRecords.add(record);
+        return record;
+    }
+
+    private void dumpRecords(TestGroupedAggregatedLogRecords records) {
+        records.dump(WRITER);
+        records.dump(PROTO_OUTPUT_STREAM);
+    }
+
+    private void assertGroupHeadersWrittenOnce(TestGroupedAggregatedLogRecords records,
+            int... groupKeys) {
+        assertThat(records.dumpGroupKeys).containsExactlyElementsIn(
+                Arrays.stream(groupKeys).boxed().toList());
+    }
+
+    private void assertRecordsInRangeWrittenOnce(int startIndexInclusive, int endIndexExclusive) {
+        for (int i = startIndexInclusive; i < endIndexExclusive; i++) {
+            assertWithMessage("record index=" + i).that(mTestRecords.get(i).dumpTextCount)
+                    .isEqualTo(1);
+            assertWithMessage("record index=" + i).that(mTestRecords.get(i).dumpProtoFieldIds)
+                    .containsExactly(PROTO_FIELD_ID);
+        }
+    }
+
+    private void assertRecordsInRangeNotWritten(int startIndexInclusive, int endIndexExclusive) {
+        for (int i = startIndexInclusive; i < endIndexExclusive; i++) {
+            assertWithMessage("record index=" + i).that(mTestRecords.get(i).dumpTextCount)
+                    .isEqualTo(0);
+            assertWithMessage("record index=" + i).that(mTestRecords.get(i).dumpProtoFieldIds)
+                    .isEmpty();
+        }
+    }
+
+    private static final class TestGroupedAggregatedLogRecords
+            extends GroupedAggregatedLogRecords<TestSingleLogRecord> {
+
+        public final List<Integer> dumpGroupKeys = new ArrayList<>();
+
+        private final long mProtoFieldId;
+
+        TestGroupedAggregatedLogRecords(int sizeLimit, int aggregationTimeLimitMs,
+                long protoFieldId) {
+            super(sizeLimit, aggregationTimeLimitMs);
+            mProtoFieldId = protoFieldId;
+        }
+
+        @Override
+        void dumpGroupHeader(IndentingPrintWriter pw, int groupKey) {
+            dumpGroupKeys.add(groupKey);
+        }
+
+        @Override
+        long findGroupKeyProtoFieldId(int groupKey) {
+            return mProtoFieldId;
+        }
+    }
+
+    private static final class TestSingleLogRecord implements SingleLogRecord {
+        public final List<Long> dumpProtoFieldIds = new ArrayList<>();
+        public int dumpTextCount = 0;
+
+        private final int mGroupKey;
+        private final int mAggregateKey;
+        private final long mCreateTime;
+
+        TestSingleLogRecord(int groupKey, int aggregateKey, long createTime) {
+            mGroupKey = groupKey;
+            mAggregateKey = aggregateKey;
+            mCreateTime = createTime;
+        }
+
+        @Override
+        public int getGroupKey() {
+            return mGroupKey;
+        }
+
+        @Override
+        public long getCreateUptimeMs() {
+            return mCreateTime;
+        }
+
+        @Override
+        public boolean mayAggregate(SingleLogRecord record) {
+            if (record instanceof TestSingleLogRecord param) {
+                return mAggregateKey == param.mAggregateKey;
+            }
+            return false;
+        }
+
+        @Override
+        public void dump(IndentingPrintWriter pw) {
+            dumpTextCount++;
+        }
+
+        @Override
+        public void dump(ProtoOutputStream proto, long fieldId) {
+            dumpProtoFieldIds.add(fieldId);
+        }
+    }
+}
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 3d0dca0..e3d4596 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
@@ -17,9 +17,11 @@
 package com.android.server.vibrator;
 
 import static android.os.VibrationAttributes.CATEGORY_KEYBOARD;
+import static android.os.VibrationAttributes.CATEGORY_UNKNOWN;
 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.VibrationAttributes.USAGE_TOUCH;
 import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
 import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
 import static android.os.VibrationEffect.EFFECT_CLICK;
@@ -285,7 +287,8 @@
         HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
 
         VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
-                SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ false);
+                SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ false,
+                false /* fromIme*/);
 
         assertThat(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)).isFalse();
     }
@@ -295,7 +298,7 @@
         HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
 
         VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
-                SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ true);
+                SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ true, false /* fromIme*/);
 
         assertThat(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)).isTrue();
     }
@@ -307,7 +310,7 @@
 
         for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
             VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
-                    effectId, /* bypassVibrationIntensitySetting= */ false);
+                    effectId, /* bypassVibrationIntensitySetting= */ false, false /* fromIme*/);
             assertWithMessage("Expected FLAG_BYPASS_INTERRUPTION_POLICY for effect " + effectId)
                    .that(attrs.isFlagSet(FLAG_BYPASS_INTERRUPTION_POLICY)).isTrue();
         }
@@ -320,40 +323,59 @@
 
         for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
             VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
-                    effectId, /* bypassVibrationIntensitySetting= */ false);
+                    effectId, /* bypassVibrationIntensitySetting= */ false, false /* fromIme*/);
             assertWithMessage("Expected no FLAG_BYPASS_INTERRUPTION_POLICY for effect " + effectId)
                    .that(attrs.isFlagSet(FLAG_BYPASS_INTERRUPTION_POLICY)).isFalse();
         }
     }
 
     @Test
-    public void testVibrationAttribute_keyboardCategoryOff_notUseKeyboardCategory() {
+    public void testVibrationAttribute_keyboardCategoryOff_isIme_notUseKeyboardCategory() {
         mSetFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
         HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
 
         for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
             VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
-                    effectId, /* bypassVibrationIntensitySetting= */ false);
+                    effectId, /* bypassVibrationIntensitySetting= */ false, true /* fromIme*/);
+            assertWithMessage("Expected USAGE_TOUCH for effect " + effectId)
+                    .that(attrs.getUsage()).isEqualTo(USAGE_TOUCH);
             assertWithMessage("Expected no CATEGORY_KEYBOARD for effect " + effectId)
-                    .that(attrs.getCategory()).isEqualTo(0);
+                    .that(attrs.getCategory()).isEqualTo(CATEGORY_UNKNOWN);
         }
     }
 
     @Test
-    public void testVibrationAttribute_keyboardCategoryOn_useKeyboardCategory() {
+    public void testVibrationAttribute_keyboardCategoryOn_notIme_notUseKeyboardCategory() {
         mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
         HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
 
         for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
             VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
-                    effectId, /* bypassVibrationIntensitySetting= */ false);
+                    effectId, /* bypassVibrationIntensitySetting= */ false, false /* fromIme*/);
+            assertWithMessage("Expected USAGE_TOUCH for effect " + effectId)
+                    .that(attrs.getUsage()).isEqualTo(USAGE_TOUCH);
+            assertWithMessage("Expected CATEGORY_KEYBOARD for effect " + effectId)
+                    .that(attrs.getCategory()).isEqualTo(CATEGORY_UNKNOWN);
+        }
+    }
+
+    @Test
+    public void testVibrationAttribute_keyboardCategoryOn_isIme_useKeyboardCategory() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
+        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+
+        for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
+            VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+                    effectId, /* bypassVibrationIntensitySetting= */ false, true /* fromIme*/);
+            assertWithMessage("Expected USAGE_TOUCH for effect " + effectId)
+                    .that(attrs.getUsage()).isEqualTo(USAGE_TOUCH);
             assertWithMessage("Expected CATEGORY_KEYBOARD for effect " + effectId)
                     .that(attrs.getCategory()).isEqualTo(CATEGORY_KEYBOARD);
         }
     }
 
     @Test
-    public void testVibrationAttribute_noFixAmplitude_keyboardCategoryOn_noBypassIntensityScale() {
+    public void testVibrationAttribute_noFixAmplitude_notBypassIntensityScale() {
         mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
         mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
         mockKeyboardVibrationFixedAmplitude(-1);
@@ -361,7 +383,7 @@
 
         for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
             VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
-                    effectId, /* bypassVibrationIntensitySetting= */ false);
+                    effectId, /* bypassVibrationIntensitySetting= */ false, true /* fromIme*/);
             assertWithMessage("Expected no FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect "
                     + effectId)
                     .that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isFalse();
@@ -369,7 +391,7 @@
     }
 
     @Test
-    public void testVibrationAttribute_fixAmplitude_keyboardCategoryOn_bypassIntensityScale() {
+    public void testVibrationAttribute_notIme_notBypassIntensityScale() {
         mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
         mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
         mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE);
@@ -377,7 +399,23 @@
 
         for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
             VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
-                    effectId, /* bypassVibrationIntensitySetting= */ false);
+                    effectId, /* bypassVibrationIntensitySetting= */ false, false /* fromIme*/);
+            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_isIme_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, true /* fromIme*/);
             assertWithMessage("Expected FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect "
                     + effectId)
                     .that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isTrue();
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
index 3e59878..b264435 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
@@ -117,32 +117,32 @@
     }
 
     @Test
-    public void testGetExternalVibrationScale() {
+    public void testGetScaleLevel() {
         setDefaultIntensity(USAGE_TOUCH, Vibrator.VIBRATION_INTENSITY_LOW);
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
         assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_VERY_HIGH,
-                mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH));
+                mVibrationScaler.getScaleLevel(USAGE_TOUCH));
 
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
         assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_HIGH,
-                mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH));
+                mVibrationScaler.getScaleLevel(USAGE_TOUCH));
 
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_LOW);
         assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_NONE,
-                mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH));
+                mVibrationScaler.getScaleLevel(USAGE_TOUCH));
 
         setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_MEDIUM);
         assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_LOW,
-                mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH));
+                mVibrationScaler.getScaleLevel(USAGE_TOUCH));
 
         setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_HIGH);
         assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_VERY_LOW,
-                mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH));
+                mVibrationScaler.getScaleLevel(USAGE_TOUCH));
 
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
         // Vibration setting being bypassed will use default setting and not scale.
         assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_NONE,
-                mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH));
+                mVibrationScaler.getScaleLevel(USAGE_TOUCH));
     }
 
     @Test
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 f54c7e5..88a9483 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -604,7 +604,8 @@
     @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);
+        setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 0 /* OFF*/);
+        setHasFixedKeyboardAmplitudeIntensity(true);
 
         // Keyboard touch ignored.
         assertVibrationIgnoredForAttributes(
@@ -628,7 +629,8 @@
     @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);
+        setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */);
+        setHasFixedKeyboardAmplitudeIntensity(true);
 
         // General touch ignored.
         assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS);
@@ -642,6 +644,25 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
+    public void shouldIgnoreVibration_noFixedKeyboardAmplitude_ignoresKeyboardTouchVibration() {
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */);
+        setHasFixedKeyboardAmplitudeIntensity(false);
+
+        // General touch ignored.
+        assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS);
+
+        // Keyboard touch ignored.
+        assertVibrationIgnoredForAttributes(
+                new VibrationAttributes.Builder()
+                        .setUsage(USAGE_TOUCH)
+                        .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
+                        .build(),
+                Vibration.Status.IGNORED_FOR_SETTINGS);
+    }
+
+    @Test
     public void shouldIgnoreVibrationFromVirtualDevices_defaultDevice_neverIgnored() {
         // Vibrations from the primary device is never ignored.
         for (int usage : ALL_USAGES) {
@@ -953,6 +974,10 @@
         when(mVibrationConfigMock.ignoreVibrationsOnWirelessCharger()).thenReturn(ignore);
     }
 
+    private void setHasFixedKeyboardAmplitudeIntensity(boolean hasFixedAmplitude) {
+        when(mVibrationConfigMock.hasFixedKeyboardAmplitude()).thenReturn(hasFixedAmplitude);
+    }
+
     private void deleteUserSetting(String settingName) {
         Settings.System.putStringForUser(
                 mContextSpy.getContentResolver(), settingName, null, UserHandle.USER_CURRENT);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 6e478d8..0b76154 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -87,6 +87,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.function.BooleanSupplier;
 import java.util.stream.Collectors;
 
@@ -110,6 +112,7 @@
     @Mock private VibratorController.OnVibrationCompleteListener mControllerCallbacks;
     @Mock private IBinder mVibrationToken;
     @Mock private VibrationConfig mVibrationConfigMock;
+    @Mock private VibratorFrameworkStatsLogger mStatsLoggerMock;
 
     private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
     private VibrationSettings mVibrationSettings;
@@ -255,6 +258,7 @@
                 USAGE_RINGTONE);
         waitForCompletion();
 
+        verify(mStatsLoggerMock, never()).logVibrationParamRequestTimeout(UID);
         assertEquals(Arrays.asList(expectedOneShot(15)),
                 mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
         List<Float> amplitudes = mVibratorProviders.get(VIBRATOR_ID).getAmplitudes();
@@ -274,6 +278,7 @@
         long vibrationId = startThreadAndDispatcher(effect, neverCompletingFuture, USAGE_RINGTONE);
         waitForCompletion();
 
+        verify(mStatsLoggerMock).logVibrationParamRequestTimeout(UID);
         assertEquals(Arrays.asList(expectedOneShot(15)),
                 mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
         assertEquals(expectedAmplitudes(1, 1, 1),
@@ -1243,6 +1248,112 @@
         assertEquals(expectedAmplitudes(6), mVibratorProviders.get(3).getAmplitudes());
     }
 
+    @Test
+    public void vibrate_withRampDown_vibrationFinishedAfterDurationAndBeforeRampDown()
+            throws Exception {
+        int expectedDuration = 100;
+        int rampDownDuration = 200;
+
+        when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(rampDownDuration);
+        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        HalVibration vibration = createVibration(
+                CombinedVibration.createParallel(
+                        VibrationEffect.createOneShot(
+                                expectedDuration, VibrationEffect.DEFAULT_AMPLITUDE)));
+        CountDownLatch vibrationCompleteLatch = new CountDownLatch(1);
+        doAnswer(unused -> {
+            vibrationCompleteLatch.countDown();
+            return null;
+        }).when(mManagerHooks).onVibrationCompleted(eq(vibration.id), any());
+
+        startThreadAndDispatcher(vibration);
+        long startTime = SystemClock.elapsedRealtime();
+
+        assertTrue(vibrationCompleteLatch.await(expectedDuration + TEST_TIMEOUT_MILLIS,
+                TimeUnit.MILLISECONDS));
+        long vibrationEndTime = SystemClock.elapsedRealtime();
+
+        waitForCompletion(rampDownDuration + TEST_TIMEOUT_MILLIS);
+        long completionTime = SystemClock.elapsedRealtime();
+
+        verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibration.id);
+        // Vibration ends after duration, thread completed after ramp down
+        assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration);
+        assertThat(vibrationEndTime - startTime).isLessThan(expectedDuration + rampDownDuration);
+        assertThat(completionTime - startTime).isAtLeast(expectedDuration + rampDownDuration);
+    }
+
+    @Test
+    public void vibrate_withVibratorCallbackDelayShorterThanTimeout_vibrationFinishedAfterDelay()
+            throws Exception {
+        long expectedDuration = 10;
+        long callbackDelay = VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT / 2;
+
+        mVibratorProviders.get(VIBRATOR_ID).setCompletionCallbackDelay(callbackDelay);
+        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        HalVibration vibration = createVibration(
+                CombinedVibration.createParallel(
+                        VibrationEffect.createOneShot(
+                                expectedDuration, VibrationEffect.DEFAULT_AMPLITUDE)));
+        CountDownLatch vibrationCompleteLatch = new CountDownLatch(1);
+        doAnswer(unused -> {
+            vibrationCompleteLatch.countDown();
+            return null;
+        }).when(mManagerHooks).onVibrationCompleted(eq(vibration.id), any());
+
+        startThreadAndDispatcher(vibration);
+        long startTime = SystemClock.elapsedRealtime();
+
+        assertTrue(vibrationCompleteLatch.await(callbackDelay + TEST_TIMEOUT_MILLIS,
+                TimeUnit.MILLISECONDS));
+        long vibrationEndTime = SystemClock.elapsedRealtime();
+
+        waitForCompletion(TEST_TIMEOUT_MILLIS);
+
+        verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibration.id);
+        assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration + callbackDelay);
+    }
+
+    @LargeTest
+    @Test
+    public void vibrate_withVibratorCallbackDelayLongerThanTimeout_vibrationFinishedAfterTimeout()
+            throws Exception {
+        long expectedDuration = 10;
+        long callbackTimeout = VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
+        long callbackDelay = callbackTimeout * 2;
+
+        mVibratorProviders.get(VIBRATOR_ID).setCompletionCallbackDelay(callbackDelay);
+        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        HalVibration vibration = createVibration(
+                CombinedVibration.createParallel(
+                        VibrationEffect.createOneShot(
+                                expectedDuration, VibrationEffect.DEFAULT_AMPLITUDE)));
+        CountDownLatch vibrationCompleteLatch = new CountDownLatch(1);
+        doAnswer(unused -> {
+            vibrationCompleteLatch.countDown();
+            return null;
+        }).when(mManagerHooks).onVibrationCompleted(eq(vibration.id), any());
+
+        startThreadAndDispatcher(vibration);
+        long startTime = SystemClock.elapsedRealtime();
+
+        assertTrue(vibrationCompleteLatch.await(callbackTimeout + TEST_TIMEOUT_MILLIS,
+                TimeUnit.MILLISECONDS));
+        long vibrationEndTime = SystemClock.elapsedRealtime();
+
+        waitForCompletion(callbackDelay + TEST_TIMEOUT_MILLIS);
+        long completionTime = SystemClock.elapsedRealtime();
+
+        verify(mControllerCallbacks, never()).onComplete(VIBRATOR_ID, vibration.id);
+        // Vibration ends and thread completes after timeout, before the HAL callback
+        assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration + callbackTimeout);
+        assertThat(vibrationEndTime - startTime).isLessThan(expectedDuration + callbackDelay);
+        assertThat(completionTime - startTime).isLessThan(expectedDuration + callbackDelay);
+    }
+
     @LargeTest
     @Test
     public void vibrate_withWaveform_totalVibrationTimeRespected() {
@@ -1679,7 +1790,7 @@
         mControllers = createVibratorControllers();
         DeviceAdapter deviceAdapter = new DeviceAdapter(mVibrationSettings, mControllers);
         mVibrationConductor = new VibrationStepConductor(vib, mVibrationSettings, deviceAdapter,
-                mVibrationScaler, requestVibrationParamsFuture, mManagerHooks);
+                mVibrationScaler, mStatsLoggerMock, requestVibrationParamsFuture, mManagerHooks);
         assertTrue(mThread.runVibrationOnVibrationThread(mVibrationConductor));
         return mVibrationConductor.getVibration().id;
     }
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index 0d5bf95..3f5217c 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -27,6 +27,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.verifyZeroInteractions;
@@ -38,10 +42,11 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.RemoteException;
+import android.os.Process;
 import android.os.test.TestLooper;
 import android.util.SparseArray;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.internal.util.ArrayUtils;
@@ -59,13 +64,14 @@
 
 public class VibratorControlServiceTest {
 
+    private static final int UID = Process.ROOT_UID;
+
     @Rule
     public MockitoRule rule = MockitoJUnit.rule();
 
-    @Mock
-    private VibrationScaler mMockVibrationScaler;
-    @Mock
-    private PackageManagerInternal mPackageManagerInternalMock;
+    @Mock private VibrationScaler mMockVibrationScaler;
+    @Mock private PackageManagerInternal mPackageManagerInternalMock;
+    @Mock private VibratorFrameworkStatsLogger mStatsLoggerMock;
 
     private TestLooper mTestLooper;
     private FakeVibratorController mFakeVibratorController;
@@ -86,20 +92,20 @@
                 ApplicationProvider.getApplicationContext(), new Handler(testLooper.getLooper()));
 
         mFakeVibratorController = new FakeVibratorController(mTestLooper.getLooper());
-        mVibratorControlService = new VibratorControlService(new VibratorControllerHolder(),
-                mMockVibrationScaler, mVibrationSettings, mLock);
+        mVibratorControlService = new VibratorControlService(
+                InstrumentationRegistry.getContext(), new VibratorControllerHolder(),
+                mMockVibrationScaler, mVibrationSettings, mStatsLoggerMock, mLock);
     }
 
     @Test
-    public void testRegisterVibratorController() throws RemoteException {
+    public void testRegisterVibratorController() {
         mVibratorControlService.registerVibratorController(mFakeVibratorController);
 
         assertThat(mFakeVibratorController.isLinkedToDeath).isTrue();
     }
 
     @Test
-    public void testUnregisterVibratorController_providingTheRegisteredController_performsRequest()
-            throws RemoteException {
+    public void testUnregisterVibratorController_providingRegisteredController_performsRequest() {
         mVibratorControlService.registerVibratorController(mFakeVibratorController);
         mVibratorControlService.unregisterVibratorController(mFakeVibratorController);
 
@@ -108,8 +114,7 @@
     }
 
     @Test
-    public void testUnregisterVibratorController_providingAnInvalidController_ignoresRequest()
-            throws RemoteException {
+    public void testUnregisterVibratorController_providingAnInvalidController_ignoresRequest() {
         FakeVibratorController controller1 = new FakeVibratorController(mTestLooper.getLooper());
         FakeVibratorController controller2 = new FakeVibratorController(mTestLooper.getLooper());
         mVibratorControlService.registerVibratorController(controller1);
@@ -120,12 +125,11 @@
     }
 
     @Test
-    public void testOnRequestVibrationParamsComplete_cachesAdaptiveHapticsScalesCorrectly()
-            throws RemoteException {
+    public void testOnRequestVibrationParamsComplete_cachesAdaptiveHapticsScalesCorrectly() {
         mVibratorControlService.registerVibratorController(mFakeVibratorController);
         int timeoutInMillis = 10;
         CompletableFuture<Void> future =
-                mVibratorControlService.triggerVibrationParamsRequest(USAGE_RINGTONE,
+                mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
                         timeoutInMillis);
         IBinder token = mVibratorControlService.getRequestVibrationParamsToken();
 
@@ -136,6 +140,11 @@
         mVibratorControlService.onRequestVibrationParamsComplete(token,
                 VibrationParamGenerator.generateVibrationParams(vibrationScales));
 
+        verify(mStatsLoggerMock).logVibrationParamRequestLatency(eq(UID), anyLong());
+        verify(mStatsLoggerMock).logVibrationParamScale(0.7f);
+        verify(mStatsLoggerMock).logVibrationParamScale(0.4f);
+        verifyNoMoreInteractions(mStatsLoggerMock);
+
         verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_ALARM, 0.7f);
         verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.4f);
         // Setting ScaleParam.TYPE_NOTIFICATION will update vibration scaling for both
@@ -148,12 +157,11 @@
     }
 
     @Test
-    public void testOnRequestVibrationParamsComplete_withIncorrectToken_ignoresRequest()
-            throws RemoteException, InterruptedException {
+    public void testOnRequestVibrationParamsComplete_withIncorrectToken_ignoresRequest() {
         mVibratorControlService.registerVibratorController(mFakeVibratorController);
         int timeoutInMillis = 10;
         CompletableFuture<Void> unusedFuture =
-                mVibratorControlService.triggerVibrationParamsRequest(USAGE_RINGTONE,
+                mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
                         timeoutInMillis);
 
         SparseArray<Float> vibrationScales = new SparseArray<>();
@@ -163,12 +171,14 @@
         mVibratorControlService.onRequestVibrationParamsComplete(new Binder(),
                 VibrationParamGenerator.generateVibrationParams(vibrationScales));
 
+        verify(mStatsLoggerMock).logVibrationParamResponseIgnored();
+        verifyNoMoreInteractions(mStatsLoggerMock);
+
         verifyZeroInteractions(mMockVibrationScaler);
     }
 
     @Test
-    public void testSetVibrationParams_cachesAdaptiveHapticsScalesCorrectly()
-            throws RemoteException {
+    public void testSetVibrationParams_cachesAdaptiveHapticsScalesCorrectly() {
         mVibratorControlService.registerVibratorController(mFakeVibratorController);
         SparseArray<Float> vibrationScales = new SparseArray<>();
         vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
@@ -178,6 +188,10 @@
                 VibrationParamGenerator.generateVibrationParams(vibrationScales),
                 mFakeVibratorController);
 
+        verify(mStatsLoggerMock).logVibrationParamScale(0.7f);
+        verify(mStatsLoggerMock).logVibrationParamScale(0.4f);
+        verifyNoMoreInteractions(mStatsLoggerMock);
+
         verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_ALARM, 0.7f);
         verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.4f);
         // Setting ScaleParam.TYPE_NOTIFICATION will update vibration scaling for both
@@ -187,8 +201,7 @@
     }
 
     @Test
-    public void testSetVibrationParams_withUnregisteredController_ignoresRequest()
-            throws RemoteException {
+    public void testSetVibrationParams_withUnregisteredController_ignoresRequest() {
         SparseArray<Float> vibrationScales = new SparseArray<>();
         vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
         vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
@@ -197,17 +210,19 @@
                 VibrationParamGenerator.generateVibrationParams(vibrationScales),
                 mFakeVibratorController);
 
+        verify(mStatsLoggerMock, never()).logVibrationParamScale(anyFloat());
         verifyZeroInteractions(mMockVibrationScaler);
     }
 
     @Test
-    public void testClearVibrationParams_clearsCachedAdaptiveHapticsScales()
-            throws RemoteException {
+    public void testClearVibrationParams_clearsCachedAdaptiveHapticsScales() {
         mVibratorControlService.registerVibratorController(mFakeVibratorController);
         int types = buildVibrationTypesMask(ScaleParam.TYPE_ALARM, ScaleParam.TYPE_NOTIFICATION);
 
         mVibratorControlService.clearVibrationParams(types, mFakeVibratorController);
 
+        verify(mStatsLoggerMock).logVibrationParamScale(-1f);
+
         verify(mMockVibrationScaler).removeAdaptiveHapticsScale(USAGE_ALARM);
         verify(mMockVibrationScaler).removeAdaptiveHapticsScale(USAGE_NOTIFICATION);
         // Clearing ScaleParam.TYPE_NOTIFICATION will clear vibration scaling for both
@@ -216,21 +231,20 @@
     }
 
     @Test
-    public void testClearVibrationParams_withUnregisteredController_ignoresRequest()
-            throws RemoteException {
+    public void testClearVibrationParams_withUnregisteredController_ignoresRequest() {
         mVibratorControlService.clearVibrationParams(ScaleParam.TYPE_ALARM,
                 mFakeVibratorController);
 
+        verify(mStatsLoggerMock, never()).logVibrationParamScale(anyFloat());
         verifyZeroInteractions(mMockVibrationScaler);
     }
 
     @Test
-    public void testRequestVibrationParams_createsFutureRequestProperly()
-            throws RemoteException {
+    public void testRequestVibrationParams_createsFutureRequestProperly() {
         int timeoutInMillis = 10;
         mVibratorControlService.registerVibratorController(mFakeVibratorController);
         CompletableFuture<Void> future =
-                mVibratorControlService.triggerVibrationParamsRequest(USAGE_RINGTONE,
+                mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
                         timeoutInMillis);
         try {
             future.orTimeout(timeoutInMillis, TimeUnit.MILLISECONDS).get();
@@ -243,8 +257,7 @@
     }
 
     @Test
-    public void testShouldRequestVibrationParams_returnsTrueForVibrationsThatShouldRequestParams()
-            throws RemoteException {
+    public void testShouldRequestVibrationParams_returnsTrueForVibrationsThatShouldRequestParams() {
         int[] vibrations =
                 new int[]{USAGE_ALARM, USAGE_RINGTONE, USAGE_MEDIA, USAGE_TOUCH, USAGE_NOTIFICATION,
                         USAGE_HARDWARE_FEEDBACK, USAGE_UNKNOWN, USAGE_COMMUNICATION_REQUEST};
@@ -258,8 +271,7 @@
     }
 
     @Test
-    public void testShouldRequestVibrationParams_unregisteredVibratorController_returnsFalse()
-            throws RemoteException {
+    public void testShouldRequestVibrationParams_unregisteredVibratorController_returnsFalse() {
         int[] vibrations =
                 new int[]{USAGE_ALARM, USAGE_RINGTONE, USAGE_MEDIA, USAGE_TOUCH, USAGE_NOTIFICATION,
                         USAGE_HARDWARE_FEEDBACK, USAGE_UNKNOWN, USAGE_COMMUNICATION_REQUEST};
@@ -269,7 +281,7 @@
         }
     }
 
-    private int buildVibrationTypesMask(int... types) {
+    private static int buildVibrationTypesMask(int... types) {
         int typesMask = 0;
         for (int type : types) {
             typesMask |= type;
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 d2ad61f..8cbcc22 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -70,7 +70,6 @@
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
 import android.os.Process;
-import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.VibrationAttributes;
@@ -1551,6 +1550,48 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+    public void vibrate_withAdaptiveHaptics_appliesCorrectAdaptiveScales() throws Exception {
+        // Keep user settings the same as device default so only adaptive scale is applied.
+        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
+                mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_ALARM));
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+                mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_NOTIFICATION));
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+                mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_TOUCH));
+
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK);
+        VibratorManagerService service = createSystemReadyService();
+
+        SparseArray<Float> vibrationScales = new SparseArray<>();
+        vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
+        vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
+
+        mVibratorControlService.setVibrationParams(
+                VibrationParamGenerator.generateVibrationParams(vibrationScales),
+                mFakeVibratorController);
+
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                .compose();
+        vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
+        vibrateAndWaitUntilFinished(service, effect, NOTIFICATION_ATTRS);
+        vibrateAndWaitUntilFinished(service, effect, HAPTIC_FEEDBACK_ATTRS);
+
+        List<VibrationEffectSegment> segments = fakeVibrator.getAllEffectSegments();
+        assertEquals(3, segments.size());
+        assertEquals(0.7f, ((PrimitiveSegment) segments.get(0)).getScale(), 1e-5);
+        assertEquals(0.4f, ((PrimitiveSegment) segments.get(1)).getScale(), 1e-5);
+        assertEquals(1f, ((PrimitiveSegment) segments.get(2)).getScale(), 1e-5);
+        verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 0.7f);
+        verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 0.4f);
+        verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 1f);
+    }
+
+    @Test
     public void vibrate_withPowerModeChange_cancelVibrationIfNotAllowed() throws Exception {
         mockVibrators(1, 2);
         VibratorManagerService service = createSystemReadyService();
@@ -1998,8 +2039,7 @@
 
     @Test
     @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
-    public void onExternalVibration_withAdaptiveHaptics_returnsCorrectAdaptiveScales()
-            throws RemoteException {
+    public void onExternalVibration_withAdaptiveHaptics_returnsCorrectAdaptiveScales() {
         mockVibrators(1);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL,
                 IVibrator.CAP_AMPLITUDE_CONTROL);
@@ -2020,6 +2060,7 @@
         mExternalVibratorService.onExternalVibrationStop(externalVibration);
 
         assertEquals(scale.adaptiveHapticsScale, 0.7f, 0);
+        verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 0.7f);
 
         externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
                 AUDIO_NOTIFICATION_ATTRS,
@@ -2028,6 +2069,7 @@
         mExternalVibratorService.onExternalVibrationStop(externalVibration);
 
         assertEquals(scale.adaptiveHapticsScale, 0.4f, 0);
+        verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 0.4f);
 
         AudioAttributes ringtoneAudioAttrs = new AudioAttributes.Builder()
                 .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
@@ -2036,14 +2078,15 @@
                 ringtoneAudioAttrs,
                 mock(IExternalVibrationController.class));
         scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+        mExternalVibratorService.onExternalVibrationStop(externalVibration);
 
         assertEquals(scale.adaptiveHapticsScale, 1f, 0);
+        verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 1f);
     }
 
     @Test
     @RequiresFlagsDisabled(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
-    public void onExternalVibration_withAdaptiveHapticsFlagDisabled_alwaysReturnScaleNone()
-            throws RemoteException {
+    public void onExternalVibration_withAdaptiveHapticsFlagDisabled_alwaysReturnScaleNone() {
         mockVibrators(1);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL,
                 IVibrator.CAP_AMPLITUDE_CONTROL);
@@ -2065,15 +2108,15 @@
 
         assertEquals(scale.adaptiveHapticsScale, 1f, 0);
 
-        mVibratorControlService.setVibrationParams(
-                VibrationParamGenerator.generateVibrationParams(vibrationScales),
-                mFakeVibratorController);
         externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
                 AUDIO_NOTIFICATION_ATTRS,
                 mock(IExternalVibrationController.class));
         scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+        mExternalVibratorService.onExternalVibrationStop(externalVibration);
 
         assertEquals(scale.adaptiveHapticsScale, 1f, 0);
+        verify(mVibratorFrameworkStatsLoggerMock, times(2))
+                .logVibrationAdaptiveHapticScale(UID, 1f);
     }
 
     @Test
@@ -2526,7 +2569,7 @@
                 int constant, boolean always) throws InterruptedException {
         HalVibration vib =
                 service.performHapticFeedbackInternal(UID, Context.DEVICE_ID_DEFAULT, PACKAGE_NAME,
-                        constant, always, "some reason", service);
+                        constant, always, "some reason", service, false /* fromIme */);
         if (vib != null) {
             vib.waitForEnd();
         }
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
index 2a010f0..0cd88ef 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
@@ -60,11 +60,7 @@
         requestTimeoutInMillis = timeoutInMillis;
         mHandler.post(() -> {
             if (mVibratorControlService != null) {
-                try {
-                    mVibratorControlService.onRequestVibrationParamsComplete(token, mRequestResult);
-                } catch (RemoteException e) {
-                    throw new RuntimeException(e);
-                }
+                mVibratorControlService.onRequestVibrationParamsComplete(token, mRequestResult);
             }
         });
     }
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
index 12815fa..2ddb47b 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -53,6 +53,7 @@
 
     private boolean mIsAvailable = true;
     private boolean mIsInfoLoadSuccessful = true;
+    private long mCompletionCallbackDelay;
     private long mOnLatency;
     private long mOffLatency;
     private int mOffCount;
@@ -206,7 +207,7 @@
 
         private void scheduleListener(long vibrationDuration, long vibrationId) {
             mHandler.postDelayed(() -> listener.onComplete(vibratorId, vibrationId),
-                    vibrationDuration);
+                    vibrationDuration + mCompletionCallbackDelay);
         }
     }
 
@@ -241,6 +242,13 @@
     }
 
     /**
+     * Sets the delay this controller should fake for triggering the vibration completed callback.
+     */
+    public void setCompletionCallbackDelay(long millis) {
+        mCompletionCallbackDelay = millis;
+    }
+
+    /**
      * Sets the latency this controller should fake for turning the vibrator hardware on or setting
      * the vibration amplitude.
      */
diff --git a/services/tests/voiceinteractiontests/Android.bp b/services/tests/voiceinteractiontests/Android.bp
index 8a79fe4..8c70851 100644
--- a/services/tests/voiceinteractiontests/Android.bp
+++ b/services/tests/voiceinteractiontests/Android.bp
@@ -45,6 +45,7 @@
         "servicestests-utils-mockito-extended",
         "truth",
         "frameworks-base-testutils",
+        "androidx.test.rules",
     ],
 
     libs: [
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/SoundTriggerTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/SoundTriggerTest.java
index 35170b3..a9c517d 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/SoundTriggerTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/SoundTriggerTest.java
@@ -16,19 +16,20 @@
 
 package com.android.server.soundtrigger;
 
+import android.hardware.soundtrigger.SoundTrigger;
 import android.hardware.soundtrigger.SoundTrigger.ConfidenceLevel;
 import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
-import android.hardware.soundtrigger.SoundTrigger;
 import android.media.AudioFormat;
+import android.os.Binder;
 import android.os.Parcel;
 import android.test.InstrumentationTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.os.Binder;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.SmallTest;
 
 import java.util.Arrays;
 import java.util.Locale;
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index ef19791..d6c5173 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -49,6 +49,8 @@
     <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION"/>
     <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/>
     <uses-permission android:name="android.permission.MONITOR_INPUT"/>
+    <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS"/>
+    <uses-permission android:name="android.permission.MANAGE_DEFAULT_APPLICATIONS"/>
 
     <!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) -->
     <application android:debuggable="true"
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index e21388e..3c02eee 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -38,6 +38,7 @@
 import static android.view.KeyEvent.KEYCODE_U;
 import static android.view.KeyEvent.KEYCODE_Z;
 
+import android.app.role.RoleManager;
 import android.content.Intent;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
@@ -51,16 +52,19 @@
 @Presubmit
 @SmallTest
 public class ModifierShortcutTests extends ShortcutKeyTestBase {
-    private static final SparseArray<String> META_SHORTCUTS =  new SparseArray<>();
+    private static final SparseArray<String> INTENT_SHORTCUTS =  new SparseArray<>();
+    private static final SparseArray<String> ROLE_SHORTCUTS =  new SparseArray<>();
     static {
-        META_SHORTCUTS.append(KEYCODE_U, Intent.CATEGORY_APP_CALCULATOR);
-        META_SHORTCUTS.append(KEYCODE_B, Intent.CATEGORY_APP_BROWSER);
-        META_SHORTCUTS.append(KEYCODE_C, Intent.CATEGORY_APP_CONTACTS);
-        META_SHORTCUTS.append(KEYCODE_E, Intent.CATEGORY_APP_EMAIL);
-        META_SHORTCUTS.append(KEYCODE_K, Intent.CATEGORY_APP_CALENDAR);
-        META_SHORTCUTS.append(KEYCODE_M, Intent.CATEGORY_APP_MAPS);
-        META_SHORTCUTS.append(KEYCODE_P, Intent.CATEGORY_APP_MUSIC);
-        META_SHORTCUTS.append(KEYCODE_S, Intent.CATEGORY_APP_MESSAGING);
+        // These shortcuts should align with those defined in bookmarks.xml
+        INTENT_SHORTCUTS.append(KEYCODE_U, Intent.CATEGORY_APP_CALCULATOR);
+        INTENT_SHORTCUTS.append(KEYCODE_C, Intent.CATEGORY_APP_CONTACTS);
+        INTENT_SHORTCUTS.append(KEYCODE_E, Intent.CATEGORY_APP_EMAIL);
+        INTENT_SHORTCUTS.append(KEYCODE_K, Intent.CATEGORY_APP_CALENDAR);
+        INTENT_SHORTCUTS.append(KEYCODE_M, Intent.CATEGORY_APP_MAPS);
+        INTENT_SHORTCUTS.append(KEYCODE_P, Intent.CATEGORY_APP_MUSIC);
+
+        ROLE_SHORTCUTS.append(KEYCODE_B, RoleManager.ROLE_BROWSER);
+        ROLE_SHORTCUTS.append(KEYCODE_S, RoleManager.ROLE_SMS);
     }
     private static final int ANY_DISPLAY_ID = 123;
 
@@ -74,13 +78,21 @@
      */
     @Test
     public void testMetaShortcuts() {
-        for (int i = 0; i < META_SHORTCUTS.size(); i++) {
-            final int keyCode = META_SHORTCUTS.keyAt(i);
-            final String category = META_SHORTCUTS.valueAt(i);
+        for (int i = 0; i < INTENT_SHORTCUTS.size(); i++) {
+            final int keyCode = INTENT_SHORTCUTS.keyAt(i);
+            final String category = INTENT_SHORTCUTS.valueAt(i);
 
             sendKeyCombination(new int[]{KEYCODE_META_LEFT, keyCode}, 0);
             mPhoneWindowManager.assertLaunchCategory(category);
         }
+
+        for (int i = 0; i < ROLE_SHORTCUTS.size(); i++) {
+            final int keyCode = ROLE_SHORTCUTS.keyAt(i);
+            final String role = ROLE_SHORTCUTS.valueAt(i);
+
+            sendKeyCombination(new int[]{KEYCODE_META_LEFT, keyCode}, 0);
+            mPhoneWindowManager.assertLaunchRole(role);
+        }
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
index e904eae..0a29dfb 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
@@ -145,7 +145,7 @@
                         KeyboardLogEvent.SYSTEM_MUTE, KeyEvent.KEYCODE_MUTE, 0},
                 {"Meta + Ctrl + DPAD_UP -> Split screen navigation",
                         new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_UP},
-                        KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION, KeyEvent.KEYCODE_DPAD_UP,
+                        KeyboardLogEvent.MULTI_WINDOW_NAVIGATION, KeyEvent.KEYCODE_DPAD_UP,
                         META_ON | CTRL_ON},
                 {"Meta + Ctrl + DPAD_LEFT -> Split screen navigation",
                         new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
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 1a26c45..0776c51 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -60,6 +60,7 @@
 import android.app.IActivityManager;
 import android.app.NotificationManager;
 import android.app.SearchManager;
+import android.app.role.RoleManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -124,6 +125,8 @@
 class TestPhoneWindowManager {
     private static final long TEST_SINGLE_KEY_DELAY_MILLIS
             = SingleKeyGestureDetector.MULTI_PRESS_TIMEOUT + 1000L * HW_TIMEOUT_MULTIPLIER;
+    private static final String TEST_BROWSER_ROLE_PACKAGE_NAME = "com.browser";
+    private static final String TEST_SMS_ROLE_PACKAGE_NAME = "com.sms";
 
     private PhoneWindowManager mPhoneWindowManager;
     private Context mContext;
@@ -151,6 +154,7 @@
     @Mock private UserManagerInternal mUserManagerInternal;
     @Mock private AudioManagerInternal mAudioManagerInternal;
     @Mock private SearchManager mSearchManager;
+    @Mock private RoleManager mRoleManager;
 
     @Mock private Display mDisplay;
     @Mock private DisplayRotation mDisplayRotation;
@@ -180,6 +184,9 @@
     private boolean mIsTalkBackEnabled;
     private boolean mIsTalkBackShortcutGestureEnabled;
 
+    private Intent mBrowserIntent;
+    private Intent mSmsIntent;
+
     private int mKeyEventPolicyFlags = FLAG_INTERACTIVE;
 
     private class TestTalkbackShortcutController extends TalkbackShortcutController {
@@ -290,6 +297,7 @@
         doReturn(mSensorPrivacyManager).when(mContext).getSystemService(
                 eq(SensorPrivacyManager.class));
         doReturn(mSearchManager).when(mContext).getSystemService(eq(SearchManager.class));
+        doReturn(mRoleManager).when(mContext).getSystemService(eq(RoleManager.class));
         doReturn(false).when(mPackageManager).hasSystemFeature(any());
         try {
             doThrow(new PackageManager.NameNotFoundException("test")).when(mPackageManager)
@@ -360,6 +368,21 @@
         doReturn(interceptionInfo)
                 .when(mWindowManagerInternal).getKeyInterceptionInfoFromToken(any());
 
+        doReturn(true).when(mRoleManager).isRoleAvailable(eq(RoleManager.ROLE_BROWSER));
+        doReturn(true).when(mRoleManager).isRoleAvailable(eq(RoleManager.ROLE_SMS));
+        doReturn(TEST_BROWSER_ROLE_PACKAGE_NAME).when(mRoleManager).getDefaultApplication(
+                eq(RoleManager.ROLE_BROWSER));
+        doReturn(TEST_SMS_ROLE_PACKAGE_NAME).when(mRoleManager).getDefaultApplication(
+                eq(RoleManager.ROLE_SMS));
+        mBrowserIntent = new Intent(Intent.ACTION_MAIN);
+        mBrowserIntent.setPackage(TEST_BROWSER_ROLE_PACKAGE_NAME);
+        mSmsIntent = new Intent(Intent.ACTION_MAIN);
+        mSmsIntent.setPackage(TEST_SMS_ROLE_PACKAGE_NAME);
+        doReturn(mBrowserIntent).when(mPackageManager).getLaunchIntentForPackage(
+                eq(TEST_BROWSER_ROLE_PACKAGE_NAME));
+        doReturn(mSmsIntent).when(mPackageManager).getLaunchIntentForPackage(
+                eq(TEST_SMS_ROLE_PACKAGE_NAME));
+
         Mockito.reset(mContext);
     }
 
@@ -670,6 +693,29 @@
         Mockito.reset(mContext);
     }
 
+    void assertLaunchRole(String role) {
+        mTestLooper.dispatchAll();
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        try {
+            verify(mContext).startActivityAsUser(intentCaptor.capture(), any());
+            switch (role) {
+                case RoleManager.ROLE_BROWSER:
+                    Assert.assertEquals(intentCaptor.getValue(), mBrowserIntent);
+                    break;
+                case RoleManager.ROLE_SMS:
+                    Assert.assertEquals(intentCaptor.getValue(), mSmsIntent);
+                    break;
+                default:
+                    throw new AssertionError("Role " + role + " not supported in tests.");
+            }
+        } catch (Throwable t) {
+            throw new AssertionError("failed to assert " + role, t);
+        }
+        // Reset verifier for next call.
+        Mockito.reset(mContext);
+    }
+
+
     void assertShowRecentApps() {
         mTestLooper.dispatchAll();
         verify(mStatusBarManagerInternal).showRecentApps(anyBoolean());
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 67c528c..daa5a5a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -527,7 +527,8 @@
 
         // The configuration change is still sent to the activity, even if it doesn't relaunch.
         final ActivityConfigurationChangeItem expected =
-                ActivityConfigurationChangeItem.obtain(activity.token, newConfig);
+                ActivityConfigurationChangeItem.obtain(activity.token, newConfig,
+                        activity.getActivityWindowInfo());
         verify(mClientLifecycleManager).scheduleTransactionItem(
                 eq(activity.app.getThread()), eq(expected));
     }
@@ -599,7 +600,8 @@
         final Configuration currentConfig = activity.getConfiguration();
         assertEquals(expectedOrientation, currentConfig.orientation);
         final ActivityConfigurationChangeItem expected =
-                ActivityConfigurationChangeItem.obtain(activity.token, currentConfig);
+                ActivityConfigurationChangeItem.obtain(activity.token, currentConfig,
+                        activity.getActivityWindowInfo());
         verify(mClientLifecycleManager).scheduleTransactionItem(activity.app.getThread(), expected);
         verify(displayRotation).onSetRequestedOrientation();
     }
@@ -818,7 +820,7 @@
 
             final ActivityConfigurationChangeItem expected =
                     ActivityConfigurationChangeItem.obtain(activity.token,
-                            activity.getConfiguration());
+                            activity.getConfiguration(), activity.getActivityWindowInfo());
             verify(mClientLifecycleManager).scheduleTransactionItem(
                     activity.app.getThread(), expected);
         } finally {
@@ -3145,12 +3147,12 @@
         // By default, activity is visible.
         assertTrue(activity.isVisible());
         assertTrue(activity.isVisibleRequested());
-        assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
 
         // Request the activity to be visible. Although the activity is already visible, app
         // transition animation should be applied on this activity. This might be unnecessary, but
         // until we verify no logic relies on this behavior, we'll keep this as is.
+        mDisplayContent.prepareAppTransition(0);
         activity.setVisibility(true);
         assertTrue(activity.isVisible());
         assertTrue(activity.isVisibleRequested());
@@ -3165,11 +3167,11 @@
         // By default, activity is visible.
         assertTrue(activity.isVisible());
         assertTrue(activity.isVisibleRequested());
-        assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
 
         // Request the activity to be invisible. Since the visibility changes, app transition
         // animation should be applied on this activity.
+        mDisplayContent.prepareAppTransition(0);
         activity.setVisibility(false);
         assertTrue(activity.isVisible());
         assertFalse(activity.isVisibleRequested());
@@ -3185,7 +3187,6 @@
         // activity.
         assertFalse(activity.isVisible());
         assertTrue(activity.isVisibleRequested());
-        assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
 
         // Request the activity to be visible. Since the visibility changes, app transition
@@ -3387,6 +3388,7 @@
         // frozen until the input started.
         mDisplayContent.setImeLayeringTarget(app1);
         mDisplayContent.updateImeInputAndControlTarget(app1);
+        mDisplayContent.computeImeTarget(true /* updateImeTarget */);
         performSurfacePlacementAndWaitForWindowAnimator();
 
         assertEquals(app1, mDisplayContent.getImeInputTarget());
@@ -3681,7 +3683,9 @@
         assertEquals(WINDOWING_MODE_FULLSCREEN, activity.getWindowingMode());
 
         registerTestTransitionPlayer();
-        task.mTransitionController.requestTransitionIfNeeded(TRANSIT_PIP, task);
+        Transition tr = task.mTransitionController.requestStartTransition(
+                task.mTransitionController.createTransition(TRANSIT_PIP), task, null, null);
+        tr.collect(task);
         task.setWindowingMode(WINDOWING_MODE_PINNED);
 
         // Collect activity in the transition if the Task windowing mode is going to change.
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 6132ee3..173a1b6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -74,7 +74,6 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -86,9 +85,9 @@
 import static org.mockito.ArgumentMatchers.notNull;
 
 import android.app.ActivityOptions;
+import android.app.ActivityOptions.BackgroundActivityStartMode;
 import android.app.AppOpsManager;
 import android.app.BackgroundStartPrivileges;
-import android.app.ComponentOptions.BackgroundActivityStartMode;
 import android.app.IApplicationThread;
 import android.app.PictureInPictureParams;
 import android.content.ComponentName;
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index ef36bff..4060d40 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -24,6 +24,7 @@
 import static com.android.server.wm.DesktopModeLaunchParamsModifier.DESKTOP_MODE_INITIAL_BOUNDS_SCALE;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_DONE;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
 
@@ -31,11 +32,14 @@
 import static org.mockito.Mockito.mock;
 
 import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.server.wm.LaunchParamsController.LaunchParamsModifier.Result;
+import com.android.window.flags.Flags;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -70,11 +74,19 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    public void testReturnsContinueIfDesktopWindowingIsDisabled() {
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(null).calculate());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void testReturnsSkipIfTaskIsNull() {
         assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(null).calculate());
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void testReturnsSkipIfNotBoundsPhase() {
         final Task task = new TaskBuilder(mSupervisor).build();
         assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(task).setPhase(
@@ -82,6 +94,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void testReturnsSkipIfTaskNotUsingActivityTypeStandardOrUndefined() {
         final Task task = new TaskBuilder(mSupervisor).setActivityType(
                 ACTIVITY_TYPE_ASSISTANT).build();
@@ -89,6 +102,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void testReturnsDoneIfTaskUsingActivityTypeStandard() {
         final Task task = new TaskBuilder(mSupervisor).setActivityType(
                 ACTIVITY_TYPE_STANDARD).build();
@@ -96,6 +110,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void testReturnsDoneIfTaskUsingActivityTypeUndefined() {
         final Task task = new TaskBuilder(mSupervisor).setActivityType(
                 ACTIVITY_TYPE_UNDEFINED).build();
@@ -103,6 +118,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void testReturnsSkipIfCurrentParamsHasBounds() {
         final Task task = new TaskBuilder(mSupervisor).setActivityType(
                 ACTIVITY_TYPE_STANDARD).build();
@@ -111,6 +127,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void testUsesDefaultBounds() {
         final Task task = new TaskBuilder(mSupervisor).setActivityType(
                 ACTIVITY_TYPE_STANDARD).build();
@@ -125,6 +142,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void testUsesDisplayAreaAndWindowingModeFromSource() {
         final Task task = new TaskBuilder(mSupervisor).setActivityType(
                 ACTIVITY_TYPE_STANDARD).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
index 99d354a..b11f9b2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
@@ -70,6 +70,7 @@
 
     @Before
     public void before() {
+        doReturn(true).when(mDisplayContent).getLastHasContent();
         mockTransitionsController(/* enabled= */ true);
         mockRemoteDisplayChangeController();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 4c32a58..a0461a6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -42,6 +42,7 @@
 import static org.junit.Assume.assumeTrue;
 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;
 
@@ -541,7 +542,7 @@
     }
 
     @Test
-    public void testUnhandledDragListenerNotCalledForNormalDrags() throws RemoteException {
+    public void testUnhandledDragNotCalledForNormalDrags() throws RemoteException {
         assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
 
         final IGlobalDragListener listener = mock(IGlobalDragListener.class);
@@ -552,15 +553,16 @@
     }
 
     @Test
-    public void testUnhandledDragListenerReceivesUnhandledDropOverWindow() {
+    public void testUnhandledDragReceivesUnhandledDropOverWindow() {
         assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
 
         final IGlobalDragListener listener = mock(IGlobalDragListener.class);
         doReturn(mock(Binder.class)).when(listener).asBinder();
         mTarget.setGlobalDragListener(listener);
         final int invalidXY = 100_000;
-        startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
-            // Notify the unhandled drag listener
+        startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG,
+                ClipData.newPlainText("label", "Test"), () -> {
+            // Trigger an unhandled drop and verify the global drag listener was called
             mTarget.reportDropWindow(mWindow.mInputChannelToken, invalidXY, invalidXY);
             mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
             mTarget.reportDropResult(mWindow.mClient, false);
@@ -575,15 +577,16 @@
     }
 
     @Test
-    public void testUnhandledDragListenerReceivesUnhandledDropOverNoValidWindow() {
+    public void testUnhandledDragReceivesUnhandledDropOverNoValidWindow() {
         assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
 
         final IGlobalDragListener listener = mock(IGlobalDragListener.class);
         doReturn(mock(Binder.class)).when(listener).asBinder();
         mTarget.setGlobalDragListener(listener);
         final int invalidXY = 100_000;
-        startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
-            // Notify the unhandled drag listener
+        startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG,
+                ClipData.newPlainText("label", "Test"), () -> {
+            // Trigger an unhandled drop and verify the global drag listener was called
             mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
             mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
             mTarget.onUnhandledDropCallback(true);
@@ -597,15 +600,38 @@
     }
 
     @Test
-    public void testUnhandledDragListenerCallbackTimeout() {
+    public void testUnhandledDragDoesNotReceiveUnhandledDropWithoutDragFlag() {
         assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
 
         final IGlobalDragListener listener = mock(IGlobalDragListener.class);
         doReturn(mock(Binder.class)).when(listener).asBinder();
         mTarget.setGlobalDragListener(listener);
         final int invalidXY = 100_000;
-        startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
-            // Notify the unhandled drag listener
+        startDrag(View.DRAG_FLAG_GLOBAL,
+                ClipData.newPlainText("label", "Test"), () -> {
+                    // Trigger an unhandled drop and verify the global drag listener was not called
+                    mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
+                    mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
+                    mToken = null;
+                    try {
+                        verify(listener, never()).onUnhandledDrop(any(), any());
+                    } catch (RemoteException e) {
+                        fail("Failed to verify unhandled drop: " + e);
+                    }
+                });
+    }
+
+    @Test
+    public void testUnhandledDragCallbackTimeout() {
+        assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
+
+        final IGlobalDragListener listener = mock(IGlobalDragListener.class);
+        doReturn(mock(Binder.class)).when(listener).asBinder();
+        mTarget.setGlobalDragListener(listener);
+        final int invalidXY = 100_000;
+        startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG,
+                ClipData.newPlainText("label", "Test"), () -> {
+            // Trigger an unhandled drop and verify the global drag listener was called
             mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
             mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
index 20bb549..faa6d97 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
@@ -27,6 +27,7 @@
 import android.graphics.PixelFormat;
 import android.platform.test.annotations.Presubmit;
 import android.view.InsetsSource;
+import android.view.inputmethod.ImeTracker;
 
 import androidx.test.filters.SmallTest;
 
@@ -34,6 +35,12 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+/**
+ * Tests for the {@link ImeInsetsSourceProvider} class.
+ *
+ * <p> Build/Install/Run:
+ * atest WmTests:ImeInsetsSourceProviderTest
+ */
 @SmallTest
 @Presubmit
 @RunWith(WindowTestRunner.class)
@@ -56,7 +63,7 @@
         mDisplayContent.setImeControlTarget(popup);
         mDisplayContent.setImeLayeringTarget(appWin);
         popup.mAttrs.format = PixelFormat.TRANSPARENT;
-        mImeProvider.scheduleShowImePostLayout(appWin, null /* statsToken */);
+        mImeProvider.scheduleShowImePostLayout(appWin, ImeTracker.Token.empty());
         assertTrue(mImeProvider.isReadyToShowIme());
     }
 
@@ -65,7 +72,7 @@
         WindowState target = createWindow(null, TYPE_APPLICATION, "app");
         mDisplayContent.setImeLayeringTarget(target);
         mDisplayContent.updateImeInputAndControlTarget(target);
-        mImeProvider.scheduleShowImePostLayout(target, null /* statsToken */);
+        mImeProvider.scheduleShowImePostLayout(target, ImeTracker.Token.empty());
         assertTrue(mImeProvider.isReadyToShowIme());
     }
 
@@ -79,7 +86,7 @@
         mDisplayContent.setImeLayeringTarget(target);
         mDisplayContent.setImeControlTarget(target);
 
-        mImeProvider.scheduleShowImePostLayout(target, null /* statsToken */);
+        mImeProvider.scheduleShowImePostLayout(target, ImeTracker.Token.empty());
         assertFalse(mImeProvider.isImeShowing());
         mImeProvider.checkShowImePostLayout();
         assertTrue(mImeProvider.isImeShowing());
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index 38a66a9..eeec54f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -411,6 +411,10 @@
         app2.mAboveInsetsState.addSource(statusBarSource);
         assertTrue(app2.getInsetsState().peekSource(statusBarId).isVisible());
 
+        // Let app2 be the focused window. Otherwise, the control target could be overwritten by
+        // DisplayPolicy#updateSystemBarAttributes unexpectedly.
+        mDisplayContent.getDisplayPolicy().focusChangedLw(null, app2);
+
         app2.setRequestedVisibleTypes(0, navigationBars() | statusBars());
         mDisplayContent.getInsetsPolicy().updateBarControlTarget(app2);
         waitUntilWindowAnimatorIdle();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index 32b3558..da437c4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -33,6 +33,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.server.wm.ActivityRecord.State.PAUSED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
@@ -164,13 +165,12 @@
         ActivityRecord recentsActivity = recentsStack.getTopNonFinishingActivity();
         // The activity is started in background so it should be invisible and will be stopped.
         assertThat(recentsActivity).isNotNull();
-        assertThat(mSupervisor.mStoppingActivities).contains(recentsActivity);
+        assertThat(recentsActivity.getState()).isEqualTo(STOPPING);
         assertFalse(recentsActivity.isVisibleRequested());
 
         // Assume it is stopped to test next use case.
         recentsActivity.activityStopped(null /* newIcicle */, null /* newPersistentState */,
                 null /* description */);
-        mSupervisor.mStoppingActivities.remove(recentsActivity);
 
         spyOn(recentsActivity);
         // Start when the recents activity exists. It should ensure the configuration.
@@ -178,7 +178,6 @@
                 null /* recentsAnimationRunner */);
 
         verify(recentsActivity).ensureActivityConfiguration(eq(true) /* ignoreVisibility */);
-        assertThat(mSupervisor.mStoppingActivities).contains(recentsActivity);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 527ea0d..ce90504 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -1239,7 +1239,7 @@
         final ActivityRecord activity1 = finishTopActivity(rootTask1);
         assertEquals(DESTROYING, activity1.getState());
         verify(mRootWindowContainer).ensureVisibilityAndConfig(eq(null) /* starting */,
-                eq(display.mDisplayId), anyBoolean());
+                eq(display), anyBoolean());
     }
 
     private ActivityRecord finishTopActivity(Task task) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 6013063..649f520 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -554,7 +554,7 @@
         mRootWindowContainer.applySleepTokens(true);
 
         // The display orientation should be changed by the activity so there is no relaunch.
-        verify(activity, never()).relaunchActivityLocked(anyBoolean());
+        verify(activity, never()).relaunchActivityLocked(anyBoolean(), anyInt());
         assertEquals(rotatedConfig.orientation, display.getConfiguration().orientation);
     }
 
@@ -835,8 +835,6 @@
                 new TestDisplayContent.Builder(mAtm, 1000, 1500)
                         .setSystemDecorations(true).build();
 
-        doReturn(true).when(mRootWindowContainer)
-                .ensureVisibilityAndConfig(any(), anyInt(), anyBoolean());
         doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(),
                 anyBoolean());
 
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 01bd96b..5360a10 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -896,6 +896,11 @@
             assertFalse(mWm.moveFocusToTopEmbeddedWindow(winRightTop));
             // The focus should NOT change.
             assertEquals(winRightTop, mDisplayContent.mCurrentFocus);
+
+            // Do not move focus if the dim is boosted.
+            taskFragmentLeft.mDimmerSurfaceBoosted = true;
+            assertFalse(mWm.moveFocusToTopEmbeddedWindow(winLeftTop));
+            assertEquals(winRightTop, mDisplayContent.mCurrentFocus);
         }
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 7551b165..1233686 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -265,7 +265,7 @@
 
     @Override
     public boolean performHapticFeedback(int uid, String packageName, int effectId,
-            boolean always, String reason) {
+            boolean always, String reason, boolean fromIme) {
         return false;
     }
 
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 ce890f6..ff7129c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -70,6 +70,8 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import static java.lang.Integer.MAX_VALUE;
+
 import android.app.ActivityManager;
 import android.content.res.Configuration;
 import android.graphics.Color;
@@ -1120,8 +1122,7 @@
 
         mDisplayContent.getDisplayRotation().setRotation(mDisplayContent.getRotation() + 1);
         mDisplayContent.setLastHasContent();
-        mDisplayContent.requestChangeTransitionIfNeeded(1 /* any changes */,
-                null /* displayChange */);
+        mDisplayContent.requestChangeTransition(1 /* any changes */, null /* displayChange */);
         assertEquals(WindowContainer.SYNC_STATE_NONE, statusBar.mSyncState);
         assertEquals(WindowContainer.SYNC_STATE_NONE, navBar.mSyncState);
         assertEquals(WindowContainer.SYNC_STATE_NONE, screenDecor.mSyncState);
@@ -1191,7 +1192,7 @@
         mDisplayContent.getDisplayRotation().setRotation(mDisplayContent.getRotation() + 1);
         final int anyChanges = 1;
         mDisplayContent.setLastHasContent();
-        mDisplayContent.requestChangeTransitionIfNeeded(anyChanges, null /* displayChange */);
+        mDisplayContent.collectDisplayChange(transition);
         transition.setKnownConfigChanges(mDisplayContent, anyChanges);
         final AsyncRotationController asyncRotationController =
                 mDisplayContent.getAsyncRotationController();
@@ -1254,7 +1255,7 @@
         // so the previous async rotation controller should still exist.
         mDisplayContent.getDisplayRotation().setRotation(mDisplayContent.getRotation() + 1);
         mDisplayContent.setLastHasContent();
-        mDisplayContent.requestChangeTransitionIfNeeded(1 /* changes */, null /* displayChange */);
+        mDisplayContent.requestChangeTransition(1 /* changes */, null /* displayChange */);
         assertTrue(mDisplayContent.hasTopFixedRotationLaunchingApp());
         assertNotNull(mDisplayContent.getAsyncRotationController());
 
@@ -1300,7 +1301,7 @@
         mDisplayContent.setFixedRotationLaunchingAppUnchecked(app);
         registerTestTransitionPlayer();
         mDisplayContent.setLastHasContent();
-        mDisplayContent.requestChangeTransitionIfNeeded(1 /* changes */, null /* displayChange */);
+        mDisplayContent.requestChangeTransition(1 /* changes */, null /* displayChange */);
         assertNotNull(mDisplayContent.getAsyncRotationController());
         mDisplayContent.setFixedRotationLaunchingAppUnchecked(null);
         assertNull("Clear rotation controller if rotation is not changed",
@@ -2571,6 +2572,37 @@
     }
 
     @Test
+    public void testConfigAtEndReparent() {
+        final TransitionController controller = mDisplayContent.mTransitionController;
+        Transition transit = createTestTransition(TRANSIT_CHANGE, controller);
+        final TestTransitionPlayer player = registerTestTransitionPlayer();
+
+        final Task taskOrig = createTask(mDisplayContent);
+        taskOrig.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 200, 300));
+        final Task task = createTask(mDisplayContent);
+        task.getConfiguration().windowConfiguration.setBounds(new Rect(10, 10, 200, 300));
+        final ActivityRecord activity = createActivityRecord(taskOrig);
+        activity.setVisibleRequested(true);
+        activity.setVisible(true);
+
+        controller.moveToCollecting(transit);
+        transit.collect(taskOrig);
+        transit.collect(task);
+        transit.collect(activity);
+        transit.setConfigAtEnd(taskOrig);
+        activity.reparent(task, MAX_VALUE);
+        task.moveToFront("test");
+
+        controller.requestStartTransition(transit, task, null, null);
+        player.start();
+        // config-at-end flag must propagate up to task even when reparented (since config-at-end
+        // only cares about after-end state).
+        assertTrue(player.mLastReady.getChange(
+                task.mRemoteToken.toWindowContainerToken()).hasFlags(FLAG_CONFIG_AT_END));
+        player.finish();
+    }
+
+    @Test
     public void testReadyTrackerBasics() {
         final TransitionController controller = new TestTransitionController(
                 mock(ActivityTaskManagerService.class));
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 80fb44a..72bedf2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -39,6 +39,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -305,12 +307,12 @@
         final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
         wallpaperController.adjustWallpaperWindows();
         // Wallpaper is visible because the show-when-locked activity is translucent.
-        assertTrue(wallpaperController.isWallpaperTarget(wallpaperWindow));
+        assertSame(wallpaperWindow, wallpaperController.getWallpaperTarget());
 
         behind.mActivityRecord.setShowWhenLocked(true);
         wallpaperController.adjustWallpaperWindows();
         // Wallpaper is invisible because the lowest show-when-locked activity is opaque.
-        assertTrue(wallpaperController.isWallpaperTarget(null));
+        assertNull(wallpaperController.getWallpaperTarget());
 
         // A show-when-locked wallpaper is used for lockscreen. So the top wallpaper should
         // be the one that is not show-when-locked.
@@ -374,10 +376,10 @@
         // The activity in restore-below task should not be the target if keyguard is not locked.
         mDisplayContent.mWallpaperController.adjustWallpaperWindows();
         assertNotEquals(appWin, mDisplayContent.mWallpaperController.getWallpaperTarget());
-        // The activity in restore-below task should be the target if keyguard is occluded.
+        // The activity in restore-below task should not be the target if keyguard is occluded.
         doReturn(true).when(mDisplayContent).isKeyguardLocked();
         mDisplayContent.mWallpaperController.adjustWallpaperWindows();
-        assertEquals(appWin, mDisplayContent.mWallpaperController.getWallpaperTarget());
+        assertNotEquals(appWin, mDisplayContent.mWallpaperController.getWallpaperTarget());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index cd3ce91..c8ad4bd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -104,6 +104,7 @@
 import android.view.View;
 import android.view.WindowInsets;
 import android.view.WindowManager;
+import android.view.inputmethod.ImeTracker;
 import android.window.ClientWindowFrames;
 import android.window.ITaskFragmentOrganizer;
 import android.window.TaskFragmentOrganizer;
@@ -126,7 +127,7 @@
 /**
  * Tests for the {@link WindowState} class.
  *
- * Build/Install/Run:
+ * <p> Build/Install/Run:
  * atest WmTests:WindowStateTests
  */
 @SmallTest
@@ -1099,7 +1100,7 @@
         mDisplayContent.setImeInputTarget(app);
         app.setRequestedVisibleTypes(ime(), ime());
         assertTrue(mDisplayContent.shouldImeAttachedToApp());
-        controller.getImeSourceProvider().scheduleShowImePostLayout(app, null /* statsToken */);
+        controller.getImeSourceProvider().scheduleShowImePostLayout(app, ImeTracker.Token.empty());
         controller.getImeSourceProvider().getSource().setVisible(true);
         controller.updateAboveInsetsState(false);
 
@@ -1137,7 +1138,7 @@
         mDisplayContent.setImeInputTarget(app);
         app.setRequestedVisibleTypes(ime(), ime());
         assertTrue(mDisplayContent.shouldImeAttachedToApp());
-        controller.getImeSourceProvider().scheduleShowImePostLayout(app, null /* statsToken */);
+        controller.getImeSourceProvider().scheduleShowImePostLayout(app, ImeTracker.Token.empty());
         controller.getImeSourceProvider().getSource().setVisible(true);
         controller.updateAboveInsetsState(false);
 
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 44f4068..883c702 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -383,7 +383,7 @@
             return queryStatsForUid(volumeUuid, appInfo.uid, callingPackage);
         } else {
             // Multiple packages means we need to go manual
-            final int appId = UserHandle.getUserId(appInfo.uid);
+            final int appId = UserHandle.getAppId(appInfo.uid);
             final String[] packageNames = new String[] { packageName };
             final long[] ceDataInodes = new long[1];
             String[] codePaths = new String[0];
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index a58cf5f..a35a35a 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -152,6 +152,10 @@
     public static final boolean ENABLE_TIME_CHANGE_CORRECTION
             = SystemProperties.getBoolean("persist.debug.time_correction", true);
 
+    private static final boolean USE_DEDICATED_HANDLER_THREAD =
+            SystemProperties.getBoolean("persist.debug.use_dedicated_handler_thread",
+            Flags.useDedicatedHandlerThread());
+
     static final boolean DEBUG = false; // Never submit with true
     static final boolean DEBUG_RESPONSE_STATS = DEBUG || Log.isLoggable(TAG, Log.DEBUG);
     static final boolean COMPRESS_TIME = false;
@@ -404,11 +408,11 @@
         IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
         filter.addAction(Intent.ACTION_USER_STARTED);
         getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter,
-                null, /* scheduler= */ Flags.useDedicatedHandlerThread() ? mHandler : null);
+                null, /* scheduler= */ USE_DEDICATED_HANDLER_THREAD ? mHandler : null);
 
         getContext().registerReceiverAsUser(new UidRemovedReceiver(), UserHandle.ALL,
                 new IntentFilter(ACTION_UID_REMOVED), null,
-                /* scheduler= */ Flags.useDedicatedHandlerThread() ? mHandler : null);
+                /* scheduler= */ USE_DEDICATED_HANDLER_THREAD ? mHandler : null);
 
         mRealTimeSnapshot = SystemClock.elapsedRealtime();
         mSystemTimeSnapshot = System.currentTimeMillis();
@@ -497,7 +501,7 @@
     }
 
     private Handler getUsageEventProcessingHandler() {
-        if (Flags.useDedicatedHandlerThread()) {
+        if (USE_DEDICATED_HANDLER_THREAD) {
             return new H(UsageStatsHandlerThread.get().getLooper());
         } else {
             return new H(BackgroundThread.get().getLooper());
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index 8b44579..36adeec 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -380,6 +380,11 @@
             return false;
         }
 
+        if (descriptors == null) {
+            Slog.e(TAG, "Failed to add device as the descriptor is null");
+            return false;
+        }
+
         UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress, descriptors);
         if (deviceClass == UsbConstants.USB_CLASS_PER_INTERFACE
                 && !checkUsbInterfacesDenyListed(parser)) {
@@ -462,8 +467,7 @@
                 }
 
                 // Tracking
-                addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT,
-                        parser.getRawDescriptors());
+                addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT, descriptors);
 
                 // Stats collection
                 FrameworkStatsLog.write(FrameworkStatsLog.USB_DEVICE_ATTACHED,
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 530a39e..2da352d 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -48,20 +48,25 @@
 import android.hardware.usb.UsbPortStatus;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.Looper;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.service.usb.UsbServiceDumpProto;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.dump.DualDumpOutputStream;
+import com.android.internal.widget.LockPatternUtils;
 import com.android.server.FgThread;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
@@ -147,6 +152,8 @@
     private final UsbSettingsManager mSettingsManager;
     private final UsbPermissionManager mPermissionManager;
 
+    static final int PACKAGE_MONITOR_OPERATION_ID = 1;
+    static final int STRONG_AUTH_OPERATION_ID = 2;
     /**
      * The user id of the current user. There might be several profiles (with separate user ids)
      * per user.
@@ -156,6 +163,10 @@
 
     private final Object mLock = new Object();
 
+    // Key: USB port id
+    // Value: A set of UIDs of requesters who request disabling usb data
+    private final ArrayMap<String, ArraySet<Integer>> mUsbDisableRequesters = new ArrayMap<>();
+
     /**
      * @return the {@link UsbUserSettingsManager} for the given userId
      */
@@ -261,6 +272,14 @@
         if (mDeviceManager != null) {
             mDeviceManager.bootCompleted();
         }
+        if (android.hardware.usb.flags.Flags.enableUsbDataSignalStaking()) {
+            new PackageUninstallMonitor()
+                    .register(mContext, UserHandle.ALL, BackgroundThread.getHandler());
+
+            new LockPatternUtils(mContext)
+                    .registerStrongAuthTracker(new StrongAuthTracker(mContext,
+                            BackgroundThread.getHandler().getLooper()));
+        }
     }
 
     /** Called when a user is unlocked. */
@@ -873,6 +892,11 @@
         Objects.requireNonNull(callback, "enableUsbData: callback must not be null. opId:"
                 + operationId);
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        if (android.hardware.usb.flags.Flags.enableUsbDataSignalStaking()) {
+            if (!shouldUpdateUsbSignaling(portId, enable, Binder.getCallingUid())) return false;
+        }
+
         final long ident = Binder.clearCallingIdentity();
         boolean wait;
         try {
@@ -892,6 +916,31 @@
         return wait;
     }
 
+    /**
+     * If enable = true, exclude UID from update list.
+     * If enable = false, include UID in update list.
+     * Return false if enable = true and the list is empty (no updates).
+     * Return true otherwise (let downstream decide on updates).
+     */
+    private boolean shouldUpdateUsbSignaling(String portId, boolean enable, int uid) {
+        synchronized (mUsbDisableRequesters) {
+            if (!mUsbDisableRequesters.containsKey(portId)) {
+                mUsbDisableRequesters.put(portId, new ArraySet<>());
+            }
+
+            ArraySet<Integer> uidsOfDisableRequesters = mUsbDisableRequesters.get(portId);
+
+            if (enable) {
+                uidsOfDisableRequesters.remove(uid);
+                // re-enable USB port (return true) if there are no other disable requesters
+                return uidsOfDisableRequesters.isEmpty();
+            } else {
+                uidsOfDisableRequesters.add(uid);
+            }
+        }
+        return true;
+    }
+
     @Override
     public void enableUsbDataWhileDocked(String portId, int operationId,
             IUsbOperationInternal callback) {
@@ -1344,4 +1393,55 @@
     private static String removeLastChar(String value) {
         return value.substring(0, value.length() - 1);
     }
+
+    /**
+     * Upon app removal, clear associated UIDs from the mUsbDisableRequesters list
+     * and re-enable USB data signaling if no remaining apps require USB disabling.
+     */
+    private class PackageUninstallMonitor extends PackageMonitor {
+        @Override
+        public void onUidRemoved(int uid) {
+            synchronized (mUsbDisableRequesters) {
+                for (String portId : mUsbDisableRequesters.keySet()) {
+                    ArraySet<Integer> disabledUid = mUsbDisableRequesters.get(portId);
+                    if (disabledUid != null) {
+                        disabledUid.remove(uid);
+                        if (disabledUid.isEmpty()) {
+                            enableUsbData(portId, true, PACKAGE_MONITOR_OPERATION_ID,
+                                    new IUsbOperationInternal.Default());
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Implements a callback within StrongAuthTracker to disable USB data signaling
+     * when the device enters lockdown mode. This likely involves updating a state
+     * that controls USB data behavior.
+     */
+    private class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
+        private boolean mLockdownModeStatus;
+
+        StrongAuthTracker(Context context, Looper looper) {
+            super(context, looper);
+        }
+
+        @Override
+        public synchronized void onStrongAuthRequiredChanged(int userId) {
+
+            boolean lockDownTriggeredByUser = (getStrongAuthForUser(userId)
+                    & STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN) != 0;
+            //if it goes into the same lockdown status, no change is needed
+            if (mLockdownModeStatus == lockDownTriggeredByUser) {
+                return;
+            }
+            mLockdownModeStatus = lockDownTriggeredByUser;
+            for (UsbPort port: mPortManager.getPorts()) {
+                enableUsbData(port.getId(), !lockDownTriggeredByUser, STRONG_AUTH_OPERATION_ID,
+                        new IUsbOperationInternal.Default());
+            }
+        }
+    }
 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index b3db2de..862aff9 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -31,12 +31,11 @@
 import static android.provider.Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY;
 import static android.provider.Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT;
 
-import static com.android.server.soundtrigger.SoundTriggerEvent.SessionEvent.Type;
-import static com.android.server.utils.EventLogger.Event.ALOGW;
-
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 import static com.android.server.soundtrigger.DeviceStateHandler.DeviceStateListener;
 import static com.android.server.soundtrigger.DeviceStateHandler.SoundTriggerDeviceState;
+import static com.android.server.soundtrigger.SoundTriggerEvent.SessionEvent.Type;
+import static com.android.server.utils.EventLogger.Event.ALOGW;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -94,8 +93,8 @@
 import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.SparseArray;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.ISoundTriggerService;
@@ -105,19 +104,17 @@
 import com.android.server.SystemService;
 import com.android.server.soundtrigger.SoundTriggerEvent.ServiceEvent;
 import com.android.server.soundtrigger.SoundTriggerEvent.SessionEvent;
-import com.android.server.utils.EventLogger.Event;
 import com.android.server.utils.EventLogger;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.function.Consumer;
-import java.util.List;
-import java.util.Set;
 import java.util.Deque;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.TreeMap;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
@@ -126,6 +123,7 @@
 import java.util.concurrent.LinkedBlockingDeque;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 /**
@@ -257,6 +255,11 @@
         publishLocalService(SoundTriggerInternal.class, mLocalSoundTriggerService);
     }
 
+    private boolean hasCalling() {
+        return mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_CALLING);
+    }
+
     @Override
     public void onBootPhase(int phase) {
         Slog.d(TAG, "onBootPhase: " + phase + " : " + isSafeMode());
@@ -282,11 +285,13 @@
             // Do so after registering the listener so we ensure that we don't drop any events
             mDeviceStateHandler.onPowerModeChanged(powerManager.getSoundTriggerPowerSaveMode());
 
-            // PhoneCallStateHandler initializes the original call state
-            mPhoneCallStateHandler = new PhoneCallStateHandler(
-                      mContext.getSystemService(SubscriptionManager.class),
-                      mContext.getSystemService(TelephonyManager.class),
-                      mDeviceStateHandler);
+            if (hasCalling()) {
+                // PhoneCallStateHandler initializes the original call state
+                mPhoneCallStateHandler = new PhoneCallStateHandler(
+                        mContext.getSystemService(SubscriptionManager.class),
+                        mContext.getSystemService(TelephonyManager.class),
+                        mDeviceStateHandler);
+            }
         }
         mMiddlewareService = ISoundTriggerMiddlewareService.Stub.asInterface(
                 ServiceManager.waitForService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE));
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index c217780..ae4faa8 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -16,15 +16,22 @@
 
 package com.android.server.voiceinteraction;
 
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
+import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
 import android.app.AppGlobals;
+import android.app.admin.DevicePolicyManagerInternal;
 import android.app.role.OnRoleHoldersChangedListener;
 import android.app.role.RoleManager;
 import android.content.ComponentName;
@@ -41,6 +48,7 @@
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.database.ContentObserver;
+import android.graphics.Bitmap;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
 import android.hardware.soundtrigger.KeyphraseMetadata;
 import android.hardware.soundtrigger.ModelParams;
@@ -59,7 +67,6 @@
 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;
@@ -69,7 +76,6 @@
 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;
@@ -84,6 +90,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
+import android.window.ScreenCapture;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -110,7 +117,9 @@
 import com.android.server.policy.AppOpsPolicy;
 import com.android.server.utils.Slogf;
 import com.android.server.utils.TimingsTraceAndSlog;
+import com.android.server.wm.ActivityAssistInfo;
 import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -127,6 +136,19 @@
     static final String TAG = "VoiceInteractionManager";
     static final boolean DEBUG = false;
 
+    /** Static constants used by Contextual Search helper. */
+    private static final String CS_KEY_FLAG_SECURE_FOUND =
+            "com.android.contextualsearch.flag_secure_found";
+    private static final String CS_KEY_FLAG_SCREENSHOT =
+            "com.android.contextualsearch.screenshot";
+    private static final String CS_KEY_FLAG_IS_MANAGED_PROFILE_VISIBLE =
+            "com.android.contextualsearch.is_managed_profile_visible";
+    private static final String CS_KEY_FLAG_VISIBLE_PACKAGE_NAMES =
+            "com.android.contextualsearch.visible_package_names";
+    private static final String CS_INTENT_FILTER =
+            "com.android.contextualsearch.LAUNCH";
+
+
     final Context mContext;
     final ContentResolver mResolver;
     // Can be overridden for testing purposes
@@ -135,6 +157,8 @@
     final ActivityManagerInternal mAmInternal;
     final ActivityTaskManagerInternal mAtmInternal;
     final UserManagerInternal mUserManagerInternal;
+    final WindowManagerInternal mWmInternal;
+    final DevicePolicyManagerInternal mDpmInternal;
     final ArrayMap<Integer, VoiceInteractionManagerServiceStub.SoundTriggerSession>
             mLoadedKeyphraseIds = new ArrayMap<>();
     ShortcutServiceInternal mShortcutServiceInternal;
@@ -156,7 +180,8 @@
                 LocalServices.getService(ActivityManagerInternal.class));
         mAtmInternal = Objects.requireNonNull(
                 LocalServices.getService(ActivityTaskManagerInternal.class));
-
+        mWmInternal = LocalServices.getService(WindowManagerInternal.class);
+        mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
         LegacyPermissionManagerInternal permissionManagerInternal = LocalServices.getService(
                 LegacyPermissionManagerInternal.class);
         permissionManagerInternal.setVoiceInteractionPackagesProvider(
@@ -1019,6 +1044,58 @@
         public boolean showSessionFromSession(@NonNull IBinder token, @Nullable Bundle sessionArgs,
                 int flags, @Nullable String attributionTag) {
             synchronized (this) {
+                final String csKey = mContext.getResources()
+                        .getString(R.string.config_defaultContextualSearchKey);
+                final String csEnabledKey = mContext.getResources()
+                        .getString(R.string.config_defaultContextualSearchEnabled);
+
+                // If the request is for Contextual Search, process it differently
+                if (sessionArgs != null && sessionArgs.containsKey(csKey)) {
+                    if (sessionArgs.getBoolean(csEnabledKey, true)) {
+                        // If Contextual Search is enabled, try to follow that path.
+                        Intent launchIntent = getContextualSearchIntent(sessionArgs);
+                        if (launchIntent != null) {
+                            // Hand over to contextual search helper.
+                            Slog.d(TAG, "Handed over to contextual search helper.");
+                            final long caller = Binder.clearCallingIdentity();
+                            try {
+                                return startContextualSearch(launchIntent);
+                            } finally {
+                                Binder.restoreCallingIdentity(caller);
+                            }
+                        }
+                    }
+
+                    // Since we are here, Contextual Search helper couldn't handle the request.
+                    final String visEnabledKey = mContext.getResources()
+                            .getString(R.string.config_defaultContextualSearchLegacyEnabled);
+                    if (sessionArgs.getBoolean(visEnabledKey, true)) {
+                        // If visEnabledKey is set to true (or absent), we try following VIS path.
+                        String csPkgName = mContext.getResources()
+                                .getString(R.string.config_defaultContextualSearchPackageName);
+                        ComponentName currInteractor =
+                                getCurInteractor(Binder.getCallingUserHandle().getIdentifier());
+                        if (currInteractor == null
+                                || !csPkgName.equals(currInteractor.getPackageName())) {
+                            // Check if the interactor can handle Contextual Search.
+                            // If not, return failure.
+                            Slog.w(TAG, "Contextual Search not supported yet. Returning failure.");
+                            return false;
+                        }
+                    } else {
+                        // If visEnabledKey is set to false AND the request was for Contextual
+                        // Search, return false.
+                        return false;
+                    }
+                    // Given that we haven't returned yet, we can say that
+                    // - Contextual Search Helper couldn't handle the request
+                    // - VIS path for Contextual Search is enabled
+                    // - The current interactor supports Contextual Search.
+                    // Hence, we will proceed with the VIS path.
+                    Slog.d(TAG, "Contextual search not supported yet. Proceeding with VIS.");
+
+                }
+
                 if (mImpl == null) {
                     Slog.w(TAG, "showSessionFromSession without running voice interaction service");
                     return false;
@@ -1331,17 +1408,6 @@
             }
         }
 
-        // 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(
@@ -1351,13 +1417,7 @@
                 @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();
@@ -2644,6 +2704,76 @@
                 }
             }
         };
+
+        private Intent getContextualSearchIntent(Bundle args) {
+            String csPkgName = mContext.getResources()
+                    .getString(R.string.config_defaultContextualSearchPackageName);
+            if (csPkgName.isEmpty()) {
+                // Return null if csPackageName is not specified.
+                return null;
+            }
+            Intent launchIntent = new Intent(CS_INTENT_FILTER);
+            launchIntent.setPackage(csPkgName);
+            ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivity(
+                    launchIntent, PackageManager.MATCH_FACTORY_ONLY);
+            if (resolveInfo == null) {
+                return null;
+            }
+            launchIntent.setComponent(resolveInfo.getComponentInfo().getComponentName());
+            launchIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_ANIMATION
+                    | FLAG_ACTIVITY_NO_USER_ACTION | FLAG_ACTIVITY_CLEAR_TASK);
+            launchIntent.putExtras(args);
+            boolean isAssistDataAllowed = mAtmInternal.isAssistDataAllowed();
+            final List<ActivityAssistInfo> records = mAtmInternal.getTopVisibleActivities();
+            ArrayList<String> visiblePackageNames = new ArrayList<>();
+            boolean isManagedProfileVisible = false;
+            for (ActivityAssistInfo record: records) {
+                // Add the package name to the list only if assist data is allowed.
+                if (isAssistDataAllowed) {
+                    visiblePackageNames.add(record.getComponentName().getPackageName());
+                }
+                if (mDpmInternal != null
+                        && mDpmInternal.isUserOrganizationManaged(record.getUserId())) {
+                    isManagedProfileVisible = true;
+                }
+            }
+            final ScreenCapture.ScreenshotHardwareBuffer shb;
+            if (mWmInternal != null) {
+                shb = mWmInternal.takeAssistScreenshot();
+            } else {
+                shb = null;
+            }
+            final Bitmap bm = shb != null ? shb.asBitmap() : null;
+            // Now that everything is fetched, putting it in the launchIntent.
+            if (bm != null) {
+                launchIntent.putExtra(CS_KEY_FLAG_SECURE_FOUND, shb.containsSecureLayers());
+                // Only put the screenshot if assist data is allowed
+                if (isAssistDataAllowed) {
+                    launchIntent.putExtra(CS_KEY_FLAG_SCREENSHOT, bm.asShared());
+                }
+            }
+            launchIntent.putExtra(CS_KEY_FLAG_IS_MANAGED_PROFILE_VISIBLE, isManagedProfileVisible);
+            // Only put the list of visible package names if assist data is allowed
+            if (isAssistDataAllowed) {
+                launchIntent.putExtra(CS_KEY_FLAG_VISIBLE_PACKAGE_NAMES, visiblePackageNames);
+            }
+
+            return launchIntent;
+        }
+
+        @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS)
+        private boolean startContextualSearch(Intent launchIntent) {
+            // Contextual search starts with a frozen screen - so we launch without
+            // any system animations or starting window.
+            final ActivityOptions opts = ActivityOptions.makeCustomTaskAnimation(mContext,
+                    /* enterResId= */ 0, /* exitResId= */ 0, null, null, null);
+            opts.setDisableStartingWindow(true);
+            int resultCode = mAtmInternal.startActivityWithScreenshot(launchIntent,
+                    mContext.getPackageName(), Binder.getCallingUid(), Binder.getCallingPid(), null,
+                    opts.toBundle(), Binder.getCallingUserHandle().getIdentifier());
+            return resultCode == ActivityManager.START_SUCCESS;
+        }
+
     }
 
     /**
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index 2fc6a22..8b503f2 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -28,6 +28,8 @@
 import com.android.internal.telecom.IConnectionServiceAdapter;
 import com.android.internal.telecom.IVideoProvider;
 import com.android.internal.telecom.RemoteServiceCallback;
+import com.android.server.telecom.flags.FeatureFlags;
+import com.android.server.telecom.flags.FeatureFlagsImpl;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -550,6 +552,9 @@
     private final Map<String, RemoteConference> mConferenceById = new HashMap<>();
     private final Set<RemoteConnection> mPendingConnections = new HashSet<>();
 
+    /** Telecom feature flags **/
+    private final FeatureFlags mTelecomFeatureFlags = new FeatureFlagsImpl();
+
     RemoteConnectionService(
             IConnectionService outgoingConnectionServiceRpc,
             ConnectionService ourConnectionServiceImpl) throws RemoteException {
@@ -578,6 +583,14 @@
         extras.putString(Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME,
                 mOurConnectionServiceImpl.getApplicationContext().getOpPackageName());
 
+        // Defaulted ConnectionRequest params
+        String telecomCallId = "";
+        boolean shouldShowIncomingUI = false;
+        if (mTelecomFeatureFlags.setRemoteConnectionCallId()) {
+            telecomCallId = id;
+            shouldShowIncomingUI = request.shouldShowIncomingCallUi();
+        }
+
         final ConnectionRequest newRequest = new ConnectionRequest.Builder()
                 .setAccountHandle(request.getAccountHandle())
                 .setAddress(request.getAddress())
@@ -585,6 +598,9 @@
                 .setVideoState(request.getVideoState())
                 .setRttPipeFromInCall(request.getRttPipeFromInCall())
                 .setRttPipeToInCall(request.getRttPipeToInCall())
+                // Flagged changes
+                .setTelecomCallId(telecomCallId)
+                .setShouldShowIncomingCallUi(shouldShowIncomingUI)
                 .build();
         try {
             if (mConnectionById.isEmpty()) {
@@ -626,10 +642,28 @@
                 mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub(),
                         null /*Session.Info*/);
             }
+
+            // Set telecom call id to what's being tracked by base ConnectionService.
+            String telecomCallId = mTelecomFeatureFlags.setRemoteConnectionCallId()
+                    ? id : request.getTelecomCallId();
+
+            final ConnectionRequest newRequest = new ConnectionRequest.Builder()
+                    .setAccountHandle(request.getAccountHandle())
+                    .setAddress(request.getAddress())
+                    .setExtras(request.getExtras())
+                    .setVideoState(request.getVideoState())
+                    .setShouldShowIncomingCallUi(request.shouldShowIncomingCallUi())
+                    .setRttPipeFromInCall(request.getRttPipeFromInCall())
+                    .setRttPipeToInCall(request.getRttPipeToInCall())
+                    .setParticipants(request.getParticipants())
+                    .setIsAdhocConferenceCall(request.isAdhocConferenceCall())
+                    .setTelecomCallId(telecomCallId)
+                    .build();
+
             RemoteConference conference = new RemoteConference(id, mOutgoingConnectionServiceRpc);
             mOutgoingConnectionServiceRpc.createConference(connectionManagerPhoneAccount,
                     id,
-                    request,
+                    newRequest,
                     isIncoming,
                     false /* isUnknownCall */,
                     null /*Session.info*/);
@@ -640,7 +674,7 @@
                     maybeDisconnectAdapter();
                 }
             });
-            conference.putExtras(request.getExtras());
+            conference.putExtras(newRequest.getExtras());
             return conference;
         } catch (RemoteException e) {
             return RemoteConference.failure(
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 9792cdd..ff4be55 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1434,11 +1434,14 @@
     }
 
     /**
-     * This API will return all {@link PhoneAccount}s registered via
-     * {@link TelecomManager#registerPhoneAccount(PhoneAccount)}. If a {@link PhoneAccount} appears
-     * to be missing from the list, Telecom has either unregistered the {@link PhoneAccount}
-     * or the caller registered the {@link PhoneAccount} under a different user and does not
-     * have the {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission.
+     * This API will return all {@link PhoneAccount}s the caller registered via
+     * {@link TelecomManager#registerPhoneAccount(PhoneAccount)}.  If a {@link PhoneAccount} appears
+     * to be missing from the list, Telecom has either unregistered the {@link PhoneAccount} (for
+     * cleanup purposes) or the caller registered the {@link PhoneAccount} under a different user
+     * and does not have the  {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission.
+     * <b>Note:</b> This API will only return {@link PhoneAccount}s registered by the same app.  For
+     * system Dialers that need all the {@link PhoneAccount}s registered by every application, see
+     * {@link TelecomManager#getAllPhoneAccounts()}.
      *
      * @return all the {@link PhoneAccount}s registered by the caller.
      */
@@ -2794,7 +2797,9 @@
 
     /**
      * Determines whether there are any ongoing {@link PhoneAccount#CAPABILITY_SELF_MANAGED}
-     * calls for a given {@code packageName} and {@code userHandle}.
+     * calls for a given {@code packageName} and {@code userHandle}. If UserHandle.ALL or a user
+     * that isn't the calling user is passed in, the caller will need to have granted the ability
+     * to interact across users.
      *
      * @param packageName the package name of the app to check calls for.
      * @param userHandle the user handle to check calls for.
@@ -2813,41 +2818,7 @@
         if (service != null) {
             try {
                 return service.isInSelfManagedCall(packageName, userHandle,
-                        mContext.getOpPackageName(), false);
-            } catch (RemoteException e) {
-                Log.e(TAG, "RemoteException isInSelfManagedCall: " + e);
-                e.rethrowFromSystemServer();
-                return false;
-            }
-        } else {
-            throw new IllegalStateException("Telecom service is not present");
-        }
-    }
-
-    /**
-     * Determines whether there are any ongoing {@link PhoneAccount#CAPABILITY_SELF_MANAGED}
-     * calls for a given {@code packageName} amongst all users, given that detectForAllUsers is true
-     * and the caller has the ability to interact across users. If detectForAllUsers isn't enabled,
-     * the calls will be checked against the caller.
-     *
-     * @param packageName the package name of the app to check calls for.
-     * @param detectForAllUsers indicates if calls should be detected across all users.
-     * @return {@code true} if there are ongoing calls, {@code false} otherwise.
-     * @throws SecurityException if detectForAllUsers is true and the caller does not grant the
-     * ability to interact across users.
-     * @hide
-     */
-    @SystemApi
-    @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
-    @RequiresPermission(allOf = {Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
-            Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
-    public boolean isInSelfManagedCall(@NonNull String packageName,
-            boolean detectForAllUsers) {
-        ITelecomService service = getTelecomService();
-        if (service != null) {
-            try {
-                return service.isInSelfManagedCall(packageName, null,
-                        mContext.getOpPackageName(), detectForAllUsers);
+                        mContext.getOpPackageName());
             } catch (RemoteException e) {
                 Log.e(TAG, "RemoteException isInSelfManagedCall: " + e);
                 e.rethrowFromSystemServer();
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 302a472..112471b 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -401,7 +401,7 @@
      * @see TelecomServiceImpl#isInSelfManagedCall
      */
     boolean isInSelfManagedCall(String packageName, in UserHandle userHandle,
-        String callingPackage, boolean detectForAllUsers);
+        String callingPackage);
 
     /**
      * @see TelecomServiceImpl#addCall
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 5d99acd..d3a50bb 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -5307,6 +5307,19 @@
                 KEY_PREFIX + "enable_presence_group_subscribe_bool";
 
         /**
+         * SIP SUBSCRIBE retry duration used when device doesn't receive a response to SIP
+         * SUBSCRIBE request.
+         * If this value is not defined or defined as negative value, the device does not retry
+         * the SIP SUBSCRIBE.
+         * If the value is 0 then device retries immediately upon timeout.
+         * If the value is > 0 then device waits for configured duration and retries after timeout
+         * is detected
+         * @hide
+         */
+        public static final String KEY_SUBSCRIBE_RETRY_DURATION_MILLIS_LONG =
+                KEY_PREFIX + "subscribe_retry_duration_millis_long";
+
+        /**
          * Flag indicating whether or not to use SIP URI when send a presence subscribe.
          * When {@code true}, the device sets the To and Contact header to be SIP URI using
          * the TelephonyManager#getIsimDomain" API.
@@ -5982,6 +5995,7 @@
             defaults.putBoolean(KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL, false);
             defaults.putBoolean(KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, false);
             defaults.putBoolean(KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL, false);
+            defaults.putInt(KEY_SUBSCRIBE_RETRY_DURATION_MILLIS_LONG, -1);
             defaults.putBoolean(KEY_USE_SIP_URI_FOR_PRESENCE_SUBSCRIBE_BOOL, false);
             defaults.putInt(KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT, 30 * 24 * 60 * 60);
             defaults.putBoolean(KEY_RCS_REQUEST_FORBIDDEN_BY_SIP_489_BOOL, false);
@@ -10244,6 +10258,31 @@
     @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
     public static final String KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY =
             "cellular_service_capabilities_int_array";
+   /**
+     * Transition delay from BT to Cellular on Wear.
+     * Specifies delay when transitioning away from BT.
+     * This minimizes the duration of the netTransitionWakelock held by ConnectivityService
+     * whenever the primary/default network disappears, while still allowing some amount of time
+     * for BT to reconnect before we enable cell.
+     *
+     * If set as -1 then value from resources will be used
+     *
+     * @hide
+     */
+    public static final String KEY_WEAR_CONNECTIVITY_BT_TO_CELL_DELAY_MS_INT =
+            "proxy_connectivity_delay_cell";
+
+    /**
+     * Transition delay from BT to Cellular on Wear.
+     * If wifi connected it extends delay that has been started for BT to Cellular transition
+     * to avoid Wifi thrashing turning Cell radio and causing higher battery drain.
+     *
+     * If set as -1 then value from resources will be used
+     *
+     * @hide
+     */
+    public static final String KEY_WEAR_CONNECTIVITY_EXTEND_BT_TO_CELL_DELAY_ON_WIFI_MS_INT =
+            "wifi_connectivity_extend_cell_delay";
 
     /** The default value for every variable. */
     private static final PersistableBundle sDefaults;
@@ -11040,6 +11079,8 @@
         sDefaults.putStringArray(KEY_CARRIER_SERVICE_NAME_STRING_ARRAY, new String[0]);
         sDefaults.putStringArray(KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY, new String[0]);
         sDefaults.putIntArray(KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY, new int[]{1, 2, 3});
+        sDefaults.putInt(KEY_WEAR_CONNECTIVITY_BT_TO_CELL_DELAY_MS_INT, -1);
+        sDefaults.putInt(KEY_WEAR_CONNECTIVITY_EXTEND_BT_TO_CELL_DELAY_ON_WIFI_MS_INT, -1);
     }
 
     /**
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 82ed340..58488d1 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -573,7 +573,7 @@
      * Returns the number of this subscription.
      *
      * Starting with API level 30, returns the number of this subscription if the calling app meets
-     * one of the following requirements:
+     * at least one of the following requirements:
      * <ul>
      *     <li>If the calling app's target SDK is API level 29 or lower and the app has been granted
      *     the READ_PHONE_STATE permission.
@@ -584,8 +584,8 @@
      *     <li>If the calling app is the default SMS role holder.
      * </ul>
      *
-     * @return the number of this subscription, or an empty string if one of these requirements is
-     * not met
+     * @return the number of this subscription, or an empty string if none of the requirements
+     * are met.
      * @deprecated use {@link SubscriptionManager#getPhoneNumber(int)} instead, which takes a
      *             {@link #getSubscriptionId() subscription ID}.
      */
diff --git a/telephony/java/android/telephony/TelephonyFrameworkInitializer.java b/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
index c2f5b8f..901daf8 100644
--- a/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
+++ b/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
@@ -19,12 +19,14 @@
 import android.annotation.NonNull;
 import android.app.SystemServiceRegistry;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.TelephonyServiceManager;
 import android.telephony.euicc.EuiccCardManager;
 import android.telephony.euicc.EuiccManager;
 import android.telephony.ims.ImsManager;
 import android.telephony.satellite.SatelliteManager;
 
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.util.Preconditions;
 
 
@@ -55,6 +57,11 @@
         sTelephonyServiceManager = Preconditions.checkNotNull(telephonyServiceManager);
     }
 
+    private static boolean hasSystemFeature(Context context, String feature) {
+        if (!Flags.minimalTelephonyManagersConditionalOnFeatures()) return true;
+        return context.getPackageManager().hasSystemFeature(feature);
+    }
+
     /**
      * Called by {@link SystemServiceRegistry}'s static initializer and registers all telephony
      * services to {@link Context}, so that {@link Context#getSystemService} can return them.
@@ -76,33 +83,39 @@
         SystemServiceRegistry.registerContextAwareService(
                 Context.CARRIER_CONFIG_SERVICE,
                 CarrierConfigManager.class,
-                context -> new CarrierConfigManager(context)
+                context -> hasSystemFeature(context, PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
+                        ? new CarrierConfigManager(context) : null
         );
         SystemServiceRegistry.registerContextAwareService(
                 Context.EUICC_SERVICE,
                 EuiccManager.class,
-                context -> new EuiccManager(context)
+                context -> hasSystemFeature(context, PackageManager.FEATURE_TELEPHONY_EUICC)
+                        ? new EuiccManager(context) : null
         );
         SystemServiceRegistry.registerContextAwareService(
                 Context.EUICC_CARD_SERVICE,
                 EuiccCardManager.class,
-                context -> new EuiccCardManager(context)
+                context -> hasSystemFeature(context, PackageManager.FEATURE_TELEPHONY_EUICC)
+                        ? new EuiccCardManager(context) : null
         );
         SystemServiceRegistry.registerContextAwareService(
                 Context.TELEPHONY_IMS_SERVICE,
                 ImsManager.class,
-                context -> new ImsManager(context)
+                context -> hasSystemFeature(context, PackageManager.FEATURE_TELEPHONY_IMS)
+                        ? new ImsManager(context) : null
         );
         SystemServiceRegistry.registerContextAwareService(
                 Context.SMS_SERVICE,
                 SmsManager.class,
-                context -> SmsManager.getSmsManagerForContextAndSubscriptionId(context,
-                        SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)
+                context -> hasSystemFeature(context, PackageManager.FEATURE_TELEPHONY_MESSAGING)
+                        ? SmsManager.getSmsManagerForContextAndSubscriptionId(context,
+                                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) : null
         );
         SystemServiceRegistry.registerContextAwareService(
                 Context.SATELLITE_SERVICE,
                 SatelliteManager.class,
-                context -> new SatelliteManager(context)
+                context -> hasSystemFeature(context, PackageManager.FEATURE_TELEPHONY_SATELLITE)
+                        ? new SatelliteManager(context) : null
         );
     }
 
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 626a2e5..a047b97 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -8435,7 +8435,7 @@
      * In addition to the {@link Manifest.permission#MODIFY_PHONE_STATE} permission, callers of this
      * API must also be listed in the device configuration as an authorized app in
      * {@code packages/services/Telephony/res/values/config.xml} under the
-     * {@code config_number_verification_package_name} key.
+     * {@code platform_number_verification_package} key.
      *
      * @hide
      * @param range The range of phone numbers the caller expects a phone call from.
@@ -15023,7 +15023,7 @@
     @FlaggedApi(android.permission.flags.Flags.FLAG_GET_EMERGENCY_ROLE_HOLDER_API_ENABLED)
     @NonNull
     @SystemApi
-    public String getEmergencyAssistancePackage() {
+    public String getEmergencyAssistancePackageName() {
         if (!isEmergencyAssistanceEnabled() || !isVoiceCapable()) {
             throw new IllegalStateException("isEmergencyAssistanceEnabled() is false or device"
                 + " not voice capable.");
@@ -18535,7 +18535,7 @@
      * @hide
      */
     @SystemApi
-    @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+    @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_GET_LAST_KNOWN_CELL_IDENTITY)
     @RequiresPermission(allOf = {Manifest.permission.ACCESS_FINE_LOCATION,
             Manifest.permission.ACCESS_LAST_KNOWN_CELL_ID})
     public @Nullable CellIdentity getLastKnownCellIdentity() {
@@ -18852,7 +18852,7 @@
      */
     @SystemApi
     @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
-    @RequiresPermission(android.Manifest.permission.DUMP)
+    @RequiresPermission(android.Manifest.permission.READ_DROPBOX_DATA)
     public void persistEmergencyCallDiagnosticData(@NonNull String dropboxTag,
             @NonNull EmergencyCallDiagnosticData data) {
         try {
diff --git a/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl b/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
index e69b60b..c349599 100644
--- a/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
+++ b/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
@@ -26,5 +26,5 @@
 {
     void onQualifiedNetworkTypesChanged(int apnTypes, in int[] qualifiedNetworkTypes);
     void onNetworkValidationRequested(int networkCapability, IIntegerConsumer callback);
-    void onReconnectQualifedNetworkType(int apnTypes, int qualifiedNetworkType);
+    void onReconnectQualifiedNetworkType(int apnTypes, int qualifiedNetworkType);
 }
diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java
index 7bfe04d..f775de6 100644
--- a/telephony/java/android/telephony/data/QualifiedNetworksService.java
+++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java
@@ -238,7 +238,7 @@
                 @AccessNetworkConstants.RadioAccessNetworkType int qualifiedNetworkType) {
             if (mCallback != null) {
                 try {
-                    mCallback.onReconnectQualifedNetworkType(apnTypes, qualifiedNetworkType);
+                    mCallback.onReconnectQualifiedNetworkType(apnTypes, qualifiedNetworkType);
                 } catch (RemoteException e) {
                     loge("Failed to call onReconnectQualifiedNetworkType. " + e);
                 }
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index b84ff29..12e04c2 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -195,7 +195,8 @@
                     // whether or not ImsFeature.FEATURE_EMERGENCY_MMTEL feature is set and should
                     // not be set by users of ImsService.
                     CAPABILITY_SIP_DELEGATE_CREATION,
-                    CAPABILITY_TERMINAL_BASED_CALL_WAITING
+                    CAPABILITY_TERMINAL_BASED_CALL_WAITING,
+                    CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING
             })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ImsServiceCapability {}
@@ -206,7 +207,9 @@
      */
     private static final Map<Long, String> CAPABILITIES_LOG_MAP = Map.of(
             CAPABILITY_EMERGENCY_OVER_MMTEL, "EMERGENCY_OVER_MMTEL",
-            CAPABILITY_SIP_DELEGATE_CREATION, "SIP_DELEGATE_CREATION");
+            CAPABILITY_SIP_DELEGATE_CREATION, "SIP_DELEGATE_CREATION",
+            CAPABILITY_TERMINAL_BASED_CALL_WAITING, "TERMINAL_BASED_CALL_WAITING",
+            CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING, "SIMULTANEOUS_CALLING");
 
     /**
      * The intent that must be defined as an intent-filter in the AndroidManifest of the ImsService.
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index 9789082..3f02ae9 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -549,13 +549,19 @@
         public static final int CAPABILITY_TYPE_SMS = 1 << 3;
 
         /**
-         * This MmTelFeature supports Call Composer (section 2.4 of RC.20)
+         * This MmTelFeature supports Call Composer (section 2.4 of RC.20). This is the superset
+         * Call Composer, meaning that all subset types of Call Composers must be enabled when this
+         * capability is enabled
          */
         public static final int CAPABILITY_TYPE_CALL_COMPOSER = 1 << 4;
 
 
         /**
-         * This MmTelFeature supports Business-only Call Composer
+         * This MmTelFeature supports Business-only Call Composer. This is a subset of
+         * {@code CAPABILITY_TYPE_CALL_COMPOSER} that only supports business related
+         * information for calling (e.g. information to signal if the call is a business call) in
+         * the SIP header.  When enabling {@code CAPABILITY_TYPE_CALL_COMPOSER}, the
+         * {@code CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY} capability must also be enabled.
          */
         @FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER)
         public static final int CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY = 1 << 5;
diff --git a/test-runner/src/android/test/InstrumentationTestRunner.java b/test-runner/src/android/test/InstrumentationTestRunner.java
index 07e3f87..b2a8847 100644
--- a/test-runner/src/android/test/InstrumentationTestRunner.java
+++ b/test-runner/src/android/test/InstrumentationTestRunner.java
@@ -16,9 +16,7 @@
 
 package android.test;
 
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
-import com.android.internal.util.Predicate;
+import static android.test.suitebuilder.TestPredicates.hasAnnotation;
 
 import android.app.Activity;
 import android.app.Instrumentation;
@@ -29,16 +27,11 @@
 import android.test.suitebuilder.TestPredicates;
 import android.test.suitebuilder.TestSuiteBuilder;
 import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.PrintStream;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.List;
+import com.android.internal.util.Predicate;
 
 import junit.framework.AssertionFailedError;
 import junit.framework.Test;
@@ -49,7 +42,14 @@
 import junit.runner.BaseTestRunner;
 import junit.textui.ResultPrinter;
 
-import static android.test.suitebuilder.TestPredicates.hasAnnotation;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.PrintStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * An {@link Instrumentation} that runs various types of {@link junit.framework.TestCase}s against
diff --git a/test-runner/src/android/test/suitebuilder/TestPredicates.java b/test-runner/src/android/test/suitebuilder/TestPredicates.java
index 616d1a9..faf31fd 100644
--- a/test-runner/src/android/test/suitebuilder/TestPredicates.java
+++ b/test-runner/src/android/test/suitebuilder/TestPredicates.java
@@ -19,7 +19,9 @@
 import android.test.InstrumentationTestCase;
 import android.test.suitebuilder.annotation.Smoke;
 import android.test.suitebuilder.annotation.Suppress;
+
 import com.android.internal.util.Predicate;
+
 import java.lang.annotation.Annotation;
 
 /**
diff --git a/test-runner/tests/src/android/test/AndroidTestRunnerTest.java b/test-runner/tests/src/android/test/AndroidTestRunnerTest.java
index 6723548..bd6c04bc 100644
--- a/test-runner/tests/src/android/test/AndroidTestRunnerTest.java
+++ b/test-runner/tests/src/android/test/AndroidTestRunnerTest.java
@@ -19,15 +19,15 @@
 import android.test.mock.MockContext;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import java.util.ArrayList;
-import junit.framework.TestCase;
 import junit.framework.AssertionFailedError;
 import junit.framework.Test;
-import junit.framework.TestSuite;
+import junit.framework.TestCase;
 import junit.framework.TestListener;
+import junit.framework.TestSuite;
 
-import java.util.List;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 /**
  * Unit tests for {@link AndroidTestRunner}
diff --git a/tests/CoreTests/android/Android.bp b/tests/CoreTests/android/Android.bp
index e2f194b..97a6e5f 100644
--- a/tests/CoreTests/android/Android.bp
+++ b/tests/CoreTests/android/Android.bp
@@ -16,5 +16,8 @@
         "android.test.base.stubs",
     ],
     sdk_version: "current",
-    static_libs: ["junit"],
+    static_libs: [
+        "junit",
+        "androidx.test.rules",
+    ],
 }
diff --git a/tests/CoreTests/android/core/RequestAPITest.java b/tests/CoreTests/android/core/RequestAPITest.java
index 206f228..2d420d3 100644
--- a/tests/CoreTests/android/core/RequestAPITest.java
+++ b/tests/CoreTests/android/core/RequestAPITest.java
@@ -19,10 +19,11 @@
 import android.net.http.RequestHandle;
 import android.net.http.RequestQueue;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.Suppress;
 import android.util.Log;
 import android.webkit.CookieSyncManager;
 
+import androidx.test.filters.Suppress;
+
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.util.HashMap;
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 217659e..700856c 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
@@ -726,7 +726,7 @@
                                       .map(Object::toString)
                                       .collect(Collectors.joining(", ")));
             int initialNumEvents = mModeChangedEvents.size();
-            surface.setFrameRate(30.f, compatibility);
+            surface.setFrameRate(70.f, compatibility);
             verifyFrameRates(expectedFrameRates, surface);
             verifyModeSwitchesDontChangeResolution(initialNumEvents, mModeChangedEvents.size());
         });
@@ -824,7 +824,7 @@
         Display display = getDisplay();
         List<Float> expectedFrameRates = getRefreshRates(display.getMode(), display)
                                                  .stream()
-                                                 .filter(rate -> rate >= 30.f)
+                                                 .filter(rate -> rate >= 70.f)
                                                  .collect(Collectors.toList());
 
         assumeTrue("**** testSurfaceControlFrameRateCompatibility SKIPPED because no refresh rate "
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 1d71f95..d658d59 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -63,17 +63,20 @@
     ],
 }
 
-android_library_import {
-    name: "wm-flicker-window-extensions_nodeps",
-    aars: ["libs/window-extensions-release.aar"],
-    sdk_version: "current",
-}
-
 java_library {
     name: "wm-flicker-window-extensions",
     sdk_version: "current",
     static_libs: [
-        "wm-flicker-window-extensions_nodeps",
+        "androidx.window.extensions_extensions-nodeps",
+    ],
+    installable: false,
+}
+
+java_library {
+    name: "wm-flicker-window-extensions-core",
+    sdk_version: "current",
+    static_libs: [
+        "androidx.window.extensions.core_core-nodeps",
     ],
     installable: false,
 }
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
index d47329e..57da05f 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
@@ -52,6 +52,7 @@
                 // Can't use TAPL due to Recents not showing in 3 Button Nav in full screen mode
                 device.pressHome()
                 tapl.getWorkspace()
+                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
             }
             teardown { testApp.exit(wmHelper) }
             transitions { testApp.launchViaIntent(wmHelper) }
diff --git a/tests/FlickerTests/libs/window-extensions-release.aar b/tests/FlickerTests/libs/window-extensions-release.aar
deleted file mode 100644
index 918e514..0000000
--- a/tests/FlickerTests/libs/window-extensions-release.aar
+++ /dev/null
Binary files differ
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 17f91eb..060015b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -40,9 +40,10 @@
 constructor(
     protected val flicker: LegacyFlickerTest,
     protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+    protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
 ) {
-    protected val tapl: LauncherInstrumentation by lazy {
-        LauncherInstrumentation().also { it.expectedRotationCheckEnabled = true }
+    init {
+        tapl.setExpectedRotationCheckEnabled(true)
     }
 
     private val logTag = this::class.java.simpleName
diff --git a/tests/GamePerformance/src/android/gameperformance/GamePerformanceTest.java b/tests/GamePerformance/src/android/gameperformance/GamePerformanceTest.java
index d6e2861..cc0255d 100644
--- a/tests/GamePerformance/src/android/gameperformance/GamePerformanceTest.java
+++ b/tests/GamePerformance/src/android/gameperformance/GamePerformanceTest.java
@@ -15,6 +15,14 @@
  */
 package android.gameperformance;
 
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.os.Bundle;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -22,18 +30,6 @@
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 
-import android.annotation.NonNull;
-import android.app.Activity;
-import android.content.Context;
-import android.graphics.PixelFormat;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Debug;
-import android.os.Trace;
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
-
 public class GamePerformanceTest extends
         ActivityInstrumentationTestCase2<GamePerformanceActivity> {
     private final static String TAG = "GamePerformanceTest";
diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index 7343ba1c..e60764f 100644
--- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -24,6 +24,7 @@
 import android.content.pm.PackageManager
 import android.content.pm.ResolveInfo
 import android.content.pm.ServiceInfo
+import android.hardware.input.KeyboardLayoutSelectionResult
 import android.hardware.input.IInputManager
 import android.hardware.input.InputManager
 import android.hardware.input.InputManagerGlobal
@@ -525,13 +526,13 @@
                 keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
                 ENGLISH_UK_LAYOUT_DESCRIPTOR
             )
-            val keyboardLayout =
+            assertEquals(
+                "Default UI: getKeyboardLayoutForInputDevice API should always return " +
+                        "KeyboardLayoutSelectionResult.FAILED",
+                KeyboardLayoutSelectionResult.FAILED,
                 keyboardLayoutManager.getKeyboardLayoutForInputDevice(
                     keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
                 )
-            assertNull(
-                "Default UI: getKeyboardLayoutForInputDevice API should always return null",
-                keyboardLayout
             )
         }
     }
@@ -545,12 +546,14 @@
                 keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
                 ENGLISH_UK_LAYOUT_DESCRIPTOR
             )
-            assertEquals(
-                "New UI: getKeyboardLayoutForInputDevice API should return the set layout",
-                ENGLISH_UK_LAYOUT_DESCRIPTOR,
+            var result =
                 keyboardLayoutManager.getKeyboardLayoutForInputDevice(
                     keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
                 )
+            assertEquals(
+                "New UI: getKeyboardLayoutForInputDevice API should return the set layout",
+                ENGLISH_UK_LAYOUT_DESCRIPTOR,
+                result.layoutDescriptor
             )
 
             // This should replace previously set layout
@@ -558,12 +561,14 @@
                 keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
                 ENGLISH_US_LAYOUT_DESCRIPTOR
             )
-            assertEquals(
-                "New UI: getKeyboardLayoutForInputDevice API should return the last set layout",
-                ENGLISH_US_LAYOUT_DESCRIPTOR,
+            result =
                 keyboardLayoutManager.getKeyboardLayoutForInputDevice(
                     keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
                 )
+            assertEquals(
+                "New UI: getKeyboardLayoutForInputDevice API should return the last set layout",
+                ENGLISH_US_LAYOUT_DESCRIPTOR,
+                result.layoutDescriptor
             )
         }
     }
@@ -734,17 +739,20 @@
                 createImeSubtypeForLanguageTag("ru"),
                 createLayoutDescriptor("keyboard_layout_russian")
             )
-            assertNull(
-                "New UI: getDefaultKeyboardLayoutForInputDevice should return null when no " +
-                        "layout available",
+            assertEquals(
+                "New UI: getDefaultKeyboardLayoutForInputDevice should return " +
+                        "KeyboardLayoutSelectionResult.FAILED when no layout available",
+                KeyboardLayoutSelectionResult.FAILED,
                 keyboardLayoutManager.getKeyboardLayoutForInputDevice(
                     keyboardDevice.identifier, USER_ID, imeInfo,
                     createImeSubtypeForLanguageTag("it")
                 )
             )
-            assertNull(
-                "New UI: getDefaultKeyboardLayoutForInputDevice should return null when no " +
-                        "layout for script code is available",
+            assertEquals(
+                "New UI: getDefaultKeyboardLayoutForInputDevice should return " +
+                        "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" +
+                        "available",
+                KeyboardLayoutSelectionResult.FAILED,
                 keyboardLayoutManager.getKeyboardLayoutForInputDevice(
                     keyboardDevice.identifier, USER_ID, imeInfo,
                     createImeSubtypeForLanguageTag("en-Deva")
@@ -811,8 +819,10 @@
                 createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
                 createLayoutDescriptor("keyboard_layout_russian")
             )
-            assertNull("New UI: getDefaultKeyboardLayoutForInputDevice should return null when " +
-                    "no layout for script code is available",
+            assertEquals("New UI: getDefaultKeyboardLayoutForInputDevice should return " +
+                    "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" +
+                    "available",
+                KeyboardLayoutSelectionResult.FAILED,
                 keyboardLayoutManager.getKeyboardLayoutForInputDevice(
                     keyboardDevice.identifier, USER_ID, imeInfo,
                     createImeSubtypeForLanguageTagAndLayoutType("en-Deva-US", "")
@@ -865,14 +875,16 @@
                         ArgumentMatchers.anyBoolean(),
                         ArgumentMatchers.eq(keyboardDevice.vendorId),
                         ArgumentMatchers.eq(keyboardDevice.productId),
-                        ArgumentMatchers.eq(createByteArray(
+                        ArgumentMatchers.eq(
+                            createByteArray(
                                 KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
                                 LAYOUT_TYPE_DEFAULT,
                                 GERMAN_LAYOUT_NAME,
-                                KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+                                KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
                                 "de-Latn",
-                                LAYOUT_TYPE_QWERTZ),
+                                LAYOUT_TYPE_QWERTZ
                             ),
+                        ),
                         ArgumentMatchers.eq(keyboardDevice.deviceBus),
                 )
             }
@@ -893,13 +905,16 @@
                         ArgumentMatchers.anyBoolean(),
                         ArgumentMatchers.eq(englishQwertyKeyboardDevice.vendorId),
                         ArgumentMatchers.eq(englishQwertyKeyboardDevice.productId),
-                        ArgumentMatchers.eq(createByteArray(
+                        ArgumentMatchers.eq(
+                            createByteArray(
                                 "en",
                                 LAYOUT_TYPE_QWERTY,
                                 ENGLISH_US_LAYOUT_NAME,
-                                KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+                                KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE,
                                 "de-Latn",
-                                LAYOUT_TYPE_QWERTZ)),
+                                LAYOUT_TYPE_QWERTZ
+                            )
+                        ),
                         ArgumentMatchers.eq(keyboardDevice.deviceBus),
                 )
             }
@@ -918,14 +933,16 @@
                         ArgumentMatchers.anyBoolean(),
                         ArgumentMatchers.eq(keyboardDevice.vendorId),
                         ArgumentMatchers.eq(keyboardDevice.productId),
-                        ArgumentMatchers.eq(createByteArray(
+                        ArgumentMatchers.eq(
+                            createByteArray(
                                 KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
                                 LAYOUT_TYPE_DEFAULT,
                                 "Default",
-                                KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEFAULT,
+                                KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT,
                                 KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
-                                LAYOUT_TYPE_DEFAULT),
+                                LAYOUT_TYPE_DEFAULT
                             ),
+                        ),
                         ArgumentMatchers.eq(keyboardDevice.deviceBus),
                 )
             }
@@ -998,12 +1015,13 @@
         imeSubtype: InputMethodSubtype,
         expectedLayout: String
     ) {
+        val result = keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+            device.identifier, USER_ID, imeInfo, imeSubtype
+        )
         assertEquals(
             "New UI: getDefaultKeyboardLayoutForInputDevice should return $expectedLayout",
             expectedLayout,
-            keyboardLayoutManager.getKeyboardLayoutForInputDevice(
-                device.identifier, USER_ID, imeInfo, imeSubtype
-            )
+            result.layoutDescriptor
         )
     }
 
diff --git a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
index 89a47b9..0615941 100644
--- a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
@@ -17,6 +17,7 @@
 package com.android.server.input
 
 import android.hardware.input.KeyboardLayout
+import android.hardware.input.KeyboardLayoutSelectionResult
 import android.icu.util.ULocale
 import android.platform.test.annotations.Presubmit
 import android.view.InputDevice
@@ -120,15 +121,15 @@
         val event = builder.addLayoutSelection(
             createImeSubtype(1, ULocale.forLanguageTag("en-US"), "qwerty"),
             "English(US)(Qwerty)",
-            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
+            KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
         ).addLayoutSelection(
             createImeSubtype(2, ULocale.forLanguageTag("en-US"), "azerty"),
             null, // Default layout type
-            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER
+            KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER
         ).addLayoutSelection(
             createImeSubtype(3, ULocale.forLanguageTag("en-US"), "qwerty"),
             "German",
-            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
+            KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE
         ).setIsFirstTimeConfiguration(true).build()
 
         assertEquals(
@@ -158,7 +159,7 @@
             "de-CH",
             KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
             "English(US)(Qwerty)",
-            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+            KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
             "en-US",
             KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
         )
@@ -167,7 +168,7 @@
             "de-CH",
             KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
             KeyboardMetricsCollector.DEFAULT_LAYOUT_NAME,
-            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER,
+            KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER,
             "en-US",
             KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"),
         )
@@ -176,7 +177,7 @@
             "de-CH",
             KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
             "German",
-            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+            KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE,
             "en-US",
             KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
         )
@@ -197,7 +198,7 @@
         val event = builder.addLayoutSelection(
             createImeSubtype(4, null, "qwerty"), // Default language tag
             "German",
-            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
+            KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE
         ).build()
 
         assertExpectedLayoutConfiguration(
@@ -205,7 +206,7 @@
             KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
             KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"),
             "German",
-            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+            KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE,
             KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
             KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
         )
diff --git a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
index a64996c..d9a4c26 100644
--- a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
@@ -59,6 +59,7 @@
 import java.io.InputStream;
 import java.io.PrintWriter;
 import java.util.LinkedList;
+import java.util.TreeMap;
 
 /**
  * Test class for {@link ProtoLogImpl}.
@@ -89,7 +90,7 @@
         //noinspection ResultOfMethodCallIgnored
         mFile.delete();
         mProtoLog = new LegacyProtoLogImpl(mFile, mViewerConfigFilename,
-                1024 * 1024, mReader, 1024);
+                1024 * 1024, mReader, 1024, new TreeMap<>());
     }
 
     @After
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java
index b9f1738..a963890 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java
@@ -37,7 +37,7 @@
 public class PerfettoDataSourceTest {
     @Before
     public void before() {
-        assumeTrue(android.tracing.Flags.perfettoProtolog());
+        assumeTrue(android.tracing.Flags.perfettoProtologTracing());
     }
 
     @Test
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index 270f595..548adef 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -64,6 +64,7 @@
 import java.io.IOException;
 import java.util.List;
 import java.util.Random;
+import java.util.TreeMap;
 
 import perfetto.protos.Protolog;
 import perfetto.protos.ProtologCommon;
@@ -152,7 +153,8 @@
                 .thenAnswer(it -> new ProtoInputStream(mViewerConfigBuilder.build().toByteArray()));
 
         mReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
-        mProtoLog = new PerfettoProtoLogImpl(viewerConfigInputStreamProvider, mReader);
+        mProtoLog =
+                new PerfettoProtoLogImpl(viewerConfigInputStreamProvider, mReader, new TreeMap<>());
     }
 
     @After
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 755636a..75284c7 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -43,13 +43,13 @@
 import android.os.test.TestLooper;
 import android.provider.DeviceConfig;
 import android.util.AtomicFile;
-import android.util.LongArrayQueue;
 import android.util.Xml;
+import android.utils.LongArrayQueue;
+import android.utils.XmlUtils;
 
 import androidx.test.InstrumentationRegistry;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.PackageWatchdog.HealthCheckState;
diff --git a/tests/SurfaceComposition/Android.bp b/tests/SurfaceComposition/Android.bp
index f5aba8f5..a02662f 100644
--- a/tests/SurfaceComposition/Android.bp
+++ b/tests/SurfaceComposition/Android.bp
@@ -32,7 +32,10 @@
         enabled: false,
     },
     srcs: ["src/**/*.java"],
-    static_libs: ["junit"],
+    static_libs: [
+        "junit",
+        "androidx.test.rules",
+    ],
     libs: [
         "android.test.runner.stubs",
         "android.test.base.stubs",
diff --git a/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionTest.java b/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionTest.java
index 261ea2e..f82585c 100644
--- a/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionTest.java
+++ b/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionTest.java
@@ -22,9 +22,10 @@
 import android.surfacecomposition.SurfaceCompositionMeasuringActivity.AllocationScore;
 import android.surfacecomposition.SurfaceCompositionMeasuringActivity.CompositorScore;
 import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
+import androidx.test.filters.SmallTest;
+
 public class SurfaceCompositionTest extends
         ActivityInstrumentationTestCase2<SurfaceCompositionMeasuringActivity> {
     private final static String TAG = "SurfaceCompositionTest";
diff --git a/tests/graphics/HwAccelerationTest/jni/native-lib.cpp b/tests/graphics/HwAccelerationTest/jni/native-lib.cpp
index 407d4bf..2977c21 100644
--- a/tests/graphics/HwAccelerationTest/jni/native-lib.cpp
+++ b/tests/graphics/HwAccelerationTest/jni/native-lib.cpp
@@ -30,7 +30,7 @@
 
     void setBuffer(AHardwareBuffer* buffer) {
         ASurfaceTransaction* transaction = ASurfaceTransaction_create();
-        ASurfaceTransaction_setBuffer(transaction, surfaceControl, buffer);
+        ASurfaceTransaction_setBuffer(transaction, surfaceControl, buffer, -1);
         ASurfaceTransaction_setVisibility(transaction, surfaceControl,
                                           ASURFACE_TRANSACTION_VISIBILITY_SHOW);
         ASurfaceTransaction_apply(transaction);
diff --git a/tests/permission/Android.bp b/tests/permission/Android.bp
index d06809b..b02f410 100644
--- a/tests/permission/Android.bp
+++ b/tests/permission/Android.bp
@@ -20,6 +20,7 @@
         "androidx.test.runner",
         "junit",
         "platform-test-annotations",
+        "androidx.test.rules",
     ],
     platform_apis: true,
     test_suites: ["device-tests"],
diff --git a/tests/permission/src/com/android/framework/permission/tests/ActivityManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/ActivityManagerPermissionTests.java
index 5fb23b0a..99ca9c08 100644
--- a/tests/permission/src/com/android/framework/permission/tests/ActivityManagerPermissionTests.java
+++ b/tests/permission/src/com/android/framework/permission/tests/ActivityManagerPermissionTests.java
@@ -21,7 +21,8 @@
 import android.app.IActivityManager;
 import android.content.res.Configuration;
 import android.os.RemoteException;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import junit.framework.TestCase;
 
diff --git a/tests/permission/src/com/android/framework/permission/tests/PmPermissionsTests.java b/tests/permission/src/com/android/framework/permission/tests/PmPermissionsTests.java
index 299d8d0..0c9d447 100644
--- a/tests/permission/src/com/android/framework/permission/tests/PmPermissionsTests.java
+++ b/tests/permission/src/com/android/framework/permission/tests/PmPermissionsTests.java
@@ -19,7 +19,8 @@
 import android.app.PackageInstallObserver;
 import android.content.pm.PackageManager;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 /**
  * Verify PackageManager api's that require specific permissions.
diff --git a/tests/permission/src/com/android/framework/permission/tests/SmsManagerPermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/SmsManagerPermissionTest.java
index 4172743..2fa0b21 100644
--- a/tests/permission/src/com/android/framework/permission/tests/SmsManagerPermissionTest.java
+++ b/tests/permission/src/com/android/framework/permission/tests/SmsManagerPermissionTest.java
@@ -18,7 +18,8 @@
 
 import android.telephony.SmsManager;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import java.util.ArrayList;
 
diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
index 330bc84..e67fd67 100644
--- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
+++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
@@ -23,9 +23,10 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.view.IWindowManager;
 
+import androidx.test.filters.SmallTest;
+
 import junit.framework.TestCase;
 
 /**
diff --git a/tests/testables/tests/src/android/testing/TestableLooperTest.java b/tests/testables/tests/src/android/testing/TestableLooperTest.java
index a02eb6b..fd5c4ca 100644
--- a/tests/testables/tests/src/android/testing/TestableLooperTest.java
+++ b/tests/testables/tests/src/android/testing/TestableLooperTest.java
@@ -30,10 +30,11 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.TestableLooper.MessageHandler;
 import android.testing.TestableLooper.RunWithLooper;
 
+import androidx.test.filters.SmallTest;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/tests/testables/tests/src/android/testing/TestableResourcesTest.java b/tests/testables/tests/src/android/testing/TestableResourcesTest.java
index dd4325c..7791694 100644
--- a/tests/testables/tests/src/android/testing/TestableResourcesTest.java
+++ b/tests/testables/tests/src/android/testing/TestableResourcesTest.java
@@ -21,9 +21,9 @@
 import static org.junit.Assert.fail;
 
 import android.content.res.Resources;
-import android.test.suitebuilder.annotation.SmallTest;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
 
 import com.android.testables.R;
 
diff --git a/tests/testables/tests/src/android/testing/TestableSettingsProviderTest.java b/tests/testables/tests/src/android/testing/TestableSettingsProviderTest.java
index 0333d51..f3f429a 100644
--- a/tests/testables/tests/src/android/testing/TestableSettingsProviderTest.java
+++ b/tests/testables/tests/src/android/testing/TestableSettingsProviderTest.java
@@ -20,9 +20,9 @@
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
-import android.test.suitebuilder.annotation.SmallTest;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/tests/utils/testutils/tests/Android.bp b/tests/utils/testutils/tests/Android.bp
index b901b18..8104280 100644
--- a/tests/utils/testutils/tests/Android.bp
+++ b/tests/utils/testutils/tests/Android.bp
@@ -31,6 +31,7 @@
         "androidx.test.runner",
         "mockito-target-minus-junit4",
         "frameworks-base-testutils",
+        "androidx.test.rules",
     ],
 
     libs: [
diff --git a/tests/utils/testutils/tests/src/android/os/test/TestLooperTest.java b/tests/utils/testutils/tests/src/android/os/test/TestLooperTest.java
index c72e20c..6205b98 100644
--- a/tests/utils/testutils/tests/src/android/os/test/TestLooperTest.java
+++ b/tests/utils/testutils/tests/src/android/os/test/TestLooperTest.java
@@ -28,7 +28,8 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
 import org.junit.Rule;
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index b054a57..b4e2758 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -15,12 +15,7 @@
 //
 
 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"],
+    default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 toolSources = [
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
deleted file mode 100644
index 15ae2ba..0000000
--- a/tools/aapt2/Android.mk
+++ /dev/null
@@ -1,4 +0,0 @@
-include $(CLEAR_VARS)
-aapt2_results := ./out/soong/.intermediates/frameworks/base/tools/aapt2/aapt2_results
-$(call declare-1p-target,$(aapt2_results))
-aapt2_results :=
diff --git a/tools/hoststubgen/TEST_MAPPING b/tools/hoststubgen/TEST_MAPPING
index eca258c..b5d5b5f 100644
--- a/tools/hoststubgen/TEST_MAPPING
+++ b/tools/hoststubgen/TEST_MAPPING
@@ -1,6 +1,7 @@
 {
   "presubmit": [
-    { "name": "tiny-framework-dump-test" },
+    // TODO(b/326897452): Reenable after JDK 21 switch.
+    // { "name": "tiny-framework-dump-test" },
     { "name": "hoststubgentest" },
     { "name": "hoststubgen-invoke-test" }
   ],
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/SubclassFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/SubclassFilter.kt
index 83e09bf..fd7474b 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/SubclassFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/SubclassFilter.kt
@@ -20,7 +20,7 @@
 
 /**
  * Filter to apply a policy to classes extending or implementing a class,
- * either directly or indirectly. (with a breadth first search.)
+ * either directly or indirectly.
  *
  * The policy won't apply to the super class itself.
  */
@@ -42,7 +42,7 @@
     }
 
     /**
-     * Find a policy for a class with a breadth-first search.
+     * Find a policy for a class.
      */
     private fun findPolicyForClass(className: String): FilterPolicyWithReason? {
         val cn = classes.findClass(className) ?: return null
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index 78f277e..f70a17d 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -1051,6 +1051,9 @@
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
             0      10     0  name   Ljava/lang/String;
+    MethodParameters:
+      Name                           Flags
+      <no name>                      mandated
 
   private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex(java.lang.String, java.lang.String);
     descriptor: (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
@@ -1074,6 +1077,12 @@
             0      18     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
             0      18     3 longName   Ljava/lang/String;
             0      18     4 shortName   Ljava/lang/String;
+    MethodParameters:
+      Name                           Flags
+      <no name>                      synthetic
+      <no name>                      synthetic
+      <no name>
+      <no name>
     Signature: #x                          // (Ljava/lang/String;Ljava/lang/String;)V
     RuntimeInvisibleAnnotations:
       x: #x()
@@ -1224,6 +1233,9 @@
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
             0      10     0  name   Ljava/lang/String;
+    MethodParameters:
+      Name                           Flags
+      <no name>                      mandated
 
   private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple();
     descriptor: (Ljava/lang/String;I)V
@@ -1239,6 +1251,10 @@
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
             0       7     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    MethodParameters:
+      Name                           Flags
+      <no name>                      synthetic
+      <no name>                      synthetic
     Signature: #x                          // ()V
 
   private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $values();
@@ -2031,6 +2047,9 @@
         Start  Length  Slot  Name   Signature
             0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
             0      10     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    MethodParameters:
+      Name                           Flags
+      <no name>                      final mandated
 
   public java.lang.Integer get();
     descriptor: ()Ljava/lang/Integer;
@@ -2147,6 +2166,9 @@
         Start  Length  Slot  Name   Signature
             0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
             0      10     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    MethodParameters:
+      Name                           Flags
+      <no name>                      final mandated
 
   public java.lang.Integer get();
     descriptor: ()Ljava/lang/Integer;
@@ -2304,6 +2326,9 @@
         Start  Length  Slot  Name   Signature
             0      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass;
             0      15     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    MethodParameters:
+      Name                           Flags
+      <no name>                      final mandated
 }
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeInvisibleAnnotations:
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 406cb74..37de857 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
@@ -795,6 +795,9 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    MethodParameters:
+      Name                           Flags
+      <no name>                      mandated
 
   private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex(java.lang.String, java.lang.String);
     descriptor: (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
@@ -815,6 +818,12 @@
     RuntimeInvisibleAnnotations:
       x: #x()
         android.hosttest.annotation.HostSideTestStub
+    MethodParameters:
+      Name                           Flags
+      <no name>                      synthetic
+      <no name>                      synthetic
+      <no name>
+      <no name>
 
   public java.lang.String getLongName();
     descriptor: ()Ljava/lang/String;
@@ -969,6 +978,9 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    MethodParameters:
+      Name                           Flags
+      <no name>                      mandated
 
   private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple();
     descriptor: (Ljava/lang/String;I)V
@@ -986,6 +998,10 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    MethodParameters:
+      Name                           Flags
+      <no name>                      synthetic
+      <no name>                      synthetic
 
   private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $values();
     descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
@@ -1769,6 +1785,9 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    MethodParameters:
+      Name                           Flags
+      <no name>                      final mandated
 }
 InnerClasses:
   public #x= #x of #x;                   // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
index c673262..c9c607c 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
@@ -1225,6 +1225,9 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    MethodParameters:
+      Name                           Flags
+      <no name>                      mandated
 
   private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex(java.lang.String, java.lang.String);
     descriptor: (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
@@ -1257,6 +1260,12 @@
     RuntimeInvisibleAnnotations:
       x: #x()
         android.hosttest.annotation.HostSideTestStub
+    MethodParameters:
+      Name                           Flags
+      <no name>                      synthetic
+      <no name>                      synthetic
+      <no name>
+      <no name>
 
   public java.lang.String getLongName();
     descriptor: ()Ljava/lang/String;
@@ -1453,6 +1462,9 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    MethodParameters:
+      Name                           Flags
+      <no name>                      mandated
 
   private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple();
     descriptor: (Ljava/lang/String;I)V
@@ -1474,6 +1486,10 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    MethodParameters:
+      Name                           Flags
+      <no name>                      synthetic
+      <no name>                      synthetic
 
   private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $values();
     descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
@@ -2578,6 +2594,9 @@
     RuntimeVisibleAnnotations:
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    MethodParameters:
+      Name                           Flags
+      <no name>                      final mandated
 
   public java.lang.Integer get();
     descriptor: ()Ljava/lang/Integer;
@@ -2745,6 +2764,9 @@
     RuntimeVisibleAnnotations:
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    MethodParameters:
+      Name                           Flags
+      <no name>                      final mandated
 
   public java.lang.Integer get();
     descriptor: ()Ljava/lang/Integer;
@@ -2977,6 +2999,9 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    MethodParameters:
+      Name                           Flags
+      <no name>                      final mandated
 }
 InnerClasses:
   public #x= #x of #x;                   // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
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 406cb74..37de857 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
@@ -795,6 +795,9 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    MethodParameters:
+      Name                           Flags
+      <no name>                      mandated
 
   private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex(java.lang.String, java.lang.String);
     descriptor: (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
@@ -815,6 +818,12 @@
     RuntimeInvisibleAnnotations:
       x: #x()
         android.hosttest.annotation.HostSideTestStub
+    MethodParameters:
+      Name                           Flags
+      <no name>                      synthetic
+      <no name>                      synthetic
+      <no name>
+      <no name>
 
   public java.lang.String getLongName();
     descriptor: ()Ljava/lang/String;
@@ -969,6 +978,9 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    MethodParameters:
+      Name                           Flags
+      <no name>                      mandated
 
   private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple();
     descriptor: (Ljava/lang/String;I)V
@@ -986,6 +998,10 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    MethodParameters:
+      Name                           Flags
+      <no name>                      synthetic
+      <no name>                      synthetic
 
   private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $values();
     descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
@@ -1769,6 +1785,9 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    MethodParameters:
+      Name                           Flags
+      <no name>                      final mandated
 }
 InnerClasses:
   public #x= #x of #x;                   // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
index 4fd5701..a57907d 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
@@ -1532,6 +1532,9 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    MethodParameters:
+      Name                           Flags
+      <no name>                      mandated
 
   private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex(java.lang.String, java.lang.String);
     descriptor: (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
@@ -1569,6 +1572,12 @@
     RuntimeInvisibleAnnotations:
       x: #x()
         android.hosttest.annotation.HostSideTestStub
+    MethodParameters:
+      Name                           Flags
+      <no name>                      synthetic
+      <no name>                      synthetic
+      <no name>
+      <no name>
 
   public java.lang.String getLongName();
     descriptor: ()Ljava/lang/String;
@@ -1798,6 +1807,9 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    MethodParameters:
+      Name                           Flags
+      <no name>                      mandated
 
   private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple();
     descriptor: (Ljava/lang/String;I)V
@@ -1824,6 +1836,10 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    MethodParameters:
+      Name                           Flags
+      <no name>                      synthetic
+      <no name>                      synthetic
 
   private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $values();
     descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
@@ -3190,6 +3206,9 @@
     RuntimeVisibleAnnotations:
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    MethodParameters:
+      Name                           Flags
+      <no name>                      final mandated
 
   public java.lang.Integer get();
     descriptor: ()Ljava/lang/Integer;
@@ -3407,6 +3426,9 @@
     RuntimeVisibleAnnotations:
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    MethodParameters:
+      Name                           Flags
+      <no name>                      final mandated
 
   public java.lang.Integer get();
     descriptor: ()Ljava/lang/Integer;
@@ -3704,6 +3726,9 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    MethodParameters:
+      Name                           Flags
+      <no name>                      final mandated
 }
 InnerClasses:
   public #x= #x of #x;                  // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
index cee29dc..1dec6ab 100755
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
@@ -46,6 +46,7 @@
 class TestWithGoldenOutput(unittest.TestCase):
 
     # Test to check the generated jar files to the golden output.
+    @unittest.skip("Disabled until JDK 21 is merged and the golden files updated")
     def test_compare_to_golden(self):
         files = os.listdir(GOLDEN_DIR)
         files.sort()
diff --git a/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt b/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
index a359155..2690bc5 100644
--- a/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
@@ -25,6 +25,7 @@
         const val READ_LOG_CMD = "read-log"
         private val commands = setOf(TRANSFORM_CALLS_CMD, GENERATE_CONFIG_CMD, READ_LOG_CMD)
 
+        // TODO: This is always the same. I don't think it's required
         private const val PROTOLOG_CLASS_PARAM = "--protolog-class"
         private const val PROTOLOGGROUP_CLASS_PARAM = "--loggroups-class"
         private const val PROTOLOGGROUP_JAR_PARAM = "--loggroups-jar"
diff --git a/tools/protologtool/src/com/android/protolog/tool/Constants.kt b/tools/protologtool/src/com/android/protolog/tool/Constants.kt
index aa3e00f..4a93de9 100644
--- a/tools/protologtool/src/com/android/protolog/tool/Constants.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/Constants.kt
@@ -18,7 +18,7 @@
 
 object Constants {
         const val NAME = "protologtool"
-        const val VERSION = "1.0.0"
+        const val VERSION = "2.0.0"
         const val IS_ENABLED_METHOD = "isEnabled"
         const val ENUM_VALUES_METHOD = "values"
 }
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
index 1381847..837dae9 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -24,11 +24,17 @@
 import com.github.javaparser.ParserConfiguration
 import com.github.javaparser.StaticJavaParser
 import com.github.javaparser.ast.CompilationUnit
+import com.github.javaparser.ast.NodeList
 import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
+import com.github.javaparser.ast.body.InitializerDeclaration
+import com.github.javaparser.ast.expr.FieldAccessExpr
 import com.github.javaparser.ast.expr.MethodCallExpr
+import com.github.javaparser.ast.expr.NameExpr
 import com.github.javaparser.ast.expr.NullLiteralExpr
+import com.github.javaparser.ast.expr.ObjectCreationExpr
 import com.github.javaparser.ast.expr.SimpleName
 import com.github.javaparser.ast.expr.StringLiteralExpr
+import com.github.javaparser.ast.stmt.BlockStmt
 import java.io.File
 import java.io.FileInputStream
 import java.io.FileNotFoundException
@@ -39,8 +45,7 @@
 import java.util.concurrent.Executors
 import java.util.jar.JarOutputStream
 import java.util.zip.ZipEntry
-import kotlin.math.abs
-import kotlin.random.Random
+import kotlin.math.absoluteValue
 import kotlin.system.exitProcess
 
 object ProtoLogTool {
@@ -72,7 +77,11 @@
     }
 
     private fun processClasses(command: CommandOptions) {
-        val generationHash = abs(Random.nextInt())
+        // A deterministic hash based on the group jar path and the source files we are processing.
+        // The hash is required to make sure different ProtoLogImpls don't conflict.
+        val generationHash = (command.javaSourceArgs.toTypedArray() + command.protoLogGroupsJarArg)
+                .contentHashCode().absoluteValue
+
         // Need to generate a new impl class to inject static constants into the class.
         val generatedProtoLogImplClass =
             "com.android.internal.protolog.ProtoLogImpl_$generationHash"
@@ -93,7 +102,8 @@
         outJar.putNextEntry(zipEntry(protologImplPath))
 
         outJar.write(generateProtoLogImpl(protologImplName, command.viewerConfigFilePathArg,
-            command.legacyViewerConfigFilePathArg, command.legacyOutputFilePath).toByteArray())
+            command.legacyViewerConfigFilePathArg, command.legacyOutputFilePath,
+            groups, command.protoLogGroupsClassNameArg).toByteArray())
 
         val executor = newThreadPool()
 
@@ -137,6 +147,8 @@
         viewerConfigFilePath: String,
         legacyViewerConfigFilePath: String?,
         legacyOutputFilePath: String?,
+        groups: Map<String, LogGroup>,
+        protoLogGroupsClassName: String,
     ): String {
         val file = File(PROTOLOG_IMPL_SRC_PATH)
 
@@ -157,7 +169,8 @@
         classNameNode.setId(protoLogImplGenName)
 
         injectConstants(classDeclaration,
-            viewerConfigFilePath, legacyViewerConfigFilePath, legacyOutputFilePath)
+            viewerConfigFilePath, legacyViewerConfigFilePath, legacyOutputFilePath, groups,
+            protoLogGroupsClassName)
 
         return code.toString()
     }
@@ -166,7 +179,9 @@
         classDeclaration: ClassOrInterfaceDeclaration,
         viewerConfigFilePath: String,
         legacyViewerConfigFilePath: String?,
-        legacyOutputFilePath: String?
+        legacyOutputFilePath: String?,
+        groups: Map<String, LogGroup>,
+        protoLogGroupsClassName: String
     ) {
         classDeclaration.fields.forEach { field ->
             field.getAnnotationByClass(ProtoLogToolInjected::class.java)
@@ -194,6 +209,35 @@
                                                 StringLiteralExpr(it)
                                             } ?: NullLiteralExpr())
                                 }
+                                ProtoLogToolInjected.Value.LOG_GROUPS.name -> {
+                                    val initializerBlockStmt = BlockStmt()
+                                    for (group in groups) {
+                                        initializerBlockStmt.addStatement(
+                                            MethodCallExpr()
+                                                    .setName("put")
+                                                    .setArguments(
+                                                        NodeList(StringLiteralExpr(group.key),
+                                                            FieldAccessExpr()
+                                                                    .setScope(
+                                                                        NameExpr(
+                                                                            protoLogGroupsClassName
+                                                                        ))
+                                                                    .setName(group.value.name)))
+                                        )
+                                        group.key
+                                    }
+
+                                    val treeMapCreation = ObjectCreationExpr()
+                                            .setType("TreeMap<String, IProtoLogGroup>")
+                                            .setAnonymousClassBody(NodeList(
+                                                InitializerDeclaration().setBody(
+                                                    initializerBlockStmt
+                                                )
+                                            ))
+
+                                    field.setFinal(true)
+                                    field.variables.first().setInitializer(treeMapCreation)
+                                }
                                 else -> error("Unhandled ProtoLogToolInjected value: $valueName.")
                             }
                         }
diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp
index b18bdff..b1b314fc 100644
--- a/tools/streaming_proto/Android.bp
+++ b/tools/streaming_proto/Android.bp
@@ -17,6 +17,7 @@
 // ==========================================================
 // Build the host executable: protoc-gen-javastream
 // ==========================================================
+
 package {
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
@@ -41,6 +42,32 @@
     static_libs: ["libprotoc"],
 }
 
+// ==========================================================
+// Build the host static library: java_streaming_proto_lib
+// ==========================================================
+
+cc_library_host_static {
+    name: "java_streaming_proto_lib",
+    defaults: ["protoc-gen-stream-defaults"],
+    target: {
+        darwin: {
+            cflags: ["-D_DARWIN_UNLIMITED_STREAMS"],
+        },
+    },
+    cflags: [
+        "-Wno-format-y2k",
+        "-DSTATIC_ANDROIDFW_FOR_TOOLS",
+    ],
+
+    srcs: [
+        "java/java_proto_stream_code_generator.cpp",
+    ],
+}
+
+// ==========================================================
+// Build the host executable: protoc-gen-javastream
+// ==========================================================
+
 cc_binary_host {
     name: "protoc-gen-javastream",
     srcs: [
@@ -48,8 +75,13 @@
     ],
 
     defaults: ["protoc-gen-stream-defaults"],
+    static_libs: ["java_streaming_proto_lib"],
 }
 
+// ==========================================================
+// Build the host executable: protoc-gen-cppstream
+// ==========================================================
+
 cc_binary_host {
     name: "protoc-gen-cppstream",
     srcs: [
@@ -60,13 +92,31 @@
 }
 
 // ==========================================================
+// Build the host tests: StreamingProtoTest
+// ==========================================================
+
+cc_test_host {
+    name: "StreamingProtoTest",
+    defaults: ["protoc-gen-stream-defaults"],
+    srcs: [
+        "test/unit/**/*.cpp",
+    ],
+    static_libs: [
+        "java_streaming_proto_lib",
+        "libgmock",
+        "libgtest",
+    ],
+}
+
+// ==========================================================
 // Build the java test
 // ==========================================================
+
 java_library {
-    name: "StreamingProtoTest",
+    name: "StreamingProtoJavaIntegrationTest",
     srcs: [
-        "test/**/*.java",
-        "test/**/*.proto",
+        "test/integration/**/*.java",
+        "test/integration/**/*.proto",
     ],
     proto: {
         type: "stream",
diff --git a/tools/streaming_proto/java/java_proto_stream_code_generator.cpp b/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
new file mode 100644
index 0000000..9d61111
--- /dev/null
+++ b/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "java_proto_stream_code_generator.h"
+
+#include <stdio.h>
+
+#include <iomanip>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include "Errors.h"
+
+using namespace android::stream_proto;
+using namespace google::protobuf::io;
+using namespace std;
+
+/**
+ * If the descriptor gives us a class name, use that. Otherwise make one up from
+ * the filename of the .proto file.
+ */
+static string make_outer_class_name(const FileDescriptorProto& file_descriptor) {
+    string name = file_descriptor.options().java_outer_classname();
+    if (name.size() == 0) {
+        name = to_camel_case(file_base_name(file_descriptor.name()));
+        if (name.size() == 0) {
+            ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE,
+                       "Unable to make an outer class name for file: %s",
+                       file_descriptor.name().c_str());
+            name = "Unknown";
+        }
+    }
+    return name;
+}
+
+/**
+ * Figure out the package name that we are generating.
+ */
+static string make_java_package(const FileDescriptorProto& file_descriptor) {
+    if (file_descriptor.options().has_java_package()) {
+        return file_descriptor.options().java_package();
+    } else {
+        return file_descriptor.package();
+    }
+}
+
+/**
+ * Figure out the name of the file we are generating.
+ */
+static string make_file_name(const FileDescriptorProto& file_descriptor, const string& class_name) {
+    string const package = make_java_package(file_descriptor);
+    string result;
+    if (package.size() > 0) {
+        result = replace_string(package, '.', '/');
+        result += '/';
+    }
+
+    result += class_name;
+    result += ".java";
+
+    return result;
+}
+
+static string indent_more(const string& indent) {
+    return indent + INDENT;
+}
+
+/**
+ * Write the constants for an enum.
+ */
+static void write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent) {
+    const int N = enu.value_size();
+    text << indent << "// enum " << enu.name() << endl;
+    for (int i = 0; i < N; i++) {
+        const EnumValueDescriptorProto& value = enu.value(i);
+        text << indent << "public static final int " << make_constant_name(value.name()) << " = "
+             << value.number() << ";" << endl;
+    }
+    text << endl;
+}
+
+/**
+ * Write a field.
+ */
+static void write_field(stringstream& text, const FieldDescriptorProto& field,
+                        const string& indent) {
+    string optional_comment =
+            field.label() == FieldDescriptorProto::LABEL_OPTIONAL ? "optional " : "";
+    string repeated_comment =
+            field.label() == FieldDescriptorProto::LABEL_REPEATED ? "repeated " : "";
+    string proto_type = get_proto_type(field);
+    string packed_comment = field.options().packed() ? " [packed=true]" : "";
+    text << indent << "// " << optional_comment << repeated_comment << proto_type << ' '
+         << field.name() << " = " << field.number() << packed_comment << ';' << endl;
+
+    text << indent << "public static final long " << make_constant_name(field.name()) << " = 0x";
+
+    ios::fmtflags fmt(text.flags());
+    text << setfill('0') << setw(16) << hex << get_field_id(field);
+    text.flags(fmt);
+
+    text << "L;" << endl;
+
+    text << endl;
+}
+
+/**
+ * Write a Message constants class.
+ */
+static void write_message(stringstream& text, const DescriptorProto& message,
+                          const string& indent) {
+    int N;
+    const string indented = indent_more(indent);
+
+    text << indent << "// message " << message.name() << endl;
+    text << indent << "public final class " << message.name() << " {" << endl;
+    text << endl;
+
+    // Enums
+    N = message.enum_type_size();
+    for (int i = 0; i < N; i++) {
+        write_enum(text, message.enum_type(i), indented);
+    }
+
+    // Nested classes
+    N = message.nested_type_size();
+    for (int i = 0; i < N; i++) {
+        write_message(text, message.nested_type(i), indented);
+    }
+
+    // Fields
+    N = message.field_size();
+    for (int i = 0; i < N; i++) {
+        write_field(text, message.field(i), indented);
+    }
+
+    text << indent << "}" << endl;
+    text << endl;
+}
+
+/**
+ * Write the contents of a file.
+ *
+ * If there are enums and generate_outer is false, invalid java code will be generated.
+ */
+static void write_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor,
+                       const string& filename, bool generate_outer,
+                       const vector<EnumDescriptorProto>& enums,
+                       const vector<DescriptorProto>& messages) {
+    stringstream text;
+
+    string const package_name = make_java_package(file_descriptor);
+    string const outer_class_name = make_outer_class_name(file_descriptor);
+
+    text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
+    text << "// source: " << file_descriptor.name() << endl << endl;
+
+    if (package_name.size() > 0) {
+        if (package_name.size() > 0) {
+            text << "package " << package_name << ";" << endl;
+            text << endl;
+        }
+    }
+
+    // This bit of policy is android api rules specific: Raw proto classes
+    // must never be in the API
+    text << "/** @hide */" << endl;
+    //    text << "@android.annotation.TestApi" << endl;
+
+    if (generate_outer) {
+        text << "public final class " << outer_class_name << " {" << endl;
+        text << endl;
+    }
+
+    size_t N;
+    const string indented = generate_outer ? indent_more("") : string();
+
+    N = enums.size();
+    for (size_t i = 0; i < N; i++) {
+        write_enum(text, enums[i], indented);
+    }
+
+    N = messages.size();
+    for (size_t i = 0; i < N; i++) {
+        write_message(text, messages[i], indented);
+    }
+
+    if (generate_outer) {
+        text << "}" << endl;
+    }
+
+    CodeGeneratorResponse::File* file_response = response->add_file();
+    file_response->set_name(filename);
+    file_response->set_content(text.str());
+}
+
+/**
+ * Write one file per class.  Put all of the enums into the "outer" class.
+ */
+static void write_multiple_files(CodeGeneratorResponse* response,
+                                 const FileDescriptorProto& file_descriptor,
+                                 set<string> messages_to_compile) {
+    // If there is anything to put in the outer class file, create one
+    if (file_descriptor.enum_type_size() > 0) {
+        vector<EnumDescriptorProto> enums;
+        int N = file_descriptor.enum_type_size();
+        for (int i = 0; i < N; i++) {
+            auto enum_full_name =
+                    file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
+            if (!messages_to_compile.empty() && !messages_to_compile.count(enum_full_name)) {
+                continue;
+            }
+            enums.push_back(file_descriptor.enum_type(i));
+        }
+
+        vector<DescriptorProto> messages;
+
+        if (messages_to_compile.empty() || !enums.empty()) {
+            write_file(response, file_descriptor,
+                       make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
+                       true, enums, messages);
+        }
+    }
+
+    // For each of the message types, make a file
+    int N = file_descriptor.message_type_size();
+    for (int i = 0; i < N; i++) {
+        vector<EnumDescriptorProto> enums;
+
+        vector<DescriptorProto> messages;
+
+        auto message_full_name =
+                file_descriptor.package() + "." + file_descriptor.message_type(i).name();
+        if (!messages_to_compile.empty() && !messages_to_compile.count(message_full_name)) {
+            continue;
+        }
+        messages.push_back(file_descriptor.message_type(i));
+
+        if (messages_to_compile.empty() || !messages.empty()) {
+            write_file(response, file_descriptor,
+                       make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
+                       false, enums, messages);
+        }
+    }
+}
+
+static void write_single_file(CodeGeneratorResponse* response,
+                              const FileDescriptorProto& file_descriptor,
+                              set<string> messages_to_compile) {
+    int N;
+
+    vector<EnumDescriptorProto> enums;
+    N = file_descriptor.enum_type_size();
+    for (int i = 0; i < N; i++) {
+        auto enum_full_name = file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
+        if (!messages_to_compile.empty() && !messages_to_compile.count(enum_full_name)) {
+            continue;
+        }
+
+        enums.push_back(file_descriptor.enum_type(i));
+    }
+
+    vector<DescriptorProto> messages;
+    N = file_descriptor.message_type_size();
+    for (int i = 0; i < N; i++) {
+        auto message_full_name =
+                file_descriptor.package() + "." + file_descriptor.message_type(i).name();
+
+        if (!messages_to_compile.empty() && !messages_to_compile.count(message_full_name)) {
+            continue;
+        }
+
+        messages.push_back(file_descriptor.message_type(i));
+    }
+
+    if (messages_to_compile.empty() || !enums.empty() || !messages.empty()) {
+        write_file(response, file_descriptor,
+                   make_file_name(file_descriptor, make_outer_class_name(file_descriptor)), true,
+                   enums, messages);
+    }
+}
+
+static void parse_args_string(stringstream args_string_stream,
+                              set<string>* messages_to_compile_out) {
+    string line;
+    while (getline(args_string_stream, line, ';')) {
+        stringstream line_ss(line);
+        string arg_name;
+        getline(line_ss, arg_name, ':');
+        if (arg_name == "include_filter") {
+            string full_message_name;
+            while (getline(line_ss, full_message_name, ',')) {
+                messages_to_compile_out->insert(full_message_name);
+            }
+        } else {
+            ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE, "Unexpected argument '%s'.", arg_name.c_str());
+        }
+    }
+}
+
+CodeGeneratorResponse generate_java_protostream_code(CodeGeneratorRequest request) {
+    CodeGeneratorResponse response;
+
+    set<string> messages_to_compile;
+    auto request_params = request.parameter();
+    if (!request_params.empty()) {
+        parse_args_string(stringstream(request_params), &messages_to_compile);
+    }
+
+    // Build the files we need.
+    const int N = request.proto_file_size();
+    for (int i = 0; i < N; i++) {
+        const FileDescriptorProto& file_descriptor = request.proto_file(i);
+        if (should_generate_for_file(request, file_descriptor.name())) {
+            if (file_descriptor.options().java_multiple_files()) {
+                write_multiple_files(&response, file_descriptor, messages_to_compile);
+            } else {
+                write_single_file(&response, file_descriptor, messages_to_compile);
+            }
+        }
+    }
+
+    return response;
+}
diff --git a/tools/streaming_proto/java/java_proto_stream_code_generator.h b/tools/streaming_proto/java/java_proto_stream_code_generator.h
new file mode 100644
index 0000000..d2492f7
--- /dev/null
+++ b/tools/streaming_proto/java/java_proto_stream_code_generator.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AOSP_MAIN_FRAMEWORKS_BASE_JAVAPROTOSTREAMCODEGENERATOR_H
+#define AOSP_MAIN_FRAMEWORKS_BASE_JAVAPROTOSTREAMCODEGENERATOR_H
+
+#include "stream_proto_utils.h"
+#include "string_utils.h"
+
+using namespace android::stream_proto;
+using namespace google::protobuf::io;
+using namespace std;
+
+CodeGeneratorResponse generate_java_protostream_code(CodeGeneratorRequest request);
+
+#endif // AOSP_MAIN_FRAMEWORKS_BASE_JAVAPROTOSTREAMCODEGENERATOR_H
\ No newline at end of file
diff --git a/tools/streaming_proto/java/main.cpp b/tools/streaming_proto/java/main.cpp
index c9c50a5..5b35504 100644
--- a/tools/streaming_proto/java/main.cpp
+++ b/tools/streaming_proto/java/main.cpp
@@ -1,268 +1,21 @@
-#include "Errors.h"
-#include "stream_proto_utils.h"
-#include "string_utils.h"
-
 #include <stdio.h>
+
 #include <iomanip>
 #include <iostream>
-#include <sstream>
 #include <map>
+#include <sstream>
+#include <string>
+
+#include "Errors.h"
+#include "java_proto_stream_code_generator.h"
+#include "stream_proto_utils.h"
 
 using namespace android::stream_proto;
 using namespace google::protobuf::io;
 using namespace std;
 
 /**
- * If the descriptor gives us a class name, use that. Otherwise make one up from
- * the filename of the .proto file.
- */
-static string
-make_outer_class_name(const FileDescriptorProto& file_descriptor)
-{
-    string name = file_descriptor.options().java_outer_classname();
-    if (name.size() == 0) {
-        name = to_camel_case(file_base_name(file_descriptor.name()));
-        if (name.size() == 0) {
-            ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE,
-                    "Unable to make an outer class name for file: %s",
-                    file_descriptor.name().c_str());
-            name = "Unknown";
-        }
-    }
-    return name;
-}
-
-/**
- * Figure out the package name that we are generating.
- */
-static string
-make_java_package(const FileDescriptorProto& file_descriptor) {
-    if (file_descriptor.options().has_java_package()) {
-        return file_descriptor.options().java_package();
-    } else {
-        return file_descriptor.package();
-    }
-}
-
-/**
- * Figure out the name of the file we are generating.
- */
-static string
-make_file_name(const FileDescriptorProto& file_descriptor, const string& class_name)
-{
-    string const package = make_java_package(file_descriptor);
-    string result;
-    if (package.size() > 0) {
-        result = replace_string(package, '.', '/');
-        result += '/';
-    }
-
-    result += class_name;
-    result += ".java";
-
-    return result;
-}
-
-static string
-indent_more(const string& indent)
-{
-    return indent + INDENT;
-}
-
-/**
- * Write the constants for an enum.
- */
-static void
-write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent)
-{
-    const int N = enu.value_size();
-    text << indent << "// enum " << enu.name() << endl;
-    for (int i=0; i<N; i++) {
-        const EnumValueDescriptorProto& value = enu.value(i);
-        text << indent << "public static final int "
-                << make_constant_name(value.name())
-                << " = " << value.number() << ";" << endl;
-    }
-    text << endl;
-}
-
-/**
- * Write a field.
- */
-static void
-write_field(stringstream& text, const FieldDescriptorProto& field, const string& indent)
-{
-    string optional_comment = field.label() == FieldDescriptorProto::LABEL_OPTIONAL
-            ? "optional " : "";
-    string repeated_comment = field.label() == FieldDescriptorProto::LABEL_REPEATED
-            ? "repeated " : "";
-    string proto_type = get_proto_type(field);
-    string packed_comment = field.options().packed()
-            ? " [packed=true]" : "";
-    text << indent << "// " << optional_comment << repeated_comment << proto_type << ' '
-            << field.name() << " = " << field.number() << packed_comment << ';' << endl;
-
-    text << indent << "public static final long " << make_constant_name(field.name()) << " = 0x";
-
-    ios::fmtflags fmt(text.flags());
-    text << setfill('0') << setw(16) << hex << get_field_id(field);
-    text.flags(fmt);
-
-    text << "L;" << endl;
-
-    text << endl;
-}
-
-/**
- * Write a Message constants class.
- */
-static void
-write_message(stringstream& text, const DescriptorProto& message, const string& indent)
-{
-    int N;
-    const string indented = indent_more(indent);
-
-    text << indent << "// message " << message.name() << endl;
-    text << indent << "public final class " << message.name() << " {" << endl;
-    text << endl;
-
-    // Enums
-    N = message.enum_type_size();
-    for (int i=0; i<N; i++) {
-        write_enum(text, message.enum_type(i), indented);
-    }
-
-    // Nested classes
-    N = message.nested_type_size();
-    for (int i=0; i<N; i++) {
-        write_message(text, message.nested_type(i), indented);
-    }
-
-    // Fields
-    N = message.field_size();
-    for (int i=0; i<N; i++) {
-        write_field(text, message.field(i), indented);
-    }
-
-    text << indent << "}" << endl;
-    text << endl;
-}
-
-/**
- * Write the contents of a file.
  *
- * If there are enums and generate_outer is false, invalid java code will be generated.
- */
-static void
-write_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor,
-        const string& filename, bool generate_outer,
-        const vector<EnumDescriptorProto>& enums, const vector<DescriptorProto>& messages)
-{
-    stringstream text;
-
-    string const package_name = make_java_package(file_descriptor);
-    string const outer_class_name = make_outer_class_name(file_descriptor);
-
-    text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
-    text << "// source: " << file_descriptor.name() << endl << endl;
-
-    if (package_name.size() > 0) {
-        if (package_name.size() > 0) {
-            text << "package " << package_name << ";" << endl;
-            text << endl;
-        }
-    }
-
-    // This bit of policy is android api rules specific: Raw proto classes
-    // must never be in the API
-    text << "/** @hide */" << endl;
-//    text << "@android.annotation.TestApi" << endl;
-
-    if (generate_outer) {
-        text << "public final class " << outer_class_name << " {" << endl;
-        text << endl;
-    }
-
-    size_t N;
-    const string indented = generate_outer ? indent_more("") : string();
-    
-    N = enums.size();
-    for (size_t i=0; i<N; i++) {
-        write_enum(text, enums[i], indented);
-    }
-
-    N = messages.size();
-    for (size_t i=0; i<N; i++) {
-        write_message(text, messages[i], indented);
-    }
-
-    if (generate_outer) {
-        text << "}" << endl;
-    }
-
-    CodeGeneratorResponse::File* file_response = response->add_file();
-    file_response->set_name(filename);
-    file_response->set_content(text.str());
-}
-
-/**
- * Write one file per class.  Put all of the enums into the "outer" class.
- */
-static void
-write_multiple_files(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
-{
-    // If there is anything to put in the outer class file, create one
-    if (file_descriptor.enum_type_size() > 0) {
-        vector<EnumDescriptorProto> enums;
-        int N = file_descriptor.enum_type_size();
-        for (int i=0; i<N; i++) {
-            enums.push_back(file_descriptor.enum_type(i));
-        }
-
-        vector<DescriptorProto> messages;
-
-        write_file(response, file_descriptor,
-                make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
-                true, enums, messages);
-    }
-
-    // For each of the message types, make a file
-    int N = file_descriptor.message_type_size();
-    for (int i=0; i<N; i++) {
-        vector<EnumDescriptorProto> enums;
-
-        vector<DescriptorProto> messages;
-        messages.push_back(file_descriptor.message_type(i));
-
-        write_file(response, file_descriptor,
-                make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
-                false, enums, messages);
-    }
-}
-
-static void
-write_single_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
-{
-    int N;
-
-    vector<EnumDescriptorProto> enums;
-    N = file_descriptor.enum_type_size();
-    for (int i=0; i<N; i++) {
-        enums.push_back(file_descriptor.enum_type(i));
-    }
-
-    vector<DescriptorProto> messages;
-    N = file_descriptor.message_type_size();
-    for (int i=0; i<N; i++) {
-        messages.push_back(file_descriptor.message_type(i));
-    }
-
-    write_file(response, file_descriptor,
-            make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
-            true, enums, messages);
-}
-
-/**
  * Main.
  */
 int
@@ -273,24 +26,11 @@
 
     GOOGLE_PROTOBUF_VERIFY_VERSION;
 
-    CodeGeneratorRequest request;
-    CodeGeneratorResponse response;
-
     // Read the request
+    CodeGeneratorRequest request;
     request.ParseFromIstream(&cin);
 
-    // Build the files we need.
-    const int N = request.proto_file_size();
-    for (int i=0; i<N; i++) {
-        const FileDescriptorProto& file_descriptor = request.proto_file(i);
-        if (should_generate_for_file(request, file_descriptor.name())) {
-            if (file_descriptor.options().java_multiple_files()) {
-                write_multiple_files(&response, file_descriptor);
-            } else {
-                write_single_file(&response, file_descriptor);
-            }
-        }
-    }
+    CodeGeneratorResponse response = generate_java_protostream_code(request);
 
     // If we had errors, don't write the response. Print the errors and exit.
     if (ERRORS.HasErrors()) {
diff --git a/tools/streaming_proto/test/imported.proto b/tools/streaming_proto/test/integration/imported.proto
similarity index 100%
rename from tools/streaming_proto/test/imported.proto
rename to tools/streaming_proto/test/integration/imported.proto
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt b/tools/streaming_proto/test/integration/src/com/android/streaming_proto_test/Main.java
similarity index 79%
copy from packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
copy to tools/streaming_proto/test/integration/src/com/android/streaming_proto_test/Main.java
index 8ad0a08..2a7001b 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
+++ b/tools/streaming_proto/test/integration/src/com/android/streaming_proto_test/Main.java
@@ -14,8 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel.component.mediaoutput
+package com.android.streaming_proto_test;
 
-import dagger.Module
-
-@Module interface MediaOutputModule
+public class Main {
+    public void main(String[] argv) {
+        System.out.println("hello world");
+    }
+}
diff --git a/tools/streaming_proto/test/test.proto b/tools/streaming_proto/test/integration/test.proto
similarity index 97%
rename from tools/streaming_proto/test/test.proto
rename to tools/streaming_proto/test/integration/test.proto
index de80ed6..3cf81b4 100644
--- a/tools/streaming_proto/test/test.proto
+++ b/tools/streaming_proto/test/integration/test.proto
@@ -16,7 +16,7 @@
 
 syntax = "proto2";
 
-import "frameworks/base/tools/streaming_proto/test/imported.proto";
+import "frameworks/base/tools/streaming_proto/test/integration/imported.proto";
 
 package com.android.streaming_proto_test;
 
diff --git a/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java b/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java
deleted file mode 100644
index 1246f53..0000000
--- a/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.android.streaming_proto_test;
-
-public class Main {
-    public void main(String[] argv) {
-        System.out.println("hello world");
-    }
-}
diff --git a/tools/streaming_proto/test/unit/streaming_proto_java.cpp b/tools/streaming_proto/test/unit/streaming_proto_java.cpp
new file mode 100644
index 0000000..8df9716
--- /dev/null
+++ b/tools/streaming_proto/test/unit/streaming_proto_java.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "java/java_proto_stream_code_generator.h"
+
+using ::testing::HasSubstr;
+using ::testing::Not;
+
+static void add_my_test_proto_file(CodeGeneratorRequest* request) {
+    request->add_file_to_generate("MyTestProtoFile");
+
+    FileDescriptorProto* file_desc = request->add_proto_file();
+    file_desc->set_name("MyTestProtoFile");
+    file_desc->set_package("test.package");
+
+    auto* file_options = file_desc->mutable_options();
+    file_options->set_java_multiple_files(false);
+
+    auto* message = file_desc->add_message_type();
+    message->set_name("MyTestMessage");
+
+    auto* field = message->add_field();
+    field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+    field->set_name("my_test_field");
+
+    field = message->add_field();
+    field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+    field->set_name("my_other_test_field");
+
+    field = message->add_field();
+    field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+    field->set_name("my_other_test_message");
+}
+
+static void add_my_other_test_proto_file(CodeGeneratorRequest* request) {
+    request->add_file_to_generate("MyOtherTestProtoFile");
+
+    FileDescriptorProto* file_desc = request->add_proto_file();
+    file_desc->set_name("MyOtherTestProtoFile");
+    file_desc->set_package("test.package");
+
+    auto* file_options = file_desc->mutable_options();
+    file_options->set_java_multiple_files(false);
+
+    auto* message = file_desc->add_message_type();
+    message->set_name("MyOtherTestMessage");
+
+    auto* field = message->add_field();
+    field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+    field->set_name("a_test_field");
+
+    field = message->add_field();
+    field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+    field->set_name("another_test_field");
+}
+
+static CodeGeneratorRequest create_simple_two_file_request() {
+    CodeGeneratorRequest request;
+
+    add_my_test_proto_file(&request);
+    add_my_other_test_proto_file(&request);
+
+    return request;
+}
+
+static CodeGeneratorRequest create_simple_multi_file_request() {
+    CodeGeneratorRequest request;
+
+    request.add_file_to_generate("MyMultiMessageTestProtoFile");
+
+    FileDescriptorProto* file_desc = request.add_proto_file();
+    file_desc->set_name("MyMultiMessageTestProtoFile");
+    file_desc->set_package("test.package");
+
+    auto* file_options = file_desc->mutable_options();
+    file_options->set_java_multiple_files(true);
+
+    auto* message = file_desc->add_message_type();
+    message->set_name("MyTestMessage");
+
+    auto* field = message->add_field();
+    field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+    field->set_name("my_test_field");
+
+    field = message->add_field();
+    field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+    field->set_name("my_other_test_field");
+
+    field = message->add_field();
+    field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+    field->set_name("my_other_test_message");
+
+    message = file_desc->add_message_type();
+    message->set_name("MyOtherTestMessage");
+
+    field = message->add_field();
+    field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+    field->set_name("a_test_field");
+
+    field = message->add_field();
+    field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+    field->set_name("another_test_field");
+
+    return request;
+}
+
+TEST(StreamingProtoJavaTest, NoFilter) {
+    CodeGeneratorRequest request = create_simple_two_file_request();
+    CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+    auto generated_file_count = response.file_size();
+    EXPECT_EQ(generated_file_count, 2);
+
+    EXPECT_EQ(response.file(0).name(), "test/package/MyTestProtoFile.java");
+    EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestProtoFile"));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+
+    EXPECT_EQ(response.file(1).name(), "test/package/MyOtherTestProtoFile.java");
+    EXPECT_THAT(response.file(1).content(), HasSubstr("class MyOtherTestProtoFile"));
+    EXPECT_THAT(response.file(1).content(), HasSubstr("class MyOtherTestMessage"));
+    EXPECT_THAT(response.file(1).content(), HasSubstr("long A_TEST_FIELD"));
+    EXPECT_THAT(response.file(1).content(), HasSubstr("long ANOTHER_TEST_FIELD"));
+}
+
+TEST(StreamingProtoJavaTest, WithFilter) {
+    CodeGeneratorRequest request = create_simple_two_file_request();
+    request.set_parameter("include_filter:test.package.MyTestMessage");
+    CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+    auto generated_file_count = response.file_size();
+    EXPECT_EQ(generated_file_count, 1);
+
+    EXPECT_EQ(response.file(0).name(), "test/package/MyTestProtoFile.java");
+    EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestProtoFile"));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+}
+
+TEST(StreamingProtoJavaTest, WithoutFilter_MultipleJavaFiles) {
+    CodeGeneratorRequest request = create_simple_multi_file_request();
+    CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+    auto generated_file_count = response.file_size();
+    EXPECT_EQ(generated_file_count, 2);
+
+    EXPECT_EQ(response.file(0).name(), "test/package/MyTestMessage.java");
+    EXPECT_THAT(response.file(0).content(), Not(HasSubstr("class MyTestProtoFile")));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+
+    EXPECT_EQ(response.file(1).name(), "test/package/MyOtherTestMessage.java");
+    EXPECT_THAT(response.file(1).content(), Not(HasSubstr("class MyOtherTestProtoFile")));
+    EXPECT_THAT(response.file(1).content(), HasSubstr("class MyOtherTestMessage"));
+    EXPECT_THAT(response.file(1).content(), HasSubstr("long A_TEST_FIELD"));
+    EXPECT_THAT(response.file(1).content(), HasSubstr("long ANOTHER_TEST_FIELD"));
+}
+
+TEST(StreamingProtoJavaTest, WithFilter_MultipleJavaFiles) {
+    CodeGeneratorRequest request = create_simple_multi_file_request();
+    request.set_parameter("include_filter:test.package.MyTestMessage");
+    CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+    auto generated_file_count = response.file_size();
+    EXPECT_EQ(generated_file_count, 1);
+
+    EXPECT_EQ(response.file(0).name(), "test/package/MyTestMessage.java");
+    EXPECT_THAT(response.file(0).content(), Not(HasSubstr("class MyTestProtoFile")));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+    EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+}
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index 58638e8..45ab986 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -718,6 +718,9 @@
         } catch (RemoteException e1) {
             Log.e(TAG, "Failed to get IClientInterface due to remote exception");
             return false;
+        } catch (NullPointerException e2) {
+            Log.e(TAG, "setupInterfaceForClientMode NullPointerException");
+            return false;
         }
 
         if (clientInterface == null) {
@@ -785,6 +788,9 @@
         } catch (RemoteException e1) {
             Log.e(TAG, "Failed to teardown client interface due to remote exception");
             return false;
+        } catch (NullPointerException e2) {
+            Log.e(TAG, "tearDownClientInterface NullPointerException");
+            return false;
         }
         if (!success) {
             Log.e(TAG, "Failed to teardown client interface");
@@ -816,6 +822,9 @@
         } catch (RemoteException e1) {
             Log.e(TAG, "Failed to get IApInterface due to remote exception");
             return false;
+        } catch (NullPointerException e2) {
+            Log.e(TAG, "setupInterfaceForSoftApMode NullPointerException");
+            return false;
         }
 
         if (apInterface == null) {
@@ -854,6 +863,9 @@
         } catch (RemoteException e1) {
             Log.e(TAG, "Failed to teardown AP interface due to remote exception");
             return false;
+        } catch (NullPointerException e2) {
+            Log.e(TAG, "tearDownSoftApInterface NullPointerException");
+            return false;
         }
         if (!success) {
             Log.e(TAG, "Failed to teardown AP interface");
@@ -1328,6 +1340,8 @@
             }
         } catch (RemoteException e1) {
             Log.e(TAG, "Failed to request getChannelsForBand due to remote exception");
+        } catch (NullPointerException e2) {
+            Log.e(TAG, "getChannelsMhzForBand NullPointerException");
         }
         if (result == null) {
             result = new int[0];
@@ -1352,7 +1366,8 @@
      */
     @Nullable public DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String ifaceName) {
         if (mWificond == null) {
-            Log.e(TAG, "getDeviceWiphyCapabilities: mWificond binder is null! Did wificond die?");
+            Log.e(TAG, "getDeviceWiphyCapabilities: mWificond binder is null! "
+                    + "Did wificond die?");
             return null;
         }
 
@@ -1360,6 +1375,9 @@
             return mWificond.getDeviceWiphyCapabilities(ifaceName);
         } catch (RemoteException e) {
             return null;
+        } catch (NullPointerException e2) {
+            Log.e(TAG, "getDeviceWiphyCapabilities NullPointerException");
+            return null;
         }
     }
 
@@ -1409,6 +1427,8 @@
             Log.i(TAG, "Receive country code change to " + newCountryCode);
         } catch (RemoteException re) {
             re.rethrowFromSystemServer();
+        } catch (NullPointerException e) {
+            new RemoteException("Wificond service doesn't exist!").rethrowFromSystemServer();
         }
     }